commit de0a45b682838ef5c188271b60934034ee916b33 Author: zhx Date: Wed Jul 1 18:25:58 2026 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..79c113f --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..f70d82d --- /dev/null +++ b/.metadata @@ -0,0 +1,33 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "ea121f8859e4b13e47a8f845e4586164519588bc" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: ea121f8859e4b13e47a8f845e4586164519588bc + base_revision: ea121f8859e4b13e47a8f845e4586164519588bc + - platform: android + create_revision: ea121f8859e4b13e47a8f845e4586164519588bc + base_revision: ea121f8859e4b13e47a8f845e4586164519588bc + - platform: ios + create_revision: ea121f8859e4b13e47a8f845e4586164519588bc + base_revision: ea121f8859e4b13e47a8f845e4586164519588bc + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..6cc4df8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,138 @@ +# CLAUDE.md + +本文档为 Claude Code (claude.ai/code) 在此代码库中工作时提供指导。 + +## 项目概览 + +这是一个名为 "Aslan" 的 Flutter 应用程序(项目 `atu`),是一个具有实时音频/视频、聊天、礼物和家族功能的社交语音聊天应用。它采用清晰架构分层:core、data、domain、presentation。 + +**主要依赖**: +- **实时通信**:Agora RTC (`agora_rtc_engine`)、腾讯云聊天 (`tencent_cloud_chat_sdk`) +- **状态管理**:Provider (`provider`) +- **路由**:Fluro (`fluro`) +- **网络请求**:Dio (`dio`) 带有自定义拦截器 +- **本地包**:修改版本的 `webview_flutter`、`tancent_vap`、`on_audio_query`、`flutter_foreground_task` 包含在 `local_packages/` 中。在更新 pub 依赖前请始终检查这些目录。 + +## 常用开发任务 + +### 运行应用 +```bash +# 运行应用 +flutter run +``` + +### 构建 +- **Android**:`flutter build apk` 或 `flutter build appbundle` +- **iOS**:`flutter build ios` + +### 测试 +- 运行单元/部件测试:`flutter test` +- 单个测试文件:`flutter test test/widget_test.dart` + +### 代码检查和分析 +- 静态分析:`flutter analyze` +- 代码格式化:`flutter format .` + +### 管理依赖 +- 获取包:`flutter pub get` +- 升级包:`flutter pub upgrade` +- 检查过时包:`flutter pub outdated` + +### iOS 特定设置 +- 更新 Flutter 插件后在 `ios/` 中运行 `pod install`。 +- Podfile 包含自定义权限标志(相机、麦克风、相册、通知已启用;其他已禁用)。按需调整。 + +## 架构 + +### 层级结构 +- **`lib/core/`**:常量、工具、共享组件、路由。 + - `constants/`:应用全局配置 (`GlobalConfig`)、颜色、屏幕尺寸。 + - `routes/`:Fluro 路由设置 (`Routes.dart`, `router_init.dart`)。 + - `compontent/`:可复用的 UI 组件(自定义应用栏、图片、按钮、对话框)。 + - `utils/`:工具类(设备 ID、键盘、深度链接)。 +- **`lib/domain/`**:业务逻辑。 + - `entities/`:数据模型。 + - `repositories/`:仓库接口。 + - `use_cases/`:业务逻辑类(文本格式化器、标签选择器等)。 +- **`lib/data/`**:数据层实现。 + - `datasources/remote/net/`:HTTP 客户端 (`http.dart`, `api.dart`),带有自定义拦截器用于头部、超时、日志。 + - `datasources/local/`:本地存储 (`StorageManager`)、用户管理 (`UserManager`)、前台服务助手。 + - `datasources/repositories/`:仓库实现。 + - `models/`:数据传输对象和枚举。 +- **`lib/presentation/`**:UI 层。 + - `pages/`:基于功能页面的目录(登录、房间、聊天、家族等)。 + - `providers/`:状态管理的 ChangeNotifier 提供者(认证、用户、房间、礼物等)。 + +### 状态管理 +- 使用 `provider` 包和 `ChangeNotifier`。 +- 提供者在 `main.dart` (`MyAppWithProviders`) 中注册。部分提供者为懒加载。 +- 关键提供者:`AuthProvider`、`UserProvider`、`RoomProvider`、`RtcProvider`、`RtmProvider`、`GiftProvider`、`LocaleProvider`、`ThemeProvider`。 + +### 路由 +- 使用 `fluro` 进行导航。 +- 路由定义在 `lib/core/routes/Routes.dart` 和各功能的模块化 `IRouterProvider` 实现 (`*_route.dart`)。 +- 导航工具:`fluro_navigator.dart` 中的 `NavigatorUtils`。 + +### 网络层 +- 基础 URL 配置在 `GlobalConfig.apiHost`。 +- 自定义 `Http` 类继承 `BaseHttp`,带有用于头部、日志、超时的拦截器。 +- 请求头包含设备信息、语言、令牌。 +- 响应包装在 `ResponseData` 中,包含 `status`、`errorCode`、`body` 字段。 + +### 配置 +- **配置架构**: 使用 `AppConfig` 系统管理应用配置 +- **全局配置**: `lib/core/constants/global_config.dart` 从 `AppConfig.current` 获取配置值 +- **配置内容**: API主机、腾讯IM App ID、Agora RTC App ID、功能开关、审核模式标志等 +- **业务逻辑差异化**: 使用策略模式(`BusinessLogicStrategy`)实现页面差异化 + - `BaseBusinessLogicStrategy`: 原始应用默认实现 + - `Variant1BusinessLogicStrategy`: 马甲包差异化实现 + - 页面通过 `GlobalConfig.businessLogicStrategy` 访问策略方法 + - 已差异化页面: 首页、登录页、家族页、探索页、个人主页、管理编辑和搜索页 + +### UI 适配 +- 使用 `flutter_screenutil` 进行响应式设计。 +- 设计尺寸为 375×667(定义在 `lib/core/constants/screen.dart`)。 +- 使用 `ScreenUtil().setWidth()`、`ScreenUtil().setHeight()`、`ScreenUtil().setSp()` 处理尺寸和字体大小。 + +### 本地化 +- `assets/l10n/` 中的 JSON 文件支持英语、中文、阿拉伯语、土耳其语、孟加拉语。 +- `ATAppLocalizations` 类加载翻译;使用 `ATAppLocalizations.of(context)!.keyName`。 + +### 资源 +- 图片按功能组织在 `images/` 中(启动页、登录、房间、个人等)。 +- 字体在 `fonts/` 中(自定义字体 `MyCustomFont`)。 + +## 重要说明 + +### 本地包 +项目在 `local_packages/` 中包含多个插件的修改版本: +- `webview_flutter-4.13.0` +- `tancent_vap-1.0.0+1`(腾讯视频动画播放器) +- `on_audio_query-2.9.0` +- `flutter_foreground_task-9.1.0` + +更新依赖时,请检查这些本地包的更改是否需要保留。除非有意替换,否则避免用 pub.dev 版本覆盖它们。 + +### iOS 权限 +`ios/Podfile` 设置预处理器标志来禁用未使用的权限(日历、联系人、提醒事项等),仅启用相机、麦克风、相册、通知。如需额外权限,请修改 `GCC_PREPROCESSOR_DEFINITIONS` 块。 + +### 审核模式 +`GlobalConfig.isReview` 控制应用是否处于审核模式(例如用于 App Store 审核)。这可能会影响功能可用性。 + +### 实时功能 +- Agora RTC 用于语音/视频房间。 +- 腾讯 IM 用于聊天消息。 +- 确保 `GlobalConfig` 中设置了正确的 App ID。 + +### 错误报告 +- 使用 Firebase Crashlytics 进行崩溃报告。 +- 未捕获的错误通过 `runZonedGuarded` 和 `FlutterError.onError` 捕获。 +- 网络错误通过日志记录并通过 toast 显示。 + + +## 故障排除 + +- **Pod install 错误**:检查 `ios/Podfile` 语法;确保 Flutter 插件兼容。 +- **本地包错误**:验证 `pubspec.yaml` 中的路径指向正确的本地包目录。 +- **网络请求失败**:确认 `GlobalConfig.apiHost` 可达;检查 `ApiInterceptor` 中的头部。 +- **状态未更新**:验证提供者是否在需要时不是懒加载状态;检查 `notifyListeners()` 调用。 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7b62d35 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# Aslan + +Aslan + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..a2ee6ed --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,95 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") + id("com.google.gms.google-services") +} + +android { + namespace = "com.chat.auu" + compileSdk = 36 + + + bundle { + abi { + enableSplit = true + } + } + + signingConfigs { + create("release") { + storeFile = file("./atu.jks") + storePassword = "abc2025" + keyAlias = "atu" + keyPassword = "abc2025" + } + + getByName("debug") { + storeFile = file("./atu_debug.jks") + storePassword = "abc2025" + keyAlias = "atu" + keyPassword = "abc2025" + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + + defaultConfig { + multiDexEnabled = true + manifestPlaceholders["appName"] = "Aslan" + manifestPlaceholders["appIcon"] = "@mipmap/icon_logo" + manifestPlaceholders["appIconRound"] = "@mipmap/icon_logo_round" + applicationId = "com.chat.auu.dev" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + + ndk { + // 指定支持的 ABI 架构 + abiFilters.add("arm64-v8a") + abiFilters.add("armeabi-v7a") + abiFilters.add("x86_64") + // 如果需要支持 x86 模拟器,可以添加 'x86', 'x86_64' + } + } + + buildTypes { + getByName("release") { + isMinifyEnabled = false + isShrinkResources = false + isZipAlignEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" + ) + signingConfig = signingConfigs["release"] + } + getByName("debug") { + isMinifyEnabled = false + signingConfig = signingConfigs["debug"] + } + } +} +dependencies { + implementation("androidx.multidex:multidex:2.0.1") +// implementation(platform("com.google.firebase:firebase-bom:34.0.0")) +// implementation("com.google.firebase:firebase-auth") +// implementation("com.google.firebase:firebase-core:16.0.8") +// implementation("com.google.android.gms:play-services-auth:21.1.1") +} + + +flutter { + source = "../.." +} diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..05c368d --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,76 @@ +{ + "project_info": { + "project_number": "1047584498014", + "project_id": "matu-cb3cf", + "storage_bucket": "matu-cb3cf.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:1047584498014:android:573c09ad7903d17a496fcf", + "android_client_info": { + "package_name": "com.chat.auu" + } + }, + "oauth_client": [ + { + "client_id": "1047584498014-9q9agpusbtu68amp79b7q4k3jo7863v9.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyAv9CdnImnsmEEvkuzXAxfLAI1VsvsOeZo" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "1047584498014-9q9agpusbtu68amp79b7q4k3jo7863v9.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:1047584498014:android:d32d514564d221e4496fcf", + "android_client_info": { + "package_name": "com.chat.auu.dev" + } + }, + "oauth_client": [ + { + "client_id": "1047584498014-bu27a6g3csmc48gei0mo4sdj5didciih.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.chat.auu.dev", + "certificate_hash": "729407a5b0e2fcd664df3dfc64624aed13f8d6d7" + } + }, + { + "client_id": "1047584498014-9q9agpusbtu68amp79b7q4k3jo7863v9.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyAv9CdnImnsmEEvkuzXAxfLAI1VsvsOeZo" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "1047584498014-9q9agpusbtu68amp79b7q4k3jo7863v9.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/app/info.txt b/android/app/info.txt new file mode 100644 index 0000000..ffaac3c --- /dev/null +++ b/android/app/info.txt @@ -0,0 +1,3 @@ +atu.jks签名信息: +pwd:2025abc +Alias:atu \ No newline at end of file diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..5ec255f --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,39 @@ +# Flutter 基础 +-keep class io.flutter.** { *; } +-keep class io.flutter.app.** { *; } +-keep class io.flutter.plugin.** { *; } +-keep class io.flutter.util.** { *; } + +# Play Core 保留 +-keep class com.google.android.play.core.** { *; } +-keep interface com.google.android.play.core.** { *; } + +# 根据 missing_rules.txt 添加的规则 +-dontwarn com.google.android.play.core.splitcompat.SplitCompatApplication +-dontwarn com.google.android.play.core.splitinstall.SplitInstallException +-dontwarn com.google.android.play.core.splitinstall.SplitInstallManager +-dontwarn com.google.android.play.core.splitinstall.SplitInstallManagerFactory +-dontwarn com.google.android.play.core.splitinstall.SplitInstallRequest$Builder +-dontwarn com.google.android.play.core.splitinstall.SplitInstallRequest +-dontwarn com.google.android.play.core.splitinstall.SplitInstallSessionState +-dontwarn com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener +-dontwarn com.google.android.play.core.tasks.OnFailureListener +-dontwarn com.google.android.play.core.tasks.OnSuccessListener +-dontwarn com.google.android.play.core.tasks.Task + +# 保留这些类以确保功能正常 +-keep class com.google.android.play.core.splitcompat.** { *; } +-keep class com.google.android.play.core.splitinstall.** { *; } +-keep class com.google.android.play.core.tasks.** { *; } +-keep class io.flutter.embedding.engine.deferredcomponents.** { *; } + +# 应用特定 +-keep class com.tkm.atu.** { *; } + +# Provider +-keep class * extends ChangeNotifier { *; } +-keep class * implements Listenable { *; } + +# 其他保留 +-keepattributes *Annotation* +-keepattributes Signature \ No newline at end of file diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..89923e6 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/tkm/likei/variant1/MainActivity.kt b/android/app/src/main/kotlin/com/tkm/likei/variant1/MainActivity.kt new file mode 100644 index 0000000..86ea755 --- /dev/null +++ b/android/app/src/main/kotlin/com/tkm/likei/variant1/MainActivity.kt @@ -0,0 +1,125 @@ +package com.chat.auu + +import android.content.Intent +import android.net.Uri +import android.util.Log +import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodChannel + +class MainActivity: FlutterActivity() { + private val externalIntentTag = "ATExternalIntent" + + private val upiPackages = listOf( + "com.phonepe.app", + "net.one97.paytm", + "com.google.android.apps.nbu.paisa.user", + "in.org.npci.upiapp" + ) + + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) // 这行必须存在! + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.chat.auu.snapchat_share/intent").setMethodCallHandler { call, result -> + if (call.method == "shareToSnapchat") { + val text = call.argument("text") ?: "" + shareToSnapchat(text, result) + } else { + result.notImplemented() + } + } + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.chat.auu.webview/external_intent").setMethodCallHandler { call, result -> + if (call.method == "openExternalUrl") { + val url = call.arguments as? String + result.success(openExternalUrl(url)) + } else { + result.notImplemented() + } + } + } + + private fun shareToSnapchat(text: String, result: MethodChannel.Result) { + val intent = Intent(Intent.ACTION_SEND) + intent.type = "text/plain" + intent.putExtra(Intent.EXTRA_TEXT, text) + // 指定Snapchat包名,直接唤起Snapchat的分享界面 + intent.setPackage("com.snapchat.android") + + try { + startActivity(intent) + result.success("Sharing success") + } catch (e: Exception) { + result.error("SHARE_FAILED", "Snapchat not installed or sharing fail", e.message) + } + } + + private fun openExternalUrl(url: String?): Boolean { + if (url.isNullOrBlank()) return false + + return tryOpenIntentUrl(url) || tryOpenViewUrl(url) + } + + private fun tryOpenIntentUrl(url: String): Boolean { + if (!url.startsWith("intent://", ignoreCase = true)) return false + + return try { + val intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + intent.component = null + intent.selector = null + startActivity(intent) + true + } catch (e: Exception) { + false + } + } + + private fun tryOpenViewUrl(url: String): Boolean { + val uri = runCatching { Uri.parse(url) }.getOrNull() ?: return false + val scheme = uri.scheme?.lowercase() + val intents = mutableListOf() + + if (scheme == "phonepe") { + intents.add(createViewIntent(uri, null, false)) + intents.add(createViewIntent(uri, "com.phonepe.app", false)) + intents.add(createViewIntent(uri, "com.phonepe.app", true)) + } + + if (scheme == "upi") { + upiPackages.forEach { packageName -> + intents.add(createViewIntent(uri, packageName, false)) + intents.add(createViewIntent(uri, packageName, true)) + } + } + + if (scheme == "bhim" || scheme == "bhimupi") { + intents.add(createViewIntent(uri, null, false)) + intents.add(createViewIntent(uri, "in.org.npci.upiapp", false)) + intents.add(createViewIntent(uri, "in.org.npci.upiapp", true)) + } + + if (scheme != "phonepe" && scheme != "bhim" && scheme != "bhimupi") { + intents.add(createViewIntent(uri, null, false)) + intents.add(createViewIntent(uri, null, true)) + } + + return intents.any { intent -> + try { + startActivity(intent) + Log.i(externalIntentTag, "Open success: ${intent.toUri(0)}") + true + } catch (e: Exception) { + Log.i(externalIntentTag, "Open failed: ${intent.toUri(0)}, ${e.javaClass.simpleName}: ${e.message}") + false + } + } + } + + private fun createViewIntent(uri: Uri, targetPackage: String?, browsable: Boolean): Intent { + return Intent(Intent.ACTION_VIEW, uri) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .apply { + if (browsable) addCategory(Intent.CATEGORY_BROWSABLE) + targetPackage?.let { setPackage(it) } + } + } +} diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/icon_logo.png b/android/app/src/main/res/mipmap-hdpi/icon_logo.png new file mode 100644 index 0000000..2e717f4 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/icon_logo.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/icon_logo.png b/android/app/src/main/res/mipmap-mdpi/icon_logo.png new file mode 100644 index 0000000..2e717f4 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/icon_logo.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/icon_logo.png b/android/app/src/main/res/mipmap-xhdpi/icon_logo.png new file mode 100644 index 0000000..2e717f4 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/icon_logo.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/icon_logo.png b/android/app/src/main/res/mipmap-xxhdpi/icon_logo.png new file mode 100644 index 0000000..2e717f4 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/icon_logo.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/icon_logo.png b/android/app/src/main/res/mipmap-xxxhdpi/icon_logo.png new file mode 100644 index 0000000..2e717f4 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/icon_logo.png differ diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..3ac76ac --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,21 @@ + + + + #FF5722 + #D84315 + #FF9800 + + + #FFFFFF + #000000 + #00000000 + + + #F5F5F5 + #FFFFFF + + + #212121 + #757575 + #BDBDBD + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..34fa2af --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + ChatVibe + \ No newline at end of file diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..069a03f --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/android/app/src/main/res/xml/file_paths.xml b/android/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..262d6d6 --- /dev/null +++ b/android/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,18 @@ + + + + + + + + \ No newline at end of file diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..6212211 --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,43 @@ +plugins { + id("com.google.gms.google-services") version "4.4.3" apply false +} +buildscript { + repositories { + google() + mavenCentral() + // 使用阿里云镜像加速 + maven { url = uri("https://maven.aliyun.com/repository/google") } + maven { url = uri("https://maven.aliyun.com/repository/public") } + maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") } + } +} + + +allprojects { + repositories { + // 使用阿里云镜像加速 + maven { url = uri("https://maven.aliyun.com/repository/google") } + maven { url = uri("https://maven.aliyun.com/repository/public") } + maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") } + // 备用官方仓库 + google() + mavenCentral() + } +} + +// 以下是你的现有配置 +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} + +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..f018a61 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..afa1e8e --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..a207f3a --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,28 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + maven { url = uri("https://maven.aliyun.com/repository/google") } + maven { url = uri("https://maven.aliyun.com/repository/public") } + maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") } + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.0" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false +} + +include(":app") diff --git a/assets/l10n/intl_ar.json b/assets/l10n/intl_ar.json new file mode 100644 index 0000000..7796f85 --- /dev/null +++ b/assets/l10n/intl_ar.json @@ -0,0 +1,759 @@ +{ + "signInWithGoogle": "سيج إنويسجوغل", + "or": "اور", + "signInWithYourAccount": "سيج ، إن ، فايس ، ويوا كونتي", + "signInWithApple": "سيج إنويس أبل", + "loginRepresentsAgreementTo": "تسجيل الدخول يمثل الموافقة على", + "termsofService": "تيمز بنسيفيتش", + "privaceyPolicy": "بريفاسيبوليس", + "tips": "تيبس", + "hot": "حار", + "selectCountry": "اختر الدولة", + "sound2": "صوت", + "winFloat": "اربح التعويم", + "giftVibration": "تأثيرات الهدية المحظوظة", + "giftEffect": "تأثير الهدية", + "dailyTasksTips": "ملاحظة: هل أنت مستعد لإنجاز المهام؟ ابحث عنها في غرفك المفضلة.", + "mine": "خاصتي", + "searchNoDataTips": "أدخل معرف الغرفة أو المستخدم الذي تريد البحث عنه.", + "party": "حفلة", + "event": "حدث", + "walletBalance": "رصيد المحفظة:", + "list": "قائمة", + "canOnlyBeShown": "*يمكن عرضه لجميع المستخدمين في منطقتك فقط.", + "sendMessage": "إرسال رسالة", + "availableCountries": "الدول المتاحة:", + "currentBalance": "الرصيد الحالي:", + "successfulTransaction": "{1} عملية ناجحة", + "hasBeenACoinAgencyForDays": "كانت وكالة عملة لمدة {1} أيام", + "rechargeRecords": "سجلات إعادة الشحن", + "balance": "الرصيد:", + "searchCountry": "ابحث عن الدولة...", + "googlePay": "جوجل باي", + "biggestDiscount": "أكبر خصم", + "officialRechargeAgent": "وكيل إعادة الشحن الرسمي", + "selectACountry": "اختر دولة:", + "playLog": "سجل اللعب:", + "luckGiftRuleTips": "قدم هدية محظوظة واربح ما يصل إلى 1000 ضعف مكافأة القطع الذهبية! يمكن للمستخدمين الذين يتلقون الهدايا المحظوظة الاستمتاع بالمزايا التالية:\n(1) للمستخدمين الذين يتلقون الهدايا المحظوظة: 4٪ قيمة خبرة الجاذبية بناءً على قيمة الهدية.\n(2) إذا كان المستلم مضيفًا: 4٪ قيمة هدف راتب المضيف بناءً على قيمة الهدية.", + "vipAccelerating": "{1} التسريع", + "games": "ألعاب", + "badge": "شارة", + "glory": "مجد", + "vipRippleTheme": "سمة VIP Ripple", + "gifProfileUpload": "تحميل صورة GIF للملف الشخصي", + "badgeHonor": "شارة/مجد", + "levelMedal": "ميدالية المستوى", + "levelIcon": "رمز المستوى", + "myRoom": "غرفتي", + "other": "آخر", + "maliciousHarassment": "التحرش الخبيث", + "aboutMe": "عنّي", + "roomEdit": "تحرير الغرفة", + "cancelRoomPassword": "هل أنت متأكد أنك تريد حذف كلمة مرور الغرفة؟", + "roomMemberFee": "رسوم عضو الغرفة", + "blockedList2": "قائمة المحظورين", + "roomTheme2": "موضوع الغرفة", + "roomPassword": "كلمة مرور الغرفة", + "numberOfMic": "عدد الميكروفونات", + "pleaseEnterContent": "يرجى إدخال المحتوى", + "profilePhoto": "صورة الملف الشخصي", + "noHistoricalRecordsAvailable": "لا توجد سجلات تاريخية متاحة.", + "inviteNewUsersToEarnCoins": "ادعُ مستخدمين جدد لكسب العملات", + "crateMyRoom": "أنشئ غرفتي", + "casualInteraction": "التفاعل العفوي", + "haveGamePlayingTips": "لديك لعبة جارية، يرجى الخروج من اللعبة الحالية أولاً، هل أنت متأكد من الخروج؟", + "historicalTour": "جولة تاريخية", + "gameCenter": "مركز الألعاب", + "returnToVoiceChat": "العودة إلى الدردشة الصوتية؟", + "game": "لعبة", + "invite": "يدعو", + "vipSpecialGiftTassel": "شرابة هدية خاصة لكبار الشخصيات", + "vipEntranceEffect": "تأثير المدخل الخاص", + "claim": "مطالبة", + "youHaveNotHadVIPYet": "لم تجرب بعد خدمة كبار الشخصيات، تعال وجربها", + "exitGameMode": "الخروج من وضع اللعبة", + "enterTheRoom": "ادخل الغرفة", + "inviteGoRoomTips": "دائمًا هنا من أجلك، في المطر أو الشمس. تعال وقل مرحبًا!", + "confirmInviteThisUserToTheRoom": "هل تؤكد دعوة هذا المستخدم (المعرف: {1}) إلى الغرفة؟", + "complete": "مكتمل", + "copyLink": "نسخ الرابط", + "shareTo": "مشاركة إلى", + "faceBook": "فيسبوك", + "whatsApp": "واتساب", + "snapChat": "سناب شات", + "taskNameRoomOwnerSendRedPacket": "مالك الغرفة يرسل مظروفًا أحمر", + "taskNameRoomNewMember": "أعضاء جدد في الغرفة", + "taskNameRoomOwnerSendGiftUser": "مالك الغرفة يرسل هدية", + "taskNameRoomMicUser120Min": "الأعضاء بقضاء 120+ دقيقة على الميكروفون", + "taskNameRoomMicUser60Min": "الأعضاء بقضاء 60+ دقيقة على الميكروفون", + "taskNameRoomMicUser30Min": "الأعضاء بقضاء 30+ دقيقة على الميكروفون", + "taskNamePersonalSendGift": "أرسل هدية لمستخدم", + "taskNameRoomOnlineUserCount": "الأعضاء المتصلون في الغرفة", + "taskNameRoomOwnerInviteMic": "دعوة عضو إلى الميكروفون", + "taskNameRoomUserSendGiftGold": "العملات المهداة من الأعضاء", + "taskNameRoomOwnerSendGiftGold": "العملات المهداة من مالك الغرفة", + "taskNamePersonalMagicGiftGold": "العملات المكتسبة من إرسال هدايا سحرية", + "taskNamePersonalLuckyGiftGold": "العملات المكتسبة من إرسال هدايا الحظ", + "taskNameRoomOwnerMicTime": "مالك الغرفة يتحدث عبر الميكروفون في الغرفة", + "taskNamePersonalActiveInRoom": "كن نشطًا في غرف الآخرين", + "taskNamePersonalGameConsume": "إنفاق في اللعبة", + "taskNamePersonalMicInRoom": "اذهب إلى المايك", + "dailyCoinBonanzaRules": "قواعد مهرجان العملات اليومي", + "magic": "سحر", + "dailyCoinBonanzaRulesDetail": "1. يمكن إكمال المهام الشخصية اليومية ومهام صاحب الغرفة اليومية مرة واحدة لكل مستخدم يوميًا. يتم إعادة تعيين المهام عند الساعة 00:00:00 بتوقيت السعودية.\n2. يمكن إكمال المهام الشخصية اليومية فقط في غرف الآخرين؛ ويمكن إكمال مهام صاحب الغرفة فقط في غرفتك الخاصة.\n3. يتم احتساب مهام تقديم الهدايا فقط على الهدايا المرسلة في الغرف، وليس الهدايا المرسلة في الخلاصات.\n4. إذا تم إنشاء عدة حسابات باستخدام نفس الجهاز أو نفس بطاقة SIM بأي شكل من الأشكال، يمكن المطالبة بالمكافآت لجميع المهام مرة واحدة فقط.", + "roomOwnerTasks": "مهام صاحب الغرفة", + "personalTasks": "المهام الشخصية", + "noPromptsToday": "لا توجد مطالبات اليوم.", + "coins4": "عملات", + "getPaidToRefer": "احصل على أجر عن الإحالة", + "forMoreRewardsPleaseCheckTheTaskCenter": "لمزيد من المكافآت، يرجى التحقق من مركز المهام", + "currentVip": "الشخص المهم الحالي", + "weekStart": "بداية الأسبوع", + "kingQuuen": "ملك-ملكة", + "ramadan": "رمضان", + "like": "مثل", + "updateNow": "تحديث الآن", + "skip2": "تخطي", + "importantReminder": "تذكير مهم", + "discard": "تخلص", + "createDynamicSuccess": "تم إنشاء الديناميكي بنجاح", + "deleteDynamicTips": "هل أنت متأكد أنك تريد حذف هذا الديناميك؟", + "deleteCommentTips": "هل أنت متأكد أنك تريد حذف هذا التعليق؟", + "deleteSuccessful": "تم الحذف بنجاح!", + "itemsLeft": "الأصناف المتبقية", + "replySucc": "تم الرد بنجاح", + "showLess": "عرض أقل", + "showMore": "عرض المزيد", + "comment": "تعليق", + "more": "أكثر", + "swipeLeftOnTheFloatingScreenAreaToQuicklyCloseIt": "*نصيحة: اسحب إلى اليسار على منطقة الشاشة العائمة لإغلاقها بسرعة.", + "enterThisVoiceChatRoom": "هل تريد دخول هذه الغرفة الصوتية؟", + "roomAnnouncement": "إعلان الغرفة", + "family3": "عائلة {1}", + "saySomething": "قل شيئاً...", + "catchFirstComment": "التقط التعليق الأول", + "posting": "نشر", + "trend": "اتجاه", + "reply": "إجابة", + "dynamic": "ديناميكي", + "customizedGiftRules": "قواعد الهدايا المخصصة", + "rulesUpload": "القواعد والرفع", + "customized": "مخصص", + "coins3": "عملات", + "customizedGiftRulesContent": "كيفية الحصول على هديتي المُخصصة:\n\n1. يتم تحديد أهلية المستخدم للحصول على هدية مُخصصة بناءً على مستوى ثروته الحالي.\n\n(1) إذا كان مستوى ثروة المستخدم ≥ 35: يمكن للمستخدم تحميل فيديو واحد فقط للهدية المُخصصة. سنستخدم صورة ملفه الشخصي الحالية كصورة للهدية، والفيديو المُقدم كتأثير على الهدية في قسم \"التخصيص\". سيستغرق الإنتاج بعض الوقت، وسنُعلمك فور توفر الهدية في المتجر.\n\n(2) إذا كان مستوى ثروة المستخدم ≥ 45: يمكن للمستخدم تحميل فيديو واحد فقط للهدية المُخصصة. سنستخدم صورة ملفه الشخصي الحالية كصورة للهدية، والفيديو المُقدم كتأثير على الهدية في قسم \"التخصيص\". سيستغرق الإنتاج بعض الوقت، وسنُعلمك فور توفر الهدية في المتجر.\n\n2. يمكنك المشاركة في أنشطة مُحددة على التطبيق، وبعد استيفاء الشروط، تواصل معنا عبر قسم \"الرسائل\" ← \"اتصل بنا\". يرجى تزويدنا بلقطات شاشة للنشاط والفيديو الذي ترغب باستخدامه في الهدية المُخصصة. سنستخدم صورة ملفك الشخصي الحالية كصورة للهدية، والفيديو المُقدم كتأثير لها، ثم نُدرجها ضمن قسم \"مُخصصة\". سيستغرق الإنتاج بعض الوقت، وسنُعلمك فور توفرها في المتجر.\n\nمدة صلاحية الهدايا المُخصصة وكيفية تمديدها:\nستكون الهدايا المُخصصة الحصرية متاحة لمدة 30 يومًا. ولتمكين المستخدمين المؤثرين والمُلهمين من الاستمرار في الاستمتاع بهذه التجربة الجديدة وعرض أسلوبهم الشخصي، يُمكن لمستخدمي الهدايا المُخصصة تمديد مدة صلاحية جميع هداياهم المُخصصة لمدة 30 يومًا إضافية عن طريق شحن رصيدهم بمبلغ 500 دولار أمريكي شهريًا.", + "clearCache": "مسح الكاش", + "multiple": "متعدد", + "areYouSureYouWantToClearLocalCache": "هل أنت متأكد أنك تريد مسح الذاكرة المؤقتة المحلية؟", + "luckGiftSpecialEffects": "تأثيرات الرسوم المتحركة للهدية السعيدة", + "receivedFromALuckyGift": "تم الاستلام من هدية محظوظة.", + "successfullyRemovedFromTheBlacklist": "تمت الإزالة من القائمة السوداء بنجاح!", + "successfullyAddedToTheBlacklist": "تمت الإضافة إلى القائمة السوداء بنجاح!", + "youAreCurrentlyCPRelationshipPleaseDissolve": "أنت حاليا في علاقة CP. الرجاء حلها أولا.", + "areYouSureToCancelBlacklist": "هل أنت متأكد من إلغاء القائمة السوداء؟", + "areYouSureYouWantToBlockThisUser": "هل أنت متأكد أنك تريد حظر هذا المستخدم؟", + "removeFromBlacklist": "إزالة من القائمة السوداء", + "moveToBlacklist": "نقل إلى القائمة السوداء", + "userBlacklist": "قائمة الحظر للمستخدمين", + "redEnvelopeNotYetClaimed": "المظروف الأحمر لم يُستلم بعد", + "redEnvelopeAmount": "مبلغ الظرف الأحمر: {1} عملات", + "followed": "تبع", + "newMessage": "رسالة جديدة", + "keep": "يحفظ", + "open": "فتح", + "clearCacheSuccessfully": "تم مسح ذاكرة التخزين المؤقت بنجاح", + "thisUserHasBeenBlacklisted": "تم إدراج هذا المستخدم في القائمة السوداء.", + "thisFeatureIsCurrentlyUnavailable": "هذه الخاصية غير متوفرة حاليا.", + "pleaseSelectTheRecipient": "المرجو اختيار المستلم", + "searchMemberIdHint": "يرجى إدخال رقم هوية العضو", + "unclaimedRedEnvelopes": "الأظرفة الحمراء غير المطالب بها يتم استردادها خلال 24 ساعة.", + "sentARedEnvelope": "أرسل ظرف أحمر.", + "theRedEnvelopeHasExpired": "الظرف الأحمر منتهي الصلاحية", + "acceptedYour": "{1} قبل طلبك", + "youAccepted": "لقد قبلت {1} ", + "openRedPackDialogTip": "الظرف الأحمر", + "wishingYouHappinessEveryDay": "أتمنى لك السعادة كل يوم.", + "createRoomSuccsess": "تم إنشاء الغرفة بنجاح!", + "family2": "العائلة:", + "contactUs": "اتصل بنا", + "specialEffectsManagement": "إدارة المؤثرات الخاصة", + "canSendMsgTips": "كلا الطرفين يجب أن يتابع بعضهما البعض قبل أن يتمكنا من إرسال رسائل خاصة.", + "systemAnnouncementTips1": "احذر الاحتيال:", + "systemAnnouncementTips": "تحقق من المعلومات عن طريق القنوات الرسمية فقط. لا تقم بتنزيل برامج من طرف ثالث، ولا تشارك البيانات الشخصية، ولا تحول المال بناءً على طلبات خارجية. بطاقات هوية الموظفين الرسمية هي فقط 10000 و10003 و10086. في حالة أي شك، توقف وقدم التقرير عبر", + "systemAnnouncement": "إعلان النظام", + "doNotClickUnfamiliarTips": "ماتضغطش على الروابط الغير معروفة، حيث قد تكشف معلوماتك الشخصية. متشاركش أبدا بطاقة الهوية أو معلومات البطاقة البنكية ديالك مع أي واحد.", + "atTag": "@تاغ", + "sayHi2": "قل مرحبا", + "msgSendRedEnvelopeTips": "سيتم فرض رسوم خدمة بنسبة 10% على المظاريف الحمراء، ولن يحصل المستلمون إلا على 90% من قيمة المظروف. يجب أن يكون مستوى ثروة المرسل أعلى من المستوى 10.", + "leavFamilyTips": "هل أنت متأكد من رغبتك في مغادرة عشيرتك الحالية؟ ستحتاج إلى الانتظار يومًا واحدًا للانضمام إلى عشيرة أخرى.", + "leavingTheFamily": "مغادرة العائلة", + "familyNotifcations": "إشعارات العائلة", + "familyNews": "أخبار العائلة", + "reapply": "أعد التقديم", + "cancelRequestFamilyMsg": "طلبك للانضمام إلى عائلة 【عائلة {1}】 قيد المراجعة، هل أنت متأكد من إلغاء الطلب؟", + "cancelRequest": "إلغاء الطلب", + "enterFamilyAnnouncement": "يرجى إدخال الإعلان العائلي", + "familyAnnouncement": "إعلان عائلي", + "disbandTheFamily": "حل الأسرة", + "editFamily": "تعديل العائلة", + "supporter": "مشجع", + "createFamilySuccess": "حقق نجاح العائلة", + "numberOfSign": "عدد العلامة: {1}", + "hostWeeklyRank": "تصنيف المضيف الأسبوعي", + "supporterWeeklyRank": "ترتيب الأسبوعي للمشجعين", + "memberList": "قائمة الأعضاء", + "treasureChest": "صندوق الكنز", + "xxfamily": "عائلة {1}", + "applicationRecord": "سجل الطلبات", + "pending": "قيد الانتظار", + "createAFamily": "إنشاء عائلة", + "searchFamilyIdHint": "يرجى إدخال معرف مالك العائلة", + "enterFamilyInfo": "يرجى تقديم عائلتك بشكل موجز!", + "enterFamilyName": "من فضلك أدخل اسم عائلتك", + "familyName": "اسم العائلة", + "familyInfo": "مقدمة عائلية", + "createFamily": "إنشاء عائلة", + "joinFamily": "انضم إلى أسرة", + "appUpdateTip": "التطبيق لديه نسخة جديدة ({1})، هل تود الذهاب وتحميلها؟", + "reconcile": "يصالح", + "separated": "منفصل", + "areYouSureYouWantToSpend5": "{1} اعترف بالمشاعر ليك؛ إذا قبلتي، غادي توليو ثنائي.", + "areYouSureYouWantToSpend6": "{1} يريد العودة للتقارب معك. إذا قررت المصالحة، سيتم استعادة جميع بياناتك السابقة.", + "reconcileInvitationTips": "إذا الطرف الآخر رفض دعوة CP، غتترجع العملات ديالك للمحفظة ديالك.", + "reconcileInvitation": "دعوة للمصالحة", + "areYouSureYouWantToSpend4": "إرسال دعوة للمصالحة لهدا المستخدم؟", + "partWaysTips": "إذا قرر أحد الشريكين في العلاقة الانفصال، سيكون هناك فترة تهدئة لمدة 7 أيام. خلال هذه الفترة، يمكن للطرفين اختيار المصالحة، وسيتم استعادة كل البيانات عند المصالحة. إذا لم يتم اختيار المصالحة بنهاية فترة السبعة أيام، سيتم مسح بيانات الزوجين.", + "areYouSureYouWantToPartWaysWithYourCP": "هل أنت متأكد أنك تريد الانفصال عن شريكك؟", + "partWays": "افترقوا", + "firstDay": "اليوم الأول:{1}", + "timeSpentTogether": "الوقت المشترك: {1} أيام", + "areYouSureYouWantToSpend3": "إذا الطرف الآخر رفض دعوة CP، غتترجع العملات ديالك للمحفظة ديالك.", + "areYouSureYouWantToSpend2": "هل تريد إرسال دعوة CP لهذا المستخدم؟", + "cpRequest": "طلب CP", + "areYouSureYouWantToSpend": "هل أنت متأكد أنك تريد أن تنفق", + "cpSexTips": "لا يمكن تكوين أزواج من نفس الجنس.", + "underReview": "قيد المراجعة", + "doYouWantToDeleteIt": "هل تريد حذفه؟", + "myPhoto": "صورتي", + "balanceNotEnough": "رصيد العملات الذهبية غير كافي. واش بغيت تزود الرصيد؟", + "skip": "تخطي {1}", + "chooseFromAblum": "اختر من الألبوم", + "information": "معلومات", + "spaceBackground": "خلفية الفضاء", + "editProfile": "تعديل الملف الشخصي", + "sendTheCpRequest": "أرسل طلب CP", + "addCp": "أضف CP", + "medal": "ميدالية", + "numberOfMyCPs": "عدد نقاط السيطرة ديالي:({1}/{2})", + "relationShip": "علاقة", + "props": "الدعائم", + "couple2": "زوج", + "dice": "نرد", + "operationsAreTooFrequent": "العمليات متكررة بشكل مفرط", + "luckNumber": "رقم الحظ", + "rps": "حجر ورقة مقص", + "couple": "زوج {1}:", + "cpList": "قائمة CP", + "noMatchedCP": "لا يوجد CP مطابق", + "adminInviteRechargeAgent": "ندعوك لتصبح وكيلا لإعادة الشحن.", + "ownerIncomeCoins": "دخل المالك: {1} عملة", + "allGames": "كل الألعاب", + "fishClass": "فئة الأسماك", + "raceSeries": "سلسلة السباق", + "others": "آخرون", + "slotsClass": "فئة السلوتس", + "greedyClass": "الفئة الجشعة", + "hotGames": "الألعاب الرائجة", + "sent": "أُرسل", + "termsOfServicePrivacyPolicyTips": "بمتابعتك، أنت توافق على شروط الخدمة و سياسة الخصوصية", + "and": " اندر ", + "honor": "شرف", + "chatBox": "صندوق الشات", + "confirm": "تأكيد", + "cancel": "إلغاء", + "wearHonor": "ارتدِ الشرف", + "join": "انضم", + "vistors": "زوار", + "fans": "المعجبون", + "items": "عناصر", + "wearMedal": "ارتداء الميدالية", + "activityHonor": "شرف النشاط", + "youDontHaveAnyHonorYet": "أنت ما عندكش أي شرف حتى دابا.", + "achievementHonor": "شرف الإنجاز", + "letGoToWatch": "هيا نذهب للمشاهدة!", + "launchedARocket": "أطلق صاروخ", + "sendUserId": "أرسل معرف المستخدم:{1}", + "giveUpIdentity": "تخلى عن الهوية", + "leaveRoomIdentityTips": "هل أنت متأكد من أنك تريد التخلي عن هوية الغرفة؟", + "joinMemberTips2": "هل تريد تأكيد انضمامك إلى الغرفة كعضو؟", + "sureUnfollowThisRoom": "متأكد بغيتي تحيد المتابعة لهاذ الغرفة؟", + "settings": "الإعدادات", + "alreadyAnTourist": "أنت بالفعل سائح", + "deleteConversationTips": "هل أنت متأكد أنك تريد حذف سجل المحادثة مع هذا المستخدم؟", + "account": "أكونتي", + "youHaventFollowed": "أنت لم تتابع أي غرفة", + "messageHasBeenRecalled": "تم استرجاع هذه الرسالة", + "propMessagePrompt": "موجه رسالة دعائية", + "deleteFromMyDevice": "احذف من جهازي", + "deleteOnAllDevices": "حذف على جميع الأجهزة", + "inputUserId": "أدخل معرف المستخدم", + "common": "شائع", + "useCoupontips": "هل أنت متأكد أنك تريد استخدام القسيمة؟", + "searchUserId": "ابحث عن معرف المستخدم", + "receiveSucc": "تم المطالبة بنجاح", + "reject": "رفض", + "sendUser": "إرسال المستخدم", + "bio": "معلومات شخصية", + "delete": "حذف", + "sendCoupontips": "هل أنت متأكد أنك تريد إرسال هذه القسيمة لهذا المستخدم؟", + "recallThisMessage": "تذكر هذه الرسالة؟", + "copy": "نسخ", + "youDontHaveAnyCouponsYet": "ليس لديك أي كوبونات بعد", + "hobby": "هواية", + "recall": "استدعاء", + "accept": "اقبل", + "signedin": "تم التوقيع", + "following": "التالي", + "inviteYouToBecomeBD": "ندعوك لتصبح BD", + "confirmAcceptTheInvitation": "هل تريد تأكيد قبول الدعوة؟", + "confirmDeclineTheInvitation": "تأكيد رفض الدعوة؟", + "friends": "أصدقاء", + "language": "اللغة", + "feedback": "تعليق", + "coupon": "قسيمة", + "get": "احصل", + "couponRecord": "سجل استخدام القسيمة", + "inRoom": "في الغرفة", + "inRocket": "في الصاروخ", + "roomRocketHelpTips": "1. إرسال الهدايا في الغرفة يزيد من طاقة الصاروخ. *هدية واحدة من العملات الذهبية = نقطة طاقة صاروخية واحدة؛ الهدايا المحظوظة تزيد من طاقة الصاروخ بنسبة 4% من قيمة العملة الذهبية للهدية.\n2. بعد اكتمال شحن الطاقة، يمكنك إطلاق الصاروخ. سيتم توزيع المكافآت تلقائيًا بعد إطلاق الصاروخ.\n3. تقدم فئات الصواريخ المختلفة مكافآت مختلفة.\n4. عند إطلاق الصاروخ، يمكن لجميع المستخدمين في الغرفة المطالبة بمكافأة الصاروخ.\n5. سيتم تحديث طاقة الصاروخ في الساعة 0:00 كل يوم.", + "searchCouponHint": "ابحث عن كوبون", + "search": "بحث", + "checkInSuccessful": "تسجيل الوصول ناجح", + "about": "حوالي", + "inviteYouToBecomeHost": "ندعوك لتصبح مضيفًا", + "receive": "استقبل", + "sginTips": "ستحصل على المكافأة في أول مرة تسجل دخولك فيها كل يوم. إذا قمت بقطع تسجيل دخولك، ستُحسب المكافأة من أول يوم تسجل فيه دخولك مرة أخرى.", + "aboutUs": "حولنا", + "theme": "موضوع", + "home": "المنزل", + "luck": "حظ", + "bDLeaderInviteYouToBecomeBDLeader": "ندعوك لتصبح قائد تطوير الأعمال", + "win2": "فوز {1}", + "level": "مستوى", + "wealthLevel": "مستوى الثروة", + "userLevel": "مستوى المستخدم", + "vip": "شخصية مهمة", + "vip1": "VIP1", + "vip2": "VIP2", + "vip3": "VIP3", + "vip4": "VIP4", + "vip5": "VIP5", + "vip6": "VIP6", + "themeGoToUploadTips": "1.سيتم مراجعة التحميل خلال 24 ساعة بعد نجاحه. 2.\n سيتم إرجاع جميع العملات إذا فشلت المراجعة.", + "goToUpload": "اذهب لتحميل", + "rechargeAgency": "وكالة الشحن", + "logout": "تسجيل الخروج", + "adminCenter": "مركز الإدارة", + "explore": "استكشاف", + "micManagement": "إدارة الميكروفون", + "followList": "قائمة المتابعة", + "fansList": "قائمة المعجبين", + "pleaseSelectTheTypeContent": "يرجى اختيار نوع المحتوى المخالف", + "me": "أنا", + "socialPrivilege": "امتياز اجتماعي", + "spam": "بريد مزعج", + "inappropriateContent": "محتوى غير لائق", + "illegalInformation": "معلومات غير قانونية", + "personalAttack": "هجوم شخصي", + "identity": "الهوية", + "adjust": "تعديل", + "roomReward": "مكافأة الغرفة", + "insufhcientGoldsGoToRecharge": "الذهب غير كافي، عاود الشحن دابا!", + "received": "تم الاستلام", + "goToRecharge": "اذهب لإعادة الشحن", + "warning": "تحذير", + "ownerSendTheRedEnvelope": "المالك أرسل عملات المكافأة", + "rewardCoins": "عملات المكافأة:{1} عملة", + "lastWeekProgress": "تقدم الأسبوع الماضي", + "currentProgress": "التقدم الحالي", + "coins2": "{1} عملات", + "roomReward2": "مكافأة الغرفة:{1}", + "currentStage": "المرحلة الحالية:{1}", + "fraud": "احتيال", + "redEnvelopeTips2": "*إذا لم يتم المطالبة بالحقيبة الحمراء خلال المهلة المحددة، فسيتم إرجاع العملات المتبقية إلى المستخدم الذي أرسل الحقيبة الحمراء.", + "remainingNumberTips": "العدد المتبقي المتاح: ({1}/{2})", + "collectionTimeTips": "وقت الجمع:{1}({2}/{3})", + "sendARedEnvelope": "أرسل ظرفاً أحمر", + "sendRedPackConfirmTips": "هل أنت متأكد أنك تريد إرسال الحزمة الحمراء؟", + "redEnvelopeSendingRecords": "سجلات إرسال الظرف الأحمر:", + "countdownMinutes": "دقائق العد التنازلي:", + "number2": "رقم:", + "redEnvelopeRecTips2": "تم المطالبة بجميع المظاريف الحمراء.", + "redEnvelopeRecTips3": "انتهى وقت جمع الظرف الأحمر!", + "openTheTreasureChest": "أرسلت حقيبة حمراء!", + "redEnvelopeRecTips1": "تم إيداع العملات المكتسبة في محفظتك.", + "redEnvelope": "ظرف أحمر", + "redEnvelopeTips1": "عملات:", + "roomTools": "أدوات الغرفة:", + "entertainment": "الترفيه:", + "reportSucc": "تم الإبلاغ بنجاح", + "reportInputTips": "من فضلك وصف المشكل بأكبر قدر ممكن من التفاصيل باش نقدر نفهموه ونحلوه.", + "pornography": "الاباحية", + "screenshotTips": "لقطة شاشة (حتى 3)", + "roomEditing": "تحرير الغرفة", + "picture": "صورة", + "inputDesHint": "من فضلك وصف المشكلة بأكبر قدر ممكن من التفاصيل باش نقدر نفهموها ونحلها.", + "description": "الوصف:", + "roomNotice": "إشعار الغرفة", + "roomTheme": "موضوع الغرفة", + "roomProfilePicture": "صورة الملف الشخصي للغرفة", + "userProfilePicture": "صورة ملف المستخدم", + "userName": "اسم المستخدم", + "pleaseSelectTheTypeToProcess": "يرجى تحديد النوع لمعالجته:", + "userEditing": "تعديل المستخدم", + "enterTheUserId": "أدخل معرف المستخدم", + "enterTheRoomId": "أدخل رقم الغرفة", + "theImageSizeCannotExceed": "فشل التحميل: يجب أن يكون حجم الملف أقل من 2 ميغابايت.", + "bdLeader": "زعيم بنك التنمية", + "playGiftMusicAndDynamicMusic": "شغل موسيقى الهدايا والموسيقى الديناميكية", + "kickedOutOfRoom": "تم طرده من الغرفة", + "lockTheMic": "اقفل الميكروفون", + "openTheMic": "افتح الميكروفون", + "finish": "أنهى", + "removeTheMic": "أزل الميكروفون", + "leavelTheMic": "اترك الميكروفون", + "unlockTheMic": "افتح الميكروفون", + "muteTheMic": "كتم الميكروفون", + "inviteToTheMicrophone": "دعوة للتحدث في الميكروفون", + "takeTheMic": "خذ الميكروفون", + "openUserProfleCard": "افتح بطاقة ملف المستخدم", + "crop": "محصول", + "host": "مضيف", + "agent": "وكالة", + "ra": "را", + "bd": "بي دي", + "unread": "غير مقروء", + "read": "اقرأ", + "image": "[صورة]", + "video": "[فيديو]", + "sound": "[صوت]", + "gift2": "[هدية]", + "clickHereToStartChatting": "قل شيئًا.....", + "receivedAMessage": "[تلقى رسالة]", + "giftCounter": "عداد الهدايا", + "wins": "يفوز", + "deleteAccount": "حذف الحساب", + "becomeAgent": "أصبح وكيلا", + "male": "ذكر", + "setAccount": "تعيين الحساب", + "female": "أنثى", + "album": "ألبوم", + "loadingFailedClickToRetry": "فشل التحميل، انقر لإعادة المحاولة", + "releaseToLoadMore": "حرر للتحميل المزيد", + "haveMyLimits": "---لدي حدودي.---", + "pullToLoadMore": "اسحب للتحميل المزيد", + "enterNickname": "أدخل الاسم المستعار", + "camera": "كاميرا", + "system": "نظام", + "selectYourCountry": "اختر دولتك", + "inviteCode": "رمز الدعوة", + "yesterday": "أمس {1}", + "monday": "الاثنين {1}", + "tuesday": "الثلاثاء {1}", + "wednesday": "الأربعاء {1}", + "thursday": "الخميس {1}", + "friday": "الجمعة {1}", + "saturday": "السبت {1}", + "sunday": "الأحد {1}", + "notifcation": "إشعار", + "inviteYouToBecomeAgent": "ندعوك لتصبح وكالة", + "theVideoSizeCannotExceed": "لا يمكن أن يتجاوز حجم الفيديو 50 ميغابايت", + "fromLuckyGifts": "من الهدايا المحظوظة", + "confirmSwitchMicModelTips": "هل تؤكد تغيير وضعية الجلوس؟", + "classicMic": "ميكروفون كلاسيكي {1}", + "number": "رقم", + "micTheme": "ثيم المايك", + "chat": "دردشة", + "rejected": "مرفوض", + "disbandTheFamilyTips": "هل أنت متأكد أنك تريد تفكيك العائلة؟", + "familyMember3": "عضو العائلة({1}):", + "refuse": "رفض", + "boxContributeTips": "لقد تم الاستثمار بالفعل اليوم، رجاءً لا تستثمر مرة أخرى", + "familyHelpTips": "(1) إذا استثمرت 50 عملة، سيتم فتح صندوق الكنز الأول عند استثمار 5 أشخاص. يمكنك النقر لاستلام 50 عملة. (2) سيتم فتح صندوق الكنز الثاني عند استثمار 10 أشخاص. يمكنك استلام 50 عملة. (3) سيتم فتح صندوق الكنز الثالث عند استثمار 20 شخصًا. يمكنك استلام 100 عملة. (4) إذا لم يتحقق العدد المطلوب من المستخدمين للمطالبة بصندوق الكنز في نفس اليوم، سيتم إعادة ضبط تقدم صندوق الكنز في اليوم التالي. (5) سيتم إعادة ضبط تقدم صندوق الكنز في اليوم التالي. يجب على المستخدمين الذين لم يطالبوا بصناديق كنزهم في نفس اليوم المطالبة بها في الوقت المناسب. (6) وقت إعادة ضبط صندوق الكنز: 00:00 بتوقيت السعودية.", + "help": "مساعدة", + "approved": "معتمد", + "refuseJoinFamilyTips": "هل يجب أن ترفض السماح للمستخدم بالانضمام للعائلة؟", + "agreeJoinFamilyTips": "هل تسمح لهذا المستخدم بالانضمام إلى العائلة؟", + "onlineUsers": "المستخدمون عبر الإنترنت ({1}/{2}):", + "applyToJoin": "قدّم للانضمام", + "hostList": "قائمة المضيف", + "supporterList": "قائمة الداعمين", + "upToMembers": "حتى {1} أعضاء", + "upToAdmins": "حتى {1} مسؤولين", + "levelPrivileges": "امتيازات المستوى", + "familyLevel": "مستوى الأسرة", + "kickFamilyUserTips": "هل أنت متأكد أنك بغيت طرد هاد المستخدم من العائلة؟", + "kickOutOfFamily": "طرَد من العائلة", + "cancelFamilyAdmin": "إلغاء إدارة الأسرة", + "setAsFamilyAdmin": "تعيين كمسؤول العائلة", + "familyMember2": "عضو العائلة({1}/{2}):", + "familyAdmin2": "إدارة العائلة ({1}/{2}):", + "familyOwner2": "صاحب العائلة:", + "agree": "موافق", + "joinRequest": "طلب الانضمام", + "family": "عائلة", + "welcomeToMyFamily": "مرحبا بكم في عائلتي!", + "gameRules": "قوانين اللعبة:", + "gift": "هدية", + "charmGameRulesTips": "قم بتشغيل لوحة التحكم لعرض العملات الهدايا المستلمة لجميع المستخدمين على الميكروفون، 1 عملة = 1 نقطة (هدية محظوظة 1 عملة = 0.04 نقطة).", + "charm": "تعويذة هدية", + "chats": "الدردشات", + "myMusic": "موسيقاي", + "membershipFeeTips1": "من فضلك حدد رسوم الانضمام لغرفتك. يمكن للمستخدمين الانضمام إلى غرفتك من خلال دفع الرسوم.", + "membershipFeeTips2": "الذهب المطلوب للعضو في الغرفة. صاحب الغرفة سيحصل على 50٪ من الذهب.", + "membershipFee": "رسوم العضوية", + "touristsSendText": "السائح يرسل نص", + "freePrice": "الرسوم: 0-10000", + "pleaseChatFfriendly": "رجاءً تحدث بطريقة ودية", + "kickPrevention": "الوقاية من الركلات", + "vipRoomCoverBorder": "حدود غطاء غرفة كبار الشخصيات", + "vipChatBox": "صندوق الدردشة VIP", + "daily": "يوميًا", + "start": "ابدأ", + "win": "فوز", + "vipUseThisThemeTips": "فقط المستخدمين اللي وصلو لمستوى VIP يقدرو يستعملو هاد الثيم.", + "stop": "توقف", + "add": "أضف", + "cp": "سي بي", + "scrollToTheBottom": "قم بالتمرير إلى الأسفل", + "apple": "تفاحة", + "music": "موسيقى", + "free": "مجاناً", + "idIcon": "رمز التعريف", + "inviteToBecomeAHost": "ادعُ لتصبح مضيفًا", + "currentLevelPrivilegesAndCostumes": "امتيازات ومستويات الأزياء الحالية: {1}", + "uploadGifAvatar": "رفع صورة رمزية متحركة (VIP2)", + "permissionSettings": "إعدادات الإذن", + "goToUpgrade": "اذهب إلى الترقية", + "vipMicSoundWave": "موجة صوت الميكروفون VIP", + "google": "جوجل", + "uploadProfilePicture": "تحميل صورة الملف الشخصي", + "exclusiveEmojiWillBeReleasedAfterBecoming": "سيتم إصدار الرموز التعبيرية الحصرية بعد أن تصبح", + "preventBeingBlocked": "منع الحظر", + "enableRankIncognitoMode": "تمكين وضع الترتيب الخفي", + "avoidBeingKicked": "تجنب الركل", + "vipPrivilege": "جوجل", + "privileges": "{1} الامتيازات", + "andAboveUsers": "{1} والمستخدمون أعلاه", + "everyone": "الجميع", + "basicPermissions": "الأذونات الأساسية", + "mysteriousInvisibility": "الاختفاء الغامض", + "antiBlock": "مضاد للانسداد", + "createFamilyForFree": "أنشئ عائلة مجانًا", + "privateChat": "دردشة خاصة", + "vipBirthdayGift": "هدية عيد ميلاد لكبار الشخصيات", + "storeDiscount": "خصم المتجر {1}", + "membershipFreeChatSpeak": "دردشة وتحدث بدون عضوية", + "priorityRoomSorting": "فرز الغرف حسب الأولوية", + "userColoredID": "معرّف المستخدم الملون", + "vipEmoticon": "رمز تعبيري VIP({1})", + "setLoginPassword": "حدد كلمة مرور تسجيل الدخول", + "theTwoPasswordsDoNotMatch": "كلمتا المرور لا تتطابقتان.", + "resetLoginPasswordtTips2": "كلمة المرور يجب أن تكون بطول 8-16 حرف ويجب أن تكون مزيج من الأحرف الإنجليزية الكبيرة والصغيرة والأرقام (ليس فقط أرقام)", + "confirmYourPassword": "أكد كلمة المرور الخاصة بك", + "enterYourNewPassword": "أدخل كلمة مرورك الجديدة", + "setYourPassword": "عيّن كلمة المرور ديالك", + "enterYourOldPassword": "أدخل كلمة السر القديمة الخاصة بك", + "inputYourOldPassword": "أدخل كلمة المرور القديمة الخاصة بك", + "resetLoginPasswordtTips1": "سجل الدخول باستخدام معرف المستخدم أو معرف النمط. من الآمن أكثر تسجيل الدخول باستخدام حساب لايكي.", + "resetLoginPassword": "إعادة تعيين كلمة مرور تسجيل الدخول", + "localMusic": "الموسيقى المحلية", + "confirmSwitchMicThemeTips": "هل تؤكد تغيير نمط المقعد؟", + "pleaseUpgradeYourVipLevelFirst": "من فضلك قم بترقية مستوى VIP الخاص بك أولاً.", + "follow2": "اتبع:{1}", + "fans2": "المعجبون:{1}", + "vistors2": "الزوار:{1}", + "personal2": "شخصي:", + "dailyTaskRewardBonus": "مكافأة مهمة يومية ({1} نقطة خبرة)", + "userLevelXPBoost": "زيادة خبرة مستوى المستخدم ({1} XP)", + "enterRoomName": "أدخل اسم الغرفة", + "theMembershipFee": "رسوم العضوية", + "theModificationsMade": "التعديلات التي تمت هذه المرة لن يتم حفظها بعد الخروج.", + "touristsTakeToTheMic": "السياح ياخذو الميكروفون.", + "vipExclusiveVehicles": "سيارات حصرية مميزة", + "renewVip": "تجديد الاشتراك المميز", + "weekly": "أسبوعي", + "pleaseUpgradeYourVipLevel": "يرجى ترقية مستوى VIP الخاص بك.", + "conntinue": "استمر", + "logIn": "تسجيل الدخول", + "sayHi": "قُل مرحبا..", + "password": "كلمة السر", + "enterAccount": "دخول الحساب", + "enterPassword": "أدخل كلمة المرور", + "roomName": "اسم الغرفة", + "enterRoomTips": "{1} ادخل الغرفة", + "startVoiceParty": "ابدأ حفلة صوتية!", + "freeChatSpeak": "دردش بحرية وتحدث", + "roomMember": "عضو الغرفة", + "vipProfileCard": "بطاقة تعريف الشخصيات البارزة", + "popular": "مشهور", + "transactionReceived": "تم استلام المعاملة", + "rechargeSuccessful": "تم إعادة الشحن بنجاح", + "buyVip": "اشترِ VIP", + "vipProfileFrame": "إطار ملف تعريف الشخصيات المهمة", + "vipBadge": "شارة VIP", + "hotRooms": "غرف حارة", + "viewMore": "عرض المزيد", + "noData": "لا توجد بيانات", + "recommend": "يوصي", + "follow": "تابع", + "monthly": "شهريًا", + "message": "رسالة", + "bdCenter": "مركز BD", + "history": "تاريخ", + "users": "المستخدمون", + "rooms": "غرف", + "days": "أيام", + "permanent": "دائم", + "unFollow": "إلغاء المتابعة", + "yourVipWillExpire": "ستنتهي صلاحية العضوية الخاصة بك في {1}", + "cantBuyVip": "غير قادر على استكمال الشراء", + "searchInputHint": "أدخل رقم الحساب / الغرفة", + "kickRoomTips": "لقد تم طردك من الغرفة", + "joinRoomTips": "غرفة متضامة!", + "roomSetting": "تجهيز الغرفة", + "roomDetails": "تفاصيل الغرفة", + "systemRoomTips": "يرجى الحفاظ على المجاملة والاحترام. أي محتوى إباحي أو فاحش ممنوع تمامًا على Aslan. أي حساب يُكتشف مخالفًا سيتم حظره بشكل دائم. نرجو من جميع المستخدمين الالتزام بضوابط مجتمع Aslan بوعي.", + "copiedToClipboard": "تم النسخ إلى الحافظة", + "recharge": "إعادة الشحن", + "agentCenter": "مركز الوكالة", + "report": "تقرير", + "done": "تمّ", + "improvementTasks": "مهام التحسين", + "followedYou": "تابعتك", + "followSucc": "تمت المتابعة بنجاح", + "edit": "تحرير", + "task": "مهمة", + "save": "احفظ", + "go": "اذهب", + "deleteAccount2": "حذف الحساب({1}s)", + "areYouSureYouWantToDeleteYourAccount": "هل أنت متأكد أنك تريد حذف حسابك؟", + "enterRoomConfirmTips": "هل أنت متأكد أنك تريد دخول الغرفة؟", + "dailyTasks": "المهام اليومية", + "nickName": "لقب", + "giftSpecialEffects": "هدية المؤثرات الخاصة", + "country": "دولة", + "basicFeatures": "الميزات الأساسية", + "floatingAnimationInGlobal": "الرسوم المتحركة العائمة في العالم", + "joinMemberTips": "إذا كنت زائرًا في الغرفة، لا يمكنك أخذ الميكروفون.", + "countryRegion": "البلد والمنطقة", + "gender": "جنس", + "likedYourDynamic": "أعجبني تعليقك.", + "likedYourComment": "أعجبتني طاقتك.", + "deleteAccountTips2": "*إذا غيرت رأيك، يمكنك تسجيل الدخول مرة أخرى إلى حسابك الحالي خلال سبعة أيام، وسنقوم تلقائيًا باستعادة حسابك. إذا لم تتم عملية الاستعادة خلال سبعة أيام، فسيتم حذف الحساب نهائيًا", + "deleteAccountTips": "لديك صلاحيات إدارية كاملة على هذا الحساب. إذا كنت تنوي حذف الحساب، يُرجى الانتباه إلى المخاطر التالية المرتبطة بهذه العملية:\n\n1. بمجرد حذف الحساب بنجاح، لن تتمكن من تسجيل الدخول إليه. حذف الحساب إجراء نهائي.\n\n2. بعد حذف الحساب بنجاح، لن تتمكن من استعادة أي بيانات. سيتم حذف جميع المعلومات (بما في ذلك الغرف، والأصدقاء)، والعملة الافتراضية، والهدايا، والعناصر الافتراضية نهائيًا ولن يكون بالإمكان استعادتها.\n\n3. فترة السماح: إذا لم تقم باستعادة الحساب، فلن تتمكن من الوصول إلى صفحة الشراء، أو صفحة السحب، أو أي صفحات أخرى في التطبيق.\n\n4. خلال فترة السماح أو بعد حذف الحساب، ستشير صفحة الملف الشخصي إلى أنه قد تم حذفه. لحماية حسابك من البحث أو الوصول إليه من قِبل الآخرين، سيتم حذف معلوماتك الشخصية من الأنظمة المتعلقة بالوظائف اليومية. عندما يتعلق حذف الحساب بالأمن القومي، أو الإجراءات المدنية أو الجنائية، أو حماية الحقوق والمصالح المشروعة لأطراف ثالثة، يحتفظ المسؤول بحقه في رفض طلب حذف حساب المستخدم.\n\nإذا كنت متأكدًا من رغبتك في حذف جميع بياناتك الشخصية من حسابك الحالي، يُرجى النقر على \"حذف الحساب\".", + "accountDeletionNotice": "إشعار حذف الحساب:", + "cantSendDynamicTips": "تم حظرك حالياً من نشر الديناميكيات.", + "entryVehicleAnimation2": "يمكن للمستخدمين الحاصلين على صلاحيات VlP3 أو أعلى استخدام هذه الوظيفة لتعطيل رسوميات المركبات المتحركة.", + "entryVehicleAnimation": "رسوم متحركة لمركبة الدخول (VIP3)", + "man": "رجل", + "woman": "امرأة", + "all": "الكل", + "activity": "نشاط", + "knapsack": "حقيبه", + "areYouRureRoRecharge": "هل أنت متأكد من شحن ؟", + "clearMessage": "مسح رسائل الشاشة", + "pleaseSelectaItem": "يرجى اختيار البند", + "birthday": "عيد ميلاد", + "pleaseEnterNickname": "يرجى إدخال لقب.", + "pleaseSelectYourCountry": "يرجى تحديد بلدك.", + "pleaseSelectYourGender": "يرجى تحديد جنسك.", + "dateOfBirth": "تاريخ الميلاد", + "mute": "صامت", + "exit": "خروج", + "send": "أرسل", + "goldList": "القائمة الذهبية", + "coins": "النقود المعدنية", + "lockTheRoom": "أغلق الغرفة", + "giftGivingSuccessful": "إن تقديم الهدايا كان ناجحًا.", + "hostCenter": "مركز الاستضافة", + "allOnMicrophone": "الجميع على الميكروفون", + "usersOnMicrophone": "المستخدمون على الميكروفون", + "mInimize": "احتفظ", + "shop": "متجر", + "expirationTime": "وقت الانتهاء", + "roomOwner": "مالك الغرفة", + "owner": "مالك", + "admin": "المشرف", + "member": "عضو", + "guest": "ضيف", + "submit": "تقديم", + "enter": "دخل", + "unLockTheRoom": "افتح الغرفة", + "setRoomPassword": "تعيين كلمة مرور الغرفة", + "inputRoomPassword": "إدخال كلمة مرور الغرفة", + "operationSuccessful": "كانت العملية ناجحة.", + "adminByHomeowner": "يتم تعيين كمسؤول من قبل مالك المنزل.", + "memberByHomeowner": "يتم تعيين كأعضاء من قبل صاحب المنزل.", + "touristByHomeowner": "تم تعيين كسائح من قبل صاحب المنزل.", + "setUpAnIdentity": "أعد إعداد هوية.", + "allInTheRoom": "كل شيء في الغرفة.", + "theAccountPasswordCannotBeEmpty": "لا يمكن أن يكون الحساب أو كلمة المرور خالية.", + "goldListort": "القائمة الذهبية", + "rechargeList": "قائمة إعادة الشحن", + "becomeHost": "قدّم لتصبح مضيفًاً", + "alreadyAnAdministrator": "مسؤول بالفعل.ً", + "alreadyAnMember": "أنت بالفعل عضو.ً", + "touristsCannotSendMessages": "لا يستطيع السائحون إرسال الرسائل", + "touristsAreNotAllowedToGoOnTheMic": "لا يسمح للسياح بالدخول إلى الميكروفون", + "superFans": "المشجعين الخارقين:ً", + "special": "خاص", + "custom": "مخصص", + "store": "دكان", + "medalAndAvatarFrameRewards": "جوائز الميداليات وإطارات الصور الشخصية", + "viewFrame": "عرض الإطار", + "headdress": "إطارات", + "mountains": "مركبات", + "buy": "ابتاع", + "visitorList": "قائمة الزوار", + "spendCoinsToGainExperiencePoints": "اصرف العملات لكسب نقاط الخبرة", + "howToUpgrade": "كيف يمكن الترقية؟", + "higherLevelFancierAvatarFrame": "مستوى أعلى، شارات/إطار صور أفخم.", + "use": "استخدم", + "expired": "منتهي", + "day": "يوم", + "obtain": "الحصول على", + "backTheRoom": "الغرفة الخلفية", + "toConsume": "الاستهلاك", + "profile": "بروفايل", + "wallet": "محفظة", + "giftwall": "جيفت وول", + "announcement": "إعلان", + "blockedList": "قائمة محظورة", + "renewal": "تجديد", + "country2": "بلد:", + "medals": "الميداليات", + "activityMedal": "ميدالية النشاط", + "achievementMedal": "ميدالية الإنجاز", + "sendTo": "أرسل إلى", + "credits": "الاعتمادات: {1}", + "successfullyUnloaded": "تم تفريغها بنجاح", + "unUse": "استخدام واحد", + "successfulWear": "ملابس ناجحة", + "confirmUnUseTips": "هل تؤكد على إزالته؟", + "inUse": "قيد الاستخدام", + "confirmUseTips": "هل تريد التأكيد على استخدامه؟", + "pleaseUploadUserAvatar": "يرجى رفع صورة شخصية.", + "myItems": "أشيائي", + "confirmBuyTips": "هل أنت متأكد من أنك تريد الشراء؟", + "purchaseIsSuccessful": "الشراء ناجح", + "purchase": "ابتاع", + "invitesYouToTheMicrophone": "{1} يدعوك إلى الميكروفون", + "english": "الإنجليزية", + "chinese": "الصينية", + "arabic": "العربية", + "darkMode": "الوضع الداكن", + "lightMode": "الوضع الفاتح", + "systemDefault": "النظام الافتراضي", + "pleaseGetOnTheMicFirst": "من فضلك استخدم الميكروفون أولاً.", + "duration2": "المدة:{1}" +} \ No newline at end of file diff --git a/assets/l10n/intl_bn.json b/assets/l10n/intl_bn.json new file mode 100644 index 0000000..9d62970 --- /dev/null +++ b/assets/l10n/intl_bn.json @@ -0,0 +1,768 @@ +{ + "signInWithGoogle": "Google দিয়ে লগইন করুন", + "or": "অথবা", + "signInWithYourAccount": "আপনার অ্যাকাউন্ট দিয়ে লগইন করুন", + "signInWithApple": "Apple দিয়ে লগইন করুন", + "loginRepresentsAgreementTo": "লগইন করলে আপনি নিম্নলিখিতগুলি স্বীকার করছেন:", + "termsofService": "সেবা শর্তাবলী", + "privaceyPolicy": "গোপনীয়তা নীতি", + "tips": "টিপস", + "mine": "আমার", + "sound2": "শব্দ", + "dailyTasksTips": "নোট: কাজ মোকাবেলা করার জন্য প্রস্তুত? আপনার প্রিয় রুমগুলোতে সেগুলো খুঁজুন।", + "games": "খেলাধুলা", + "party": "পার্টি", + "hot": "গরম", + "selectCountry": "দেশ নির্বাচন করুন", + "list": "তালিকা", + "giftVibration": "লাকি উপহার প্রভাব", + "winFloat": "উইন ফ্লোট", + "giftEffect": "উপহার প্রভাব", + "walletBalance": "ওয়ালেট ব্যালেন্স:", + "canOnlyBeShown": "*শুধুমাত্র আপনার এলাকায় সমস্ত ব্যবহারকারীদের দেখানো যেতে পারে।", + "sendMessage": "বার্তা পাঠান", + "availableCountries": "উপলব্ধ দেশসমূহ:", + "currentBalance": "বর্তমান ব্যালেন্স:", + "successfulTransaction": "{1} সফল লেনদেন", + "hasBeenACoinAgencyForDays": "{1} দিনের জন্য একটি কয়েন এজেন্সি হয়েছে", + "rechargeRecords": "রিচার্জ রেকর্ড", + "balance": "সমতা:", + "searchCountry": "দেশ খুঁজুন...", + "appStore": "অ্যাপ স্টোর", + "googlePay": "গুগল পে", + "biggestDiscount": "সর্ববৃহৎ ছাড়", + "officialRechargeAgent": "সরাসরি রিচার্জ এজেন্ট", + "selectACountry": "একটি দেশ নির্বাচন করুন:", + "playLog": "খেলার লগ:", + "vipAccelerating": "{1} দ্রুত করা", + "badge": "ব্যাজ", + "glory": "গৌরব", + "luckGiftRuleTips": "লাকি গিফট দিন এবং সোনার কয়েন পুরস্কার সর্বোচ্চ ১০০০ গুণ পর্যন্ত জিতে নিন! লাকি গিফট প্রাপ্ত ব্যবহারকারীরা নিম্নলিখিত সুবিধা উপভোগ করতে পারবেন: \n(1) লাকি গিফট প্রাপ্ত ব্যবহারকারীদের জন্য: উপহারের মূল্যের ভিত্তিতে +4% চার্ম অভিজ্ঞতা মান। \n(2) যদি প্রাপক হোস্ট হন: উপহারের মূল্যের ভিত্তিতে +4% হোস্ট বেতন লক্ষ্য মান।", + "vipRippleTheme": "ভিআইপি রিপল থিম", + "gifProfileUpload": "জিআইএফ প্রোফাইল আপলোড", + "badgeHonor": "ব্যাজ/গৌরব", + "levelMedal": "স্তরের পদক", + "levelIcon": "স্তরের আইকন", + "searchNoDataTips": "আপনি যে রুম বা ব্যবহারকারীর আইডি অনুসন্ধান করতে চান তা লিখুন।", + "event": "ইভেন্ট", + "other": "অন্য", + "maliciousHarassment": "দূরাচার হয়রানি", + "roomEdit": "রুম এডিট", + "cancelRoomPassword": "আপনি কি নিশ্চিত যে আপনি রুমের পাসওয়ার্ডটি মুছে ফেলতে চান?", + "roomMemberFee": "রুম সদস্য ফি", + "blockedList2": "ব্লক করা তালিকা", + "roomTheme2": "রুম থিম", + "roomPassword": "রুম পাসওয়ার্ড", + "numberOfMic": "মাইকের সংখ্যা", + "pleaseEnterContent": "অনুগ্রহ করে বিষয়বস্তু লিখুন", + "profilePhoto": "প্রোফাইল ছবি", + "aboutMe": "আমার সম্পর্কে", + "noHistoricalRecordsAvailable": "কোনও ঐতিহাসিক রেকর্ড পাওয়া যায়নি।", + "inviteNewUsersToEarnCoins": "নতুন ব্যবহারকারীকে আমন্ত্রণ করুন কয়েন উপার্জনের জন্য", + "crateMyRoom": "আমার ঘর তৈরি করো", + "myRoom": "আমার ঘর", + "vipSpecialGiftTassel": "ভিআইপি বিশেষ উপহার ট্যাসেল", + "vipEntranceEffect": "ভিআইপি প্রবেশের প্রভাব", + "youHaveNotHadVIPYet": "আপনি এখনও ভিআইপি গ্রহণ করেননি, আসুন এবং এটি চেষ্টা করুন", + "casualInteraction": "সাধারণ আলোচনাচিত্র", + "haveGamePlayingTips": "আপনার গেমটি চলমান আছে, দয়া করে প্রথমে বর্তমান গেমটি ছাড়ুন, আপনি কি নিশ্চিতভাবে বের হতে চান?", + "historicalTour": "ঐতিহাসিক ভ্রমণ", + "gameCenter": "গেম সেন্টার", + "returnToVoiceChat": "ভয়েস চ্যাটে ফিরে যেতে চাই?", + "exitGameMode": "গেম মোড থেকে বের হন", + "enterTheRoom": "ঘরে প্রবেশ করো", + "honor": "সম্মান", + "inviteGoRoomTips": "সব সময় তোমার জন্য এখানে আছি, বৃষ্টি হোক বা রোদ। এসে সালাম জানাও!", + "invite": "আমন্ত্রণ করা", + "confirmInviteThisUserToTheRoom": "এই ব্যবহারকারী (ID:{1}) কে রুমে নিমন্ত্রণ নিশ্চিত করবেন?", + "clearCacheSuccessfully": "ক্যাশ সফলভাবে মুছে ফেলা হয়েছে", + "sent": "পাঠানো হয়েছে", + "keep": "রাখুন", + "open": "খুলুন", + "deleteAccountTips2": "*আপনি যদি মন পরিবর্তন করেন, তবে সাত দিনের মধ্যে আপনার বর্তমান অ্যাকাউন্টে আবার লগইন করতে পারবেন এবং আপনার অ্যাকাউন্ট স্বয়ংক্রিয়ভাবে পুনরুদ্ধার করা হবে। সাত দিনের মধ্যে পুনরুদ্ধার না করলে, অ্যাকাউন্ট স্থায়ীভাবে মুছে ফেলা হবে", + "deleteAccountTips": "আপনার এই অ্যাকাউন্টে সম্পূর্ণ প্রশাসনিক অধিকার রয়েছে। আপনি যদি অ্যাকাউন্ট মুছে ফেলতে চান, তবে এই ক্রিয়াটির সাথে সম্পর্কিত নিম্নলিখিত ঝুঁকিগুলি কृপয়া ভুলবেন না:\n1.অ্যাকাউন্ট সফলভাবে মুছে ফেলা হলে, আপনি আর আপনার বর্তমান অ্যাকাউন্টে লগইন করতে পারবেন না। অ্যাকাউন্ট মুছে ফেলা একটি স্থায়ী ক্রিয়া।\n2. অ্যাকাউন্ট সফলভাবে মুছে ফেলা হলে, আপনি অ্যাকাউন্টের কোনো ডেটা পুনরুদ্ধার করতে পারবেন না। সমস্ত তথ্য (রুম, বন্ধু সহ), ভার্চুয়াল মুদ্রা, উপহার এবং ভার্চুয়াল আইটেম স্থায়ীভাবে মুছে ফেলা হবে এবং পুনরুদ্ধার করা যাবে না।\n3. কুলডাউন সময়: আপনি যদি অ্যাকাউন্ট পুনরুদ্ধার না করেন, তবে আপনি অ্যাপের ক্রয় পৃষ্ঠা, পেমেন্ট উত্তোলন পৃষ্ঠা বা অন্য কোনো পৃষ্ঠায় অ্যাক্সেস করতে পারবেন না।\n4.কুলডাউন সময়কালীন বা অ্যাকাউন্ট মুছে ফেলার পর, প্রোফাইল পৃষ্ঠা এটি মুছে ফেলা হয়েছে বলে দেখাবে। আপনার অ্যাকাউন্ট অন্যদের দ্বারা সনাক্ত বা অ্যাক্সেস করা থেকে বাধা দেওয়ার জন্য, আপনার ব্যক্তিগত তথ্য দৈনন্দিন ফাংশন সম্পর্কিত সিস্টেম থেকে মুছে ফেলা হবে। অ্যাকাউন্ট মুছে ফেলা জাতীয় নিরাপত্তা, নাগরিক বা দণ্ডী মামলা বা তৃতীয় পক্ষের বৈধ অধিকার ও সুবিধার রক্ষণসংস্কারকে অন্তর্ভুক্ত করলে, সরকারি প্রশাসন ব্যবহারকারীর অ্যাকাউন্ট মুছে ফেলার অনুরোধ প্রত্যাখ্যান করার অধিকার সংরক্ষণ করে।\nআপনি যদি আপনার বর্তমান অ্যাকাউন্টের সমস্ত ব্যক্তিগত ডেটা মুছে ফেলতে নিশ্চিত হন, তবে কृপয়া \"অ্যাকাউন্ট মুছে ফেলুন\" বোতামে ক্লিক করুন।", + "accountDeletionNotice": "অ্যাকাউন্ট মুছে ফেলার নোটিশ:", + "thisUserHasBeenBlacklisted": "এই ব্যবহারকারী ব্ল্যাকলিস্টে যুক্ত করা হয়েছে।", + "trend": "ট্রেন্ড", + "like": "পছন্দ করুন", + "more": "আরও", + "discard": "বাতিল করুন", + "catchFirstComment": "প্রথম মন্তব্য ক্যাচ করুন", + "reply": "উত্তর দিন", + "posting": "পোস্ট করা হচ্ছে", + "dynamic": "গতিশীল", + "multiple": "একাধিক", + "successfullyAddedToTheDynamicBlacklist": "গতিশীল ব্ল্যাকলিস্টে সফলভাবে যুক্ত করা হয়েছে!", + "successfullyRemovedFromTheBlacklist": "ব্ল্যাকলিস্ট থেকে সফলভাবে সরানো হয়েছে!", + "successfullyRemovedFromTheDynamicBlacklist": "গতিশীল ব্ল্যাকলিস্ট থেকে সফলভাবে সরানো হয়েছে!", + "successfullyAddedToTheBlacklist": "ব্ল্যাকলিস্টে সফলভাবে যুক্ত করা হয়েছে!", + "youAreCurrentlyCPRelationshipPleaseDissolve": "আপনি বর্তমানে একটি CP সম্পর্কে আছেন।\nকृপয়া প্রথমে সম্পর্কটি সমাধান করুন।", + "areYouSureToCancelBlacklist": "আপনি কি ব্ল্যাকলিস্ট বাতিল করতে চান?", + "areYouSureYouWantToBlockThisUser": "আপনি কি এই ব্যবহারকারীকে ব্লক করতে চান?", + "areYouSureYouWantToDynamicBlockThisUser": "আপনি কি এই ব্যবহারকারীকে গতিশীল ব্ল্যাকলিস্টে যুক্ত করতে চান?", + "removeFromBlacklist": "ব্ল্যাকলিস্ট থেকে সরান", + "moveToBlacklist": "ব্ল্যাকলিস্টে যুক্ত করুন", + "userBlacklist": "ব্যবহারকারী ব্ল্যাকলিস্ট", + "specialEffectsManagement": "বিশেষ প্রভাব ব্যবস্থাপনা", + "wishingYouHappinessEveryDay": "আপনাকে প্রতিদিন সুখের কামনা করছি।", + "newMessage": "নতুন বার্তা", + "createRoomSuccsess": "রুম সফলভাবে তৈরি হয়েছে!", + "contactUs": "আমাদের সাথে যোগাযোগ করুন", + "systemAnnouncementTips1": "ধোঁকাধন্ডা থেকে সতর্ক:", + "systemAnnouncementTips": "শুধুমাত্র অফিসিয়াল চ্যানেলের মাধ্যমে তথ্য যাচাই করুন। কখনওই তৃতীয় পক্ষের সফটওয়্যার ডাউনলোড করবেন না, ব্যক্তিগত তথ্য শেয়ার করবেন না বা বাহ্যিক অনুরোধের উপর অর্থ ট্রান্সফার করবেন না। অফিসিয়াল কর্মী আইডি শুধুমাত্র 10000, 10003 এবং 10086। আপনার কোনো সন্দেহ থাকলে, ক্রিয়াটি বন্ধ করুন এবং এর মাধ্যমে রিপোর্ট করুন", + "systemAnnouncement": "সিস্টেম ঘোষণা", + "doNotClickUnfamiliarTips": "অপরিচিত লিঙ্কে ক্লিক করবেন না, কারণ এগুলি আপনার ব্যক্তিগত তথ্য প্রকাশ করতে পারে। আপনার আইডি নম্বর বা ব্যাংক কার্ডের বিবরণ কখনওই কারো সাথে শেয়ার করবেন না।", + "atTag": "@ট্যাগ", + "sayHi2": "হাই", + "canSendMsgTips": "ব্যক্তিগত বার্তা পাঠানোর জন্য উভয় পক্ষকে একে অপরকে ফলো করতে হবে।", + "msgSendRedEnvelopeTips": "*লাল খাম之上 10% সেবা ফি কেটে নেওয়া হবে এবং প্রাপক শুধুমাত্র লাল খামের মানের 90% পাবেন। প্রেরকের সম্পদ স্তর 10 তম স্তরের থেকে বেশি হতে হবে।", + "leavFamilyTips": "আপনি কি আপনার বর্তমান ফ্যামিলি ছেড়ে দিতে চান? ফ্যামিলিতে আবার যোগদান করতে আপনাকে 1 দিন অপেক্ষা করতে হবে।", + "leavingTheFamily": "ফ্যামিলি ছেড়ে দেওয়া", + "familyNotifcations": "ফ্যামিলি নোটিফিকেশন", + "familyNews": "ফ্যামিলি নিউজ", + "reapply": "আবার আবেদন করুন", + "cancelRequestFamilyMsg": "{1} ফ্যামিলিতে যোগদানের আপনার অনুরোধ বিচার করা হচ্ছে, আপনি কি অনুরোধটি বাতিল করতে চান?", + "cancelRequest": "অনুরোধ বাতিল করুন", + "pending": "পেন্ডিং", + "familyAnnouncement": "ফ্যামিলি ঘোষণা", + "enterFamilyAnnouncement": "কृপয়া ফ্যামিলি ঘোষণা লিখুন।", + "disbandTheFamily": "ফ্যামিলি ভেঙে ফেলুন", + "editFamily": "ফ্যামিলি সম্পাদনা করুন", + "supporter": "সমর্থক", + "transactionReceived": "লেনদেন গ্রহণ করা হয়েছে", + "rechargeSuccessful": "রিচার্জ সফল", + "createFamilySuccess": "ফ্যামিলি সফলভাবে তৈরি হয়েছে।", + "numberOfSign": "সাইন ইন সংখ্যা: {1}", + "hostWeeklyRank": "হোস্ট সাপ্তাহিক র‌্যাঙ্ক", + "supporterWeeklyRank": "সমর্থক সাপ্তাহিক র‌্যাঙ্ক", + "memberList": "সদস্য তালিকা", + "treasureChest": "সম্পদ বাক্স", + "xxfamily": "{1} এর ফ্যামিলি", + "applicationRecord": "আবেদন রেকর্ড", + "createFamily": "ফ্যামিলি তৈরি করুন", + "familyName": "ফ্যামিলি নাম", + "createAFamily": "ফ্যামিলি তৈরি করুন", + "searchFamilyIdHint": "কृপয়া ফ্যামিলি মালিকের আইডি নম্বর লিখুন", + "enterFamilyInfo": "কृপয়া আপনার ফ্যামিলিকে সংক্ষিপ্তভাবে পরিচিত করান!", + "enterFamilyName": "কृপয়া আপনার ফ্যামিলি নাম লিখুন।", + "familyInfo": "ফ্যামিলি পরিচিতি", + "joinFamily": "ফ্যামিলিতে যোগদান করুন", + "appUpdateTip": "অ্যাপের একটি নতুন সংস্করণ আছে ({1}), কি ডাউনলোড করবেন?", + "ownerIncomeCoins": "মালিকের আয়:{1} কয়েন", + "game": "গেম", + "skip2": "স্কিপ করুন", + "coins4": "কয়েন", + "claim": "দাবি", + "complete": "সম্পূর্ণ", + "shareTo": "শেয়ার করুন", + "copyLink": "লিঙ্ক কপি করুন", + "currentVip": "বর্তমান VIP", + "weekStart": "সপ্তাহের শুরু", + "faceBook": "ফেসবুক", + "whatsApp": "হোয়াটসঅ্যাপ", + "snapChat": "স্ন্যাপচ্যাট", + "taskNameRoomNewMember": "রুমে নতুন সদস্যের সংখ্যা", + "taskNameRoomOwnerSendRedPacket": "রুম মালিক একটি লাল লিফলেট পাঠান", + "taskNameRoomOwnerSendGiftUser": "রুম মালিক উপহার পাঠান", + "taskNameRoomMicUser120Min": "১২০+ মিনিট মাইকে কাটানো সদস্য", + "taskNameRoomMicUser60Min": "৬০+ মিনিট মাইকে কাটানো সদস্য", + "taskNameRoomMicUser30Min": "৩০+ মিনিট মাইকে কাটানো সদস্য", + "taskNamePersonalSendGift": "অন্যের রুমে মাইক্রোফোনে আসুন", + "taskNameRoomOnlineUserCount": "রুমের অনলাইন সদস্যরা", + "taskNameRoomOwnerInviteMic": "সদস্যকে মাইকে আমন্ত্রণ করুন", + "taskNameRoomUserSendGiftGold": "সদস্যদের দেওয়া উপহার কয়েন", + "taskNameRoomOwnerSendGiftGold": "রুম মালিকের দেওয়া উপহার কয়েন", + "taskNamePersonalMagicGiftGold": "যাদুকরী উপহার পাঠিয়ে জয় করা কয়েন", + "taskNamePersonalLuckyGiftGold": "ভাগ্যবান উপহার পাঠিয়ে জয় করা কয়েন", + "taskNameRoomOwnerMicTime": "রুমের মালিক রুমে মাইকে কথা বলেন", + "taskNamePersonalActiveInRoom": "অন্যের রুমে সক্রিয় হোন", + "taskNamePersonalGameConsume": "গেম ব্যয়", + "taskNamePersonalMicInRoom": "মাইকে যান", + "forMoreRewardsPleaseCheckTheTaskCenter": "আরও পুরস্কারের জন্য কृপয়া টাস্ক সেন্টার চেক করুন", + "kingQuuen": "রাজা-রানী", + "dailyCoinBonanzaRulesDetail": "১. দৈনিক ব্যক্তিগত কাজ এবং দৈনিক রুম মালিকের কাজ প্রতিটি ব্যবহারকারীর জন্য দিনে একবার সম্পন্ন করা যেতে পারে। কাজগুলি সৌদি সময় ০০:০০:০০ এ রিসেট হয়।\n২. দৈনিক ব্যক্তিগত কাজ কেবল অন্যের রুমে সম্পন্ন করা যায়; রুম মালিকের কাজ কেবল আপনার নিজের রুমে সম্পন্ন করা যায়।\n৩. উপহার দেওয়ার কাজগুলিতে কেবল রুমে পাঠানো উপহারগুলি গণ্য হবে, ফিডে পাঠানো উপহার নয়।\n৪. যদি একই ডিভাইস বা একই সিম কার্ড ব্যবহার করে একাধিক অ্যাকাউন্ট তৈরি করা হয়, সমস্ত কাজের পুরস্কার কেবল একবারই দাবি করা যেতে পারে।", + "dailyCoinBonanzaRules": "দৈনিক কয়েন বোনাঞ্জার নিয়ম", + "roomOwnerTasks": "রুম মালিকের কাজ", + "personalTasks": "ব্যক্তিগত কাজ", + "getPaidToRefer": "প্রস্তাব দেওয়ার জন্য অর্থ উপার্জন করুন", + "ramadan": "রমজান", + "noPromptsToday": "আজ কোনো প্রম্পট নেই।", + "updateNow": "এখন update করুন", + "allGames": "সমস্ত গেম", + "fishClass": "মাছ শ্রেণী", + "greedyClass": "লোভী শ্রেণী", + "raceSeries": "রেস সিরিজ", + "slotsClass": "স্লট শ্রেণী", + "others": "অন্যান্য", + "hotGames": "হট গেমস", + "chatBox": "চ্যাট বক্স", + "termsOfServicePrivacyPolicyTips": "এগিয়ে যাওয়ার মাধ্যমে আপনি সেবা শর্তাবলী এবং গোপনীয়তা নীতি স্বীকার করছেন", + "and": " এবং ", + "pleaseSelectTheTypeContent": "কृপয়া লঙ্ঘনকারী কন্টেন্টের টাইপ নির্বাচন করুন।", + "wearHonor": "সম্মান পরুন", + "illegalInformation": "বেআইনি তথ্য", + "inappropriateContent": "অনুপযুক্ত কন্টেন্ট", + "personalAttack": "ব্যক্তিগত আক্রমণ", + "confirm": "নিশ্চিত করুন", + "spam": "স্প্যাম", + "countdownMinutes": "কাউন্টডাউন মিনিট :", + "number2": "নম্বর:", + "fraud": "ধোঁকাধন্ডা", + "received": "প্রাপ্ত হয়েছে", + "currentProgress": "বর্তমান অগ্রগতি", + "currentStage": "বর্তমান স্টেজ:{1}", + "roomReward2": "রুম পুরস্কার:{1}", + "roomReward": "রুম পুরস্কার", + "ownerSendTheRedEnvelope": "মালিক পুরস্কার কয়েন পাঠিয়েছেন।", + "rewardCoins": "পুরস্কার কয়েন:{1} কয়েন", + "lastWeekProgress": "গত সপ্তাহের অগ্রগতি", + "redEnvelopeTips2": "*যদি লাল ব্যাগ সময়সীমার মধ্যে দাবি না করা হয়, তবে বাকি কয়েনগুলি সেই ব্যবহারকারীর কাছে ফেরত পাঠানো হবে যিনি লাল ব্যাগটি পাঠিয়েছেন।", + "goToRecharge": "রিচার্জ করতে যান", + "deleteAccount2": " অ্যাকাউন্ট মুছে ফেলুন({1}সেকেন্ড)", + "areYouSureYouWantToDeleteYourAccount": " আপনি কি আপনার অ্যাকাউন্ট মুছে ফেলতে চান?", + "insufhcientGoldsGoToRecharge": "সোনা অপর্যাপ্ত, দ্রুত রিচার্জ করুন!", + "coins2": "{1}কয়েন", + "remainingNumberTips": "বাকি ব্যবহারযোগ্য সংখ্যা:({1}/{2})", + "collectionTimeTips": "সংগ্রহের সময়:{1}({2}/{3})", + "sendARedEnvelope": "লাল খাম পাঠান", + "sendRedPackConfirmTips": "আপনি কি লাল প্যাক পাঠাতে চান?", + "redEnvelopeSendingRecords": "লাল খাম প্রেরণ রেকর্ড:", + "redEnvelope": "লাল খাম", + "redEnvelopeRecTips2": "সব লাল খাম দাবি করা হয়েছে।", + "redEnvelopeRecTips3": "লাল খাম সংগ্রহের সময় শেষ হয়ে গেছে!", + "openTheTreasureChest": "একটি লাল ব্যাগ পাঠানো হয়েছে!", + "redEnvelopeRecTips1": "বিজয়ী কয়েন আপনার ওয়ালেটে জমা হয়েছে।", + "redEnvelopeTips1": "কয়েন:", + "roomTools": "রুম টুলস:", + "entertainment": "বিনোদন:", + "reportSucc": "রিপোর্ট সফল", + "pornography": "পর্নোগ্রাফি", + "reportInputTips": "সমস্যাটি বুঝতে এবং সমাধান করতে আমাদের সাহায্য করার জন্য কृপয়া সমস্যাটি যতটা সম্ভব বিস্তারিতভাবে বর্ণনা করুন।", + "cancel": "বাতিল করুন", + "join": "যোগদান করুন", + "items": "আইটেম", + "vistors": "ভিজিটর", + "fans": "ফ্যান", + "balanceNotEnough": "সোনা কয়েন ব্যালেন্স অপর্যাপ্ত। আপনি কি রিচার্জ করতে চান?", + "skip": "{1} স্কিপ করুন", + "wearMedal": "মেডেল পরুন", + "activityHonor": "অ্যাক্টিভিটি সম্মান", + "achievementHonor": "সাফল্য সম্মান", + "youDontHaveAnyHonorYet": "আপনার এখনও কোনো সম্মান নেই।", + "letGoToWatch": "চলুন দেখতে যাই!", + "launchedARocket": "রকেট চালু করেছে", + "sendUserId": "ব্যবহারকারী ID পাঠান:{1}", + "giveUpIdentity": "আইডেন্টিটি ছেড়ে দিন", + "leaveRoomIdentityTips": "আপনি কি রুম আইডেন্টিটি ছেড়ে দিতে চান?", + "joinMemberTips2": "আপনি কি সদস্য হিসেবে রুমে যোগদান করতে নিশ্চিত করছেন?", + "sureUnfollowThisRoom": "আপনি কি এই রুমকে আনফলো করতে চান?", + "welcomeMessage": "আমাদের অ্যাপে স্বাগতম, {name}!", + "settings": "সেটিংস", + "account": "অ্যাকাউন্ট", + "common": "সাধারণ", + "delete": "মুছে ফেলুন", + "copy": "কপি করুন", + "bio": "জীবনী", + "useCoupontips": "আপনি কি কুপন ব্যবহার করতে চান?", + "searchUserId": "ব্যবহারকারী ID সার্চ করুন", + "sendUser": "ব্যবহারকারীকে পাঠান", + "hobby": "শখ", + "sendCoupontips": "আপনি কি এই কুপনটি এই ব্যবহারকারীকে পাঠাতে চান?", + "youDontHaveAnyCouponsYet": "আপনার এখনও কোনো কুপন নেই।", + "recall": "ফিরে ডাকুন", + "youHaventFollowed": "আপনি কোনো রুম ফলো করেন না", + "deleteFromMyDevice": "আমার ডিভাইস থেকে মুছে ফেলুন", + "deleteOnAllDevices": "সমস্ত ডিভাইসে মুছে ফেলুন", + "messageHasBeenRecalled": "এই বার্তা ফিরে ডাকা হয়েছে", + "recallThisMessage": "আপনি কি এই বার্তা ফিরে ডাকতে চান?", + "language": "ভাষা", + "feedback": "ফিডব্যাক", + "signedin": "সাইন ইন করা হয়েছে", + "receiveSucc": "সফলভাবে দাবি করা হয়েছে", + "about": "সম্পর্কে", + "aboutUs": "আমাদের সম্পর্কে", + "theme": "থিম", + "wealthLevel": "সম্পদ স্তর", + "userLevel": "ব্যবহারকারী স্তর", + "goToUpload": "আপলোড করতে যান", + "logout": "লগআউট", + "luck": "ভাগ্য", + "level": "স্তর", + "vip": "VIP", + "vip1": "VIP1", + "vip2": "VIP2", + "vip3": "VIP3", + "vip4": "VIP4", + "vip5": "VIP5", + "vip6": "VIP6", + "themeGoToUploadTips": "1.আপলোড সফল হলে 24 ঘন্টার মধ্যে রিভিউ করা হবে।\n2.রিভিউ ব্যর্থ হলে সমস্ত কয়েন ফেরত দেওয়া হবে।", + "home": "হোম", + "explore": "অন্বেষণ", + "me": "আমি", + "socialPrivilege": "সামাজিক সুবিধা", + "information": "তথ্য", + "myPhoto": "আমার ফটো", + "cpRequest": "CP অনুরোধ", + "areYouSureYouWantToSpend3": "*অন্য পক্ষ CP আমন্ত্রণ প্রত্যাখ্যান করলে, আপনার কয়েন আপনার ওয়ালেটে ফেরত দেওয়া হবে।", + "areYouSureYouWantToSpend": "আপনি কি খরচ করতে চান", + "areYouSureYouWantToSpend2": "এই ব্যবহারকারীকে CP আমন্ত্রণ পাঠাতে?", + "cpSexTips": "একই লিঙ্গের জোড়া তৈরি করা যাবে না।", + "underReview": "রিভিউ চলছে", + "doYouWantToDeleteIt": "আপনি কি এটি মুছে ফেলতে চান?", + "chooseFromAblum": "অ্যালবাম থেকে নির্বাচন করুন", + "spaceBackground": "স্পেস ব্যাকগ্রাউন্ড", + "editProfile": "প্রোফাইল সম্পাদনা করুন", + "sendTheCpRequest": "CP অনুরোধ পাঠান", + "addCp": "CP যোগ করুন", + "partWays": "পথ ভেদ করুন", + "reconcile": "মিলন করুন", + "separated": "বিচ্ছিন্ন", + "areYouSureYouWantToSpend5": "{1} আপনাকে তার অনুভূতি প্রকাশ করেছে; আপনি যদি গ্রহণ করেন তবে আপনি জোড়া হবেন।", + "areYouSureYouWantToSpend6": "{1} আপনার সাথে আবার মিলিত হতে চান। আপনি যদি মিলনের সিদ্ধান্ত নেন, তবে আপনার সমস্ত পূর্বের ডেটা পুনরুদ্ধার করা হবে।", + "reconcileInvitationTips": "*অন্য পক্ষ CP আমন্ত্রণ প্রত্যাখ্যান করলে, আপনার কয়েন আপনার ওয়ালেটে ফেরত দেওয়া হবে।", + "reconcileInvitation": "মিলন আমন্ত্রণ", + "areYouSureYouWantToSpend4": "এই ব্যবহারকারীকে মিলন আমন্ত্রণ পাঠাতে?", + "partWaysTips": "*জোড়ার একজন পার্টনার পথ ভেদ করার সিদ্ধান্ত নিলে, 7 দিনের কুলডাউন সময় থাকবে। এই সময়কালীন উভয় পক্ষই মিলনের সিদ্ধান্ত নিতে পারে এবং মিলনের ক্ষেত্রে সমস্ত ডেটা পুনরুদ্ধার করা হবে। 7 দিনের সময় শেষ হলে মিলন না করলে, জোড়া ডেটা মুছে ফেলা হবে।", + "areYouSureYouWantToPartWaysWithYourCP": "আপনি কি আপনার CP와 পথ ভেদ করতে চান?", + "timeSpentTogether": "একসাথে কাটানো সময়: {1} দিন", + "firstDay": "প্রথম দিন:{1}", + "numberOfMyCPs": "আমার CP সংখ্যা:({1}/{2})", + "props": "প্রপস", + "medal": "মেডেল", + "win": "বিজয়ী", + "dice": "ডাইস", + "rps": "কাগজ-কাঁচা-কামড়া", + "areYouSureToCancelDynamicBlacklist": "আপনি কি গতিশীল ব্ল্যাকলিস্ট বাতিল করতে চান?", + "blockUserDynamic": "ব্যবহারকারী গতিশীল ব্লক করুন", + "unblockUserDynamic": "ব্যবহারকারী গতিশীল ব্লক আন করুন", + "operationFail": "অপারেশন ব্যর্থ হয়েছে।", + "likedYourComment": "আপনার মন্তব্য পছন্দ করেছেন।", + "likedYourDynamic": "আপনার গতিশীল পছন্দ করেছেন।", + "doYouWantToKeepTheDraft": "আপনি কি ড্রাফট রাখতে চান?", + "cantSendDynamicTips": "আপনি বর্তমানে গতিশীল পাঠানো ব্লক করা হয়েছে।", + "operationsAreTooFrequent": "অপারেশন খুব ঘন ঘন", + "luckNumber": "ভাগ্য নম্বর", + "relationShip": "সম্পর্ক", + "couple": "জোড়া {1}:", + "couple2": "জোড়া", + "reject": "প্রত্যাখ্যান করুন", + "cpList": "CP তালিকা", + "accept": "গ্রহণ করুন", + "noMatchedCP": "মিলে যাওয়া CP নেই", + "inviteYouToBecomeBD": "আপনাকে BD बनতে আমন্ত্রণ জানাচ্ছি।", + "adminInviteRechargeAgent": "আপনাকে রিচার্জ এজেন্ট बनতে আমন্ত্রণ জানাচ্ছি।", + "confirmAcceptTheInvitation": "আপনি কি আমন্ত্রণ গ্রহণ করতে নিশ্চিত করছেন?", + "confirmDeclineTheInvitation": "আপনি কি আমন্ত্রণ প্রত্যাখ্যান করতে নিশ্চিত করছেন?", + "host": "হোস্ট", + "following": "ফলো করা হচ্ছে", + "agent": "এজেন্ট", + "approved": "অনুমোদিত", + "agreeJoinFamilyTips": "আপনি কি এই ব্যবহারকারীকে ফ্যামিলিতে যোগদান করতে অনুমতি দিচ্ছেন?", + "refuseJoinFamilyTips": "আপনি কি ব্যবহারকারীকে ফ্যামিলিতে যোগদান করতে প্রত্যাখ্যান করতে চান?", + "onlineUsers": "অনলাইন ব্যবহারকারী({1}/{2}):", + "applyToJoin": "যোগদানের জন্য আবেদন করুন", + "supporterList": "সমর্থক তালিকা", + "hostList": "হোস্ট তালিকা", + "upToAdmins": "সর্বাধিক {1} অ্যাডমিন", + "upToMembers": "সর্বাধিক {1} সদস্য", + "levelPrivileges": "স্তরের অধিকার", + "familyLevel": "ফ্যামিলি স্তর", + "kickOutOfFamily": "ফ্যামিলি থেকে বের করুন", + "disbandTheFamilyTips": "আপনি কি ফ্যামিলি ভেঙে ফেলতে চান?", + "setAsFamilyAdmin": "ফ্যামিলি অ্যাডমিন হিসেবে নিযুক্ত করুন", + "cancelFamilyAdmin": "ফ্যামিলি অ্যাডমিন বাতিল করুন", + "kickFamilyUserTips": "আপনি কি এই ব্যবহারকারীকে ফ্যামিলি থেকে বের করতে চান?", + "familyMember2": "ফ্যামিলি সদস্য({1}/{2}):", + "familyMember3": "ফ্যামিলি সদস্য({1}):", + "familyAdmin2": "ফ্যামিলি অ্যাডমিন({1}/{2}):", + "familyOwner2": "ফ্যামিলি মালিক:", + "ra": "RA", + "roomAnnouncement": "রুম ঘোষণা", + "family3": "{1} 'এর ফ্যামিলি", + "help": "সহায়তা", + "rejected": "প্রত্যাখ্যান করা হয়েছে", + "boxContributeTips": "আজ ইতিমধ্যে অবদান রাখা হয়েছে, কृপয়া আবার অবদান না রাখুন", + "familyHelpTips": "(1)50 কয়েন অবদান রাখলে, 5 জন ব্যবহারকারী অবদান রাখলে প্রথম সম্পদ বাক্স খুলবে। 50 কয়েন পেতে ক্লিক করতে পারেন।(2)10 জন ব্যবহারকারী অবদান রাখলে দ্বিতীয় সম্পদ বাক্স খুলবে। 50 কয়েন পেতে পারেন।\n(3)20 জন ব্যবহারকারী অবদান রাখলে দ্বিতীয় সম্পদ বাক্স খুলবে। 100 কয়েন পেতে পারেন।\n(4) যদি সম্পদ বাক্স দাবি করার জন্য প্রয়োজনীয় ব্যবহারকারী সংখ্যা একই দিনে পৌঁছান না হয়, তবে সম্পদ বাক্সের অগ্রগতি পরের দিন শূন্য হয়ে যাবে।\n(5)সম্পদ বাক্সের অগ্রগতি পরের দিন শূন্য হয়ে যাবে। একই দিন সম্পদ বাক্স দাবি না করা ব্যবহারকারীদের সময়মত দাবি করতে হবে।\n(6)সম্পদ বাক্স শূন্য করার সময়: সৌদি আরবের সময় 00:00।", + "bd": "BD", + "coupon": "কুপন", + "search": "সার্চ", + "get": "পান করুন", + "inRocket": "রকেটে", + "roomRocketHelpTips": "1. রুমে উপহার পাঠান রকেট শক্তি বাড়ায়। *1 সোনা কয়েন উপহার = 1 রকেট শক্তি পয়েন্ট; ভাগ্যশালী উপহার রকেট শক্তি উপহারের সোনা কয়েন মানের 4% পরিমাণে বাড়ায়।\n2. রকেট শক্তি সম্পূর্ণভাবে পূর্ণ হলে, রুম রকেট চালু করতে পারে। চালু করার পর পুরস্কার স্বয়ংক্রিয়ভাবে বিতরণ করা হবে।\n3. বিভিন্ন রকেট স্তর বিভিন্ন পুরস্কার প্রদান করে।\n4. রকেট চালু হলে, রুমের সমস্ত ব্যবহারকারী রকেট পুরস্কার দাবি করতে পারে।5. রকেট শক্তি প্রতিদিন 00:00 এ শূন্য হয়ে যায়।", + "couponRecord": "কুপন ব্যবহার রেকর্ড", + "inRoom": "রুমে", + "searchCouponHint": "কুপন সার্চ করুন", + "giftCounter": "উপহার কাউন্টার", + "bDLeaderInviteYouToBecomeBDLeader": "আপনাকে BD লিডার बनতে আমন্ত্রণ জানাচ্ছি", + "wins": "বিজয়", + "inviteYouToBecomeHost": "আপনাকে হোস্ট बनতে আমন্ত্রণ জানাচ্ছি।", + "friends": "বন্ধু", + "deleteConversationTips": "আপনি কি এই ব্যবহারকারীর সাথে চ্যাট ইতিহাস মুছে ফেলতে চান?", + "propMessagePrompt": "প্রপ মেসেজ টিপস", + "inputUserId": "ব্যবহারকারী ID লিখুন", + "fromLuckyGifts": "সৌভাগ্যপূর্ণ উপহার থেকে", + "receive": "প্রাপ্ত করুন", + "checkInSuccessful": "সাইন ইন সফল", + "sginTips": "আপনি প্রতিদিন প্রথমবার সাইন ইন করলে পুরস্কার পাবেন। আপনি যদি সাইন ইন বন্ধ করেন, তবে আবার সাইন ইন করলে পুরস্কার প্রথম দিন থেকে গণনা করা হবে।", + "vipBadge": "VIP ব্যাজ", + "vipProfileFrame": "VIP প্রোফাইল ফ্রেম", + "vipProfileCard": "VIP প্রোফাইল কার্ড", + "popular": "জনপ্রিয়", + "recommend": "সুপারিশ", + "follow": "ফলো করুন", + "history": "ইতিহাস", + "hotRooms": "হট রুম", + "viewMore": "আরও দেখুন", + "noData": "ডেটা নেই", + "users": "ব্যবহারকারী", + "rooms": "রুম", + "coins": "কয়েন", + "unread": "অপঠিত", + "read": "পঠিত", + "image": "[ছবি]", + "video": "[ভিডিও]", + "sound": "[সাউন্ড]", + "gift2": "[উপহার]", + "clickHereToStartChatting": "কিছু বলুন.....", + "receivedAMessage": "[মেসেজ প্রাপ্ত]", + "confirmSwitchMicModelTips": "আপনি কি চেয়ার মোড পরিবর্তন করতে নিশ্চিত করছেন?", + "number": "নম্বর", + "album": "অ্যালবাম", + "camera": "ক্যামেরা", + "system": "সিস্টেম", + "notifcation": "নোটিফিকেশন", + "inviteYouToBecomeAgent": "আপনাকে এজেন্ট बनতে আমন্ত্রণ জানাচ্ছি।", + "myMusic": "আমার মিউজিক", + "add": "যোগ করুন", + "pullToLoadMore": "লোড করার জন্য টানুন", + "loadingFailedClickToRetry": "লোড ব্যর্থ, পুনরায় চেষ্টা করার জন্য ক্লিক করুন", + "releaseToLoadMore": "লোড করার জন্য ছেড়ে দিন", + "haveMyLimits": "---আমার নিজস্ব সীমা আছে।---", + "music": "মিউজিক", + "free": "বিনামূল্যে", + "charm": "আকর্ষণ", + "start": "শুরু করুন", + "vipUseThisThemeTips": "শুধুমাত্র VIP স্তরে পৌঁছানো ব্যবহারকারীরা এই থিম ব্যবহার করতে পারে।", + "stop": "বন্ধ করুন", + "chats": "চ্যাট", + "family": "ফ্যামিলি", + "refuse": "প্রত্যাখ্যান করুন", + "agree": "সম্মত হন", + "thisFeatureIsCurrentlyUnavailable": "এই ফিচারটি বর্তমানে অনুপলব্ধ।", + "pleaseSelectTheRecipient": "কृপয়া প্রাপক নির্বাচন করুন।", + "searchMemberIdHint": "কৃপয়া সদস্যের আইডি নম্বর লিখুন", + "unclaimedRedEnvelopes": "দাবি না করা লাল খাম 24 ঘন্টার মধ্যে ফেরত দেওয়া হবে।", + "redEnvelopeNotYetClaimed": "লাল খাম এখনও দাবি করা হয়নি।", + "redEnvelopeAmount": "লাল খামের পরিমাণ: {1} কয়েন", + "sentARedEnvelope": "লাল খাম পাঠিয়েছে।", + "theRedEnvelopeHasExpired": "লাল খামের মেয়াদ শেষ হয়ে গেছে।", + "joinRequest": "যোগদানের অনুরোধ", + "welcomeToMyFamily": "আমার ফ্যামিলিতে স্বাগতম!", + "scrollToTheBottom": "নিচে স্ক্রোল করুন", + "gameRules": "গেম নিয়ম:", + "charmGameRulesTips": "মিটার প্যানেল খুলুন, মাইক্রোফোনে সমস্ত ব্যবহারকারী প্রাপ্ত উপহার কয়েন দেখান,1 কয়েন উপহার = 1 পয়েন্ট (ভাগ্যশালী উপহার 0.04 পয়েন্ট)।", + "inputYourOldPassword": "আপনার পুরানো পাসওয়ার্ড লিখুন", + "enterYourOldPassword": "আপনার পুরানো পাসওয়ার্ড লিখুন", + "setYourPassword": "আপনার পাসওয়ার্ড সেট করুন", + "enterYourNewPassword": "আপনার নতুন পাসওয়ার্ড লিখুন", + "confirmYourPassword": "আপনার পাসওয়ার্ড নিশ্চিত করুন", + "theTwoPasswordsDoNotMatch": "দুটি পাসওয়ার্ড মিলছে না।", + "resetLoginPasswordtTips2": "পাসওয়ার্ড 8-16 অক্ষরের দৈর্ঘ্যের হতে হবে এবং বড়/ছোট ইংরেজি অক্ষর এবং সংখ্যার সমন্বয়ে গঠিত হতে হবে (শুধুমাত্র সংখ্যা নয়)", + "resetLoginPassword": "লগইন পাসওয়ার্ড রিসেট করুন", + "resetLoginPasswordtTips1": "আপনার ব্যবহারকারী ID বা কাস্টম ID দিয়ে লগইন করুন। আজী অ্যাকাউন্ট দিয়ে লগইন করা আরও নিরাপদ।", + "localMusic": "লোকাল মিউজিক", + "setLoginPassword": "লগইন পাসওয়ার্ড সেট করুন", + "confirmSwitchMicThemeTips": "আপনি কি চেয়ার স্টাইল পরিবর্তন করতে নিশ্চিত করছেন?", + "pleaseUpgradeYourVipLevelFirst": "কृপয়া প্রথমে আপনার VIP স্তর উন্নত করুন।", + "micTheme": "মাইক্রো থিম", + "classicMic": "ক্লাসিক {1} মাইক্রো", + "yesterday": "গতকাল {1}", + "monday": "সোমবার {1}", + "tuesday": "মঙ্গলবার {1}", + "wednesday": "বুধবার {1}", + "thursday": "বৃহস্পতিবার {1}", + "friday": "শুক্রবার {1}", + "saturday": "শনিবার {1}", + "sunday": "রবিবার {1}", + "acceptedYour": "{1} আপনার আমন্ত্রণ গ্রহণ করেছে", + "youAccepted": "আপনি {1} এর আমন্ত্রণ গ্রহণ করেছেন", + "openRedPackDialogTip": "লাল খাম", + "micManagement": "মাইক্রো ম্যানেজমেন্ট", + "vipChatBox": "VIP চ্যাট বক্স", + "vipRoomCoverBorder": "VIP রুম কভার বর্ডার", + "bdCenter": "BD সেন্টার", + "renewVip": "VIP পুনর্নবীকরণ করুন", + "rechargeAgency": "রিচার্জ এজেন্সি", + "adminCenter": "অ্যাডমিন সেন্টার", + "goldList": "সোনা তালিকা", + "followList": "ফলো তালিকা", + "fansList": "ফ্যান তালিকা", + "daily": "দৈনন্দিন", + "cp": "CP", + "female": "মহিলা", + "male": "পুরুষ", + "identity": "আইডেন্টিটি", + "adjust": "সামঞ্জস্য করুন", + "warning": "সতর্কতা", + "screenshotTips": "স্ক্রিনশট (সর্বাধিক 3)", + "roomNotice": "রুম নোটিশ", + "roomTheme": "রুম থিম", + "description": "বর্ণনা:", + "inputDesHint": "সমস্যাটি বুঝতে এবং সমাধান করতে আমাদের সাহায্য করার জন্য কृপয়া সমস্যাটি যতটা সম্ভব বিস্তারিতভাবে বর্ণনা করুন।", + "roomProfilePicture": "রুম প্রোফাইল ছবি", + "userProfilePicture": "ব্যবহারকারী প্রোফাইল ছবি", + "userName": "ব্যবহারকারী নাম", + "pleaseSelectTheTypeToProcess": "কৃপয়া প্রক্রিয়াকরণের টাইপ নির্বাচন করুন:", + "roomEditing": "রুম সম্পাদনা", + "setAccount": "অ্যাকাউন্ট সেট করুন", + "userEditing": "ব্যবহারকারী সম্পাদনা", + "enterTheUserId": "ব্যবহারকারী ID লিখুন", + "enterTheRoomId": "রুম ID লিখুন", + "deleteAccount": "অ্যাকাউন্ট মুছে ফেলুন", + "becomeAgent": "এজেন্ট बनুন", + "enterNickname": "উপনাম লিখুন", + "selectYourCountry": "আপনার দেশ নির্বাচন করুন", + "inviteCode": "আমন্ত্রণ কোড", + "magic": "জাদু", + "luckGiftSpecialEffects": "ভাগ্যশালী উপহার অ্যানিমেশন প্রভাব", + "theVideoSizeCannotExceed": "ভিডিও আকার 50M অতিক্রম করতে পারবেন না", + "weekly": "সাপ্তাহিক", + "customizedGiftRulesContent": "আমি কীভাবে কাস্টমাইজড গিফট পেতে পারি:\n1.বর্তমান সম্পদ স্তর অনুসারে ব্যবহারকারীর কাস্টমাইজড গিফট পাওয়ার যোগ্যতা নির্ধারণ করুন.\n(1) ব্যবহারকারীর সম্পদ স্তর ≥35 হলে: \nব্যবহারকারী কাস্টমাইজড গিফটের জন্য একবার ভিডিও আপলোড করতে পারেন। আমরা ব্যবহারকারীর বর্তমান প্রোফাইল ছবিকে গিফট ইমেজ হিসেবে এবং প্রদত্ত ভিডিওকে \"কাস্টম\" এর উপর গিফট প্রভাব হিসেবে ব্যবহার করব। প্রDUCTION কিছুটা সময় নেবে এবং স্টোরে উপলব্ধ হওয়ার সাথে সাথে আপনাকে অবহিত করব।\n(2) ব্যবহারকারীর সম্পদ স্তর ≥45 হলে: \nব্যবহারকারী কাস্টমাইজড গিফটের জন্য একবার ভিডিও আপলোড করতে পারেন। আমরা ব্যবহারকারীর বর্তমান প্রোফাইল ছবিকে গিফট ইমেজ হিসেবে এবং প্রদত্ত ভিডিওকে \"কাস্টম\" এর উপর গিফট প্রভাব হিসেবে ব্যবহার করব। প্রDUCTION কিছুটা সময় নেবে এবং স্টোরে উপলব্ধ হওয়ার সাথে সাথে আপনাকে অবহিত করব।\n2.অ্যাপের নির্দিষ্ট কার্যকলাপে অংশ নিতে পারেন এবং মানদণ্ড পূরণ করার পর \"মেসেজ\" → \"আমাদের সাথে যোগাযোগ করুন\" বিভাগ থেকে আমাদের সাথে যোগাযোগ করতে পারেন। কার্যকলাপের স্ক্রিনশট এবং কাস্টমাইজড গিফটের জন্য ব্যবহার করতে চান এমন ভিডিও প্রদান করুন। আমরা আপনার বর্তমান প্রোফাইল ছবিকে গিফট ইমেজ হিসেবে এবং প্রদত্ত ভিডিওকে গিফট প্রভাব হিসেবে ব্যবহার করব, তারপর \"কাস্টম\" এর অধীনে তালিকাভুক্ত করা হবে। প্রDUCTION কিছুটা সময় নেবে এবং স্টোরে উপলব্ধ হওয়ার সাথে সাথে আপনাকে অবহিত করব।\nকাস্টমাইজড গিফটের মেয়াদী সময় & কীভাবে সম্প্রসারিত করব:\nকাস্টমাইজড কাস্টম গিফটগুলি র্যাকে 30 দিনের মেয়াদী সময়সহ থাকবে। কার্যকর এবং অনুপ্রেরণামূলক ব্যবহারকারীদের এই নতুন অভিজ্ঞতা চালিয়ে যেতে এবং তাদের ব্যক্তিগত স্টাইল প্রদর্শন করতে সাহায্য করার জন্য, কাস্টমাইজড গিফট ধারণকারী ব্যবহারকারীরা যেকোনো মাসে 500 মার্কিন ডলার রিচার্জ করে তাদের সমস্ত কাস্টমাইজড গিফটের র্যাক সময় 30 দিন বাড়াতে পারেন।", + "customizedGiftRules": "কাস্টমাইজড গিফট নিয়ম", + "rulesUpload": "নিয়ম&আপলোড", + "monthly": "মাসিক", + "message": "মেসেজ", + "clearCache": "ক্যাশ মুছে ফেলুন", + "customized": "কাস্টমাইজড", + "searchInputHint": "অ্যাকাউন্ট/রুম নম্বর লিখুন", + "kickRoomTips": "আপনাকে রুম থেকে বের করা হয়েছে।", + "joinRoomTips": "রুমে যোগদান করেছেন !", + "roomSetting": "রুম সেটিংস", + "roomDetails": "রুম বিবরণ", + "systemRoomTips": "দয়া করে ভদ্রতা এবং শ্রদ্ধা বজায় রাখুন। আসলানে কোন ধরনের пор্নোগ্রাফিক বা অশ্লীল বিষয়বস্তু কঠোরভাবে নিষিদ্ধ। যে কোনো অ্যাকাউন্টকে লঙ্ঘন করতে দেখা গেলে স্থায়ীভাবে নিষিদ্ধ করা হবে। আমরা সকল ব্যবহারকারীকে বিনীতভাবে অনুরোধ করছি আসলানের কমিউনিটি নির্দেশিকা সচেতনভাবে মেনে চলার জন্য।", + "copiedToClipboard": "ক্লিপবোর্ডে কপি করা হয়েছে", + "recharge": "রিচার্জ করুন", + "receivedFromALuckyGift": "একটি ভাগ্যবানের উপহার থেকে প্রাপ্ত।", + "followedYou": "আপনাকে ফলো করেছেন", + "agentCenter": "এজেন্ট সেন্টার", + "areYouSureYouWantToClearLocalCache": "আপনি কি লোকাল ক্যাশ মুছে ফেলতে চান?", + "clearMessage": "স্ক্রিন মেসেজ মুছে ফেলুন", + "report": "রিপোর্ট করুন", + "coins3": "কয়েন", + "giftSpecialEffects": "গিফট বিশেষ প্রভাব", + "basicFeatures": "মৌলিক বৈশিষ্ট্য", + "task": "টাস্ক", + "importantReminder": "গুরুত্বপূর্ণ স্মারক", + "entryVehicleAnimation": "প্রবেশ যানবাহনের অ্যানিমেশন (ভিআইপি৩)", + "floatingAnimationInGlobal": "গ্লোবালে ভাসমান অ্যানিমেশন", + "entryVehicleAnimation2": "VIP3 বা তার বেশি অধিকার ધারণকারী ব্যবহারকারীরা ফাংশন ব্যবহার করে গাড়ির অ্যানিমেশন বন্ধ করতে পারেন।", + "dailyTasks": "দৈনন্দিন টাস্ক", + "enterRoomConfirmTips": "আপনি কি রুমে যেতে চান?", + "followSucc": "সফলভাবে ফলো করা হয়েছে", + "goldListort": "সোনা তালিকা", + "rechargeList": "রিচার্জ তালিকা", + "edit": "সম্পাদনা করুন", + "swipeLeftOnTheFloatingScreenAreaToQuicklyCloseIt": "*টিপস: ভাসমান স্ক্রিন এলাকায় বামে সвайপ করে দ্রুত বন্ধ করুন।", + "enterThisVoiceChatRoom": "আপনি কি এই ভয়েস চ্যাট রুমে যেতে চান?", + "go": "যান", + "done": "সম্পন্ন", + "improvementTasks": "সুশোধন টাস্ক", + "save": "সংরক্ষণ করুন", + "kickPrevention": "কিক প্রতিরোধ", + "freeChatSpeak": "বিনামূল্যে চ্যাট & কথা বলুন", + "vipExclusiveVehicles": "VIP একচেটিয়া গাড়ি", + "nickName": "উপনাম", + "buyVip": "VIP কিনুন", + "gender": "লিঙ্গ", + "unFollow": "আনফলো করুন", + "days": "দিন", + "permanent": "স্থায়ী", + "yourVipWillExpire": "আপনার VIP {1} তারিখে মেয়াদ শেষ হবে", + "cantBuyVip": "ক্রয় চালিয়ে যেতে পারে না", + "country": "দেশ", + "birthday": "জন্মদিন", + "man": "পুরুষ", + "woman": "মহিলা", + "apple": "Apple", + "google": "Google", + "everyone": "সবার", + "dailyTaskRewardBonus": "দৈনন্দিন কাজের পুরস্কার বোনাস({1} XP)", + "userLevelXPBoost": "ব্যবহারকারীর স্তরের এক্সপি বৃদ্ধি({1} এক্সপি)", + "pleaseUpgradeYourVipLevel": "দয়া করে আপনার ভিআইপি স্তর উন্নীত করুন।", + "goToUpgrade": "আপগ্রেড করতে যান", + "exclusiveEmojiWillBeReleasedAfterBecoming": "এক্সক্লুসিভ ইমোজি হওয়ার পরে মুক্তি পাবে", + "preventBeingBlocked": "ব্লক হওয়া প্রতিরোধ করুন", + "enableRankIncognitoMode": "র\u200C্যাঙ্ক ইনকগনিটো মোড সক্ষম করুন", + "avoidBeingKicked": "পেটানো থেকে বিরত থাকুন", + "privileges": "{1} সুবিধাসমূহ", + "andAboveUsers": "{1} এবং উপরের ব্যবহারকারীরা", + "basicPermissions": "মূল অনুমতিসমূহ", + "mysteriousInvisibility": "রহস্যময় অদৃশ্যতা", + "antiBlock": "ব্লক-বিরোধী", + "shop": "দোকান", + "expirationTime": "মেয়াদ শেষ হওয়ার সময়", + "createFamilyForFree": "ফ্রিতে পরিবার তৈরি করুন", + "privateChat": "ব্যক্তিগত চ্যাট", + "vipBirthdayGift": "ভিআইপি জন্মদিনের উপহার", + "storeDiscount": "স্টোর ডিসকাউন্ট {1} ছাড়", + "membershipFreeChatSpeak": "সদস্যতা ছাড়াই চ্যাট ও কথা বলা", + "vipEmoticon": "ভিআইপি ইমোটিকন({1})", + "vipMicSoundWave": "ভিআইপি মাইক্রোফোন সাউন্ড ওয়েভ", + "startVoiceParty": "ভয়েস পার্টি শুরু করুন!", + "enterRoomTips": "{1} রুমে প্রবেশ করেছেন", + "roomName": "রুম নাম", + "idIcon": "আইডি আইকন", + "inviteToBecomeAHost": "একজন হোস্ট হতে আমন্ত্রণ জানান", + "currentLevelPrivilegesAndCostumes": "বর্তমান স্তরের বিশেষাধিকার এবং পোশাক: {1}", + "uploadGifAvatar": "GIF অ্যাভাটার আপলোড করুন (ভিআইপি২)", + "uploadProfilePicture": "প্রোফাইল ছবি আপলোড করুন", + "permissionSettings": "অনুমতি সেটিংস", + "roomMember": "রুম সদস্য", + "priorityRoomSorting": "প্রাথমিক কক্ষ শ্রেণীবিন্যাস", + "userColoredID": "ব্যবহারকারী রঙিন পরিচয়পত্র", + "pleaseSelectYourCountry": "কৃপয়া আপনার দেশ নির্বাচন করুন।", + "pleaseSelectYourGender": "কৃপয়া আপনার লিঙ্গ নির্বাচন করুন।", + "pleaseEnterNickname": "কৃপয়া একটি উপনাম লিখুন।", + "countryRegion": "দেশ&অঞ্চল", + "dateOfBirth": "জন্ম তারিখ", + "mute": "মিউট করুন", + "exit": "প্রস্থান", + "pleaseSelectaItem": "কৃপয়া একটি আইটেম নির্বাচন করুন", + "areYouRureRoRecharge": "আপনি কি রিচার্জ করতে চান?", + "mInimize": "রাখো", + "roomOwner": "রুমের মালিক", + "hostCenter": "হোস্ট সেন্টার", + "allOnMicrophone": "সবাই মাইক্রোফোনে", + "usersOnMicrophone": "মাইক্রোফোনে ব্যবহারকারী", + "allInTheRoom": "সবাই রুমে", + "send": "পাঠান", + "crop": "ক্রপ করুন", + "finish": "শেষ করুন", + "takeTheMic": "মাইক্রো নিন", + "openTheMic": "মাইক্রো খুলুন", + "muteTheMic": "মাইক্রো মিউট করুন", + "unlockTheMic": "মাইক্রো আনলক করুন", + "leavelTheMic": "মাইক্রো ছেড়ে দিন", + "lockTheMic": "মাইক্রো লক করুন", + "removeTheMic": "মাইক্রো সরান", + "inviteToTheMicrophone": "মাইক্রোফোনে আমন্ত্রণ দিন", + "openUserProfleCard": "ব্যবহারকারী প্রোফাইল কার্ড খুলুন", + "obtain": "প্রাপ্ত করুন", + "win2": "{1} বিজয়", + "backTheRoom": "রুমে ফিরে যান", + "toConsume": "খরচ করার জন্য", + "howToUpgrade": "কীভাবে উন্নত করব?", + "spendCoinsToGainExperiencePoints": "অভিজ্ঞতা পয়েন্ট পাওয়ার জন্য কয়েন খরচ করুন", + "higherLevelFancierAvatarFrame": "উচ্চতর স্তর, আরও সুন্দর ব্যাজ/প্রোফাইল ফ্রেম", + "medalAndAvatarFrameRewards": "মেডেল এবং প্রোফাইল ফ্রেম পুরস্কার", + "all": "সব", + "gift": "গিফট", + "chat": "চ্যাট", + "owner": "মালিক", + "store": "স্টোর", + "admin": "অ্যাডমিন", + "member": "সদস্য", + "guest": "অতিথি", + "submit": "জমা দিন", + "membershipFee": "সদস্যপদ ফি", + "membershipFeeTips1": "কৃপয়া আপনার রুমের জন্য সদস্যপদ ফি সেট করুন। ব্যবহারকারীরা ফি পরিশোধ করে আপনার রুমে যোগদান করতে পারেন।", + "membershipFeeTips2": "রুম সদস্য হওয়ার জন্য ব্যবহারকারীর প্রয়োজনীয় সোনা।রুম মালিক সোনার 50% পাবেন।", + "freePrice": "ফি:0-10000", + "touristsSendText": "পর্যটক টেক্সট পাঠায়", + "touristsTakeToTheMic": "পর্যটক মাইক্রো নেয়", + "theMembershipFee": "সদস্যপদ ফি", + "theModificationsMade": "এবার করা পরিবর্তনগুলি প্রস্থানের পর সংরক্ষণ করা হবে না", + "viewFrame": "ফ্রেম দেখুন", + "enterRoomName": "রুম নাম লিখুন", + "headdress": "হেডড্রেস", + "mountains": "গাড়ি", + "purchaseIsSuccessful": "ক্রয় সফল", + "buy": "কিনুন", + "followed": "ফলো করা হয়েছে", + "follow2": "ফলো:{1}", + "fans2": "ফ্যান:{1}", + "vistors2": "ভিজিটর:{1}", + "personal2": "ব্যক্তিগত:", + "family2": "ফ্যামিলি:", + "conntinue": "চালিয়ে যান", + "confirmBuyTips": "আপনি কি কিনতে চান?", + "purchase": "ক্রয়", + "setRoomPassword": "রুম পাসওয়ার্ড সেট করুন", + "inputRoomPassword": "রুম পাসওয়ার্ড লিখুন", + "enter": "প্রবেশ করুন", + "createDynamicSuccess": "গতিশীল সফলভাবে তৈরি হয়েছে", + "deleteDynamicTips": "আপনি কি এই গতিশীল মুছে ফেলতে চান?", + "deleteCommentTips": "আপনি কি এই মন্তব্য মুছে ফেলতে চান?", + "deleteSuccessful": "মুছে ফেলা সফল!", + "itemsLeft": "বাকি আইটেম", + "password": "পাসওয়ার্ড", + "replySucc": "উত্তর সফল", + "comment": "মন্তব্য", + "showMore": "আরও দেখুন", + "showLess": "কম দেখুন", + "enterPassword": "পাসওয়ার্ড লিখুন", + "enterAccount": "অ্যাকাউন্ট লিখুন", + "logIn": "লগইন করুন", + "saySomething": "কিছু বলুন...", + "sayHi": "হাই..", + "pleaseChatFfriendly": "কৃপয়া বন্ধুত্বপূর্ণভাবে চ্যাট করুন", + "unLockTheRoom": "রুম আনলক করুন", + "operationSuccessful": "অপারেশন সফল হয়েছে।", + "adminByHomeowner": "হোম ওনার দ্বারা অ্যাডমিন হিসেবে নিযুক্ত করা হয়েছে।", + "memberByHomeowner": "হোম ওনার দ্বারা সদস্য হিসেবে নিযুক্ত করা হয়েছে।", + "touristByHomeowner": "হোম ওনার দ্বারা পর্যটক হিসেবে নিযুক্ত করা হয়েছে।", + "becomeHost": "হোস্ট হতে আবেদন করুন", + "superFans": " সুপার ফ্যান:", + "setUpAnIdentity": "আইডেন্টিটি সেট আপ করুন", + "kickedOutOfRoom": "রুম থেকে বের করা হয়েছে", + "playGiftMusicAndDynamicMusic": "গিফট মিউজিক এবং গতিশীল মিউজিক চালু করুন", + "knapsack": "ব্যাকপ্যাক", + "bdLeader": "BD লিডার", + "picture": "ছবি", + "theImageSizeCannotExceed": "আপলোড ব্যর্থ: ফাইলের আকার ২ মেগাবাইটের কম হতে হবে।", + "activity": "কার্যকলাপ", + "alreadyAnAdministrator": "ইতিমধ্যে অ্যাডমিন", + "alreadyAnMember": "ইতিমধ্যে সদস্য", + "alreadyAnTourist": "ইতিমধ্যে পর্যটক", + "touristsCannotSendMessages": "পর্যটক মেসেজ পাঠাতে পারেন না", + "touristsAreNotAllowedToGoOnTheMic": "পর্যটকদের মাইক্রোফোনে যাওয়ার অনুমতি নেই", + "lockTheRoom": "রুম লক করুন", + "special": "বিশেষ", + "visitorList": "ভিজিটর তালিকা", + "successfulWear": "সফলভাবে পরা হয়েছে", + "confirmUnUseTips": "আপনি কি অপসারণ করতে নিশ্চিত করছেন?", + "custom": "কাস্টম", + "myItems": "আমার আইটেম", + "use": "ব্যবহার করুন", + "unUse": "ব্যবহার না করুন", + "renewal": "পুনর্নবীকরণ", + "wallet": "ওয়ালেট", + "profile": "প্রোফাইল", + "giftwall": "গিফট ওয়াল", + "announcement": "ঘোষণা", + "blockedList": "ব্লক করা তালিকা", + "country2": "দেশ:", + "sendTo": "পাঠানো হবে", + "medals": "মেডেল", + "activityMedal": "কার্যকলাপ মেডেল", + "achievementMedal": "সাফল্য মেডেল", + "credits": "ক্রেডিট: {1}", + "successfullyUnloaded": "সফলভাবে আনলোড করা হয়েছে", + "expired": "মেয়াদ শেষ", + "day": "দিন", + "inUse": "ব্যবহারে", + "confirmUseTips": "আপনি কি ব্যবহার করতে নিশ্চিত করছেন?", + "pleaseUploadUserAvatar": "কৃপয়া একটি প্রোফাইল ছবি আপলোড করুন।", + "joinMemberTips": "আপনি যদি রুমে পর্যটক হন, তবে আপনি মাইক্রোফোন নিতে পারবেন না।", + "giftGivingSuccessful": "গিফট দেওয়া সফল।", + "theAccountPasswordCannotBeEmpty": "অ্যাকাউন্ট বা পাসওয়ার্ড খালি হতে পারে না।", + "invitesYouToTheMicrophone": "{1} আপনাকে মাইক্রোফোনে আমন্ত্রণ জানাচ্ছে", + "english": "ইংরেজি", + "chinese": "চীনা", + "arabic": "আরবি", + "darkMode": "ডার্ক মোড", + "lightMode": "লাইট মোড", + "systemDefault": "সিস্টেম ডিফল্ট", + "pleaseGetOnTheMicFirst": "কৃপয়া প্রথমে মাইক্রোফোনে যান।", + "duration2": "সময়:{1}" +} \ No newline at end of file diff --git a/assets/l10n/intl_en.json b/assets/l10n/intl_en.json new file mode 100644 index 0000000..f6c09c3 --- /dev/null +++ b/assets/l10n/intl_en.json @@ -0,0 +1,769 @@ +{ + "signInWithGoogle": "Sign in with Google", + "or": "Or", + "signInWithYourAccount": "Sign in with your account", + "signInWithApple": "Sign in with Apple", + "loginRepresentsAgreementTo": "Login represents agreement to", + "termsofService": "Terms of service", + "privaceyPolicy": "Privacey Policy", + "tips": "Tips", + "dailyTasksTips": "Note:Ready to tackle tasks? Find them in your favorite rooms.", + "searchNoDataTips": "Enter the room or user lD you want to search.", + "youHaveNotHadVIPYet": "You have not had VIP yet, come and try it", + "games": "Games", + "vipAccelerating": "{1} Accelerating", + "mine": "Mine", + "luckGiftRuleTips": "Give a lucky gift and win up to 1000 times the gold coin reward! Users who receive lucky gifts can enjoy the following benefits: \n(1) For users who receive lucky gifts: +4% charm experience value based on the gift's worth. \u2028(2) If the recipient is a host: +4% host salary target value based on the gift's worth.", + "badge": "Badge", + "vipRippleTheme": "VIP Ripple Theme", + "glory": "Glory", + "badgeHonor": "Badge/Glory", + "party": "Party", + "other": "Other", + "gifProfileUpload": "GIF Profile Upload", + "levelMedal": "Level Medal", + "levelIcon": "Level Icon", + "maliciousHarassment": "Malicious harassment", + "event": "Event", + "roomEdit": "Room Edit", + "cancelRoomPassword": "Are you sure you want to delete the room password?", + "roomMemberFee": "Room Member Fee", + "roomTheme2": "Room Theme", + "blockedList2": "Blocked List", + "roomPassword": "Room Password", + "numberOfMic": "Number of Mic", + "pleaseEnterContent": "Please enter content", + "profilePhoto": "Profile Photo", + "aboutMe": "About me", + "myRoom": "My room", + "noHistoricalRecordsAvailable": "No historical records available.", + "inviteNewUsersToEarnCoins": "Invite new users to earn coins", + "crateMyRoom": "Create my room", + "vipSpecialGiftTassel": "VIP Special gift tassel", + "vipEntranceEffect": "VIP Entrance Effect", + "casualInteraction": "Casual Interaction", + "haveGamePlayingTips": "You have a game in progress. Please exit the current game. Are you sure you want to exit?", + "historicalTour": "Historical Tour", + "gameCenter": "Game Center", + "returnToVoiceChat": "Return to voice chat?", + "exitGameMode": "Exit Game Mode", + "enterTheRoom": "Enter the room", + "invite": "Invite", + "inviteGoRoomTips": "Always here for you, rain or shine. Drop in and say hi!", + "confirmInviteThisUserToTheRoom": "Confirm invite this user(ID:{1}) to the room?", + "honor": "Honor", + "clearCacheSuccessfully": "Clear cache successfully", + "sent": "Sent", + "keep": "Keep", + "open": "Open", + "deleteAccountTips2": "*lf you change your mind, you can log back into your current account with in seven days, and we will automatically restore your account. lf no restoration occurs within seven days, the account will be permanently deleted", + "deleteAccountTips": "You have full administrative rights to this account. lf you intend to delete the account, please be aware of the following risks associated with this operation:\n1.Once the account is successfully deleted, you will no longer be able to login to your current account. Account deletion is a permanent action.\n2. After the account is successfully deleted, you will not be able to recover any account data. All information (including rooms, friends), virtual currency, gifts, and virtual items will be permanently deleted and cannot be restored.\n3. Cooling-off period: lf you do not restore the account, you will be unable to access the purchase page, withdrawal page, or any other pages on the app.\n4.During the cooling-off period or after the account has been deleted, the profile page will indicate that it has been deleted. To protect your account from being searched or accessed by others, your personal information will be removed from systems related to daily functions. When account deletion involves national security, civil or criminal proceedings, or the protection of the legitimate rights and interests of third parties, the official reserves the right to reject the user's account deletion request.\nIf you are certain that you want to delete all personal data from your current account, please click \"Delete Account.\"", + "accountDeletionNotice": "Account Deletion Notice:", + "thisUserHasBeenBlacklisted": "This user has been blacklisted.", + "trend": "Trend", + "like": "Like", + "more": "More", + "discard": "Discard", + "catchFirstComment": "Catch first comment", + "reply": "Reply", + "posting": "Posting", + "dynamic": "Dynamic", + "multiple": "Multiple", + "successfullyAddedToTheDynamicBlacklist": "Successfully added to the dynamic blacklist!", + "successfullyRemovedFromTheBlacklist": "Successfully removed from the blacklist!", + "successfullyRemovedFromTheDynamicBlacklist": "Successfully removed from the dynamic blacklist!", + "successfullyAddedToTheBlacklist": "Successfully added to the blacklist!", + "youAreCurrentlyCPRelationshipPleaseDissolve": "You are currently in a CP relationship.\nPlease dissolve it first.", + "areYouSureToCancelBlacklist": "Are you sure to cancel blacklist?", + "areYouSureYouWantToBlockThisUser": "Are you sure you want to block this user?", + "areYouSureYouWantToDynamicBlockThisUser": "Are you sure to add dynamic blacklist?", + "removeFromBlacklist": "Remove from blacklist", + "moveToBlacklist": "Move to blacklist", + "userBlacklist": "User blacklist", + "specialEffectsManagement": "Special effects management", + "wishingYouHappinessEveryDay": "Wishing you happiness every day.", + "newMessage": "New message", + "createRoomSuccsess": "Create room succsess!", + "contactUs": "Contact Me", + "systemAnnouncementTips1": "Guard against fraud:", + "systemAnnouncementTips": "Verify information solely through official channels. Never download third-party software, share personal data, or transfer money based on external requests. Official staff IDs are only 10000, 10003, and 10086. For any suspicion, stop and report via", + "systemAnnouncement": "System Announcement", + "doNotClickUnfamiliarTips": "Do not click unfamiliar links, as they can expose your personal information. Never share your ID or bank card details with anyone.", + "atTag": "@Tag", + "sayHi2": "Say Hi", + "canSendMsgTips": "Both parties need to follow each other before they can send private messages.", + "msgSendRedEnvelopeTips": "*A 10% service fee will be charged on red envelopes, and recipients will only receive 90% of the red envelope's value. The sender's wealth level must be higher than Level 10.", + "leavFamilyTips": "Are you sure you want to leave your current clan? You will need to wait 1 day to rejoin a clan.", + "leavingTheFamily": "Leaving the family", + "familyNotifcations": "Family Notifcations", + "familyNews": "Family News", + "reapply": "Reapply", + "cancelRequestFamilyMsg": "Your request to join family 【{1}'s family】 is in review, are you sure to cancel the request?", + "cancelRequest": "Cancel Request", + "pending": "Pending", + "familyAnnouncement": "Family Announcement", + "enterFamilyAnnouncement": "Please enter the family announcement.", + "disbandTheFamily": "Disband the family", + "editFamily": "Edit Family", + "supporter": "Supporter", + "rechargeSuccessful": "Recharge Successful", + "transactionReceived": "Transaction Received", + "createFamilySuccess": "Create family success.", + "numberOfSign": "Number of sign: {1}", + "hostWeeklyRank": "Host Weekly Rank", + "supporterWeeklyRank": "Supporter Weekly Rank", + "memberList": "Member List", + "treasureChest": "Treasure Chest", + "xxfamily": "{1}'s family", + "applicationRecord": "Application Record", + "createFamily": "Create Family", + "familyName": "Family name", + "createAFamily": "Create a family", + "searchFamilyIdHint": "Please enter the family owner's ID", + "enterFamilyInfo": "Please briefly introduce your family!", + "enterFamilyName": "Please enter your family name.", + "familyInfo": "Family intro", + "joinFamily": "Join a family", + "appUpdateTip": "The app has a new version ({1}), please go and download it?", + "ownerIncomeCoins": "Owner's income:{1} coins", + "game": "Game", + "skip2": "Skip", + "coins4": "Coins", + "currentVip": "Current VIP", + "weekStart": "Week-Start", + "forMoreRewardsPleaseCheckTheTaskCenter": "For more rewards,please check the task center", + "kingQuuen": "King-Quuen", + "ramadan": "Ramadan", + "updateNow": "Update Now", + "allGames": "All Games", + "fishClass": "Fish Class", + "greedyClass": "Greedy Class", + "raceSeries": "Race Series", + "slotsClass": "Slots Class", + "others": "Others", + "hotGames": "Hot Games", + "chatBox": "Chat box", + "termsOfServicePrivacyPolicyTips": "By continuing you agree to the Terms of Service& Privacy Policy", + "and": " and ", + "pleaseSelectTheTypeContent": "Please select the type of offending content.", + "wearHonor": "Wear Honor", + "illegalInformation": "Illegal information", + "inappropriateContent": "Inappropriate content", + "personalAttack": "Personal attack", + "confirm": "Confirm", + "spam": "Spam", + "countdownMinutes": "Countdown Minutes :", + "number2": "Number:", + "fraud": "Fraud", + "received": "Received", + "currentProgress": "Current progress", + "currentStage": "Current stage:{1}", + "roomReward2": "Room Reward:{1}", + "roomReward": "Room Reward", + "expirationTime": "Expiration time", + "ownerSendTheRedEnvelope": "Owner send the Reward coins.", + "rewardCoins": "Reward coins:{1} coins", + "lastWeekProgress": "Last week's progress", + "redEnvelopeTips2": "*If the red bag is not claimed within the time limit, the remaining coins will be returned to the user who sent the red bag.", + "goToRecharge": "Go to recharge", + "deleteAccount2": " Delete Account({1}s)", + "areYouSureYouWantToDeleteYourAccount": " Are you sure you want to delete your account?", + "insufhcientGoldsGoToRecharge": "Insufhcient golds,recharge now!", + "coins2": "{1}Coins", + "remainingNumberTips": "Remaining number of available:({1}/{2})", + "collectionTimeTips": "Collection time:{1}({2}/{3})", + "sendARedEnvelope": "Send a red envelope", + "sendRedPackConfirmTips": "Are you sure you want to send the red packet?", + "redEnvelopeSendingRecords": "Red envelope sending records:", + "redEnvelope": "Red Envelope", + "redEnvelopeRecTips2": "The red envelopes have all been claimed.", + "redEnvelopeRecTips3": "The red envelope collection time has expired!", + "openTheTreasureChest": "Sent out a red bag !", + "redEnvelopeRecTips1": "The earned coins have been deposited into your wallet.", + "redEnvelopeTips1": "Coins:", + "roomTools": "Room Tools:", + "entertainment": "Entertainment:", + "reportSucc": "Report successful", + "pornography": "Pornography", + "reportInputTips": "Please describe the problem in as much detail as possible sothat we can understand and solve it.", + "cancel": "Cancel", + "join": "Join", + "items": "Items", + "vistors": "Vistors", + "fans": "Fans", + "balanceNotEnough": "Insufficient gold coin balance. Do you want to go to top up?", + "skip": "Skip {1}", + "wearMedal": "Wear Medal", + "activityHonor": "Activity Honor", + "achievementHonor": "Achievement Honor", + "youDontHaveAnyHonorYet": "You don't have any honor yet.", + "letGoToWatch": "Let's go to watch!", + "launchedARocket": "launched a rocket", + "sendUserId": "Send UserID:{1}", + "giveUpIdentity": "Give up identity", + "leaveRoomIdentityTips": "Are you sure you want to give up the room identity?", + "joinMemberTips2": "Do you want to confirm joining the room as a member?", + "sureUnfollowThisRoom": "Sure to unfollow this room?", + "welcomeMessage": "Welcome to our application, {name}!", + "settings": "Settings", + "account": "Account", + "common": "Common", + "delete": "Delete", + "copy": "Copy", + "bio": "Bio", + "useCoupontips": "Are you sure you want to use the coupon?", + "searchUserId": "Search user's ID", + "sendUser": "Send User", + "hobby": "Hobby", + "sendCoupontips": "Are you sure you want to send this coupon to this user?", + "youDontHaveAnyCouponsYet": "You don't have any coupons yet.", + "recall": "Recall", + "youHaventFollowed": "You haven't followed any room", + "deleteFromMyDevice": "Delete from my device", + "deleteOnAllDevices": "Delete on all devices", + "messageHasBeenRecalled": "This message has been recalled", + "recallThisMessage": "Recall this message?", + "language": "Language", + "feedback": "Feedback", + "signedin": "Signed in", + "receiveSucc": "Successfully claimed", + "about": "About", + "aboutUs": "About Us", + "theme": "Theme", + "wealthLevel": "Wealth Level", + "userLevel": "User Level", + "goToUpload": "Go to upload", + "logout": "Log out", + "luck": "Luck", + "level": "Level", + "vip": "VIP", + "vip1": "VIP1", + "vip2": "VIP2", + "vip3": "VIP3", + "vip4": "VIP4", + "vip5": "VIP5", + "vip6": "VIP6", + "themeGoToUploadTips": "1.Review within24 hours after the upload is successful.\n2.All coins will be returned if the review fails.", + "home": "Home", + "explore": "Explore", + "me": "Me", + "socialPrivilege": "Social privilege", + "information": "Information", + "myPhoto": "My Photo", + "cpRequest": "CP Request", + "areYouSureYouWantToSpend3": "*lf the other party declines the CP invitation, your coins willbe returned to your wallet.", + "areYouSureYouWantToSpend": "Are you sure you want to spend", + "areYouSureYouWantToSpend2": "to send a CP invitation to this user?", + "cpSexTips": "Couples of the same gender cannot be created.", + "underReview": "Under review", + "doYouWantToDeleteIt": "Do you want to delete it?", + "chooseFromAblum": "Choose from Ablum", + "spaceBackground": "Space Background", + "editProfile": "Edit Profile", + "sendTheCpRequest": "Send the CP request", + "addCp": "Add CP", + "partWays": "Part Ways", + "reconcile": "Reconcile", + "separated": "Separated", + "areYouSureYouWantToSpend5": "{1} confessed the feeling to you; if you accept, you will become a couple.", + "areYouSureYouWantToSpend6": "{1} wants to get back together with you. lf you decide to reconcile, all of your previous data will be restored.", + "reconcileInvitationTips": "*lf the other party declines the CP invitation, your coins willbe returned to your wallet.", + "reconcileInvitation": "Reconcile Invitation", + "areYouSureYouWantToSpend4": "to send a reconciliation invitation to this user?", + "partWaysTips": "*lf one partner in a couple chooses to part ways, there willbe a 7-day cooling-off period. During this time, both partiescan choose to reconcile, and all data will be restored uponreconciliation. lf no reconciliation is chosen by the end ofthe 7-day period, the couple's data will be cleared.", + "areYouSureYouWantToPartWaysWithYourCP": "Are you sure you want to part ways with your CP?", + "timeSpentTogether": "Time spent together: {1} days", + "firstDay": "First day:{1}", + "numberOfMyCPs": "Number of my CPs:({1}/{2})", + "props": "Props", + "medal": "Medal", + "win": "Win", + "dice": "Dice", + "rps": "RPS", + "areYouSureToCancelDynamicBlacklist": "Are you sure to cancel dynamic blacklist?", + "blockUserDynamic": "Block User Dynamic", + "unblockUserDynamic": "Unblock User Dynamic", + "operationFail": "The operation was fail.", + "likedYourComment": "Liked your Comment.", + "likedYourDynamic": "Liked your Dynamic.", + "doYouWantToKeepTheDraft": "Do you want to keep the draft?", + "cantSendDynamicTips": "You are currently blocked from posting Dynamics.", + "operationsAreTooFrequent": "Operations are too frequent", + "luckNumber": "Luck Number", + "relationShip": "Relationship", + "couple": "Couple {1}:", + "couple2": "Couple", + "reject": "Reject", + "cpList": "CP List", + "sound2": "Sound", + "hot": "Hot", + "selectCountry": "Select country", + "giftEffect": "Gift Effect", + "winFloat": "Win Float", + "giftVibration": "Lucky Gift Effects", + "accept": "Accept", + "noMatchedCP": "No matched CP", + "inviteYouToBecomeBD": "Invite you to become a BD.", + "adminInviteRechargeAgent": "Invite you to become a Recharge agent.", + "confirmAcceptTheInvitation": "Confirm to accept the invitation?", + "confirmDeclineTheInvitation": "Confirm decline of the invitation?", + "host": "Host", + "following": "Following", + "agent": "Agency", + "approved": "Approved", + "agreeJoinFamilyTips": "Do you allow this user to join the family?", + "refuseJoinFamilyTips": "Should you refuse to let the userjoin the family?", + "onlineUsers": "Online Users({1}/{2}):", + "applyToJoin": "Apply to join", + "supporterList": "Supporter List", + "hostList": "Host List", + "upToAdmins": "Up to {1} Admins", + "upToMembers": "Up to {1} Members", + "levelPrivileges": "Level privileges", + "familyLevel": "Family Level", + "kickOutOfFamily": "Kick out of family", + "disbandTheFamilyTips": "Are you sure you want to disband the family?", + "setAsFamilyAdmin": "Set as family admin", + "cancelFamilyAdmin": "Cancel family admin", + "kickFamilyUserTips": "Are you sure you want to kick this user out of the family?", + "familyMember2": "Family Member({1}/{2}):", + "familyMember3": "Family Member({1}):", + "familyAdmin2": "Family Admin({1}/{2}):", + "familyOwner2": "Family Owner:", + "ra": "RA", + "roomAnnouncement": "Room Announcement", + "family3": "{1} s'Family", + "help": "Help", + "rejected": "Rejected", + "boxContributeTips": "Investment has already been made today, please do not invest again", + "familyHelpTips": "(1)lf you invest 50 coins, the first treasure chest will be unlocked when 5 people invest. You can click to receive 50 coins.(2)The second treasure chest will be unlocked when 10 people invest. You can receive 50 coins.\n(3)The second treasure chest will be unlocked when 20 people invest. You can receive 100 coins.\n(4) lf the required number of users to claim the treasure chest is not reached on the same day, thetreasure chest progress will be reset the next day.\n(5)The treasure chest progress will reset the next day. Users who have not yet claimed their treasure chests on the same day should claim them in time.\n(6)Treasure chest reset time: 00:00 Saudi time.", + "bd": "BD", + "coupon": "Coupon", + "search": "Search", + "get": "Get", + "inRocket": "In the rocket", + "roomRocketHelpTips": "1. Sending gifts in the room increases rocket energy. *1 gold coin gift = 1 rocket energy point; lucky gifts increase rocket energy by 4% of the gift's gold coin value.\n2. Once the rocket energy is fully charged, the room can launch the rocket. Rewards will be automatically distributed after launch.\n3. Different rocket levels offer different rewards.\n4. When the rocket launches, all users in the room can claim the rocket reward.5. Rocket energy is reset at 00:00 every day.", + "couponRecord": "Coupon usage record", + "inRoom": "In Room", + "searchCouponHint": "Search Coupon", + "giftCounter": "Gift counter", + "bDLeaderInviteYouToBecomeBDLeader": "Invite you to become a BDLeader", + "wins": "wins", + "inviteYouToBecomeHost": "Invite you to become a host.", + "friends": "Friends", + "deleteConversationTips": "Are you sure you want to delete the chat history with this user?", + "propMessagePrompt": "Prop message prompt", + "inputUserId": "Enter User ID", + "fromLuckyGifts": "from lucky gifts", + "receive": "Receive", + "checkInSuccessful": "Check-in successful", + "sginTips": "You will get reward at the frst time you log in eachday. lf you interrupt your login, the reward will becalculated from the frst day when you log in again.", + "vipBadge": "VIP Badge", + "vipProfileFrame": "VIP Profile Frame", + "vipProfileCard": "VIP Profile Card", + "popular": "Popular", + "recommend": "Recommend", + "follow": "Follow", + "history": "History", + "hotRooms": "Hot Rooms", + "viewMore": "View more", + "noData": "No data", + "users": "Users", + "rooms": "Rooms", + "coins": "coins", + "unread": "Unread", + "read": "Read", + "image": "[Image]", + "video": "[Video]", + "sound": "[Sound]", + "gift2": "[Gift]", + "clickHereToStartChatting": "Say something.....", + "receivedAMessage": "[Received a message]", + "confirmSwitchMicModelTips": "Do you confirm the switch in seating mode?", + "number": "Number", + "album": "Album", + "camera": "Camera", + "system": "System", + "notifcation": "Notice", + "inviteYouToBecomeAgent": "Invite you to become a agency.", + "myMusic": "My Music", + "add": "Add", + "pullToLoadMore": "Pull to load more", + "loadingFailedClickToRetry": "Loading failed, click to retry", + "releaseToLoadMore": "Release to load more", + "haveMyLimits": "---I have my limits.---", + "music": "Music", + "free": "Free", + "charm": "Gift charm", + "start": "Start", + "vipUseThisThemeTips": "Only users who meet the VlP level can use this theme.", + "stop": "Stop", + "chats": "Chats", + "family": "Family", + "refuse": "Refuse", + "agree": "Agree", + "thisFeatureIsCurrentlyUnavailable": "This feature is currently unavailable.", + "pleaseSelectTheRecipient": "Please select the recipient.", + "searchMemberIdHint": "Please enter the member's ID", + "unclaimedRedEnvelopes": "Unclaimed red envelopes are refunded in 24 hours.", + "redEnvelopeNotYetClaimed": "Red envelope not yet claimed.", + "redEnvelopeAmount": "Red envelope amount: {1} coins", + "sentARedEnvelope": "sent a red envelope.", + "theRedEnvelopeHasExpired": "The red envelope has expired.", + "joinRequest": "Join Request", + "welcomeToMyFamily": "Welcome to my family!", + "scrollToTheBottom": "Scroll to the bottom", + "gameRules": "Game Rules:", + "charmGameRulesTips": "Turn on the dashboard to show received gift coin of all users on mic,1 coin = 1 score (Lucky gift 1 coin = 0.04 score).", + "inputYourOldPassword": "Input your old password", + "enterYourOldPassword": "Enter your old password", + "setYourPassword": "Set your password", + "enterYourNewPassword": "Enter your new password", + "confirmYourPassword": "Confirm your password", + "theTwoPasswordsDoNotMatch": "The two passwords do not match.", + "resetLoginPasswordtTips2": "The password must be 8-16 characters long and must be a combination ofuppercase and lowercase English letters and numbers (not just numbers)", + "resetLoginPassword": "Reset Login Password", + "resetLoginPasswordtTips1": "Log in with your user ID or vanity ID. lt is safer to log in with a Aslan account.", + "localMusic": "Local Music", + "setLoginPassword": "Set Login Password", + "confirmSwitchMicThemeTips": "Do you confirm the switch of seat style?", + "pleaseUpgradeYourVipLevelFirst": "Please upgrade your VIP level first.", + "micTheme": "Mic Theme", + "classicMic": "Classic {1} Mic", + "yesterday": "Yesterday {1}", + "monday": "Monday {1}", + "tuesday": "Tuesday {1}", + "wednesday": "Wednesday {1}", + "thursday": "Thursday {1}", + "friday": "Friday {1}", + "saturday": "Saturday {1}", + "sunday": "Sunday {1}", + "acceptedYour": "{1} accepted your ", + "youAccepted": "You accepted {1} ", + "openRedPackDialogTip": "The red envelope", + "micManagement": "Mic Management", + "vipChatBox": "VIP Chat Box", + "vipRoomCoverBorder": "VIP Room Cover Border", + "bdCenter": "BD Center", + "renewVip": "Renew VIP", + "rechargeAgency": "Recharge Agency", + "adminCenter": "Admin Center", + "goldList": "Gold List", + "followList": "Follow List", + "fansList": "Fans List", + "daily": "Daily", + "cp": "CP", + "female": "Female", + "male": "Male", + "identity": "Identity", + "adjust": "Adjust", + "warning": "Warning", + "screenshotTips": "Screenshot (Up to 3)", + "roomNotice": "Room notice", + "roomTheme": "Room theme", + "description": "Description:", + "inputDesHint": "Please describe the problem in as much detail as possible sothat we can understand and solve it.", + "roomProfilePicture": "Room profile picture", + "userProfilePicture": "User profile picture", + "userName": "User name", + "pleaseSelectTheTypeToProcess": "Please select the type to process:", + "roomEditing": "Room Editing", + "setAccount": "Set Account", + "userEditing": "User Editing", + "enterTheUserId": "Enter the userId", + "enterTheRoomId": "Enter the roomId", + "deleteAccount": "Delete Account", + "becomeAgent": "Become a agent", + "enterNickname": "Enter Nickname", + "selectYourCountry": "Select your country", + "inviteCode": "Invite Code", + "magic": "Magic", + "list": "List", + "walletBalance": "Wallet balance:", + "canOnlyBeShown": "*Can only be shown to all users in your area.", + "sendMessage": "Send Message", + "availableCountries": "Available Countries:", + "currentBalance": "Current Balance:", + "successfulTransaction": "{1} Successful Transaction", + "hasBeenACoinAgencyForDays": "Has Been A Coin Agency For {1} Days", + "luckGiftSpecialEffects": "Lucky gift animation effets", + "theVideoSizeCannotExceed": "The video size cannot exceed 50M", + "weekly": "Weekly", + "rechargeRecords": "Recharge Records", + "balance": "balance:", + "appStore": "App Store", + "searchCountry": "Search country...", + "googlePay": "Google Pay", + "biggestDiscount": "Biggest discount", + "officialRechargeAgent": "Official recharge agent", + "customizedGiftRulesContent": "How to get my customized gift:\n1.Determine whether the user is eligible to receive a customized gift based on their current wealth level.\n(1) When the user's wealth level is ≥35: \u2028The user can upload a video for the customized gift once. We will use the user's current profile picture as the gift image and the provided video as the gift effect on \"Customized.\" Production will take some time, and we will notify you as soon as it is available in the store.\n(2) When the user's wealth level is ≥45: \u2028The user can upload a video for the customized gift once. We will use the user's current profile picture as the gift image and the provided video as the gift effect on \"Customized.\" Production will take some time, and we will notify you as soon as it is available in the store.\n2.You can participate in specific activities on app and, after meeting the criteria, contact us through the \"Message\" → \"Contact us\" section. Provide screenshots of the activity and the video you wish to use for the customized gift. We will use your current profile picture as the gift image and the provided video as the gift effect, then list it under \"Customized.\" Production will take some time, and we will notify you as soon as it is available in the store.\nCustomized Gift Validity Period & How to Extend It:\nThe exclusive customized gifts will be available on the shelf for a validity period of 30 days. To enable influential and inspiring users to continue enjoying this new experience and showcase their personal style, users who own customized gifts can extend the shelf time of all their customized gifts by 30 days by recharging $500 in any given month.", + "customizedGiftRules": "Customized Gift Rules", + "rulesUpload": "Rules&Upload", + "monthly": "Monthly", + "playLog": "Play Log:", + "selectACountry": "Select a country:", + "message": "Message", + "clearCache": "Clear Cache", + "customized": "Customized", + "searchInputHint": "Enter account/room number", + "kickRoomTips": "You have been kicked out of the room.", + "joinRoomTips": "joined room !", + "roomSetting": "Room Setting", + "roomDetails": "Room Details", + "systemRoomTips": "Please maintain courtesy and respect.Any pornographic or vulgar content is strictly prohibited on Aslan. Any account found in violation will be permanently banned. We kindly ask all users to consciously abide by Aslan's community guidelines.", + "copiedToClipboard": "Copied to clipboard", + "recharge": "Recharge", + "receivedFromALuckyGift": "Received from a lucky gift.", + "followedYou": "Followed you", + "agentCenter": "Agency Center", + "areYouSureYouWantToClearLocalCache": "Are you sure you want to clear local cache?", + "clearMessage": "Clear screen messages", + "report": "Report", + "coins3": "Coins", + "giftSpecialEffects": "Gift special effects", + "basicFeatures": "Basic Features", + "task": "Task", + "importantReminder": "Important Reminder", + "entryVehicleAnimation": "Entry vehicle animation(VIP3)", + "floatingAnimationInGlobal": "Floating animation in global", + "entryVehicleAnimation2": "Users with VlP3 or higher privileges can use the functionto disable vehicle animations.", + "dailyTasks": "Daily Tasks", + "enterRoomConfirmTips": "Are you sure you want to enter the room?", + "followSucc": "Followed successfully", + "goldListort": "Gold List", + "rechargeList": "Recharge List", + "edit": "Edit", + "swipeLeftOnTheFloatingScreenAreaToQuicklyCloseIt": "*Tip: Swipe left on the floating screen area to quickly close it.", + "enterThisVoiceChatRoom": "Enter this voice chat room?", + "go": "Go", + "done": "Done", + "improvementTasks": "Improvement Tasks", + "save": "Save", + "kickPrevention": "Kick prevention", + "freeChatSpeak": "Free Chat & Speak", + "vipExclusiveVehicles": "VIP Exclusive Vehicles", + "nickName": "NickName", + "buyVip": "Buy VIP", + "gender": "Gender", + "unFollow": "Unfollow", + "days": "Days", + "permanent": "Permanent", + "yourVipWillExpire": "Your VIP will expire on {1}", + "cantBuyVip": "Unable to continue the purchase", + "country": "Country", + "birthday": "Birthday", + "man": "Man", + "woman": "Woman", + "apple": "Apple", + "google": "Google", + "idIcon": "ID Icon", + "inviteToBecomeAHost": "Invite to become a host", + "currentLevelPrivilegesAndCostumes": "Current level privileges and costumes: {1}", + "uploadGifAvatar": "Upload GlF avatar(VIP2)", + "uploadProfilePicture": "Upload Profile Picture", + "permissionSettings": "Permission settings", + "vipMicSoundWave": "VIP Mic Sound Wave", + "startVoiceParty": "Start a voice party!", + "enterRoomTips": "{1} Enter the room", + "roomName": "Room Name", + "roomMember": "Room Member", + "pleaseSelectYourCountry": "Please select your country.", + "pleaseSelectYourGender": "Please select your gender.", + "pleaseEnterNickname": "Please enter a nickname.", + "countryRegion": "Country&Region", + "dateOfBirth": "Date of birth", + "mute": "Mute", + "exit": "Exit", + "mysteriousInvisibility": "Mysterious invisibility", + "antiBlock": "Anti-Block", + "createFamilyForFree": "Create Family for Free", + "privateChat": "Private Chat", + "vipBirthdayGift": "VIP Birthday Gift", + "storeDiscount": "Store Discount {1} Off", + "membershipFreeChatSpeak": "Membership-free Chat & Speak", + "priorityRoomSorting": "Priority Room Sorting", + "userColoredID": "User Colored ID", + "vipEmoticon": "VIP Emoticon({1})", + "pleaseSelectaItem": "Please select a item", + "areYouRureRoRecharge": "Are you sure to recharge?", + "mInimize": "Keep", + "everyone": "Everyone", + "shop": "Shop", + "roomOwner": "Room Owner", + "dailyTaskRewardBonus": "Daily Task Reward Bonus({1} XP)", + "userLevelXPBoost": "User Level XP Boost({1} XP)", + "pleaseUpgradeYourVipLevel": "Please upgrade your VIP level.", + "andAboveUsers": "{1} And Above Users", + "privileges": "{1} Privileges", + "basicPermissions": "Basic permissions", + "hostCenter": "Host Center", + "allOnMicrophone": "All on mic", + "usersOnMicrophone": "Users on mic", + "allInTheRoom": "All in room", + "send": "Send", + "crop": "Crop", + "goToUpgrade": "Go to upgrade", + "exclusiveEmojiWillBeReleasedAfterBecoming": "Exclusive emoji will be released after becoming", + "preventBeingBlocked": "Prevent Being Blocked", + "enableRankIncognitoMode": "Enable Rank Incognito Mode", + "avoidBeingKicked": "Avoid Being Kicked", + "vipPrivilege": "VIP privilege", + "finish": "Finish", + "takeTheMic": "Take the mic", + "openTheMic": "Open the mic", + "muteTheMic": "Mute the mic", + "unlockTheMic": "Unlock the mic", + "leavelTheMic": "Leavel the mic", + "lockTheMic": "Lock the mic", + "removeTheMic": "Remove the mic", + "inviteToTheMicrophone": "Invite to the microphone", + "openUserProfleCard": "Open user profle card", + "obtain": "obtain", + "win2": "Win {1}", + "backTheRoom": "Back room", + "toConsume": "To consume", + "howToUpgrade": "How to upgrade?", + "spendCoinsToGainExperiencePoints": "Spend Coins to gain experience points", + "higherLevelFancierAvatarFrame": "Higher level, fancier badges/avatar frame", + "medalAndAvatarFrameRewards": "Medal and avatar frame rewards", + "all": "All", + "gift": "Gift", + "chat": "Chat", + "owner": "Owner", + "store": "Store", + "admin": "Admin", + "member": "Member", + "guest": "Guest", + "submit": "Submit", + "claim": "Claim", + "complete": "Complete", + "shareTo": "Share to", + "copyLink": "Copy Link", + "faceBook": "Facebook", + "whatsApp": "Whatsapp", + "snapChat": "Snapchat", + "taskNamePersonalGameConsume": "Game Spending", + "taskNameRoomNewMember": "New Room Members", + "taskNameRoomOwnerSendRedPacket": "Room Owner sends one red packet", + "taskNameRoomOwnerSendGiftUser": "Room Owner Sends Gift", + "taskNameRoomMicUser120Min": "Members with 120+ minutes on mic", + "taskNameRoomMicUser60Min": "Members with 60+ minutes on mic", + "taskNameRoomMicUser30Min": "Members with 30+ minutes on mic", + "taskNamePersonalSendGift": "Send Gift to User", + "taskNameRoomOnlineUserCount": "Room online members", + "taskNameRoomOwnerInviteMic": "Invite member to Mic", + "taskNameRoomUserSendGiftGold": "Coins gifted by members", + "taskNameRoomOwnerSendGiftGold": "Coins gifted by room owner", + "taskNamePersonalMagicGiftGold": "Coins Won by Sending Magic Gifts", + "taskNamePersonalLuckyGiftGold": "Coins Won by Sending Lucky Gifts", + "taskNameRoomOwnerMicTime": "Room Owner goes on mic in the room", + "taskNamePersonalActiveInRoom": "Be Active in Others' Rooms", + "taskNamePersonalMicInRoom": "Go on Mic", + "dailyCoinBonanzaRulesDetail": "1. Daily Personal Tasks and Daily Room Owner Tasks can be completed once per user per day. Tasks reset at 00:00:00 Saudi time.\n2.Daily personal tasks can only be completed in others' rooms; room owner tasks can only be completed in your own room.\n3. Gift-giving tasks only count gifts sent in rooms, not gifts sent on feeds.\n4. If multiple accounts are created using the same device or the same SIM card in any way, rewards for all tasks can only be claimed once.", + "dailyCoinBonanzaRules": "Daily Coin Bonanza Rules", + "roomOwnerTasks": "Room Owner Tasks", + "personalTasks": "Personal Tasks", + "noPromptsToday": "No prompts today.", + "getPaidToRefer": "Get Paid to Refer", + "membershipFee": "Membership Fee", + "membershipFeeTips1": "Please set the membership fee for your room. Users can join your room by paying the fee.", + "membershipFeeTips2": "The golds required for the user to become the room member.The room owner will get 50% of the golds.", + "freePrice": "Fee:0-10000", + "touristsSendText": "Tourist send text", + "touristsTakeToTheMic": "Tourists take to the mic", + "theMembershipFee": "The membership fee", + "theModificationsMade": "The modifications made this time will not be saved after exiting", + "viewFrame": "View Frame", + "enterRoomName": "Enter room name", + "headdress": "Frames", + "mountains": "Vehicles", + "purchaseIsSuccessful": "Purchase is successful", + "buy": "Buy", + "followed": "Followed", + "follow2": "Follow:{1}", + "fans2": "Fans:{1}", + "vistors2": "Vistors:{1}", + "personal2": "Personal:", + "family2": "Family:", + "conntinue": "Conntinue", + "confirmBuyTips": "Are you sure you want to buy?", + "purchase": "Purchase", + "setRoomPassword": "Set room password", + "inputRoomPassword": "Input room password", + "enter": "Enter", + "createDynamicSuccess": "Created dynamic successfully", + "deleteDynamicTips": "Are you sure you want to delete this dynamic?", + "deleteCommentTips": "Are you sure you want to delete this comment?", + "deleteSuccessful": "Deletion successful!", + "itemsLeft": "Items left", + "password": "Password", + "replySucc": "Reply successful", + "comment": "Comment", + "showMore": "Show more", + "showLess": "Show less", + "enterPassword": "Enter Password", + "enterAccount": "Enter Account", + "logIn": "Log In", + "saySomething": "Say something...", + "sayHi": "Say Hi..", + "pleaseChatFfriendly": "Please chat friendly", + "unLockTheRoom": "Unlock The Room", + "operationSuccessful": "The operation was successful.", + "adminByHomeowner": "is set as an administrator by the homeowner.", + "memberByHomeowner": "is set as members by the homeowner.", + "touristByHomeowner": "is set as a tourist by the homeowner.", + "becomeHost": "Apply To Become A Host", + "superFans": "Super fans:", + "setUpAnIdentity": "Set up an identity", + "kickedOutOfRoom": "Kicked out of room", + "playGiftMusicAndDynamicMusic": "Play gift music and dynamic music", + "knapsack": "Knapsack", + "bdLeader": "BD Leader", + "picture": "Picture", + "theImageSizeCannotExceed": "Upload failed: File must be under 2MB.", + "activity": "Activity", + "alreadyAnAdministrator": "Already an administrator", + "alreadyAnMember": "Already an member", + "alreadyAnTourist": "Already an tourist", + "touristsCannotSendMessages": "Tourists cannot send messages", + "touristsAreNotAllowedToGoOnTheMic": "Tourists are not allowed to go on the mic", + "lockTheRoom": "Lock The Room", + "special": "Special", + "visitorList": "Visitor List", + "successfulWear": "Successful wear", + "confirmUnUseTips": "Do you confirm to remove it?", + "custom": "Custom", + "myItems": "My Items", + "use": "Use", + "unUse": "Un use", + "renewal": "Renewal", + "wallet": "Wallet", + "profile": "Profile", + "giftwall": "Giftwall", + "announcement": "Announcement", + "blockedList": "Blocked list", + "country2": "Country:", + "sendTo": "Send to", + "medals": "Medals", + "activityMedal": "Activity Medal", + "achievementMedal": "Achievement Medal", + "credits": "Credits: {1}", + "successfullyUnloaded": "Successfully unloaded", + "expired": "Expired", + "day": "Day", + "inUse": "In use", + "confirmUseTips": "Do you want to confirm using it?", + "pleaseUploadUserAvatar": "Please upload an avatar.", + "joinMemberTips": "If you are a visitor in the room, you cannot take the microphone.", + "giftGivingSuccessful": "Gift giving successful.", + "theAccountPasswordCannotBeEmpty": "The account or password cannot be empty.", + "invitesYouToTheMicrophone": "{1} invites you to the microphone", + "english": "English", + "chinese": "Chinese", + "arabic": "Arabic", + "darkMode": "Dark Mode", + "lightMode": "Light Mode", + "systemDefault": "System Default", + "pleaseGetOnTheMicFirst": "Please get on the mic first.", + "duration2": "Duration:{1}" +} \ No newline at end of file diff --git a/assets/l10n/intl_tr.json b/assets/l10n/intl_tr.json new file mode 100644 index 0000000..57912ae --- /dev/null +++ b/assets/l10n/intl_tr.json @@ -0,0 +1,768 @@ +{ + "signInWithGoogle": "Google ile Giriş Yap", + "or": "Veya", + "signInWithYourAccount": "Hesabınız ile Giriş Yap", + "signInWithApple": "Apple ile Giriş Yap", + "loginRepresentsAgreementTo": "Giriş yapmak, aşağıdakilere kabul ettiğiniz anlamına gelir", + "termsofService": "Hizmet Şartları", + "privaceyPolicy": "Gizlilik Politikası", + "tips": "İpuçları", + "dailyTasksTips": "Not: Görevleri üstlenmeye hazır mısınız? Onları en sevdiğiniz odalarda bulun.", + "honor": "Onur", + "searchNoDataTips": "Aramak istediğiniz oda veya kullanıcı kimliğini girin.", + "mine": "Benimki", + "party": "Parti", + "sound2": "Ses", + "selectCountry": "Ülke seç", + "hot": "Sıcak", + "giftVibration": "Şanslı Hediye Etkileri", + "winFloat": "Kazan Yüzen", + "giftEffect": "Hediye Etkisi", + "myRoom": "Odam", + "list": "Liste", + "walletBalance": "Cüzdan bakiyesi:", + "canOnlyBeShown": "*Yalnızca bulunduğunuz bölgedeki tüm kullanıcılara gösterilebilir.", + "sendMessage": "Mesaj Gönder", + "availableCountries": "Mevcut Ülkeler:", + "currentBalance": "Mevcut Bakiye:", + "successfulTransaction": "{1} Başarılı İşlem", + "hasBeenACoinAgencyForDays": "{1} Gündür Bir Madeni Para Ajansı Olmuştur", + "rechargeRecords": "Şarj Kayıtları", + "balance": "bakiye:", + "searchCountry": "Ülke ara...", + "appStore": "Uygulama Mağazası", + "googlePay": "Google Pay", + "biggestDiscount": "En büyük indirim", + "officialRechargeAgent": "Resmi dolum acentesi", + "selectACountry": "Bir ülke seçin:", + "playLog": "Oyun Kaydı:", + "luckGiftRuleTips": "Şanslı bir hediye verin ve altın madeni ödülünde 1000 katına kadar kazanın! Şanslı hediyeler alan kullanıcılar aşağıdaki avantajlardan yararlanabilir: \n(1) Şanslı hediye alan kullanıcılar için: Hediyenin değeri üzerinden %4 cazibe deneyimi puanı.\n(2) Eğer alıcı bir yayıncı ise: Hediyenin değeri üzerinden %4 yayıncı maaş hedefi puanı.", + "vipAccelerating": "{1} Hızlanıyor", + "badge": "Rozet", + "glory": "Zafer", + "vipRippleTheme": "VIP Ripple Teması", + "gifProfileUpload": "GIF Profil Yükleme", + "badgeHonor": "Rozet/Zafer", + "levelMedal": "Seviye Madalyası", + "levelIcon": "Seviye Simgesi", + "other": "Diğer", + "maliciousHarassment": "Kötü niyetli taciz", + "numberOfMic": "Mikrofon Sayısı", + "pleaseEnterContent": "Lütfen içerik girin", + "profilePhoto": "Profil Fotoğrafı", + "aboutMe": "Hakkımda", + "roomEdit": "Oda Düzenle", + "cancelRoomPassword": "Oda şifresini silmek istediğinizden emin misiniz?", + "roomMemberFee": "Oda Üyelik Ücreti", + "blockedList2": "Engellenen Liste", + "roomTheme2": "Oda Teması", + "roomPassword": "Oda Şifresi", + "noHistoricalRecordsAvailable": "Mevcut tarihi kayıt yok.", + "inviteNewUsersToEarnCoins": "Yeni kullanıcıları davet ederek coin kazanın", + "crateMyRoom": "Odamı oluştur", + "event": "Etkinlik", + "vipSpecialGiftTassel": "VIP Özel hediye püskülü", + "vipEntranceEffect": "VIP Giriş Efekti", + "youHaveNotHadVIPYet": "Henüz VIP olmadınız, gelin ve deneyin", + "clearCacheSuccessfully": "Önbellek başarıyla temizlendi", + "sent": "Gönderildi", + "keep": "Sakla", + "open": "Aç", + "confirmInviteThisUserToTheRoom": "Bu kullanıcıyı (ID:{1}) odaya davet etmeyi onaylıyor musunuz?", + "deleteAccountTips2": "*Fikrinizi değiştirirseniz, yedi gün içinde mevcut hesabınıza tekrar giriş yapabilir ve hesapınız otomatik olarak geri yüklenecektir. Yedi gün içinde geri yüklenmezse, hesap kalıcı olarak silinir", + "deleteAccountTips": "Bu hesap üzerinde tam yönetim haklarınız bulunur. Hesabı silmek istiyorsanız, bu işlemin ilişkili olduğu aşağıdaki riskleri lütfen unutmayın:\n1.Hesap başarıyla silindikten sonra, mevcut hesabınıza artık giriş yapamazsınız. Hesap silme kalıcı bir eylemdir.\n2. Hesap başarıyla silindikten sonra, hesap verilerinin hiçbirini kurtaramazsınız. Tüm bilgiler (odalar, arkadaşlar dahil), sanal para, hediyeler ve sanal eşyalar kalıcı olarak silinir ve geri yüklenemez.\n3. Soğuma süresi: Hesabı geri yüklemezseniz, uygulamadaki satın alma sayfasına, para çekme sayfasına veya diğer herhangi bir sayfaya erişemezsiniz.\n4.Soğuma süresi boyunca veya hesap silindikten sonra, profil sayfası silindiğini gösterecektir. Hesabınızın başkaları tarafından aranmasını veya erişilmesini engellemek için, kişisel bilgilerinizin günlük işlevlerle ilgili sistemlerden kaldırılacaktır. Hesap silme ulusal güvenliği, siviller veya cezai davaları veya üçüncü tarafların geçerli hak ve menfaatlerinin korunmasını içerdiğinde, resmi yönetim kullanıcının hesap silme isteğini reddetme hakkını saklı tutar.\nEğer mevcut hesabınızdaki tüm kişisel verileri silmekten eminseniz, lütfen \"Hesabı Sil\" butonuna tıklayın.", + "accountDeletionNotice": "Hesap Silme Bildirimi:", + "thisUserHasBeenBlacklisted": "Bu kullanıcı kara listeye alındı.", + "trend": "Trend", + "like": "Beğen", + "more": "Daha Fazla", + "discard": "Vur", + "catchFirstComment": "İlk Yorumu Yakala", + "reply": "Cevapla", + "posting": "Gönderiliyor", + "dynamic": "Dinamik", + "multiple": "Çoklu", + "successfullyAddedToTheDynamicBlacklist": "Dinamik kara listeye başarıyla eklendi!", + "successfullyRemovedFromTheBlacklist": "Kara listeden başarıyla kaldırıldı!", + "successfullyRemovedFromTheDynamicBlacklist": "Dinamik kara listeden başarıyla kaldırıldı!", + "successfullyAddedToTheBlacklist": "Kara listeye başarıyla eklendi!", + "youAreCurrentlyCPRelationshipPleaseDissolve": "Şu anda bir CP ilişkisindesiniz.\nLütfen önce ilişkiyi sonlandırın.", + "areYouSureToCancelBlacklist": "Kara listeyi iptal etmek istediğinizden emin misiniz?", + "areYouSureYouWantToBlockThisUser": "Bu kullanıcıyı engellemek istediğinizden emin misiniz?", + "areYouSureYouWantToDynamicBlockThisUser": "Dinamik kara listeye eklemek istediğinizden emin misiniz?", + "removeFromBlacklist": "Kara listeden kaldır", + "moveToBlacklist": "Kara listeye ekle", + "userBlacklist": "Kullanıcı kara listesi", + "specialEffectsManagement": "Özel Efekt Yönetimi", + "wishingYouHappinessEveryDay": "Her gün mutlu olmanızı dileyiyoruz.", + "newMessage": "Yeni mesaj", + "createRoomSuccsess": "Oda başarıyla oluşturuldu!", + "contactUs": "Bize Ulaşın", + "systemAnnouncementTips1": "Sahtekarlığa karşı dikkat:", + "systemAnnouncementTips": "Bilgileri yalnızca resmi kanallar aracılığıyla doğrulayın. Hiçbir zaman üçüncü taraf yazılımı indirme, kişisel verileri paylaşma veya dış istekler üzerine para transferi yapmayın. Resmi personel kimlik numaraları yalnızca 10000, 10003 ve 10086'dır. Herhangi bir şüpheniz olursa, işlemi durdurun ve üzerinden bildirin", + "systemAnnouncement": "Sistem Duyurusu", + "doNotClickUnfamiliarTips": "Tanımadığınız bağlantılara tıklayın, çünkü bunlar kişisel bilgilerinizi ifşa edebilir. Kimlik numaranızı veya banka kartı detaylarınızı asla kimseyle paylaşmayın.", + "atTag": "@Etiket", + "sayHi2": "Merhaba De", + "canSendMsgTips": "Özel mesaj göndermek için her iki tarafın da birbirini takip etmesi gerekir.", + "msgSendRedEnvelopeTips": "*Kırmızı zarflar üzerinde %10 hizmet ücreti kesilecektir ve alıcılar yalnızca kırmızı zarfın değerinin %90'ını alacaktır. Göndericinin servet seviyesi 10. Seviyeden yüksek olmalıdır.", + "leavFamilyTips": "Mevcut klanı terk etmek istediğinizden emin misiniz? Klanına tekrar katılmak için 1 gün beklemelisiniz.", + "leavingTheFamily": "Klanı Terk Etme", + "familyNotifcations": "Klan Bildirimleri", + "familyNews": "Klan Haberleri", + "reapply": "Tekrar Başvur", + "cancelRequestFamilyMsg": "{1} klanına katılma isteğiniz inceleniyor, isteği iptal etmek istediğinizden emin misiniz?", + "cancelRequest": "İsteği İptal Et", + "pending": "Beklemede", + "familyAnnouncement": "Klan Duyurusu", + "enterFamilyAnnouncement": "Lütfen klan duyurusunu girin.", + "disbandTheFamily": "Klanı Dağıt", + "editFamily": "Klanı Düzenle", + "supporter": "Destekçi", + "transactionReceived": "İşlem Alındı", + "rechargeSuccessful": "Şarj Başarılı", + "createFamilySuccess": "Klan başarıyla oluşturuldu.", + "numberOfSign": "Giriş Sayısı: {1}", + "hostWeeklyRank": "Sunucu Haftalık Sıralaması", + "supporterWeeklyRank": "Destekçi Haftalık Sıralaması", + "memberList": "Üye Listesi", + "treasureChest": "Hazine Sandığı", + "xxfamily": "{1}'in Klanı", + "applicationRecord": "Başvuru Kaydı", + "createFamily": "Klan Oluştur", + "familyName": "Klan Adı", + "createAFamily": "Klan Oluştur", + "searchFamilyIdHint": "Lütfen klan sahibinin kimlik numarasını girin", + "enterFamilyInfo": "Lütfen klanınızı kısa tanıtın!", + "enterFamilyName": "Lütfen klan adınızı girin.", + "familyInfo": "Klan Tanıtımı", + "joinFamily": "Klan Katıl", + "appUpdateTip": "Uygulamanın yeni bir sürümü var ({1}), lütfen indirin mi?", + "ownerIncomeCoins": "Sahibin Geliri:{1} jetton", + "game": "Oyun", + "skip2": "Atla", + "coins4": "Jetton", + "currentVip": "Mevcut VIP", + "weekStart": "Hafta Başlangıcı", + "forMoreRewardsPleaseCheckTheTaskCenter": "Daha fazla ödül için lütfen görev merkezini kontrol edin", + "kingQuuen": "Kral-Kraliçe", + "ramadan": "Ramazan", + "updateNow": "Şimdi Güncelle", + "allGames": "Tüm Oyunlar", + "fishClass": "Balık Sınıfı", + "greedyClass": "Aşırı İstekli Sınıfı", + "raceSeries": "Yarış Serisi", + "slotsClass": "Slot Sınıfı", + "others": "Diğerleri", + "hotGames": "Popüler Oyunlar", + "chatBox": "Sohbet Kutusu", + "termsOfServicePrivacyPolicyTips": "Devam ederek Hizmet Şartları'nı ve Gizlilik Politikası'nı kabul ediyorsunuz", + "and": " ve ", + "pleaseSelectTheTypeContent": "Lütfen ihlalci içeriğin türünü seçin.", + "wearHonor": "Onuru Tak", + "illegalInformation": "Yasaksız Bilgi", + "inappropriateContent": "Uygunsuz İçerik", + "personalAttack": "Kişisel Saldırı", + "confirm": "Onayla", + "spam": "İstenmeyen Mesaj", + "countdownMinutes": "Geri Sayım Dakikaları :", + "number2": "Numara:", + "fraud": "Sahtekarlık", + "received": "Alındı", + "currentProgress": "Mevcut İlerleme", + "currentStage": "Mevcut Aşama:{1}", + "roomReward2": "Oda Ödülü:{1}", + "roomReward": "Oda Ödülü", + "ownerSendTheRedEnvelope": "Sahip ödül jettonlarını gönderdi.", + "rewardCoins": "Ödül jettonları:{1} jetton", + "lastWeekProgress": "Geçen Haftanın İlerlemesi", + "redEnvelopeTips2": "*Kırmızı çanta belirtilen süre içinde alınmazsa, kalan paralar kırmızı çantayı gönderen kullanıcıya iade edilecektir.", + "goToRecharge": "Yüklemeye Git", + "deleteAccount2": " Hesabı Sil({1}s)", + "areYouSureYouWantToDeleteYourAccount": " Hesabınızı silmek istediğinizden emin misiniz?", + "insufhcientGoldsGoToRecharge": "Altın yetersiz, hemen yükle!", + "coins2": "{1}Jetton", + "remainingNumberTips": "Kalan Kullanılabilir Sayı:({1}/{2})", + "collectionTimeTips": "Toplama Zamanı:{1}({2}/{3})", + "sendARedEnvelope": "Kırmızı Zarf Gönder", + "sendRedPackConfirmTips": "Kırmızı paket göndermek istediğinizden emin misiniz?", + "redEnvelopeSendingRecords": "Kırmızı zarf gönderim kayıtları:", + "redEnvelope": "Kırmızı Zarf", + "redEnvelopeRecTips2": "Kırmızı zarfların hepsi talep edildi.", + "redEnvelopeRecTips3": "Kırmızı zarf toplama zamanı doldu!", + "openTheTreasureChest": "Kırmızı bir çanta gönderildi!", + "redEnvelopeRecTips1": "Kazanan jettonlar cüzdanınıza yatırıldı.", + "redEnvelopeTips1": "Jettonlar:", + "roomTools": "Oda Araçları:", + "entertainment": "Eğlence:", + "reportSucc": "Bildirim başarılı", + "pornography": "Pornografi", + "reportInputTips": "Sorunu anlayabilmemiz ve çözebilmemiz için lütfen sorunu mümkün olduğunca detaylı anlatın.", + "cancel": "İptal", + "join": "Katıl", + "items": "Eşyalar", + "vistors": "Ziyaretçiler", + "fans": "Hayranlar", + "balanceNotEnough": "Altın jetton bakiyesi yetersiz. Yüklemek ister misiniz?", + "skip": "{1} Atla", + "wearMedal": "Madalyonu Tak", + "activityHonor": "Etkinlik Onuru", + "achievementHonor": "Başarı Onuru", + "youDontHaveAnyHonorYet": "Henüz hiç onurunuz yok.", + "letGoToWatch": "Haydi izlemeye gidelim!", + "launchedARocket": "roket fırlattı", + "sendUserId": "Kullanıcı ID'si Gönder:{1}", + "giveUpIdentity": "Kimliği Bırak", + "leaveRoomIdentityTips": "Oda kimliğini bırakmak istediğinizden emin misiniz?", + "joinMemberTips2": "Üye olarak odaya katılmayı onaylamak ister misiniz?", + "sureUnfollowThisRoom": "Bu odayı takipten çıkarmak istediğinizden emin misiniz?", + "welcomeMessage": "Uygulamamıza hoş geldiniz, {name}!", + "settings": "Ayarlar", + "account": "Hesap", + "common": "Genel", + "delete": "Sil", + "copy": "Kopyala", + "bio": "Biyografi", + "useCoupontips": "Kuponu kullanmak istediğinizden emin misiniz?", + "searchUserId": "Kullanıcı ID'sini Ara", + "sendUser": "Kullanıcıya Gönder", + "hobby": "Hobi", + "sendCoupontips": "Bu kuponu bu kullanıcıya göndermek istediğinizden emin misiniz?", + "youDontHaveAnyCouponsYet": "Henüz hiç kuponunuz yok.", + "recall": "Geri Çağır", + "youHaventFollowed": "Hiç oda takip etmediniz", + "deleteFromMyDevice": "Cihazımdan Sil", + "deleteOnAllDevices": "Tüm Cihazlarda Sil", + "messageHasBeenRecalled": "Bu mesaj geri çağırıldı", + "recallThisMessage": "Bu mesajı geri çağırmak ister misiniz?", + "language": "Dil", + "feedback": "Geri Bildirim", + "signedin": "Giriş Yapıldı", + "receiveSucc": "Başarıyla talep edildi", + "about": "Hakkında", + "aboutUs": "Hakkımızda", + "theme": "Tema", + "wealthLevel": "Servet Seviyesi", + "userLevel": "Kullanıcı Seviyesi", + "goToUpload": "Yüklemeye Git", + "logout": "Çıkış Yap", + "luck": "Şans", + "level": "Seviye", + "vip": "VIP", + "vip1": "VIP1", + "vip2": "VIP2", + "vip3": "VIP3", + "vip4": "VIP4", + "vip5": "VIP5", + "vip6": "VIP6", + "themeGoToUploadTips": "1.Yükleme başarılı olduktan sonra 24 saat içinde inceleme yapılacaktır.\n2.Inceleme başarısız olursa tüm jettonlar iade edilecektir.", + "home": "Anasayfa", + "explore": "Keşfet", + "me": "Ben", + "socialPrivilege": "Sosyal ayrıcalık", + "games": "Oyunlar", + "casualInteraction": "Günlük Etkileşim", + "haveGamePlayingTips": "Oynadığınız bir oyun devam ediyor, lütfen önce mevcut oyundan çıkın, çıkmak istediğinizden emin misiniz?", + "historicalTour": "Tarihi Tur", + "gameCenter": "Oyun Merkezi", + "returnToVoiceChat": "Sesli sohbete geri dön?", + "exitGameMode": "Oyun Modundan Çık", + "enterTheRoom": "Odaya gir", + "inviteGoRoomTips": "Her zaman senin için buradayım, yağmurda ya da güneşte. Gel ve merhaba de!", + "invite": "Davet et", + "information": "Bilgi", + "myPhoto": "Fotoğraflarım", + "cpRequest": "CP İsteği", + "areYouSureYouWantToSpend3": "*Diğer taraf CP davetini reddederse, jettonlarınız cüzdanınıza iade edilecektir.", + "areYouSureYouWantToSpend": "Harcamak istediğinizden emin misiniz", + "areYouSureYouWantToSpend2": "bu kullanıcıya CP daveti göndermek için?", + "cpSexTips": "Aynı cinsiyetten çiftler oluşturulamaz.", + "underReview": "İnceleniyor", + "doYouWantToDeleteIt": "Silmek ister misiniz?", + "chooseFromAblum": "Albümdan Seç", + "spaceBackground": "Mekan Arka Planı", + "editProfile": "Profili Düzenle", + "sendTheCpRequest": "CP İsteğini Gönder", + "addCp": "CP Ekle", + "partWays": "Yollarını Ayırmak", + "reconcile": "Uzlaşmak", + "separated": "Ayırılmış", + "areYouSureYouWantToSpend5": "{1} size hislerini ifade etti; kabul ederseniz çift olacaksınız.", + "areYouSureYouWantToSpend6": "{1} sizinle tekrar bir araya gelmek istiyor. Uzlaşmaya karar verirseniz, tüm önceki verileriniz geri yüklenecektir.", + "reconcileInvitationTips": "*Diğer taraf CP davetini reddederse, jettonlarınız cüzdanınıza iade edilecektir.", + "reconcileInvitation": "Uzlaşma Daveti", + "areYouSureYouWantToSpend4": "bu kullanıcıya uzlaşma daveti göndermek için?", + "partWaysTips": "*Çiftin bir partneri yollarını ayırmayı seçerse, 7 günlük bir soğuma süresi olacaktır. Bu süre boyunca her iki taraf da uzlaşmayı seçebilir ve uzlaşma durumunda tüm veriler geri yüklenecektir. 7 günlük sürenin sonunda uzlaşma seçilmezse, çift verileri temizlenecektir.", + "areYouSureYouWantToPartWaysWithYourCP": "CP'nizle yollarınızı ayırmak istediğinizden emin misiniz?", + "timeSpentTogether": "Birlikte Geçirilen Zaman: {1} gün", + "firstDay": "İlk Gün:{1}", + "numberOfMyCPs": "CP'lerim Sayısı:({1}/{2})", + "props": "Özellikler", + "medal": "Madalya", + "win": "Kazanan", + "dice": "Zar", + "rps": "Kağıt-Kaçak-Makas", + "areYouSureToCancelDynamicBlacklist": "Dinamik kara listeyi iptal etmek istediğinizden emin misiniz?", + "blockUserDynamic": "Kullanıcı Dinamğini Engelle", + "unblockUserDynamic": "Kullanıcı Dinamğini Engellemeyi Kaldır", + "operationFail": "İşlem başarısız oldu.", + "likedYourComment": "Yorumunuzu beğendi.", + "likedYourDynamic": "Dinamikinizi beğendi.", + "doYouWantToKeepTheDraft": "Taslağı saklamak ister misiniz?", + "cantSendDynamicTips": "Şu anda Dinamik gönderme engellenmiş durumdasınız.", + "operationsAreTooFrequent": "İşlemler çok sık", + "luckNumber": "Şans Numarası", + "relationShip": "İlişki", + "couple": "Çift {1}:", + "couple2": "Çift", + "reject": "Reddet", + "cpList": "CP Listesi", + "accept": "Kabul Et", + "noMatchedCP": "Eşleşen CP yok", + "inviteYouToBecomeBD": "Sizi BD yapmaya davet ediyoruz.", + "adminInviteRechargeAgent": "Sizi Yükleme temsilcisi yapmaya davet ediyoruz.", + "confirmAcceptTheInvitation": "Daveti kabul etmek için onaylıyor musunuz?", + "confirmDeclineTheInvitation": "Daveti reddetmek için onaylıyor musunuz?", + "host": "Sunucu", + "following": "Takip Edilenler", + "agent": "Temsilcilik", + "approved": "Onaylandı", + "agreeJoinFamilyTips": "Bu kullanıcının klana katılmasına izin veriyor musunuz?", + "refuseJoinFamilyTips": "Kullanıcıya klana katılmasını reddetmek istiyor musunuz?", + "onlineUsers": "Çevrimiçi Kullanıcılar({1}/{2}):", + "applyToJoin": "Katılmak İçin Başvur", + "supporterList": "Destekçi Listesi", + "hostList": "Sunucu Listesi", + "upToAdmins": "En Fazla {1} Yönetici", + "upToMembers": "En Fazla {1} Üye", + "levelPrivileges": "Seviye Hakları", + "familyLevel": "Klan Seviyesi", + "kickOutOfFamily": "Klandan At", + "disbandTheFamilyTips": "Klanı dağıtmak istediğinizden emin misiniz?", + "setAsFamilyAdmin": "Klan Yöneticisi Olarak Ata", + "cancelFamilyAdmin": "Klan Yöneticiliğini İptal Et", + "kickFamilyUserTips": "Bu kullanıcıyı klandan atmak istediğinizden emin misiniz?", + "familyMember2": "Klan Üyesi({1}/{2}):", + "familyMember3": "Klan Üyesi({1}):", + "familyAdmin2": "Klan Yöneticisi({1}/{2}):", + "familyOwner2": "Klan Sahibi:", + "ra": "RA", + "roomAnnouncement": "Oda Duyurusu", + "family3": "{1} 'in Klanı", + "help": "Yardım", + "rejected": "Reddedildi", + "boxContributeTips": "Bugün zaten yatırım yapıldı, lütfen tekrar yatırım yapmayın", + "familyHelpTips": "(1)50 jetton yatırırsanız, 5 kişi yatırım yaptığında ilk hazine sandığı açılacaktır. 50 jetton almak için tıklayabilirsiniz.(2)10 kişi yatırım yaptığında ikinci hazine sandığı açılacaktır. 50 jetton alabilirsiniz.\n(3)20 kişi yatırım yaptığında ikinci hazine sandığı açılacaktır. 100 jetton alabilirsiniz.\n(4) Eğer hazine sandığını talep etmek için gereken kullanıcı sayısı aynı gün içinde ulaşılmazsa, hazine sandığı ilerlemesi ertesi gün sıfırlanacaktır.\n(5)Hazine sandığı ilerlemesi ertesi gün sıfırlanacaktır. Aynı gün hazine sandığını talep etmemiş kullanıcıların zamanında talep etmeleri gerekir.\n(6)Hazine sandığı sıfırlama zamanı: Suudi Arabistan saati 00:00.", + "bd": "BD", + "coupon": "Kupon", + "search": "Ara", + "get": "Al", + "inRocket": "Rokette", + "roomRocketHelpTips": "1. Odada hediye göndermek roket enerjisini artırır. *1 altın jetton hediyesi = 1 roket enerji puanı; şanslı hediyeler roket enerjisini hediyenin altın jetton değerinin %4'ü kadar artırır.\n2. Roket enerjisi tamamen dolduğunda, oda roketi fırlatabilir. Fırlatmadan sonra ödüller otomatik olarak dağıtılacaktır.\n3. Farklı roket seviyeleri farklı ödüller sunar.\n4. Roket fırlatıldığında, odadaki tüm kullanıcılar roket ödülünü talep edebilir.5. Roket enerjisi her gün 00:00'da sıfırlanır.", + "couponRecord": "Kupon Kullanım Kaydı", + "inRoom": "Odada", + "searchCouponHint": "Kupon Ara", + "giftCounter": "Hediye Sayacı", + "bDLeaderInviteYouToBecomeBDLeader": "Sizi BD Lideri yapmaya davet ediyoruz", + "wins": "kazanmalar", + "inviteYouToBecomeHost": "Sizi sunucu yapmaya davet ediyoruz.", + "friends": "Arkadaşlar", + "deleteConversationTips": "Bu kullanıcıyla olan sohbet geçmişini silmek istediğinizden emin misiniz?", + "propMessagePrompt": "Özellik Mesaj İpucu", + "inputUserId": "Kullanıcı ID'sini Girin", + "fromLuckyGifts": "şanslı hediyelerden", + "receive": "Al", + "checkInSuccessful": "Giriş başarılı", + "sginTips": "Her gün ilk defa giriş yaptığınızda ödül alacaksınız. Girişi keserseniz, tekrar giriş yaptığınızda ödül ilk günden itibaren hesaplanacaktır.", + "vipBadge": "VIP Rozeti", + "vipProfileFrame": "VIP Profil Çerçevesi", + "vipProfileCard": "VIP Profil Kartı", + "popular": "Popüler", + "recommend": "Öner", + "follow": "Takip Et", + "history": "Tarihçe", + "hotRooms": "Popüler Odalar", + "viewMore": "Daha Fazlasını Gör", + "noData": "Veri yok", + "users": "Kullanıcılar", + "rooms": "Odalar", + "coins": "jetton", + "unread": "Okunmadı", + "read": "Okundu", + "image": "[Görsel]", + "video": "[Video]", + "sound": "[Ses]", + "gift2": "[Hediye]", + "clickHereToStartChatting": "Bir şey söyle.....", + "receivedAMessage": "[Mesaj alındı]", + "confirmSwitchMicModelTips": "Koltuk modunu değiştirmek için onaylıyor musunuz?", + "number": "Numara", + "album": "Albüm", + "camera": "Kamera", + "system": "Sistem", + "notifcation": "Bildirim", + "inviteYouToBecomeAgent": "Sizi temsilcilik yapmaya davet ediyoruz.", + "myMusic": "Müziklerim", + "add": "Ekle", + "pullToLoadMore": "Yüklemek için çekin", + "loadingFailedClickToRetry": "Yükleme başarısız, tekrar denemek için tıklayın", + "releaseToLoadMore": "Yüklemek için bırakın", + "haveMyLimits": "---Kendi sınırlarım var.---", + "music": "Müzik", + "free": "Ücretsiz", + "charm": "Hediye Çekiciliği", + "start": "Başlat", + "vipUseThisThemeTips": "Yalnızca VIP seviyesine ulaşan kullanıcılar bu temayı kullanabilir.", + "stop": "Durdur", + "chats": "Sohbetler", + "family": "Klan", + "refuse": "Reddet", + "agree": "Kabul Et", + "thisFeatureIsCurrentlyUnavailable": "Bu özellik şu anda kullanılamıyor.", + "pleaseSelectTheRecipient": "Lütfen alıcıyı seçin.", + "searchMemberIdHint": "Lütfen üyenin kimlik numarasını girin", + "unclaimedRedEnvelopes": "Talep edilmemiş kırmızı zarflar 24 saat içinde iade edilir.", + "redEnvelopeNotYetClaimed": "Kırmızı zarf henüz talep edilmedi.", + "redEnvelopeAmount": "Kırmızı zarf miktarı: {1} jetton", + "sentARedEnvelope": "kırmızı zarf gönderdi.", + "theRedEnvelopeHasExpired": "Kırmızı zarfın süresi doldu.", + "joinRequest": "Katılma İsteği", + "welcomeToMyFamily": "Klanıma hoş geldiniz!", + "scrollToTheBottom": "En alta kaydır", + "gameRules": "Oyun Kuralları:", + "charmGameRulesTips": "Gösterge panelini açın, mikrodaki tüm kullanıcıların aldığı hediye jettonunu gösterin,1 jetton = 1 puan (Şanslı hediye 1 jetton = 0.04 puan).", + "inputYourOldPassword": "Eski şifrenizi girin", + "enterYourOldPassword": "Eski şifrenizi girin", + "setYourPassword": "Şifrenizi ayarlayın", + "enterYourNewPassword": "Yeni şifrenizi girin", + "confirmYourPassword": "Şifrenizi onaylayın", + "theTwoPasswordsDoNotMatch": "İki şifre eşleşmiyor.", + "resetLoginPasswordtTips2": "Şifre 8-16 karakter uzunluğunda olmalı ve büyük/küçük harf İngilizce harfleri ile sayılardan oluşan bir kombinasyon olmalıdır (yalnızca sayılar olmamalı)", + "resetLoginPassword": "Giriş Şifresini Sıfırla", + "resetLoginPasswordtTips1": "Kullanıcı ID'niz veya özel ID'niz ile giriş yapın. Aslan hesabı ile giriş yapmak daha güvenlidir.", + "localMusic": "Yerel Müzik", + "setLoginPassword": "Giriş Şifresi Ayarlayın", + "confirmSwitchMicThemeTips": "Koltuk stili değiştirmeyi onaylıyor musunuz?", + "pleaseUpgradeYourVipLevelFirst": "Lütfen önce VIP seviyenizi yükseltin.", + "micTheme": "Mikro Tema", + "classicMic": "Klasik {1} Mikro", + "yesterday": "Dün {1}", + "monday": "Pazartesi {1}", + "tuesday": "Salı {1}", + "wednesday": "Çarşamba {1}", + "thursday": "Perşembe {1}", + "friday": "Cuma {1}", + "saturday": "Cumartesi {1}", + "sunday": "Pazar {1}", + "acceptedYour": "{1} sizin davetinizi kabul etti", + "youAccepted": "Sen {1} davetini kabul ettin", + "openRedPackDialogTip": "Kırmızı zarf", + "micManagement": "Mikro Yönetimi", + "vipChatBox": "VIP Sohbet Kutusu", + "vipRoomCoverBorder": "VIP Oda Kapağı Sınırı", + "bdCenter": "BD Merkezi", + "renewVip": "VIP'yi Yenile", + "rechargeAgency": "Yükleme Temsilciliği", + "adminCenter": "Yönetici Merkezi", + "goldList": "Altın Listesi", + "followList": "Takip Listesi", + "fansList": "Hayran Listesi", + "daily": "Günlük", + "cp": "CP", + "female": "Kadın", + "male": "Erkek", + "identity": "Kimlik", + "adjust": "Ayarlama", + "warning": "Uyarı", + "screenshotTips": "Ekran Görüntüsü (En Fazla 3)", + "roomNotice": "Oda Bildirimi", + "roomTheme": "Oda Tema", + "description": "Açıklama:", + "inputDesHint": "Sorunu anlayabilmemiz ve çözebilmemiz için lütfen sorunu mümkün olduğunca detaylı anlatın.", + "roomProfilePicture": "Oda Profil Fotoğrafı", + "userProfilePicture": "Kullanıcı Profil Fotoğrafı", + "userName": "Kullanıcı Adı", + "pleaseSelectTheTypeToProcess": "Lütfen işlem türünü seçin:", + "roomEditing": "Oda Düzenleme", + "setAccount": "Hesap Ayarla", + "userEditing": "Kullanıcı Düzenleme", + "enterTheUserId": "Kullanıcı ID'sini Girin", + "enterTheRoomId": "Oda ID'sini Girin", + "deleteAccount": "Hesabı Sil", + "becomeAgent": "Temsilci Ol", + "enterNickname": "Takma Ad Girin", + "selectYourCountry": "Ülkenizi Seçin", + "inviteCode": "Davet Kodu", + "magic": "Sihir", + "luckGiftSpecialEffects": "Şanslı hediye animasyon efektleri", + "theVideoSizeCannotExceed": "Video boyutu 50M'yi geçemez", + "weekly": "Haftalık", + "customizedGiftRulesContent": "Özel hediyemi nasıl alabilirim:\n1.Kullanıcının özel hediye almaya haklı olup olmadığını mevcut servet seviyesine göre belirleyin.\n(1) Kullanıcının servet seviyesi ≥35 olduğunda: \nKullanıcı özel hediye için bir kez video yükleyebilir. Kullanıcının mevcut profil fotoğrafını hediye görüntüsü olarak ve sağlanan videoyu \"Özel\" üzerindeki hediye efekti olarak kullanacağız. Üretim biraz zaman alacak ve mağazada kullanılabilir hale gelir gelmez sizi bilgilendireceğiz.\n(2) Kullanıcının servet seviyesi ≥45 olduğunda: \nKullanıcı özel hediye için bir kez video yükleyebilir. Kullanıcının mevcut profil fotoğrafını hediye görüntüsü olarak ve sağlanan videoyu \"Özel\" üzerindeki hediye efekti olarak kullanacağız. Üretim biraz zaman alacak ve mağazada kullanılabilir hale gelir gelmez sizi bilgilendireceğiz.\n2.Uygulamadaki belirli etkinliklere katılabilir ve kriterleri karşıladıktan sonra \"Mesaj\" → \"Bize Ulaşın\" bölümünden bizimle iletişime geçebilirsiniz. Etkinliğin ekran görüntülerini ve özel hediye için kullanmak istediğiniz videoyu sağlayın. Mevcut profil fotoğrafınızı hediye görüntüsü olarak ve sağlanan videoyu hediye efekti olarak kullanacağız, ardından \"Özel\" altında listelenecektir. Üretim biraz zaman alacak ve mağazada kullanılabilir hale gelir gelmez sizi bilgilendireceğiz.\nÖzel Hediye Geçerlilik Süresi & Nasıl Uzatılır:\nÖzel özel hediyeler 30 günlük bir geçerlilik süresiyle raflarda bulunacaktır. Etkili ve ilham verici kullanıcıların bu yeni deneyimi devam ettirmesini ve kişisel tarzlarını sergilemesini sağlamak için, özel hediyelere sahip kullanıcılar herhangi bir ayda 500 ABD doları yükleyerek tüm özel hediyelerinin raf zamanını 30 gün uzatabilir.", + "customizedGiftRules": "Özel Hediye Kuralları", + "rulesUpload": "Kurallar&Yükleme", + "monthly": "Aylık", + "message": "Mesaj", + "clearCache": "Önbelleği Temizle", + "customized": "Özel", + "searchInputHint": "Hesap/oda numarasını girin", + "kickRoomTips": "Odadan atıldınız.", + "joinRoomTips": "odaya katıldı !", + "roomSetting": "Oda Ayarları", + "roomDetails": "Oda Detayları", + "systemRoomTips": "Lütfen nezaket ve saygıyı koruyun. Herhangi bir pornografik veya müstehcen içerik Aslan'da kesinlikle yasaktır. İhlal tespit edilen herhangi bir hesap kalıcı olarak yasaklanacaktır. Tüm kullanıcılardan Aslan'ın topluluk kurallarına bilinçli bir şekilde uymalarını rica ediyoruz.", + "copiedToClipboard": "Panoya kopyalandı", + "recharge": "Yükle", + "receivedFromALuckyGift": "Şanslı bir hediyeden alındı.", + "followedYou": "Seni takip etti", + "agentCenter": "Temsilci Merkezi", + "areYouSureYouWantToClearLocalCache": "Yerel önbelleği temizlemek istediğinizden emin misiniz?", + "clearMessage": "Ekran mesajlarını temizle", + "report": "Bildir", + "coins3": "Jetton", + "giftSpecialEffects": "Hediye Özel Efektleri", + "basicFeatures": "Temel Özellikler", + "task": "Görev", + "importantReminder": "Önemli Hatırlatma", + "entryVehicleAnimation": "Giriş aracı animasyonu (VIP3)", + "floatingAnimationInGlobal": "Küreselde Süzme Animasyon", + "entryVehicleAnimation2": "VIP3 veya daha yüksek haklara sahip kullanıcılar araç animasyonlarını devre dışı bırakmak için fonksiyonu kullanabilir.", + "dailyTasks": "Günlük Görevler", + "enterRoomConfirmTips": "Odaya girmek istediğinizden emin misiniz?", + "followSucc": "Başarıyla takip edildi", + "goldListort": "Altın Listesi", + "rechargeList": "Yükleme Listesi", + "edit": "Düzenle", + "swipeLeftOnTheFloatingScreenAreaToQuicklyCloseIt": "*İpucu: Süzme ekran alanına sola kaydırarak hızlıca kapatın.", + "enterThisVoiceChatRoom": "Bu sesli sohbet odasına girmek ister misiniz?", + "go": "Git", + "done": "Bitti", + "improvementTasks": "Geliştirme Görevleri", + "save": "Kaydet", + "kickPrevention": "Atma Koruması", + "freeChatSpeak": "Ücretsiz Sohbet & Konuşma", + "vipExclusiveVehicles": "VIP Özel Araçlar", + "nickName": "Takma Ad", + "buyVip": "VIP Satın Al", + "gender": "Cinsiyet", + "unFollow": "Takipten Çık", + "days": "Günler", + "permanent": "Kalıcı", + "yourVipWillExpire": "VIP'niz {1} tarihinde süresi dolacak", + "cantBuyVip": "Satın alma devam edilemiyor", + "country": "Ülke", + "birthday": "Doğum Günü", + "man": "Erkek", + "woman": "Kadın", + "apple": "Apple", + "dailyTaskRewardBonus": "Günlük Görev Ödül Bonusu ({1} XP)", + "userLevelXPBoost": "Kullanıcı Seviyesi XP Artışı({1} XP)", + "pleaseUpgradeYourVipLevel": "Lütfen VIP seviyenizi yükseltin.", + "vipEmoticon": "VIP İfade({1})", + "google": "Google", + "mysteriousInvisibility": "Gizemli görünmezlik", + "antiBlock": "Tıkanmayı Önleyici", + "createFamilyForFree": "Aileyi Ücretsiz Oluştur", + "privateChat": "Özel Sohbet", + "everyone": "Herkes", + "goToUpgrade": "Yükseltmeye git", + "exclusiveEmojiWillBeReleasedAfterBecoming": "Özel emoji olunduktan sonra yayınlanacak", + "preventBeingBlocked": "Engellenmekten Kaçının", + "enableRankIncognitoMode": "Rütbe Gizli Modunu Etkinleştir", + "avoidBeingKicked": "Tekme Yemekten Kaçının", + "privileges": "{1} Ayrıcalıklar", + "andAboveUsers": "{1} ve üzeri kullanıcılar", + "basicPermissions": "Temel izinler", + "vipBirthdayGift": "VIP Doğum Günü Hediyesi", + "storeDiscount": "Mağaza İndirimi {1} Tutarında", + "membershipFreeChatSpeak": "Üyeliksiz Sohbet ve Konuşma", + "priorityRoomSorting": "Öncelikli Oda Sıralaması", + "userColoredID": "Kullanıcı Renkli Kimliği", + "vipMicSoundWave": "VIP Mikrofon Ses Dalgası", + "startVoiceParty": "Sesli parti başlat!", + "enterRoomTips": "{1} odaya girdi", + "roomName": "Oda Adı", + "idIcon": "Kimlik Simgesi", + "inviteToBecomeAHost": "Ev sahibi olmaya davet et", + "currentLevelPrivilegesAndCostumes": "Mevcut seviye ayrıcalıkları ve kostümler: {1}", + "uploadGifAvatar": "GIF avatar yükle (VIP2)", + "uploadProfilePicture": "Profil Resmi Yükle", + "permissionSettings": "İzin ayarları", + "roomMember": "Oda Üyesi", + "pleaseSelectYourCountry": "Lütfen ülkenizi seçin.", + "pleaseSelectYourGender": "Lütfen cinsiyetinizi seçin.", + "pleaseEnterNickname": "Lütfen bir takma ad girin.", + "countryRegion": "Ülke&Bölge", + "dateOfBirth": "Doğum Tarihi", + "mute": "Sesi Kapat", + "exit": "Çıkış", + "pleaseSelectaItem": "Lütfen bir öğe seçin", + "areYouRureRoRecharge": "Yüklemek istediğinizden emin misiniz?", + "mInimize": "Tutmak", + "shop": "Mağaza", + "expirationTime": "Son kullanma süresi", + "roomOwner": "Oda Sahibi", + "hostCenter": "Sunucu Merkezi", + "allOnMicrophone": "Hepsi Mikrodadır", + "usersOnMicrophone": "Mikrodaki Kullanıcılar", + "allInTheRoom": "Hepsi Odadadır", + "send": "Gönder", + "crop": "Kırp", + "finish": "Bitir", + "takeTheMic": "Mikroyu Al", + "openTheMic": "Mikroyu Aç", + "muteTheMic": "Mikronun Sesini Kapat", + "unlockTheMic": "Mikroyu Kilidini Aç", + "leavelTheMic": "Mikroyu Bırak", + "lockTheMic": "Mikroyu Kilitle", + "removeTheMic": "Mikroyu Kaldır", + "inviteToTheMicrophone": "Mikroya Davet Et", + "openUserProfleCard": "Kullanıcı Profili Kartını Aç", + "obtain": "elde etmek", + "win2": "{1} Kazan", + "backTheRoom": "Odaya Dön", + "toConsume": "Tüketmek İçin", + "howToUpgrade": "Nasıl yükseltirim?", + "spendCoinsToGainExperiencePoints": "Deneyim puanı kazanmak için jetton harcayın", + "higherLevelFancierAvatarFrame": "Daha yüksek seviye, daha şık rozetler/profil çerçevesi", + "medalAndAvatarFrameRewards": "Madalya ve profil çerçevesi ödülleri", + "all": "Tümü", + "gift": "Hediye", + "chat": "Sohbet", + "owner": "Sahip", + "store": "Mağaza", + "admin": "Yönetici", + "member": "Üye", + "guest": "Misafir", + "submit": "Gönder", + "membershipFee": "Üyelik Ücreti", + "membershipFeeTips1": "Lütfen odanız için üyelik ücretini ayarlayın. Kullanıcılar ücreti ödeyerek odanıza katılabilir.", + "membershipFeeTips2": "Kullanıcı'nın oda üyesi olması için gerekli altınlar.Oda sahibi altınların %50'sini alacaktır.", + "freePrice": "Ücret:0-10000", + "touristsSendText": "Turist metin gönderir", + "touristsTakeToTheMic": "Turist mikro alır", + "theMembershipFee": "Üyelik Ücreti", + "theModificationsMade": "Bu sefer yapılan değişiklikler çıkıştan sonra kaydedilmeyecektir", + "viewFrame": "Çerçeveyi Gör", + "enterRoomName": "Oda adını girin", + "headdress": "Çerçeveler", + "mountains": "Araçlar", + "purchaseIsSuccessful": "Satın alma başarılı", + "buy": "Satın Al", + "followed": "Takip Edildi", + "follow2": "Takip:{1}", + "fans2": "Hayranlar:{1}", + "vistors2": "Ziyaretçiler:{1}", + "personal2": "Kişisel:", + "family2": "Klan:", + "conntinue": "Devam Et", + "confirmBuyTips": "Satın almak istediğinizden emin misiniz?", + "purchase": "Satın Alma", + "setRoomPassword": "Oda Şifresi Ayarlayın", + "inputRoomPassword": "Oda şifresini girin", + "enter": "Gir", + "createDynamicSuccess": "Dinamik başarıyla oluşturuldu", + "deleteDynamicTips": "Bu dinamiki silmek istediğinizden emin misiniz?", + "deleteCommentTips": "Bu yorumu silmek istediğinizden emin misiniz?", + "deleteSuccessful": "Silme başarılı!", + "itemsLeft": "Kalan Eşyalar", + "password": "Şifre", + "replySucc": "Cevap başarılı", + "comment": "Yorum", + "showMore": "Daha Fazlasını Göster", + "showLess": "Daha Azını Göster", + "enterPassword": "Şifre Girin", + "enterAccount": "Hesap Girin", + "logIn": "Giriş Yap", + "saySomething": "Bir şey söyle...", + "sayHi": "Merhaba..", + "pleaseChatFfriendly": "Lütfen arkadaşça sohbet edin", + "unLockTheRoom": "Odayı Kilidini Aç", + "operationSuccessful": "İşlem başarılı oldu.", + "adminByHomeowner": "ev sahibi tarafından yönetici olarak atandı.", + "memberByHomeowner": "ev sahibi tarafından üye olarak atandı.", + "touristByHomeowner": "ev sahibi tarafından turist olarak atandı.", + "becomeHost": "Ev Sahibi Olmak İçin Başvur", + "superFans": " Süper hayranlar:", + "setUpAnIdentity": "Kimlik Kur", + "kickedOutOfRoom": "Odadan Atıldı", + "playGiftMusicAndDynamicMusic": "Hediye müziği ve dinamik müziği çal", + "knapsack": "Sırt Çantası", + "bdLeader": "BD Lideri", + "picture": "Resim", + "claim": "İddia", + "taskNameRoomNewMember": "Yeni Oda Üyeleri", + "taskNameRoomOwnerSendRedPacket": "Oda Sahibi bir kırmızı paket gönderiyor", + "taskNameRoomOwnerSendGiftUser": "Oda Sahibi Hediye Gönderir", + "taskNameRoomMicUser120Min": "Mikrofon süresi 120+ dakika olan üyeler", + "taskNameRoomMicUser60Min": "Mikrofon süresi 60+ dakika olan üyeler", + "taskNameRoomMicUser30Min": "Mikrofon süresi 30+ dakika olan üyeler", + "taskNamePersonalSendGift": "Kullanıcıya Hediye Gönder", + "taskNameRoomOnlineUserCount": "Odadaki Çevrimiçi Üyeler", + "taskNameRoomOwnerInviteMic": "Üyeyi Mikrofona Davet Et", + "taskNameRoomUserSendGiftGold": "Üyelerin hediye ettiği jetonlar", + "taskNameRoomOwnerSendGiftGold": "Oda sahibinin hediye ettiği jetonlar", + "taskNamePersonalMagicGiftGold": "Büyülü Hediyeler Göndererek Kazanılan Jetonlar", + "taskNamePersonalLuckyGiftGold": "Şanslı Hediyeler Göndererek Kazanılan Jetonlar", + "taskNameRoomOwnerMicTime": "Oda Sahibi odada mikrofona geçer", + "taskNamePersonalActiveInRoom": "Başkalarının Odalarında Aktif Ol", + "taskNamePersonalGameConsume": "Oyun Harcaması", + "taskNamePersonalMicInRoom": "Mikrofonu Aç", + "complete": "Tamamla", + "shareTo": "Paylaş", + "faceBook": "Facebook", + "whatsApp": "Whatsapp", + "snapChat": "Snapchat", + "dailyCoinBonanzaRulesDetail": "1. Günlük Kişisel Görevler ve Günlük Oda Sahibi Görevleri, kullanıcı başına günde bir kez tamamlanabilir. Görevler, Suudi zamanı ile 00:00:00'da sıfırlanır.\n2. Günlük kişisel görevler yalnızca başkalarının odalarında tamamlanabilir; oda sahibi görevleri yalnızca kendi odanızda tamamlanabilir.\n3. Hediye verme görevleri yalnızca odalarda gönderilen hediyeleri sayar, akışlarda gönderilen hediyeleri saymaz.\n4. Aynı cihaz veya aynı SIM kart kullanılarak birden fazla hesap oluşturulursa, tüm görevlerin ödülleri yalnızca bir kez alınabilir.", + "copyLink": "Bağlantıyı Kopyala", + "dailyCoinBonanzaRules": "Günlük Jeton Çılgınlığı Kuralları", + "roomOwnerTasks": "Oda Sahibi Görevleri", + "personalTasks": "Kişisel Görevler", + "noPromptsToday": "Bugün hiçbir istem yok.", + "getPaidToRefer": "Tavsiye Ederek Para Kazanın", + "theImageSizeCannotExceed": "Yükleme başarısız: Dosya 2MB altında olmalıdır.", + "activity": "Etkinlik", + "alreadyAnAdministrator": "Zaten yönetici", + "alreadyAnMember": "Zaten üye", + "alreadyAnTourist": "Zaten turist", + "touristsCannotSendMessages": "Turistler mesaj gönderemez", + "touristsAreNotAllowedToGoOnTheMic": "Turistlerin mikroya çıkmasına izin verilmiyor", + "lockTheRoom": "Odayı Kilitle", + "special": "Özel", + "visitorList": "Ziyaretçi Listesi", + "successfulWear": "Başarıyla takıldı", + "confirmUnUseTips": "Kaldırmak için onaylıyor musunuz?", + "custom": "Özel", + "myItems": "Eşyalarım", + "use": "Kullan", + "unUse": "Kullanmama", + "renewal": "Yenileme", + "wallet": "Cüzdan", + "profile": "Profil", + "giftwall": "Hediye Duvarı", + "announcement": "Duyuru", + "blockedList": "Engellenen Liste", + "country2": "Ülke:", + "sendTo": "Gönderilecek Kişi", + "medals": "Madalyalar", + "activityMedal": "Etkinlik Madalyası", + "achievementMedal": "Başarı Madalyası", + "credits": "Krediler: {1}", + "successfullyUnloaded": "Başarıyla çıkarıldı", + "expired": "Süresi Doldu", + "day": "Gün", + "inUse": "Kullanımda", + "confirmUseTips": "Kullanmak için onaylıyor musunuz?", + "pleaseUploadUserAvatar": "Lütfen bir profil fotoğrafı yükleyin.", + "joinMemberTips": "Eğer odada turist iseniz, mikroyu alamazsınız.", + "giftGivingSuccessful": "Hediye verme başarılı.", + "theAccountPasswordCannotBeEmpty": "Hesap veya şifre boş olamaz.", + "invitesYouToTheMicrophone": "{1} seni mikroya davet ediyor", + "english": "İngilizce", + "chinese": "Çince", + "arabic": "Arapça", + "darkMode": "Karanlık Mod", + "lightMode": "Açık Mod", + "systemDefault": "Sistem Varsayılanı", + "pleaseGetOnTheMicFirst": "Lütfen önce mikroya çıkın.", + "duration2": "Süre:{1}" +} \ No newline at end of file diff --git a/atu_images/coupon/at_icon_coupon_headdress_item_bg.png b/atu_images/coupon/at_icon_coupon_headdress_item_bg.png new file mode 100644 index 0000000..536d139 Binary files /dev/null and b/atu_images/coupon/at_icon_coupon_headdress_item_bg.png differ diff --git a/atu_images/coupon/at_icon_coupon_mountains_item_bg.png b/atu_images/coupon/at_icon_coupon_mountains_item_bg.png new file mode 100644 index 0000000..70dac70 Binary files /dev/null and b/atu_images/coupon/at_icon_coupon_mountains_item_bg.png differ diff --git a/atu_images/coupon/at_icon_coupon_vip_item_bg.png b/atu_images/coupon/at_icon_coupon_vip_item_bg.png new file mode 100644 index 0000000..6de6ff0 Binary files /dev/null and b/atu_images/coupon/at_icon_coupon_vip_item_bg.png differ diff --git a/atu_images/dynamic/at_icon_add_dynamic_btn.png b/atu_images/dynamic/at_icon_add_dynamic_btn.png new file mode 100644 index 0000000..a21b279 Binary files /dev/null and b/atu_images/dynamic/at_icon_add_dynamic_btn.png differ diff --git a/atu_images/dynamic/at_icon_comment_input_message_send_en.png b/atu_images/dynamic/at_icon_comment_input_message_send_en.png new file mode 100644 index 0000000..658089d Binary files /dev/null and b/atu_images/dynamic/at_icon_comment_input_message_send_en.png differ diff --git a/atu_images/dynamic/at_icon_comment_input_message_send_no.png b/atu_images/dynamic/at_icon_comment_input_message_send_no.png new file mode 100644 index 0000000..1ff4fc6 Binary files /dev/null and b/atu_images/dynamic/at_icon_comment_input_message_send_no.png differ diff --git a/atu_images/dynamic/at_icon_dy_gift.png b/atu_images/dynamic/at_icon_dy_gift.png new file mode 100644 index 0000000..914f947 Binary files /dev/null and b/atu_images/dynamic/at_icon_dy_gift.png differ diff --git a/atu_images/dynamic/at_icon_dy_gift2.png b/atu_images/dynamic/at_icon_dy_gift2.png new file mode 100644 index 0000000..03cc5ac Binary files /dev/null and b/atu_images/dynamic/at_icon_dy_gift2.png differ diff --git a/atu_images/dynamic/at_icon_dynamic_comment_gift.png b/atu_images/dynamic/at_icon_dynamic_comment_gift.png new file mode 100644 index 0000000..79182db Binary files /dev/null and b/atu_images/dynamic/at_icon_dynamic_comment_gift.png differ diff --git a/atu_images/dynamic/at_icon_dynamic_emoji.png b/atu_images/dynamic/at_icon_dynamic_emoji.png new file mode 100644 index 0000000..44fa915 Binary files /dev/null and b/atu_images/dynamic/at_icon_dynamic_emoji.png differ diff --git a/atu_images/dynamic/at_icon_like.png b/atu_images/dynamic/at_icon_like.png new file mode 100644 index 0000000..94f30d7 Binary files /dev/null and b/atu_images/dynamic/at_icon_like.png differ diff --git a/atu_images/dynamic/at_icon_like_red.png b/atu_images/dynamic/at_icon_like_red.png new file mode 100644 index 0000000..b4077ef Binary files /dev/null and b/atu_images/dynamic/at_icon_like_red.png differ diff --git a/atu_images/dynamic/at_icon_reply.png b/atu_images/dynamic/at_icon_reply.png new file mode 100644 index 0000000..587caac Binary files /dev/null and b/atu_images/dynamic/at_icon_reply.png differ diff --git a/atu_images/family/at_icon_announcement_tag.png b/atu_images/family/at_icon_announcement_tag.png new file mode 100644 index 0000000..c9f6c75 Binary files /dev/null and b/atu_images/family/at_icon_announcement_tag.png differ diff --git a/atu_images/family/at_icon_announcement_tag2.png b/atu_images/family/at_icon_announcement_tag2.png new file mode 100644 index 0000000..4e4083c Binary files /dev/null and b/atu_images/family/at_icon_announcement_tag2.png differ diff --git a/atu_images/family/at_icon_box_btn_bg.png b/atu_images/family/at_icon_box_btn_bg.png new file mode 100644 index 0000000..c651e11 Binary files /dev/null and b/atu_images/family/at_icon_box_btn_bg.png differ diff --git a/atu_images/family/at_icon_create_family_camer.png b/atu_images/family/at_icon_create_family_camer.png new file mode 100644 index 0000000..a42a83f Binary files /dev/null and b/atu_images/family/at_icon_create_family_camer.png differ diff --git a/atu_images/family/at_icon_family_box1.png b/atu_images/family/at_icon_family_box1.png new file mode 100644 index 0000000..9864452 Binary files /dev/null and b/atu_images/family/at_icon_family_box1.png differ diff --git a/atu_images/family/at_icon_family_box1_open.webp b/atu_images/family/at_icon_family_box1_open.webp new file mode 100644 index 0000000..92a0fb4 Binary files /dev/null and b/atu_images/family/at_icon_family_box1_open.webp differ diff --git a/atu_images/family/at_icon_family_box1_opened.png b/atu_images/family/at_icon_family_box1_opened.png new file mode 100644 index 0000000..8308d73 Binary files /dev/null and b/atu_images/family/at_icon_family_box1_opened.png differ diff --git a/atu_images/family/at_icon_family_box2.png b/atu_images/family/at_icon_family_box2.png new file mode 100644 index 0000000..5159bdb Binary files /dev/null and b/atu_images/family/at_icon_family_box2.png differ diff --git a/atu_images/family/at_icon_family_box2_open.webp b/atu_images/family/at_icon_family_box2_open.webp new file mode 100644 index 0000000..6b1fa99 Binary files /dev/null and b/atu_images/family/at_icon_family_box2_open.webp differ diff --git a/atu_images/family/at_icon_family_box2_opened.png b/atu_images/family/at_icon_family_box2_opened.png new file mode 100644 index 0000000..d072c60 Binary files /dev/null and b/atu_images/family/at_icon_family_box2_opened.png differ diff --git a/atu_images/family/at_icon_family_box3.png b/atu_images/family/at_icon_family_box3.png new file mode 100644 index 0000000..cb0db9b Binary files /dev/null and b/atu_images/family/at_icon_family_box3.png differ diff --git a/atu_images/family/at_icon_family_box3_open.webp b/atu_images/family/at_icon_family_box3_open.webp new file mode 100644 index 0000000..62755d8 Binary files /dev/null and b/atu_images/family/at_icon_family_box3_open.webp differ diff --git a/atu_images/family/at_icon_family_box3_opened.png b/atu_images/family/at_icon_family_box3_opened.png new file mode 100644 index 0000000..6fe16e9 Binary files /dev/null and b/atu_images/family/at_icon_family_box3_opened.png differ diff --git a/atu_images/family/at_icon_family_box_item_bg.png b/atu_images/family/at_icon_family_box_item_bg.png new file mode 100644 index 0000000..4962db9 Binary files /dev/null and b/atu_images/family/at_icon_family_box_item_bg.png differ diff --git a/atu_images/family/at_icon_family_edit_tag.png b/atu_images/family/at_icon_family_edit_tag.png new file mode 100644 index 0000000..17d2cd1 Binary files /dev/null and b/atu_images/family/at_icon_family_edit_tag.png differ diff --git a/atu_images/family/at_icon_family_head_bg.png b/atu_images/family/at_icon_family_head_bg.png new file mode 100644 index 0000000..47797a2 Binary files /dev/null and b/atu_images/family/at_icon_family_head_bg.png differ diff --git a/atu_images/family/at_icon_family_info_item_bg_0.9.png b/atu_images/family/at_icon_family_info_item_bg_0.9.png new file mode 100644 index 0000000..8bed967 Binary files /dev/null and b/atu_images/family/at_icon_family_info_item_bg_0.9.png differ diff --git a/atu_images/family/at_icon_family_info_item_bg_1.9.png b/atu_images/family/at_icon_family_info_item_bg_1.9.png new file mode 100644 index 0000000..baea432 Binary files /dev/null and b/atu_images/family/at_icon_family_info_item_bg_1.9.png differ diff --git a/atu_images/family/at_icon_family_info_item_bg_10.9.png b/atu_images/family/at_icon_family_info_item_bg_10.9.png new file mode 100644 index 0000000..11f7f95 Binary files /dev/null and b/atu_images/family/at_icon_family_info_item_bg_10.9.png differ diff --git a/atu_images/family/at_icon_family_info_item_bg_11.9.png b/atu_images/family/at_icon_family_info_item_bg_11.9.png new file mode 100644 index 0000000..74e5af7 Binary files /dev/null and b/atu_images/family/at_icon_family_info_item_bg_11.9.png differ diff --git a/atu_images/family/at_icon_family_info_item_bg_2.9.png b/atu_images/family/at_icon_family_info_item_bg_2.9.png new file mode 100644 index 0000000..625de4d Binary files /dev/null and b/atu_images/family/at_icon_family_info_item_bg_2.9.png differ diff --git a/atu_images/family/at_icon_family_info_item_bg_3.9.png b/atu_images/family/at_icon_family_info_item_bg_3.9.png new file mode 100644 index 0000000..e0f1554 Binary files /dev/null and b/atu_images/family/at_icon_family_info_item_bg_3.9.png differ diff --git a/atu_images/family/at_icon_family_join_request_tag.png b/atu_images/family/at_icon_family_join_request_tag.png new file mode 100644 index 0000000..46f2ef5 Binary files /dev/null and b/atu_images/family/at_icon_family_join_request_tag.png differ diff --git a/atu_images/family/at_icon_family_level_0.png b/atu_images/family/at_icon_family_level_0.png new file mode 100644 index 0000000..b919762 Binary files /dev/null and b/atu_images/family/at_icon_family_level_0.png differ diff --git a/atu_images/family/at_icon_family_level_1.png b/atu_images/family/at_icon_family_level_1.png new file mode 100644 index 0000000..004ef3c Binary files /dev/null and b/atu_images/family/at_icon_family_level_1.png differ diff --git a/atu_images/family/at_icon_family_level_10.png b/atu_images/family/at_icon_family_level_10.png new file mode 100644 index 0000000..6868d0b Binary files /dev/null and b/atu_images/family/at_icon_family_level_10.png differ diff --git a/atu_images/family/at_icon_family_level_11.png b/atu_images/family/at_icon_family_level_11.png new file mode 100644 index 0000000..065235a Binary files /dev/null and b/atu_images/family/at_icon_family_level_11.png differ diff --git a/atu_images/family/at_icon_family_level_2.png b/atu_images/family/at_icon_family_level_2.png new file mode 100644 index 0000000..04281ec Binary files /dev/null and b/atu_images/family/at_icon_family_level_2.png differ diff --git a/atu_images/family/at_icon_family_level_3.png b/atu_images/family/at_icon_family_level_3.png new file mode 100644 index 0000000..595d974 Binary files /dev/null and b/atu_images/family/at_icon_family_level_3.png differ diff --git a/atu_images/family/at_icon_family_level_4.png b/atu_images/family/at_icon_family_level_4.png new file mode 100644 index 0000000..d0306d9 Binary files /dev/null and b/atu_images/family/at_icon_family_level_4.png differ diff --git a/atu_images/family/at_icon_family_level_5.png b/atu_images/family/at_icon_family_level_5.png new file mode 100644 index 0000000..812f664 Binary files /dev/null and b/atu_images/family/at_icon_family_level_5.png differ diff --git a/atu_images/family/at_icon_family_level_6.png b/atu_images/family/at_icon_family_level_6.png new file mode 100644 index 0000000..97f86bf Binary files /dev/null and b/atu_images/family/at_icon_family_level_6.png differ diff --git a/atu_images/family/at_icon_family_level_7.png b/atu_images/family/at_icon_family_level_7.png new file mode 100644 index 0000000..bf480af Binary files /dev/null and b/atu_images/family/at_icon_family_level_7.png differ diff --git a/atu_images/family/at_icon_family_level_8.png b/atu_images/family/at_icon_family_level_8.png new file mode 100644 index 0000000..a733e9c Binary files /dev/null and b/atu_images/family/at_icon_family_level_8.png differ diff --git a/atu_images/family/at_icon_family_level_9.png b/atu_images/family/at_icon_family_level_9.png new file mode 100644 index 0000000..ca6274f Binary files /dev/null and b/atu_images/family/at_icon_family_level_9.png differ diff --git a/atu_images/family/at_icon_family_level_exp_tag.png b/atu_images/family/at_icon_family_level_exp_tag.png new file mode 100644 index 0000000..0e35d06 Binary files /dev/null and b/atu_images/family/at_icon_family_level_exp_tag.png differ diff --git a/atu_images/family/at_icon_family_level_head_bg.png b/atu_images/family/at_icon_family_level_head_bg.png new file mode 100644 index 0000000..c4b17f2 Binary files /dev/null and b/atu_images/family/at_icon_family_level_head_bg.png differ diff --git a/atu_images/family/at_icon_family_member_btn_bg.png b/atu_images/family/at_icon_family_member_btn_bg.png new file mode 100644 index 0000000..1a60feb Binary files /dev/null and b/atu_images/family/at_icon_family_member_btn_bg.png differ diff --git a/atu_images/family/at_icon_family_member_btn_bg2.png b/atu_images/family/at_icon_family_member_btn_bg2.png new file mode 100644 index 0000000..2320a21 Binary files /dev/null and b/atu_images/family/at_icon_family_member_btn_bg2.png differ diff --git a/atu_images/family/at_icon_family_member_tag.png b/atu_images/family/at_icon_family_member_tag.png new file mode 100644 index 0000000..f5831fe Binary files /dev/null and b/atu_images/family/at_icon_family_member_tag.png differ diff --git a/atu_images/family/at_icon_family_rank_tag.png b/atu_images/family/at_icon_family_rank_tag.png new file mode 100644 index 0000000..88fc0db Binary files /dev/null and b/atu_images/family/at_icon_family_rank_tag.png differ diff --git a/atu_images/family/at_icon_family_req_record.png b/atu_images/family/at_icon_family_req_record.png new file mode 100644 index 0000000..d9b32c4 Binary files /dev/null and b/atu_images/family/at_icon_family_req_record.png differ diff --git a/atu_images/family/at_icon_family_role_admin.png b/atu_images/family/at_icon_family_role_admin.png new file mode 100644 index 0000000..b73d559 Binary files /dev/null and b/atu_images/family/at_icon_family_role_admin.png differ diff --git a/atu_images/family/at_icon_family_role_agency.png b/atu_images/family/at_icon_family_role_agency.png new file mode 100644 index 0000000..04d070c Binary files /dev/null and b/atu_images/family/at_icon_family_role_agency.png differ diff --git a/atu_images/family/at_icon_family_role_host.png b/atu_images/family/at_icon_family_role_host.png new file mode 100644 index 0000000..abc63db Binary files /dev/null and b/atu_images/family/at_icon_family_role_host.png differ diff --git a/atu_images/family/at_icon_family_role_owner.png b/atu_images/family/at_icon_family_role_owner.png new file mode 100644 index 0000000..4cbf639 Binary files /dev/null and b/atu_images/family/at_icon_family_role_owner.png differ diff --git a/atu_images/family/at_icon_family_role_setting.png b/atu_images/family/at_icon_family_role_setting.png new file mode 100644 index 0000000..262c0e8 Binary files /dev/null and b/atu_images/family/at_icon_family_role_setting.png differ diff --git a/atu_images/family/at_icon_family_tab_rank_bg.9.png b/atu_images/family/at_icon_family_tab_rank_bg.9.png new file mode 100644 index 0000000..7b88527 Binary files /dev/null and b/atu_images/family/at_icon_family_tab_rank_bg.9.png differ diff --git a/atu_images/family/at_icon_family_user_online_tag.png b/atu_images/family/at_icon_family_user_online_tag.png new file mode 100644 index 0000000..df67886 Binary files /dev/null and b/atu_images/family/at_icon_family_user_online_tag.png differ diff --git a/atu_images/family/at_icon_family_user_rank_1.png b/atu_images/family/at_icon_family_user_rank_1.png new file mode 100644 index 0000000..c32a54c Binary files /dev/null and b/atu_images/family/at_icon_family_user_rank_1.png differ diff --git a/atu_images/family/at_icon_family_user_rank_2.png b/atu_images/family/at_icon_family_user_rank_2.png new file mode 100644 index 0000000..8d2899f Binary files /dev/null and b/atu_images/family/at_icon_family_user_rank_2.png differ diff --git a/atu_images/family/at_icon_family_user_rank_3.png b/atu_images/family/at_icon_family_user_rank_3.png new file mode 100644 index 0000000..df91d9f Binary files /dev/null and b/atu_images/family/at_icon_family_user_rank_3.png differ diff --git a/atu_images/family/at_icon_family_user_tag.png b/atu_images/family/at_icon_family_user_tag.png new file mode 100644 index 0000000..adc07b4 Binary files /dev/null and b/atu_images/family/at_icon_family_user_tag.png differ diff --git a/atu_images/family/at_icon_inroom_familytag_bg_0.png b/atu_images/family/at_icon_inroom_familytag_bg_0.png new file mode 100644 index 0000000..fd671d5 Binary files /dev/null and b/atu_images/family/at_icon_inroom_familytag_bg_0.png differ diff --git a/atu_images/family/at_icon_inroom_familytag_bg_1.png b/atu_images/family/at_icon_inroom_familytag_bg_1.png new file mode 100644 index 0000000..460f3e2 Binary files /dev/null and b/atu_images/family/at_icon_inroom_familytag_bg_1.png differ diff --git a/atu_images/family/at_icon_inroom_familytag_bg_2.png b/atu_images/family/at_icon_inroom_familytag_bg_2.png new file mode 100644 index 0000000..77718f6 Binary files /dev/null and b/atu_images/family/at_icon_inroom_familytag_bg_2.png differ diff --git a/atu_images/family/at_icon_inroom_familytag_bg_3.png b/atu_images/family/at_icon_inroom_familytag_bg_3.png new file mode 100644 index 0000000..44fcb4e Binary files /dev/null and b/atu_images/family/at_icon_inroom_familytag_bg_3.png differ diff --git a/atu_images/family/at_icon_inroom_familytag_bg_4.png b/atu_images/family/at_icon_inroom_familytag_bg_4.png new file mode 100644 index 0000000..90dd2db Binary files /dev/null and b/atu_images/family/at_icon_inroom_familytag_bg_4.png differ diff --git a/atu_images/family/at_icon_inroom_familytag_bg_5.png b/atu_images/family/at_icon_inroom_familytag_bg_5.png new file mode 100644 index 0000000..884c4a5 Binary files /dev/null and b/atu_images/family/at_icon_inroom_familytag_bg_5.png differ diff --git a/atu_images/family/at_icon_req_family_state_tag_1.png b/atu_images/family/at_icon_req_family_state_tag_1.png new file mode 100644 index 0000000..209c929 Binary files /dev/null and b/atu_images/family/at_icon_req_family_state_tag_1.png differ diff --git a/atu_images/family/at_icon_req_family_state_tag_2.png b/atu_images/family/at_icon_req_family_state_tag_2.png new file mode 100644 index 0000000..fe9287f Binary files /dev/null and b/atu_images/family/at_icon_req_family_state_tag_2.png differ diff --git a/atu_images/family/at_icon_req_family_state_tag_3.png b/atu_images/family/at_icon_req_family_state_tag_3.png new file mode 100644 index 0000000..af454cb Binary files /dev/null and b/atu_images/family/at_icon_req_family_state_tag_3.png differ diff --git a/atu_images/family/at_icon_req_family_state_tag_4.png b/atu_images/family/at_icon_req_family_state_tag_4.png new file mode 100644 index 0000000..3351c35 Binary files /dev/null and b/atu_images/family/at_icon_req_family_state_tag_4.png differ diff --git a/atu_images/general/at_icon_add_pic.png b/atu_images/general/at_icon_add_pic.png new file mode 100644 index 0000000..0d51bce Binary files /dev/null and b/atu_images/general/at_icon_add_pic.png differ diff --git a/atu_images/general/at_icon_app_update_bg.png b/atu_images/general/at_icon_app_update_bg.png new file mode 100644 index 0000000..27debe0 Binary files /dev/null and b/atu_images/general/at_icon_app_update_bg.png differ diff --git a/atu_images/general/at_icon_apple_pay.png b/atu_images/general/at_icon_apple_pay.png new file mode 100644 index 0000000..c172035 Binary files /dev/null and b/atu_images/general/at_icon_apple_pay.png differ diff --git a/atu_images/general/at_icon_avar_defalt.png b/atu_images/general/at_icon_avar_defalt.png new file mode 100644 index 0000000..1000bd3 Binary files /dev/null and b/atu_images/general/at_icon_avar_defalt.png differ diff --git a/atu_images/general/at_icon_back.png b/atu_images/general/at_icon_back.png new file mode 100644 index 0000000..c91076f Binary files /dev/null and b/atu_images/general/at_icon_back.png differ diff --git a/atu_images/general/at_icon_checked.png b/atu_images/general/at_icon_checked.png new file mode 100644 index 0000000..720bf2e Binary files /dev/null and b/atu_images/general/at_icon_checked.png differ diff --git a/atu_images/general/at_icon_clear_c.png b/atu_images/general/at_icon_clear_c.png new file mode 100644 index 0000000..f3d3747 Binary files /dev/null and b/atu_images/general/at_icon_clear_c.png differ diff --git a/atu_images/general/at_icon_create_dynamic_add_pic.png b/atu_images/general/at_icon_create_dynamic_add_pic.png new file mode 100644 index 0000000..a07a161 Binary files /dev/null and b/atu_images/general/at_icon_create_dynamic_add_pic.png differ diff --git a/atu_images/general/at_icon_delete.png b/atu_images/general/at_icon_delete.png new file mode 100644 index 0000000..4ec49a7 Binary files /dev/null and b/atu_images/general/at_icon_delete.png differ diff --git a/atu_images/general/at_icon_edit_head_camera_alt.png b/atu_images/general/at_icon_edit_head_camera_alt.png new file mode 100644 index 0000000..5fba404 Binary files /dev/null and b/atu_images/general/at_icon_edit_head_camera_alt.png differ diff --git a/atu_images/general/at_icon_edit_user_info_add_pic.png b/atu_images/general/at_icon_edit_user_info_add_pic.png new file mode 100644 index 0000000..bd9fe8d Binary files /dev/null and b/atu_images/general/at_icon_edit_user_info_add_pic.png differ diff --git a/atu_images/general/at_icon_game_num0.png b/atu_images/general/at_icon_game_num0.png new file mode 100644 index 0000000..e96e310 Binary files /dev/null and b/atu_images/general/at_icon_game_num0.png differ diff --git a/atu_images/general/at_icon_game_num1.png b/atu_images/general/at_icon_game_num1.png new file mode 100644 index 0000000..8bd5e0b Binary files /dev/null and b/atu_images/general/at_icon_game_num1.png differ diff --git a/atu_images/general/at_icon_game_num2.png b/atu_images/general/at_icon_game_num2.png new file mode 100644 index 0000000..56ed7d3 Binary files /dev/null and b/atu_images/general/at_icon_game_num2.png differ diff --git a/atu_images/general/at_icon_game_num3.png b/atu_images/general/at_icon_game_num3.png new file mode 100644 index 0000000..7464933 Binary files /dev/null and b/atu_images/general/at_icon_game_num3.png differ diff --git a/atu_images/general/at_icon_game_num4.png b/atu_images/general/at_icon_game_num4.png new file mode 100644 index 0000000..b92c20b Binary files /dev/null and b/atu_images/general/at_icon_game_num4.png differ diff --git a/atu_images/general/at_icon_game_num5.png b/atu_images/general/at_icon_game_num5.png new file mode 100644 index 0000000..cc72066 Binary files /dev/null and b/atu_images/general/at_icon_game_num5.png differ diff --git a/atu_images/general/at_icon_game_num6.png b/atu_images/general/at_icon_game_num6.png new file mode 100644 index 0000000..84ff7d1 Binary files /dev/null and b/atu_images/general/at_icon_game_num6.png differ diff --git a/atu_images/general/at_icon_game_num7.png b/atu_images/general/at_icon_game_num7.png new file mode 100644 index 0000000..cc94734 Binary files /dev/null and b/atu_images/general/at_icon_game_num7.png differ diff --git a/atu_images/general/at_icon_game_num8.png b/atu_images/general/at_icon_game_num8.png new file mode 100644 index 0000000..a5c06a6 Binary files /dev/null and b/atu_images/general/at_icon_game_num8.png differ diff --git a/atu_images/general/at_icon_game_num9.png b/atu_images/general/at_icon_game_num9.png new file mode 100644 index 0000000..a03b487 Binary files /dev/null and b/atu_images/general/at_icon_game_num9.png differ diff --git a/atu_images/general/at_icon_game_numk.png b/atu_images/general/at_icon_game_numk.png new file mode 100644 index 0000000..bb3df0b Binary files /dev/null and b/atu_images/general/at_icon_game_numk.png differ diff --git a/atu_images/general/at_icon_game_numxx.png b/atu_images/general/at_icon_game_numxx.png new file mode 100644 index 0000000..4d7a831 Binary files /dev/null and b/atu_images/general/at_icon_game_numxx.png differ diff --git a/atu_images/general/at_icon_google_pay.png b/atu_images/general/at_icon_google_pay.png new file mode 100644 index 0000000..452f11d Binary files /dev/null and b/atu_images/general/at_icon_google_pay.png differ diff --git a/atu_images/general/at_icon_jb.png b/atu_images/general/at_icon_jb.png new file mode 100644 index 0000000..fb5befc Binary files /dev/null and b/atu_images/general/at_icon_jb.png differ diff --git a/atu_images/general/at_icon_jb2.png b/atu_images/general/at_icon_jb2.png new file mode 100644 index 0000000..e54e9a2 Binary files /dev/null and b/atu_images/general/at_icon_jb2.png differ diff --git a/atu_images/general/at_icon_jb3.png b/atu_images/general/at_icon_jb3.png new file mode 100644 index 0000000..ed3b6a5 Binary files /dev/null and b/atu_images/general/at_icon_jb3.png differ diff --git a/atu_images/general/at_icon_loading.png b/atu_images/general/at_icon_loading.png new file mode 100644 index 0000000..316ea9f Binary files /dev/null and b/atu_images/general/at_icon_loading.png differ diff --git a/atu_images/general/at_icon_loading.webp b/atu_images/general/at_icon_loading.webp new file mode 100644 index 0000000..4d4ebde Binary files /dev/null and b/atu_images/general/at_icon_loading.webp differ diff --git a/atu_images/general/at_icon_logo.png b/atu_images/general/at_icon_logo.png new file mode 100644 index 0000000..ee770ec Binary files /dev/null and b/atu_images/general/at_icon_logo.png differ diff --git a/atu_images/general/at_icon_no_data_icon.png b/atu_images/general/at_icon_no_data_icon.png new file mode 100644 index 0000000..49138df Binary files /dev/null and b/atu_images/general/at_icon_no_data_icon.png differ diff --git a/atu_images/general/at_icon_no_data_icon2.png b/atu_images/general/at_icon_no_data_icon2.png new file mode 100644 index 0000000..ccaab88 Binary files /dev/null and b/atu_images/general/at_icon_no_data_icon2.png differ diff --git a/atu_images/general/at_icon_online_user.gif b/atu_images/general/at_icon_online_user.gif new file mode 100644 index 0000000..eaf1d1e Binary files /dev/null and b/atu_images/general/at_icon_online_user.gif differ diff --git a/atu_images/general/at_icon_pay_price_item_bg.png b/atu_images/general/at_icon_pay_price_item_bg.png new file mode 100644 index 0000000..98b1689 Binary files /dev/null and b/atu_images/general/at_icon_pay_price_item_bg.png differ diff --git a/atu_images/general/at_icon_pay_select_country_bg.png b/atu_images/general/at_icon_pay_select_country_bg.png new file mode 100644 index 0000000..f7ca6f5 Binary files /dev/null and b/atu_images/general/at_icon_pay_select_country_bg.png differ diff --git a/atu_images/general/at_icon_pic_close.png b/atu_images/general/at_icon_pic_close.png new file mode 100644 index 0000000..cec98dc Binary files /dev/null and b/atu_images/general/at_icon_pic_close.png differ diff --git a/atu_images/general/at_icon_search.png b/atu_images/general/at_icon_search.png new file mode 100644 index 0000000..b87f25c Binary files /dev/null and b/atu_images/general/at_icon_search.png differ diff --git a/atu_images/general/at_icon_select.png b/atu_images/general/at_icon_select.png new file mode 100644 index 0000000..ab735d8 Binary files /dev/null and b/atu_images/general/at_icon_select.png differ diff --git a/atu_images/general/at_icon_select2.png b/atu_images/general/at_icon_select2.png new file mode 100644 index 0000000..aff80e7 Binary files /dev/null and b/atu_images/general/at_icon_select2.png differ diff --git a/atu_images/general/at_icon_select_ok.png b/atu_images/general/at_icon_select_ok.png new file mode 100644 index 0000000..7bcc624 Binary files /dev/null and b/atu_images/general/at_icon_select_ok.png differ diff --git a/atu_images/general/at_icon_select_un_ok.png b/atu_images/general/at_icon_select_un_ok.png new file mode 100644 index 0000000..c777106 Binary files /dev/null and b/atu_images/general/at_icon_select_un_ok.png differ diff --git a/atu_images/general/at_icon_share_facebook.png b/atu_images/general/at_icon_share_facebook.png new file mode 100644 index 0000000..5ec937d Binary files /dev/null and b/atu_images/general/at_icon_share_facebook.png differ diff --git a/atu_images/general/at_icon_share_link.png b/atu_images/general/at_icon_share_link.png new file mode 100644 index 0000000..6be8f2c Binary files /dev/null and b/atu_images/general/at_icon_share_link.png differ diff --git a/atu_images/general/at_icon_share_snapchat.png b/atu_images/general/at_icon_share_snapchat.png new file mode 100644 index 0000000..c9557df Binary files /dev/null and b/atu_images/general/at_icon_share_snapchat.png differ diff --git a/atu_images/general/at_icon_share_whatsapp.png b/atu_images/general/at_icon_share_whatsapp.png new file mode 100644 index 0000000..c4b3330 Binary files /dev/null and b/atu_images/general/at_icon_share_whatsapp.png differ diff --git a/atu_images/general/at_icon_social_privilege_close.png b/atu_images/general/at_icon_social_privilege_close.png new file mode 100644 index 0000000..a01d154 Binary files /dev/null and b/atu_images/general/at_icon_social_privilege_close.png differ diff --git a/atu_images/general/at_icon_social_privilege_open.png b/atu_images/general/at_icon_social_privilege_open.png new file mode 100644 index 0000000..0f5fca7 Binary files /dev/null and b/atu_images/general/at_icon_social_privilege_open.png differ diff --git a/atu_images/general/at_icon_social_privilege_select.png b/atu_images/general/at_icon_social_privilege_select.png new file mode 100644 index 0000000..bd36512 Binary files /dev/null and b/atu_images/general/at_icon_social_privilege_select.png differ diff --git a/atu_images/general/at_icon_social_privilege_unselect.png b/atu_images/general/at_icon_social_privilege_unselect.png new file mode 100644 index 0000000..8b79afe Binary files /dev/null and b/atu_images/general/at_icon_social_privilege_unselect.png differ diff --git a/atu_images/general/at_icon_switch_off.png b/atu_images/general/at_icon_switch_off.png new file mode 100644 index 0000000..5b33132 Binary files /dev/null and b/atu_images/general/at_icon_switch_off.png differ diff --git a/atu_images/general/at_icon_switch_on.png b/atu_images/general/at_icon_switch_on.png new file mode 100644 index 0000000..7d58822 Binary files /dev/null and b/atu_images/general/at_icon_switch_on.png differ diff --git a/atu_images/general/at_icon_unselect.png b/atu_images/general/at_icon_unselect.png new file mode 100644 index 0000000..65a7094 Binary files /dev/null and b/atu_images/general/at_icon_unselect.png differ diff --git a/atu_images/general/at_icon_unselect2.png b/atu_images/general/at_icon_unselect2.png new file mode 100644 index 0000000..609cb96 Binary files /dev/null and b/atu_images/general/at_icon_unselect2.png differ diff --git a/atu_images/index/at_icon_add_room.png b/atu_images/index/at_icon_add_room.png new file mode 100644 index 0000000..1153867 Binary files /dev/null and b/atu_images/index/at_icon_add_room.png differ diff --git a/atu_images/index/at_icon_admin_center.png b/atu_images/index/at_icon_admin_center.png new file mode 100644 index 0000000..6de3fc6 Binary files /dev/null and b/atu_images/index/at_icon_admin_center.png differ diff --git a/atu_images/index/at_icon_agen_center.png b/atu_images/index/at_icon_agen_center.png new file mode 100644 index 0000000..68798e2 Binary files /dev/null and b/atu_images/index/at_icon_agen_center.png differ diff --git a/atu_images/index/at_icon_agent_center.png b/atu_images/index/at_icon_agent_center.png new file mode 100644 index 0000000..3463e68 Binary files /dev/null and b/atu_images/index/at_icon_agent_center.png differ diff --git a/atu_images/index/at_icon_bag.png b/atu_images/index/at_icon_bag.png new file mode 100644 index 0000000..82afa40 Binary files /dev/null and b/atu_images/index/at_icon_bag.png differ diff --git a/atu_images/index/at_icon_bd_center.png b/atu_images/index/at_icon_bd_center.png new file mode 100644 index 0000000..98832cb Binary files /dev/null and b/atu_images/index/at_icon_bd_center.png differ diff --git a/atu_images/index/at_icon_bd_leader.png b/atu_images/index/at_icon_bd_leader.png new file mode 100644 index 0000000..5015901 Binary files /dev/null and b/atu_images/index/at_icon_bd_leader.png differ diff --git a/atu_images/index/at_icon_become_host_center.png b/atu_images/index/at_icon_become_host_center.png new file mode 100644 index 0000000..c6920a0 Binary files /dev/null and b/atu_images/index/at_icon_become_host_center.png differ diff --git a/atu_images/index/at_icon_claimed_text.png b/atu_images/index/at_icon_claimed_text.png new file mode 100644 index 0000000..0f39fa9 Binary files /dev/null and b/atu_images/index/at_icon_claimed_text.png differ diff --git a/atu_images/index/at_icon_coupon.png b/atu_images/index/at_icon_coupon.png new file mode 100644 index 0000000..2c60d4a Binary files /dev/null and b/atu_images/index/at_icon_coupon.png differ diff --git a/atu_images/index/at_icon_coupon_head_bg.png b/atu_images/index/at_icon_coupon_head_bg.png new file mode 100644 index 0000000..77006ef Binary files /dev/null and b/atu_images/index/at_icon_coupon_head_bg.png differ diff --git a/atu_images/index/at_icon_coupon_recod.png b/atu_images/index/at_icon_coupon_recod.png new file mode 100644 index 0000000..6b9ec85 Binary files /dev/null and b/atu_images/index/at_icon_coupon_recod.png differ diff --git a/atu_images/index/at_icon_dynamic_en.png b/atu_images/index/at_icon_dynamic_en.png new file mode 100644 index 0000000..e3c7624 Binary files /dev/null and b/atu_images/index/at_icon_dynamic_en.png differ diff --git a/atu_images/index/at_icon_dynamic_no.png b/atu_images/index/at_icon_dynamic_no.png new file mode 100644 index 0000000..f897554 Binary files /dev/null and b/atu_images/index/at_icon_dynamic_no.png differ diff --git a/atu_images/index/at_icon_explore_en.png b/atu_images/index/at_icon_explore_en.png new file mode 100644 index 0000000..2b6ce62 Binary files /dev/null and b/atu_images/index/at_icon_explore_en.png differ diff --git a/atu_images/index/at_icon_explore_no.png b/atu_images/index/at_icon_explore_no.png new file mode 100644 index 0000000..7cb4aaa Binary files /dev/null and b/atu_images/index/at_icon_explore_no.png differ diff --git a/atu_images/index/at_icon_explore_room_model_1_rank_1.png b/atu_images/index/at_icon_explore_room_model_1_rank_1.png new file mode 100644 index 0000000..7d06324 Binary files /dev/null and b/atu_images/index/at_icon_explore_room_model_1_rank_1.png differ diff --git a/atu_images/index/at_icon_explore_room_model_1_rank_2.png b/atu_images/index/at_icon_explore_room_model_1_rank_2.png new file mode 100644 index 0000000..b515537 Binary files /dev/null and b/atu_images/index/at_icon_explore_room_model_1_rank_2.png differ diff --git a/atu_images/index/at_icon_explore_room_model_1_rank_3.png b/atu_images/index/at_icon_explore_room_model_1_rank_3.png new file mode 100644 index 0000000..88365dc Binary files /dev/null and b/atu_images/index/at_icon_explore_room_model_1_rank_3.png differ diff --git a/atu_images/index/at_icon_explore_room_model_2_rank_1.png b/atu_images/index/at_icon_explore_room_model_2_rank_1.png new file mode 100644 index 0000000..9e829dd Binary files /dev/null and b/atu_images/index/at_icon_explore_room_model_2_rank_1.png differ diff --git a/atu_images/index/at_icon_explore_room_model_2_rank_2.png b/atu_images/index/at_icon_explore_room_model_2_rank_2.png new file mode 100644 index 0000000..bbf2b0b Binary files /dev/null and b/atu_images/index/at_icon_explore_room_model_2_rank_2.png differ diff --git a/atu_images/index/at_icon_explore_room_model_2_rank_3.png b/atu_images/index/at_icon_explore_room_model_2_rank_3.png new file mode 100644 index 0000000..5ff26ff Binary files /dev/null and b/atu_images/index/at_icon_explore_room_model_2_rank_3.png differ diff --git a/atu_images/index/at_icon_fire_department.png b/atu_images/index/at_icon_fire_department.png new file mode 100644 index 0000000..c1556c2 Binary files /dev/null and b/atu_images/index/at_icon_fire_department.png differ diff --git a/atu_images/index/at_icon_first_recharge_bg.webp b/atu_images/index/at_icon_first_recharge_bg.webp new file mode 100644 index 0000000..bdb51c1 Binary files /dev/null and b/atu_images/index/at_icon_first_recharge_bg.webp differ diff --git a/atu_images/index/at_icon_first_recharge_btn.png b/atu_images/index/at_icon_first_recharge_btn.png new file mode 100644 index 0000000..c09bf83 Binary files /dev/null and b/atu_images/index/at_icon_first_recharge_btn.png differ diff --git a/atu_images/index/at_icon_first_recharge_tag.png b/atu_images/index/at_icon_first_recharge_tag.png new file mode 100644 index 0000000..41b7ac5 Binary files /dev/null and b/atu_images/index/at_icon_first_recharge_tag.png differ diff --git a/atu_images/index/at_icon_game_en.png b/atu_images/index/at_icon_game_en.png new file mode 100644 index 0000000..32e2418 Binary files /dev/null and b/atu_images/index/at_icon_game_en.png differ diff --git a/atu_images/index/at_icon_game_hot1_tag.png b/atu_images/index/at_icon_game_hot1_tag.png new file mode 100644 index 0000000..d76c5be Binary files /dev/null and b/atu_images/index/at_icon_game_hot1_tag.png differ diff --git a/atu_images/index/at_icon_game_hot2_tag.png b/atu_images/index/at_icon_game_hot2_tag.png new file mode 100644 index 0000000..4ecbef2 Binary files /dev/null and b/atu_images/index/at_icon_game_hot2_tag.png differ diff --git a/atu_images/index/at_icon_game_hot3_tag.png b/atu_images/index/at_icon_game_hot3_tag.png new file mode 100644 index 0000000..1c63117 Binary files /dev/null and b/atu_images/index/at_icon_game_hot3_tag.png differ diff --git a/atu_images/index/at_icon_game_new_tag.png b/atu_images/index/at_icon_game_new_tag.png new file mode 100644 index 0000000..1aac20d Binary files /dev/null and b/atu_images/index/at_icon_game_new_tag.png differ diff --git a/atu_images/index/at_icon_game_no.png b/atu_images/index/at_icon_game_no.png new file mode 100644 index 0000000..e2407bf Binary files /dev/null and b/atu_images/index/at_icon_game_no.png differ diff --git a/atu_images/index/at_icon_game_rank_01_bg.png b/atu_images/index/at_icon_game_rank_01_bg.png new file mode 100644 index 0000000..e80ceba Binary files /dev/null and b/atu_images/index/at_icon_game_rank_01_bg.png differ diff --git a/atu_images/index/at_icon_game_rank_02_bg.png b/atu_images/index/at_icon_game_rank_02_bg.png new file mode 100644 index 0000000..6432195 Binary files /dev/null and b/atu_images/index/at_icon_game_rank_02_bg.png differ diff --git a/atu_images/index/at_icon_gamebroad_lv1_bg.webp b/atu_images/index/at_icon_gamebroad_lv1_bg.webp new file mode 100644 index 0000000..7cb788d Binary files /dev/null and b/atu_images/index/at_icon_gamebroad_lv1_bg.webp differ diff --git a/atu_images/index/at_icon_gamebroad_lv2_bg.webp b/atu_images/index/at_icon_gamebroad_lv2_bg.webp new file mode 100644 index 0000000..adbc7f1 Binary files /dev/null and b/atu_images/index/at_icon_gamebroad_lv2_bg.webp differ diff --git a/atu_images/index/at_icon_gamebroad_lv3_bg.webp b/atu_images/index/at_icon_gamebroad_lv3_bg.webp new file mode 100644 index 0000000..f090553 Binary files /dev/null and b/atu_images/index/at_icon_gamebroad_lv3_bg.webp differ diff --git a/atu_images/index/at_icon_gamebroad_lv4_bg.webp b/atu_images/index/at_icon_gamebroad_lv4_bg.webp new file mode 100644 index 0000000..bf54ff1 Binary files /dev/null and b/atu_images/index/at_icon_gamebroad_lv4_bg.webp differ diff --git a/atu_images/index/at_icon_gamebroad_lv5_bg.webp b/atu_images/index/at_icon_gamebroad_lv5_bg.webp new file mode 100644 index 0000000..5ffc2c3 Binary files /dev/null and b/atu_images/index/at_icon_gamebroad_lv5_bg.webp differ diff --git a/atu_images/index/at_icon_home_en.png b/atu_images/index/at_icon_home_en.png new file mode 100644 index 0000000..7b12ff5 Binary files /dev/null and b/atu_images/index/at_icon_home_en.png differ diff --git a/atu_images/index/at_icon_home_no.png b/atu_images/index/at_icon_home_no.png new file mode 100644 index 0000000..0cc00f9 Binary files /dev/null and b/atu_images/index/at_icon_home_no.png differ diff --git a/atu_images/index/at_icon_honor.png b/atu_images/index/at_icon_honor.png new file mode 100644 index 0000000..a710d19 Binary files /dev/null and b/atu_images/index/at_icon_honor.png differ diff --git a/atu_images/index/at_icon_honor_detail_bg.png b/atu_images/index/at_icon_honor_detail_bg.png new file mode 100644 index 0000000..8da54cd Binary files /dev/null and b/atu_images/index/at_icon_honor_detail_bg.png differ diff --git a/atu_images/index/at_icon_honor_item_bg.png b/atu_images/index/at_icon_honor_item_bg.png new file mode 100644 index 0000000..a913abd Binary files /dev/null and b/atu_images/index/at_icon_honor_item_bg.png differ diff --git a/atu_images/index/at_icon_honor_userinfo_bg.png b/atu_images/index/at_icon_honor_userinfo_bg.png new file mode 100644 index 0000000..2053739 Binary files /dev/null and b/atu_images/index/at_icon_honor_userinfo_bg.png differ diff --git a/atu_images/index/at_icon_honor_userinfo_btn.png b/atu_images/index/at_icon_honor_userinfo_btn.png new file mode 100644 index 0000000..8fc6a6c Binary files /dev/null and b/atu_images/index/at_icon_honor_userinfo_btn.png differ diff --git a/atu_images/index/at_icon_host_center.png b/atu_images/index/at_icon_host_center.png new file mode 100644 index 0000000..88751a8 Binary files /dev/null and b/atu_images/index/at_icon_host_center.png differ diff --git a/atu_images/index/at_icon_hotgames_tag_bg.png b/atu_images/index/at_icon_hotgames_tag_bg.png new file mode 100644 index 0000000..15a10ed Binary files /dev/null and b/atu_images/index/at_icon_hotgames_tag_bg.png differ diff --git a/atu_images/index/at_icon_index_bg.png b/atu_images/index/at_icon_index_bg.png new file mode 100644 index 0000000..cff8e93 Binary files /dev/null and b/atu_images/index/at_icon_index_bg.png differ diff --git a/atu_images/index/at_icon_index_room_model_1.png b/atu_images/index/at_icon_index_room_model_1.png new file mode 100644 index 0000000..b891656 Binary files /dev/null and b/atu_images/index/at_icon_index_room_model_1.png differ diff --git a/atu_images/index/at_icon_index_room_model_2.png b/atu_images/index/at_icon_index_room_model_2.png new file mode 100644 index 0000000..1835303 Binary files /dev/null and b/atu_images/index/at_icon_index_room_model_2.png differ diff --git a/atu_images/index/at_icon_invite_new_users_to_earn_coins.png b/atu_images/index/at_icon_invite_new_users_to_earn_coins.png new file mode 100644 index 0000000..52c03e1 Binary files /dev/null and b/atu_images/index/at_icon_invite_new_users_to_earn_coins.png differ diff --git a/atu_images/index/at_icon_leader_spinner_charm_bg.png b/atu_images/index/at_icon_leader_spinner_charm_bg.png new file mode 100644 index 0000000..526c72f Binary files /dev/null and b/atu_images/index/at_icon_leader_spinner_charm_bg.png differ diff --git a/atu_images/index/at_icon_leader_spinner_room_bg.png b/atu_images/index/at_icon_leader_spinner_room_bg.png new file mode 100644 index 0000000..55a504f Binary files /dev/null and b/atu_images/index/at_icon_leader_spinner_room_bg.png differ diff --git a/atu_images/index/at_icon_leader_spinner_wealth_bg.png b/atu_images/index/at_icon_leader_spinner_wealth_bg.png new file mode 100644 index 0000000..ef8f97e Binary files /dev/null and b/atu_images/index/at_icon_leader_spinner_wealth_bg.png differ diff --git a/atu_images/index/at_icon_level.png b/atu_images/index/at_icon_level.png new file mode 100644 index 0000000..7239e44 Binary files /dev/null and b/atu_images/index/at_icon_level.png differ diff --git a/atu_images/index/at_icon_me_en.png b/atu_images/index/at_icon_me_en.png new file mode 100644 index 0000000..5bea8ef Binary files /dev/null and b/atu_images/index/at_icon_me_en.png differ diff --git a/atu_images/index/at_icon_me_no.png b/atu_images/index/at_icon_me_no.png new file mode 100644 index 0000000..020cda5 Binary files /dev/null and b/atu_images/index/at_icon_me_no.png differ diff --git a/atu_images/index/at_icon_medal_detail_bg.png b/atu_images/index/at_icon_medal_detail_bg.png new file mode 100644 index 0000000..fb15b90 Binary files /dev/null and b/atu_images/index/at_icon_medal_detail_bg.png differ diff --git a/atu_images/index/at_icon_medals.png b/atu_images/index/at_icon_medals.png new file mode 100644 index 0000000..51483f8 Binary files /dev/null and b/atu_images/index/at_icon_medals.png differ diff --git a/atu_images/index/at_icon_medals_bg.png b/atu_images/index/at_icon_medals_bg.png new file mode 100644 index 0000000..8dbfb13 Binary files /dev/null and b/atu_images/index/at_icon_medals_bg.png differ diff --git a/atu_images/index/at_icon_medals_en.png b/atu_images/index/at_icon_medals_en.png new file mode 100644 index 0000000..142ccc3 Binary files /dev/null and b/atu_images/index/at_icon_medals_en.png differ diff --git a/atu_images/index/at_icon_medals_honor_item_bg.png b/atu_images/index/at_icon_medals_honor_item_bg.png new file mode 100644 index 0000000..4f7db7e Binary files /dev/null and b/atu_images/index/at_icon_medals_honor_item_bg.png differ diff --git a/atu_images/index/at_icon_medals_honor_item_head_bg.png b/atu_images/index/at_icon_medals_honor_item_head_bg.png new file mode 100644 index 0000000..8d19f2a Binary files /dev/null and b/atu_images/index/at_icon_medals_honor_item_head_bg.png differ diff --git a/atu_images/index/at_icon_medals_no.png b/atu_images/index/at_icon_medals_no.png new file mode 100644 index 0000000..33cd040 Binary files /dev/null and b/atu_images/index/at_icon_medals_no.png differ diff --git a/atu_images/index/at_icon_medals_userinfo_bg.png b/atu_images/index/at_icon_medals_userinfo_bg.png new file mode 100644 index 0000000..e6dc227 Binary files /dev/null and b/atu_images/index/at_icon_medals_userinfo_bg.png differ diff --git a/atu_images/index/at_icon_message_en.png b/atu_images/index/at_icon_message_en.png new file mode 100644 index 0000000..07ffd61 Binary files /dev/null and b/atu_images/index/at_icon_message_en.png differ diff --git a/atu_images/index/at_icon_message_no.png b/atu_images/index/at_icon_message_no.png new file mode 100644 index 0000000..a04f11a Binary files /dev/null and b/atu_images/index/at_icon_message_no.png differ diff --git a/atu_images/index/at_icon_my_drawer_item_bg.png b/atu_images/index/at_icon_my_drawer_item_bg.png new file mode 100644 index 0000000..1ba71db Binary files /dev/null and b/atu_images/index/at_icon_my_drawer_item_bg.png differ diff --git a/atu_images/index/at_icon_my_items.png b/atu_images/index/at_icon_my_items.png new file mode 100644 index 0000000..d69e01a Binary files /dev/null and b/atu_images/index/at_icon_my_items.png differ diff --git a/atu_images/index/at_icon_paid.png b/atu_images/index/at_icon_paid.png new file mode 100644 index 0000000..2095315 Binary files /dev/null and b/atu_images/index/at_icon_paid.png differ diff --git a/atu_images/index/at_icon_recharge_agency.png b/atu_images/index/at_icon_recharge_agency.png new file mode 100644 index 0000000..52d7cf5 Binary files /dev/null and b/atu_images/index/at_icon_recharge_agency.png differ diff --git a/atu_images/index/at_icon_recharge_bg.png b/atu_images/index/at_icon_recharge_bg.png new file mode 100644 index 0000000..80436e4 Binary files /dev/null and b/atu_images/index/at_icon_recharge_bg.png differ diff --git a/atu_images/index/at_icon_rocket_broad_bg_lv1.webp b/atu_images/index/at_icon_rocket_broad_bg_lv1.webp new file mode 100644 index 0000000..23b837a Binary files /dev/null and b/atu_images/index/at_icon_rocket_broad_bg_lv1.webp differ diff --git a/atu_images/index/at_icon_rocket_broad_bg_lv2.webp b/atu_images/index/at_icon_rocket_broad_bg_lv2.webp new file mode 100644 index 0000000..6576684 Binary files /dev/null and b/atu_images/index/at_icon_rocket_broad_bg_lv2.webp differ diff --git a/atu_images/index/at_icon_rocket_broad_bg_lv3.webp b/atu_images/index/at_icon_rocket_broad_bg_lv3.webp new file mode 100644 index 0000000..f690b46 Binary files /dev/null and b/atu_images/index/at_icon_rocket_broad_bg_lv3.webp differ diff --git a/atu_images/index/at_icon_room_flot_ani.gif b/atu_images/index/at_icon_room_flot_ani.gif new file mode 100644 index 0000000..39f080d Binary files /dev/null and b/atu_images/index/at_icon_room_flot_ani.gif differ diff --git a/atu_images/index/at_icon_room_flot_close.png b/atu_images/index/at_icon_room_flot_close.png new file mode 100644 index 0000000..445a733 Binary files /dev/null and b/atu_images/index/at_icon_room_flot_close.png differ diff --git a/atu_images/index/at_icon_room_suo.png b/atu_images/index/at_icon_room_suo.png new file mode 100644 index 0000000..c269cdb Binary files /dev/null and b/atu_images/index/at_icon_room_suo.png differ diff --git a/atu_images/index/at_icon_serach.png b/atu_images/index/at_icon_serach.png new file mode 100644 index 0000000..a6f8231 Binary files /dev/null and b/atu_images/index/at_icon_serach.png differ diff --git a/atu_images/index/at_icon_serach2.png b/atu_images/index/at_icon_serach2.png new file mode 100644 index 0000000..7e5c5ac Binary files /dev/null and b/atu_images/index/at_icon_serach2.png differ diff --git a/atu_images/index/at_icon_settings.png b/atu_images/index/at_icon_settings.png new file mode 100644 index 0000000..37a3f80 Binary files /dev/null and b/atu_images/index/at_icon_settings.png differ diff --git a/atu_images/index/at_icon_sgin_awd_vip_1_bg.png b/atu_images/index/at_icon_sgin_awd_vip_1_bg.png new file mode 100644 index 0000000..2e77e5f Binary files /dev/null and b/atu_images/index/at_icon_sgin_awd_vip_1_bg.png differ diff --git a/atu_images/index/at_icon_sgin_awd_vip_2_bg.png b/atu_images/index/at_icon_sgin_awd_vip_2_bg.png new file mode 100644 index 0000000..cf613ea Binary files /dev/null and b/atu_images/index/at_icon_sgin_awd_vip_2_bg.png differ diff --git a/atu_images/index/at_icon_sgin_awd_vip_3_bg.png b/atu_images/index/at_icon_sgin_awd_vip_3_bg.png new file mode 100644 index 0000000..b77fcb2 Binary files /dev/null and b/atu_images/index/at_icon_sgin_awd_vip_3_bg.png differ diff --git a/atu_images/index/at_icon_sgin_awd_vip_4_bg.png b/atu_images/index/at_icon_sgin_awd_vip_4_bg.png new file mode 100644 index 0000000..4f27656 Binary files /dev/null and b/atu_images/index/at_icon_sgin_awd_vip_4_bg.png differ diff --git a/atu_images/index/at_icon_sgin_awd_vip_5_bg.png b/atu_images/index/at_icon_sgin_awd_vip_5_bg.png new file mode 100644 index 0000000..deddc07 Binary files /dev/null and b/atu_images/index/at_icon_sgin_awd_vip_5_bg.png differ diff --git a/atu_images/index/at_icon_sgin_awd_vip_6_bg.png b/atu_images/index/at_icon_sgin_awd_vip_6_bg.png new file mode 100644 index 0000000..4ebef44 Binary files /dev/null and b/atu_images/index/at_icon_sgin_awd_vip_6_bg.png differ diff --git a/atu_images/index/at_icon_sgin_awd_vip_bg2.png b/atu_images/index/at_icon_sgin_awd_vip_bg2.png new file mode 100644 index 0000000..1fc2750 Binary files /dev/null and b/atu_images/index/at_icon_sgin_awd_vip_bg2.png differ diff --git a/atu_images/index/at_icon_sgin_bg.png b/atu_images/index/at_icon_sgin_bg.png new file mode 100644 index 0000000..4ac4cbe Binary files /dev/null and b/atu_images/index/at_icon_sgin_bg.png differ diff --git a/atu_images/index/at_icon_sgin_item_bg1.png b/atu_images/index/at_icon_sgin_item_bg1.png new file mode 100644 index 0000000..e9646f5 Binary files /dev/null and b/atu_images/index/at_icon_sgin_item_bg1.png differ diff --git a/atu_images/index/at_icon_sgin_item_bg2.png b/atu_images/index/at_icon_sgin_item_bg2.png new file mode 100644 index 0000000..f9a8733 Binary files /dev/null and b/atu_images/index/at_icon_sgin_item_bg2.png differ diff --git a/atu_images/index/at_icon_sgin_item_bg3.png b/atu_images/index/at_icon_sgin_item_bg3.png new file mode 100644 index 0000000..c9a1a0c Binary files /dev/null and b/atu_images/index/at_icon_sgin_item_bg3.png differ diff --git a/atu_images/index/at_icon_sgin_item_day7_bg1.png b/atu_images/index/at_icon_sgin_item_day7_bg1.png new file mode 100644 index 0000000..4d8b867 Binary files /dev/null and b/atu_images/index/at_icon_sgin_item_day7_bg1.png differ diff --git a/atu_images/index/at_icon_sgin_item_day7_bg2.png b/atu_images/index/at_icon_sgin_item_day7_bg2.png new file mode 100644 index 0000000..4ae5e3e Binary files /dev/null and b/atu_images/index/at_icon_sgin_item_day7_bg2.png differ diff --git a/atu_images/index/at_icon_sgin_item_day7_bg3.png b/atu_images/index/at_icon_sgin_item_day7_bg3.png new file mode 100644 index 0000000..c900c51 Binary files /dev/null and b/atu_images/index/at_icon_sgin_item_day7_bg3.png differ diff --git a/atu_images/index/at_icon_sgin_rec_bg.png b/atu_images/index/at_icon_sgin_rec_bg.png new file mode 100644 index 0000000..26b9fd5 Binary files /dev/null and b/atu_images/index/at_icon_sgin_rec_bg.png differ diff --git a/atu_images/index/at_icon_shop.png b/atu_images/index/at_icon_shop.png new file mode 100644 index 0000000..2745a6a Binary files /dev/null and b/atu_images/index/at_icon_shop.png differ diff --git a/atu_images/index/at_icon_signedin_bg.png b/atu_images/index/at_icon_signedin_bg.png new file mode 100644 index 0000000..84c2e1c Binary files /dev/null and b/atu_images/index/at_icon_signedin_bg.png differ diff --git a/atu_images/index/at_icon_splash_cp_name_bg.png b/atu_images/index/at_icon_splash_cp_name_bg.png new file mode 100644 index 0000000..6913776 Binary files /dev/null and b/atu_images/index/at_icon_splash_cp_name_bg.png differ diff --git a/atu_images/index/at_icon_splash_king_games_name_bg.png b/atu_images/index/at_icon_splash_king_games_name_bg.png new file mode 100644 index 0000000..05d22fc Binary files /dev/null and b/atu_images/index/at_icon_splash_king_games_name_bg.png differ diff --git a/atu_images/index/at_icon_task.png b/atu_images/index/at_icon_task.png new file mode 100644 index 0000000..3d0ee5a Binary files /dev/null and b/atu_images/index/at_icon_task.png differ diff --git a/atu_images/index/at_icon_task_exp.png b/atu_images/index/at_icon_task_exp.png new file mode 100644 index 0000000..341ca7c Binary files /dev/null and b/atu_images/index/at_icon_task_exp.png differ diff --git a/atu_images/index/at_icon_task_head_bg.png b/atu_images/index/at_icon_task_head_bg.png new file mode 100644 index 0000000..e1ab44e Binary files /dev/null and b/atu_images/index/at_icon_task_head_bg.png differ diff --git a/atu_images/index/at_icon_task_list_bg.png b/atu_images/index/at_icon_task_list_bg.png new file mode 100644 index 0000000..e72d322 Binary files /dev/null and b/atu_images/index/at_icon_task_list_bg.png differ diff --git a/atu_images/index/at_icon_vip.png b/atu_images/index/at_icon_vip.png new file mode 100644 index 0000000..eb32ef6 Binary files /dev/null and b/atu_images/index/at_icon_vip.png differ diff --git a/atu_images/index/at_icon_wallet_bg.png b/atu_images/index/at_icon_wallet_bg.png new file mode 100644 index 0000000..b36ab34 Binary files /dev/null and b/atu_images/index/at_icon_wallet_bg.png differ diff --git a/atu_images/index/at_icon_wallet_icon.png b/atu_images/index/at_icon_wallet_icon.png new file mode 100644 index 0000000..27552b7 Binary files /dev/null and b/atu_images/index/at_icon_wallet_icon.png differ diff --git a/atu_images/index/at_icon_wear_honor_dialog_bg.png b/atu_images/index/at_icon_wear_honor_dialog_bg.png new file mode 100644 index 0000000..6a8e071 Binary files /dev/null and b/atu_images/index/at_icon_wear_honor_dialog_bg.png differ diff --git a/atu_images/index/at_icon_wear_honor_dialog_item_on_use.png b/atu_images/index/at_icon_wear_honor_dialog_item_on_use.png new file mode 100644 index 0000000..685c625 Binary files /dev/null and b/atu_images/index/at_icon_wear_honor_dialog_item_on_use.png differ diff --git a/atu_images/index/at_icon_wear_honor_dialog_item_un_use.png b/atu_images/index/at_icon_wear_honor_dialog_item_un_use.png new file mode 100644 index 0000000..c8f7149 Binary files /dev/null and b/atu_images/index/at_icon_wear_honor_dialog_item_un_use.png differ diff --git a/atu_images/index/atu_icon_medals_honor_bg.png b/atu_images/index/atu_icon_medals_honor_bg.png new file mode 100644 index 0000000..48b67fa Binary files /dev/null and b/atu_images/index/atu_icon_medals_honor_bg.png differ diff --git a/atu_images/index/icon_game_new_tag.png b/atu_images/index/icon_game_new_tag.png new file mode 100644 index 0000000..7692741 Binary files /dev/null and b/atu_images/index/icon_game_new_tag.png differ diff --git a/atu_images/level/at_icon_level_pd_en.png b/atu_images/level/at_icon_level_pd_en.png new file mode 100644 index 0000000..3410c3b Binary files /dev/null and b/atu_images/level/at_icon_level_pd_en.png differ diff --git a/atu_images/level/at_icon_level_pd_no.png b/atu_images/level/at_icon_level_pd_no.png new file mode 100644 index 0000000..d01f997 Binary files /dev/null and b/atu_images/level/at_icon_level_pd_no.png differ diff --git a/atu_images/level/at_icon_user_level_10_20.png b/atu_images/level/at_icon_user_level_10_20.png new file mode 100644 index 0000000..aa38d83 Binary files /dev/null and b/atu_images/level/at_icon_user_level_10_20.png differ diff --git a/atu_images/level/at_icon_user_level_1_10.png b/atu_images/level/at_icon_user_level_1_10.png new file mode 100644 index 0000000..d912c99 Binary files /dev/null and b/atu_images/level/at_icon_user_level_1_10.png differ diff --git a/atu_images/level/at_icon_user_level_20_30.png b/atu_images/level/at_icon_user_level_20_30.png new file mode 100644 index 0000000..c8ead11 Binary files /dev/null and b/atu_images/level/at_icon_user_level_20_30.png differ diff --git a/atu_images/level/at_icon_user_level_30_40.png b/atu_images/level/at_icon_user_level_30_40.png new file mode 100644 index 0000000..11ad191 Binary files /dev/null and b/atu_images/level/at_icon_user_level_30_40.png differ diff --git a/atu_images/level/at_icon_user_level_40_50.png b/atu_images/level/at_icon_user_level_40_50.png new file mode 100644 index 0000000..d09ff13 Binary files /dev/null and b/atu_images/level/at_icon_user_level_40_50.png differ diff --git a/atu_images/level/at_icon_user_level_bg.png b/atu_images/level/at_icon_user_level_bg.png new file mode 100644 index 0000000..7f2804b Binary files /dev/null and b/atu_images/level/at_icon_user_level_bg.png differ diff --git a/atu_images/level/at_icon_user_level_center_bg_1.png b/atu_images/level/at_icon_user_level_center_bg_1.png new file mode 100644 index 0000000..ff9cb89 Binary files /dev/null and b/atu_images/level/at_icon_user_level_center_bg_1.png differ diff --git a/atu_images/level/at_icon_user_level_center_bg_2.png b/atu_images/level/at_icon_user_level_center_bg_2.png new file mode 100644 index 0000000..2a83781 Binary files /dev/null and b/atu_images/level/at_icon_user_level_center_bg_2.png differ diff --git a/atu_images/level/at_icon_user_level_go.png b/atu_images/level/at_icon_user_level_go.png new file mode 100644 index 0000000..fc41406 Binary files /dev/null and b/atu_images/level/at_icon_user_level_go.png differ diff --git a/atu_images/level/at_icon_user_level_tag1.png b/atu_images/level/at_icon_user_level_tag1.png new file mode 100644 index 0000000..90340b8 Binary files /dev/null and b/atu_images/level/at_icon_user_level_tag1.png differ diff --git a/atu_images/level/at_icon_user_level_tag2.png b/atu_images/level/at_icon_user_level_tag2.png new file mode 100644 index 0000000..86617e6 Binary files /dev/null and b/atu_images/level/at_icon_user_level_tag2.png differ diff --git a/atu_images/level/at_icon_user_level_tag3.png b/atu_images/level/at_icon_user_level_tag3.png new file mode 100644 index 0000000..31b70a4 Binary files /dev/null and b/atu_images/level/at_icon_user_level_tag3.png differ diff --git a/atu_images/level/at_icon_user_level_tag4.png b/atu_images/level/at_icon_user_level_tag4.png new file mode 100644 index 0000000..53e7dbb Binary files /dev/null and b/atu_images/level/at_icon_user_level_tag4.png differ diff --git a/atu_images/level/at_icon_user_level_tag5.png b/atu_images/level/at_icon_user_level_tag5.png new file mode 100644 index 0000000..25eb623 Binary files /dev/null and b/atu_images/level/at_icon_user_level_tag5.png differ diff --git a/atu_images/level/at_icon_user_wealth_center_bg_1.png b/atu_images/level/at_icon_user_wealth_center_bg_1.png new file mode 100644 index 0000000..8f43251 Binary files /dev/null and b/atu_images/level/at_icon_user_wealth_center_bg_1.png differ diff --git a/atu_images/level/at_icon_user_wealth_center_bg_2.png b/atu_images/level/at_icon_user_wealth_center_bg_2.png new file mode 100644 index 0000000..3192a43 Binary files /dev/null and b/atu_images/level/at_icon_user_wealth_center_bg_2.png differ diff --git a/atu_images/level/at_icon_user_wealth_go.png b/atu_images/level/at_icon_user_wealth_go.png new file mode 100644 index 0000000..8211c42 Binary files /dev/null and b/atu_images/level/at_icon_user_wealth_go.png differ diff --git a/atu_images/level/at_icon_wealth_id_icon_1.png b/atu_images/level/at_icon_wealth_id_icon_1.png new file mode 100644 index 0000000..4bcc733 Binary files /dev/null and b/atu_images/level/at_icon_wealth_id_icon_1.png differ diff --git a/atu_images/level/at_icon_wealth_id_icon_2.png b/atu_images/level/at_icon_wealth_id_icon_2.png new file mode 100644 index 0000000..9c45044 Binary files /dev/null and b/atu_images/level/at_icon_wealth_id_icon_2.png differ diff --git a/atu_images/level/at_icon_wealth_id_icon_3.png b/atu_images/level/at_icon_wealth_id_icon_3.png new file mode 100644 index 0000000..7f2453d Binary files /dev/null and b/atu_images/level/at_icon_wealth_id_icon_3.png differ diff --git a/atu_images/level/at_icon_wealth_id_icon_4.png b/atu_images/level/at_icon_wealth_id_icon_4.png new file mode 100644 index 0000000..c5fb4b2 Binary files /dev/null and b/atu_images/level/at_icon_wealth_id_icon_4.png differ diff --git a/atu_images/level/at_icon_wealth_level_10_20.png b/atu_images/level/at_icon_wealth_level_10_20.png new file mode 100644 index 0000000..42167f5 Binary files /dev/null and b/atu_images/level/at_icon_wealth_level_10_20.png differ diff --git a/atu_images/level/at_icon_wealth_level_1_10.png b/atu_images/level/at_icon_wealth_level_1_10.png new file mode 100644 index 0000000..b937e89 Binary files /dev/null and b/atu_images/level/at_icon_wealth_level_1_10.png differ diff --git a/atu_images/level/at_icon_wealth_level_20_30.png b/atu_images/level/at_icon_wealth_level_20_30.png new file mode 100644 index 0000000..4a0d27c Binary files /dev/null and b/atu_images/level/at_icon_wealth_level_20_30.png differ diff --git a/atu_images/level/at_icon_wealth_level_30_40.png b/atu_images/level/at_icon_wealth_level_30_40.png new file mode 100644 index 0000000..e53454f Binary files /dev/null and b/atu_images/level/at_icon_wealth_level_30_40.png differ diff --git a/atu_images/level/at_icon_wealth_level_40_50.png b/atu_images/level/at_icon_wealth_level_40_50.png new file mode 100644 index 0000000..ccb3fae Binary files /dev/null and b/atu_images/level/at_icon_wealth_level_40_50.png differ diff --git a/atu_images/level/at_icon_wealth_level_bg.png b/atu_images/level/at_icon_wealth_level_bg.png new file mode 100644 index 0000000..f6c109b Binary files /dev/null and b/atu_images/level/at_icon_wealth_level_bg.png differ diff --git a/atu_images/level/at_icon_wealth_level_otsb_tg.png b/atu_images/level/at_icon_wealth_level_otsb_tg.png new file mode 100644 index 0000000..a2f79a3 Binary files /dev/null and b/atu_images/level/at_icon_wealth_level_otsb_tg.png differ diff --git a/atu_images/level/at_icon_wealth_level_tag1.png b/atu_images/level/at_icon_wealth_level_tag1.png new file mode 100644 index 0000000..97ed51a Binary files /dev/null and b/atu_images/level/at_icon_wealth_level_tag1.png differ diff --git a/atu_images/level/at_icon_wealth_level_tag2.png b/atu_images/level/at_icon_wealth_level_tag2.png new file mode 100644 index 0000000..6693a57 Binary files /dev/null and b/atu_images/level/at_icon_wealth_level_tag2.png differ diff --git a/atu_images/level/at_icon_wealth_level_tag3.png b/atu_images/level/at_icon_wealth_level_tag3.png new file mode 100644 index 0000000..0c41200 Binary files /dev/null and b/atu_images/level/at_icon_wealth_level_tag3.png differ diff --git a/atu_images/level/at_icon_wealth_level_tag4.png b/atu_images/level/at_icon_wealth_level_tag4.png new file mode 100644 index 0000000..3378c58 Binary files /dev/null and b/atu_images/level/at_icon_wealth_level_tag4.png differ diff --git a/atu_images/level/at_icon_wealth_level_tag5.png b/atu_images/level/at_icon_wealth_level_tag5.png new file mode 100644 index 0000000..0b8b835 Binary files /dev/null and b/atu_images/level/at_icon_wealth_level_tag5.png differ diff --git a/atu_images/level/at_icon_wealth_title_01_left.png b/atu_images/level/at_icon_wealth_title_01_left.png new file mode 100644 index 0000000..318ddc5 Binary files /dev/null and b/atu_images/level/at_icon_wealth_title_01_left.png differ diff --git a/atu_images/level/at_icon_wealth_title_01_right.png b/atu_images/level/at_icon_wealth_title_01_right.png new file mode 100644 index 0000000..b683f85 Binary files /dev/null and b/atu_images/level/at_icon_wealth_title_01_right.png differ diff --git a/atu_images/login/at_edit_profile_bg.png b/atu_images/login/at_edit_profile_bg.png new file mode 100644 index 0000000..b323969 Binary files /dev/null and b/atu_images/login/at_edit_profile_bg.png differ diff --git a/atu_images/login/at_icon_account.png b/atu_images/login/at_icon_account.png new file mode 100644 index 0000000..023072d Binary files /dev/null and b/atu_images/login/at_icon_account.png differ diff --git a/atu_images/login/at_icon_google.png b/atu_images/login/at_icon_google.png new file mode 100644 index 0000000..7822f37 Binary files /dev/null and b/atu_images/login/at_icon_google.png differ diff --git a/atu_images/login/at_icon_iphone.png b/atu_images/login/at_icon_iphone.png new file mode 100644 index 0000000..381ca1d Binary files /dev/null and b/atu_images/login/at_icon_iphone.png differ diff --git a/atu_images/login/at_icon_login_ser_select.png b/atu_images/login/at_icon_login_ser_select.png new file mode 100644 index 0000000..b883353 Binary files /dev/null and b/atu_images/login/at_icon_login_ser_select.png differ diff --git a/atu_images/login/at_icon_login_ser_select_un.png b/atu_images/login/at_icon_login_ser_select_un.png new file mode 100644 index 0000000..ed78f91 Binary files /dev/null and b/atu_images/login/at_icon_login_ser_select_un.png differ diff --git a/atu_images/login/at_icon_pass.png b/atu_images/login/at_icon_pass.png new file mode 100644 index 0000000..4106361 Binary files /dev/null and b/atu_images/login/at_icon_pass.png differ diff --git a/atu_images/login/at_icon_pass1.png b/atu_images/login/at_icon_pass1.png new file mode 100644 index 0000000..a9a0de3 Binary files /dev/null and b/atu_images/login/at_icon_pass1.png differ diff --git a/atu_images/login/at_icon_sc.png b/atu_images/login/at_icon_sc.png new file mode 100644 index 0000000..a195711 Binary files /dev/null and b/atu_images/login/at_icon_sc.png differ diff --git a/atu_images/login/at_icon_sex_man.png b/atu_images/login/at_icon_sex_man.png new file mode 100644 index 0000000..5921600 Binary files /dev/null and b/atu_images/login/at_icon_sex_man.png differ diff --git a/atu_images/login/at_icon_sex_man_bg.png b/atu_images/login/at_icon_sex_man_bg.png new file mode 100644 index 0000000..a4e1d45 Binary files /dev/null and b/atu_images/login/at_icon_sex_man_bg.png differ diff --git a/atu_images/login/at_icon_sex_woman.png b/atu_images/login/at_icon_sex_woman.png new file mode 100644 index 0000000..002d246 Binary files /dev/null and b/atu_images/login/at_icon_sex_woman.png differ diff --git a/atu_images/login/at_icon_sex_woman_bg.png b/atu_images/login/at_icon_sex_woman_bg.png new file mode 100644 index 0000000..528bf75 Binary files /dev/null and b/atu_images/login/at_icon_sex_woman_bg.png differ diff --git a/atu_images/login/at_login.png b/atu_images/login/at_login.png new file mode 100644 index 0000000..554e244 Binary files /dev/null and b/atu_images/login/at_login.png differ diff --git a/atu_images/login/at_login_account.png b/atu_images/login/at_login_account.png new file mode 100644 index 0000000..9902434 Binary files /dev/null and b/atu_images/login/at_login_account.png differ diff --git a/atu_images/msg/at_icon_add.png b/atu_images/msg/at_icon_add.png new file mode 100644 index 0000000..7951369 Binary files /dev/null and b/atu_images/msg/at_icon_add.png differ diff --git a/atu_images/msg/at_icon_chat_key.png b/atu_images/msg/at_icon_chat_key.png new file mode 100644 index 0000000..e7938b0 Binary files /dev/null and b/atu_images/msg/at_icon_chat_key.png differ diff --git a/atu_images/msg/at_icon_chat_message_send.png b/atu_images/msg/at_icon_chat_message_send.png new file mode 100644 index 0000000..0257e1e Binary files /dev/null and b/atu_images/msg/at_icon_chat_message_send.png differ diff --git a/atu_images/msg/at_icon_cp_lover_bg.png b/atu_images/msg/at_icon_cp_lover_bg.png new file mode 100644 index 0000000..349304e Binary files /dev/null and b/atu_images/msg/at_icon_cp_lover_bg.png differ diff --git a/atu_images/msg/at_icon_cp_lover_text_ar.png b/atu_images/msg/at_icon_cp_lover_text_ar.png new file mode 100644 index 0000000..eb2d27a Binary files /dev/null and b/atu_images/msg/at_icon_cp_lover_text_ar.png differ diff --git a/atu_images/msg/at_icon_cp_lover_text_en.png b/atu_images/msg/at_icon_cp_lover_text_en.png new file mode 100644 index 0000000..30bfcc8 Binary files /dev/null and b/atu_images/msg/at_icon_cp_lover_text_en.png differ diff --git a/atu_images/msg/at_icon_emoji.png b/atu_images/msg/at_icon_emoji.png new file mode 100644 index 0000000..55ae291 Binary files /dev/null and b/atu_images/msg/at_icon_emoji.png differ diff --git a/atu_images/msg/at_icon_hongbao.png b/atu_images/msg/at_icon_hongbao.png new file mode 100644 index 0000000..9549dd7 Binary files /dev/null and b/atu_images/msg/at_icon_hongbao.png differ diff --git a/atu_images/msg/at_icon_message_activity.png b/atu_images/msg/at_icon_message_activity.png new file mode 100644 index 0000000..de00e62 Binary files /dev/null and b/atu_images/msg/at_icon_message_activity.png differ diff --git a/atu_images/msg/at_icon_message_noti.png b/atu_images/msg/at_icon_message_noti.png new file mode 100644 index 0000000..0f217b0 Binary files /dev/null and b/atu_images/msg/at_icon_message_noti.png differ diff --git a/atu_images/msg/at_icon_message_system.png b/atu_images/msg/at_icon_message_system.png new file mode 100644 index 0000000..92a94c5 Binary files /dev/null and b/atu_images/msg/at_icon_message_system.png differ diff --git a/atu_images/msg/at_icon_msg_menu_copy.png b/atu_images/msg/at_icon_msg_menu_copy.png new file mode 100644 index 0000000..c0741df Binary files /dev/null and b/atu_images/msg/at_icon_msg_menu_copy.png differ diff --git a/atu_images/msg/at_icon_msg_menu_delete.png b/atu_images/msg/at_icon_msg_menu_delete.png new file mode 100644 index 0000000..acff626 Binary files /dev/null and b/atu_images/msg/at_icon_msg_menu_delete.png differ diff --git a/atu_images/msg/at_icon_msg_menu_recall.png b/atu_images/msg/at_icon_msg_menu_recall.png new file mode 100644 index 0000000..9629f9c Binary files /dev/null and b/atu_images/msg/at_icon_msg_menu_recall.png differ diff --git a/atu_images/msg/at_icon_msg_send_hongbao_bg.png b/atu_images/msg/at_icon_msg_send_hongbao_bg.png new file mode 100644 index 0000000..dcefa3b Binary files /dev/null and b/atu_images/msg/at_icon_msg_send_hongbao_bg.png differ diff --git a/atu_images/msg/at_icon_notifcation_title_bg.png b/atu_images/msg/at_icon_notifcation_title_bg.png new file mode 100644 index 0000000..e8f558b Binary files /dev/null and b/atu_images/msg/at_icon_notifcation_title_bg.png differ diff --git a/atu_images/msg/at_icon_notifcation_title_bg_ar.png b/atu_images/msg/at_icon_notifcation_title_bg_ar.png new file mode 100644 index 0000000..9b826cd Binary files /dev/null and b/atu_images/msg/at_icon_notifcation_title_bg_ar.png differ diff --git a/atu_images/msg/at_icon_red_envelopes_msg_item_bg.png b/atu_images/msg/at_icon_red_envelopes_msg_item_bg.png new file mode 100644 index 0000000..36f0210 Binary files /dev/null and b/atu_images/msg/at_icon_red_envelopes_msg_item_bg.png differ diff --git a/atu_images/msg/at_icon_red_envelopes_msg_open_bg.png b/atu_images/msg/at_icon_red_envelopes_msg_open_bg.png new file mode 100644 index 0000000..8b8b9d6 Binary files /dev/null and b/atu_images/msg/at_icon_red_envelopes_msg_open_bg.png differ diff --git a/atu_images/msg/at_icon_red_envelopes_msg_opened_bg.png b/atu_images/msg/at_icon_red_envelopes_msg_opened_bg.png new file mode 100644 index 0000000..e0a0f2b Binary files /dev/null and b/atu_images/msg/at_icon_red_envelopes_msg_opened_bg.png differ diff --git a/atu_images/msg/at_icon_system_title_bg.png b/atu_images/msg/at_icon_system_title_bg.png new file mode 100644 index 0000000..38bab8c Binary files /dev/null and b/atu_images/msg/at_icon_system_title_bg.png differ diff --git a/atu_images/msg/at_icon_system_title_bg_ar.png b/atu_images/msg/at_icon_system_title_bg_ar.png new file mode 100644 index 0000000..4179aca Binary files /dev/null and b/atu_images/msg/at_icon_system_title_bg_ar.png differ diff --git a/atu_images/msg/at_icon_tupian.png b/atu_images/msg/at_icon_tupian.png new file mode 100644 index 0000000..56e5395 Binary files /dev/null and b/atu_images/msg/at_icon_tupian.png differ diff --git a/atu_images/msg/at_icon_xiangji.png b/atu_images/msg/at_icon_xiangji.png new file mode 100644 index 0000000..a7f3f60 Binary files /dev/null and b/atu_images/msg/at_icon_xiangji.png differ diff --git a/atu_images/msg/im_noti_music.MP3 b/atu_images/msg/im_noti_music.MP3 new file mode 100644 index 0000000..f3b58b2 Binary files /dev/null and b/atu_images/msg/im_noti_music.MP3 differ diff --git a/atu_images/person/at_icon_agent_contact.png b/atu_images/person/at_icon_agent_contact.png new file mode 100644 index 0000000..e44f294 Binary files /dev/null and b/atu_images/person/at_icon_agent_contact.png differ diff --git a/atu_images/person/at_icon_cp_gift_record.png b/atu_images/person/at_icon_cp_gift_record.png new file mode 100644 index 0000000..04717a2 Binary files /dev/null and b/atu_images/person/at_icon_cp_gift_record.png differ diff --git a/atu_images/person/at_icon_cp_head_ring.png b/atu_images/person/at_icon_cp_head_ring.png new file mode 100644 index 0000000..4a8ff26 Binary files /dev/null and b/atu_images/person/at_icon_cp_head_ring.png differ diff --git a/atu_images/person/at_icon_cp_head_ring2.png b/atu_images/person/at_icon_cp_head_ring2.png new file mode 100644 index 0000000..8a098a3 Binary files /dev/null and b/atu_images/person/at_icon_cp_head_ring2.png differ diff --git a/atu_images/person/at_icon_cp_help_ar.webp b/atu_images/person/at_icon_cp_help_ar.webp new file mode 100644 index 0000000..961a330 Binary files /dev/null and b/atu_images/person/at_icon_cp_help_ar.webp differ diff --git a/atu_images/person/at_icon_cp_help_en.webp b/atu_images/person/at_icon_cp_help_en.webp new file mode 100644 index 0000000..3e40d0a Binary files /dev/null and b/atu_images/person/at_icon_cp_help_en.webp differ diff --git a/atu_images/person/at_icon_cp_helpe.png b/atu_images/person/at_icon_cp_helpe.png new file mode 100644 index 0000000..aeee190 Binary files /dev/null and b/atu_images/person/at_icon_cp_helpe.png differ diff --git a/atu_images/person/at_icon_cp_opt_bg.png b/atu_images/person/at_icon_cp_opt_bg.png new file mode 100644 index 0000000..a835907 Binary files /dev/null and b/atu_images/person/at_icon_cp_opt_bg.png differ diff --git a/atu_images/person/at_icon_edit_user_info2.png b/atu_images/person/at_icon_edit_user_info2.png new file mode 100644 index 0000000..7efd77e Binary files /dev/null and b/atu_images/person/at_icon_edit_user_info2.png differ diff --git a/atu_images/person/at_icon_edit_userinfo_bg.png b/atu_images/person/at_icon_edit_userinfo_bg.png new file mode 100644 index 0000000..599dc6f Binary files /dev/null and b/atu_images/person/at_icon_edit_userinfo_bg.png differ diff --git a/atu_images/person/at_icon_edit_userinfo_camera.png b/atu_images/person/at_icon_edit_userinfo_camera.png new file mode 100644 index 0000000..f849526 Binary files /dev/null and b/atu_images/person/at_icon_edit_userinfo_camera.png differ diff --git a/atu_images/person/at_icon_giftwall_item_bg.png b/atu_images/person/at_icon_giftwall_item_bg.png new file mode 100644 index 0000000..603ca46 Binary files /dev/null and b/atu_images/person/at_icon_giftwall_item_bg.png differ diff --git a/atu_images/person/at_icon_honor_item_a_bg.png b/atu_images/person/at_icon_honor_item_a_bg.png new file mode 100644 index 0000000..e7523f2 Binary files /dev/null and b/atu_images/person/at_icon_honor_item_a_bg.png differ diff --git a/atu_images/person/at_icon_honor_item_b_bg.png b/atu_images/person/at_icon_honor_item_b_bg.png new file mode 100644 index 0000000..59e2bce Binary files /dev/null and b/atu_images/person/at_icon_honor_item_b_bg.png differ diff --git a/atu_images/person/at_icon_honor_item_c_bg.png b/atu_images/person/at_icon_honor_item_c_bg.png new file mode 100644 index 0000000..fcdab71 Binary files /dev/null and b/atu_images/person/at_icon_honor_item_c_bg.png differ diff --git a/atu_images/person/at_icon_honor_item_s_bg.png b/atu_images/person/at_icon_honor_item_s_bg.png new file mode 100644 index 0000000..b61ce35 Binary files /dev/null and b/atu_images/person/at_icon_honor_item_s_bg.png differ diff --git a/atu_images/person/at_icon_medal_item_a_bg.png b/atu_images/person/at_icon_medal_item_a_bg.png new file mode 100644 index 0000000..29b1f60 Binary files /dev/null and b/atu_images/person/at_icon_medal_item_a_bg.png differ diff --git a/atu_images/person/at_icon_medal_item_b_bg.png b/atu_images/person/at_icon_medal_item_b_bg.png new file mode 100644 index 0000000..af28637 Binary files /dev/null and b/atu_images/person/at_icon_medal_item_b_bg.png differ diff --git a/atu_images/person/at_icon_medal_item_c_bg.png b/atu_images/person/at_icon_medal_item_c_bg.png new file mode 100644 index 0000000..34cf758 Binary files /dev/null and b/atu_images/person/at_icon_medal_item_c_bg.png differ diff --git a/atu_images/person/at_icon_medal_item_s_bg.png b/atu_images/person/at_icon_medal_item_s_bg.png new file mode 100644 index 0000000..03b9c1f Binary files /dev/null and b/atu_images/person/at_icon_medal_item_s_bg.png differ diff --git a/atu_images/person/at_icon_my_head_bg_defalt.png b/atu_images/person/at_icon_my_head_bg_defalt.png new file mode 100644 index 0000000..d6dd224 Binary files /dev/null and b/atu_images/person/at_icon_my_head_bg_defalt.png differ diff --git a/atu_images/person/at_icon_no_cp_item_bg.png b/atu_images/person/at_icon_no_cp_item_bg.png new file mode 100644 index 0000000..9405d01 Binary files /dev/null and b/atu_images/person/at_icon_no_cp_item_bg.png differ diff --git a/atu_images/person/at_icon_no_cp_item_bg2_lv_0.png b/atu_images/person/at_icon_no_cp_item_bg2_lv_0.png new file mode 100644 index 0000000..21c0c89 Binary files /dev/null and b/atu_images/person/at_icon_no_cp_item_bg2_lv_0.png differ diff --git a/atu_images/person/at_icon_no_cp_item_bg2_lv_1.png b/atu_images/person/at_icon_no_cp_item_bg2_lv_1.png new file mode 100644 index 0000000..21c0c89 Binary files /dev/null and b/atu_images/person/at_icon_no_cp_item_bg2_lv_1.png differ diff --git a/atu_images/person/at_icon_no_cp_item_bg2_lv_2.png b/atu_images/person/at_icon_no_cp_item_bg2_lv_2.png new file mode 100644 index 0000000..ce2947d Binary files /dev/null and b/atu_images/person/at_icon_no_cp_item_bg2_lv_2.png differ diff --git a/atu_images/person/at_icon_no_cp_item_bg2_lv_3.png b/atu_images/person/at_icon_no_cp_item_bg2_lv_3.png new file mode 100644 index 0000000..07be0e1 Binary files /dev/null and b/atu_images/person/at_icon_no_cp_item_bg2_lv_3.png differ diff --git a/atu_images/person/at_icon_no_cp_item_bg3.png b/atu_images/person/at_icon_no_cp_item_bg3.png new file mode 100644 index 0000000..676a64a Binary files /dev/null and b/atu_images/person/at_icon_no_cp_item_bg3.png differ diff --git a/atu_images/person/at_icon_no_cp_item_bg4.png b/atu_images/person/at_icon_no_cp_item_bg4.png new file mode 100644 index 0000000..bfd57bd Binary files /dev/null and b/atu_images/person/at_icon_no_cp_item_bg4.png differ diff --git a/atu_images/person/at_icon_person_follow.png b/atu_images/person/at_icon_person_follow.png new file mode 100644 index 0000000..73bb3a2 Binary files /dev/null and b/atu_images/person/at_icon_person_follow.png differ diff --git a/atu_images/person/at_icon_person_in_room.png b/atu_images/person/at_icon_person_in_room.png new file mode 100644 index 0000000..bb6a5a4 Binary files /dev/null and b/atu_images/person/at_icon_person_in_room.png differ diff --git a/atu_images/person/at_icon_person_tochat.png b/atu_images/person/at_icon_person_tochat.png new file mode 100644 index 0000000..ea616c8 Binary files /dev/null and b/atu_images/person/at_icon_person_tochat.png differ diff --git a/atu_images/person/at_icon_person_unfollow.png b/atu_images/person/at_icon_person_unfollow.png new file mode 100644 index 0000000..972484c Binary files /dev/null and b/atu_images/person/at_icon_person_unfollow.png differ diff --git a/atu_images/person/at_icon_send_cp_requst_cancel_bg.png b/atu_images/person/at_icon_send_cp_requst_cancel_bg.png new file mode 100644 index 0000000..3352f15 Binary files /dev/null and b/atu_images/person/at_icon_send_cp_requst_cancel_bg.png differ diff --git a/atu_images/person/at_icon_send_cp_requst_dialog_bg.png b/atu_images/person/at_icon_send_cp_requst_dialog_bg.png new file mode 100644 index 0000000..ebdef7a Binary files /dev/null and b/atu_images/person/at_icon_send_cp_requst_dialog_bg.png differ diff --git a/atu_images/person/at_icon_send_cp_requst_dialog_content.png b/atu_images/person/at_icon_send_cp_requst_dialog_content.png new file mode 100644 index 0000000..aa340be Binary files /dev/null and b/atu_images/person/at_icon_send_cp_requst_dialog_content.png differ diff --git a/atu_images/person/at_icon_send_cp_requst_dialog_head.png b/atu_images/person/at_icon_send_cp_requst_dialog_head.png new file mode 100644 index 0000000..bf62a2a Binary files /dev/null and b/atu_images/person/at_icon_send_cp_requst_dialog_head.png differ diff --git a/atu_images/person/at_icon_send_cp_requst_dialog_head2.png b/atu_images/person/at_icon_send_cp_requst_dialog_head2.png new file mode 100644 index 0000000..b0a98ff Binary files /dev/null and b/atu_images/person/at_icon_send_cp_requst_dialog_head2.png differ diff --git a/atu_images/person/at_icon_send_cp_requst_ok_bg.png b/atu_images/person/at_icon_send_cp_requst_ok_bg.png new file mode 100644 index 0000000..2601f75 Binary files /dev/null and b/atu_images/person/at_icon_send_cp_requst_ok_bg.png differ diff --git a/atu_images/person/at_icon_send_cp_requst_username_bg.png b/atu_images/person/at_icon_send_cp_requst_username_bg.png new file mode 100644 index 0000000..0d8d8d7 Binary files /dev/null and b/atu_images/person/at_icon_send_cp_requst_username_bg.png differ diff --git a/atu_images/person/at_icon_shipping_agent.png b/atu_images/person/at_icon_shipping_agent.png new file mode 100644 index 0000000..d229d95 Binary files /dev/null and b/atu_images/person/at_icon_shipping_agent.png differ diff --git a/atu_images/person/at_icon_user_cp_level_value_bg.png b/atu_images/person/at_icon_user_cp_level_value_bg.png new file mode 100644 index 0000000..e9cfa68 Binary files /dev/null and b/atu_images/person/at_icon_user_cp_level_value_bg.png differ diff --git a/atu_images/person/at_icon_vistors_follow_fans_bg_man.png b/atu_images/person/at_icon_vistors_follow_fans_bg_man.png new file mode 100644 index 0000000..520b4bf Binary files /dev/null and b/atu_images/person/at_icon_vistors_follow_fans_bg_man.png differ diff --git a/atu_images/person/at_icon_vistors_follow_fans_bg_woman.png b/atu_images/person/at_icon_vistors_follow_fans_bg_woman.png new file mode 100644 index 0000000..6b128af Binary files /dev/null and b/atu_images/person/at_icon_vistors_follow_fans_bg_woman.png differ diff --git a/atu_images/room/at_icon_activity_gift_head_bg_ar.png b/atu_images/room/at_icon_activity_gift_head_bg_ar.png new file mode 100644 index 0000000..3d9e6f4 Binary files /dev/null and b/atu_images/room/at_icon_activity_gift_head_bg_ar.png differ diff --git a/atu_images/room/at_icon_activity_gift_head_bg_en.png b/atu_images/room/at_icon_activity_gift_head_bg_en.png new file mode 100644 index 0000000..60815ad Binary files /dev/null and b/atu_images/room/at_icon_activity_gift_head_bg_en.png differ diff --git a/atu_images/room/at_icon_add_user.png b/atu_images/room/at_icon_add_user.png new file mode 100644 index 0000000..f5a08e7 Binary files /dev/null and b/atu_images/room/at_icon_add_user.png differ diff --git a/atu_images/room/at_icon_all_in_the_room.png b/atu_images/room/at_icon_all_in_the_room.png new file mode 100644 index 0000000..43e9fd4 Binary files /dev/null and b/atu_images/room/at_icon_all_in_the_room.png differ diff --git a/atu_images/room/at_icon_all_on_microphone.png b/atu_images/room/at_icon_all_on_microphone.png new file mode 100644 index 0000000..a09bf54 Binary files /dev/null and b/atu_images/room/at_icon_all_on_microphone.png differ diff --git a/atu_images/room/at_icon_at_tag_user.png b/atu_images/room/at_icon_at_tag_user.png new file mode 100644 index 0000000..cac49d9 Binary files /dev/null and b/atu_images/room/at_icon_at_tag_user.png differ diff --git a/atu_images/room/at_icon_botton_game.png b/atu_images/room/at_icon_botton_game.png new file mode 100644 index 0000000..66387ac Binary files /dev/null and b/atu_images/room/at_icon_botton_game.png differ diff --git a/atu_images/room/at_icon_botton_gift.png b/atu_images/room/at_icon_botton_gift.png new file mode 100644 index 0000000..a15234c Binary files /dev/null and b/atu_images/room/at_icon_botton_gift.png differ diff --git a/atu_images/room/at_icon_botton_menu.png b/atu_images/room/at_icon_botton_menu.png new file mode 100644 index 0000000..823775f Binary files /dev/null and b/atu_images/room/at_icon_botton_menu.png differ diff --git a/atu_images/room/at_icon_botton_message.png b/atu_images/room/at_icon_botton_message.png new file mode 100644 index 0000000..5602228 Binary files /dev/null and b/atu_images/room/at_icon_botton_message.png differ diff --git a/atu_images/room/at_icon_botton_mic_close.png b/atu_images/room/at_icon_botton_mic_close.png new file mode 100644 index 0000000..1c03f02 Binary files /dev/null and b/atu_images/room/at_icon_botton_mic_close.png differ diff --git a/atu_images/room/at_icon_botton_mic_open.png b/atu_images/room/at_icon_botton_mic_open.png new file mode 100644 index 0000000..b3e6770 Binary files /dev/null and b/atu_images/room/at_icon_botton_mic_open.png differ diff --git a/atu_images/room/at_icon_config_red_bag_btn_bg.png b/atu_images/room/at_icon_config_red_bag_btn_bg.png new file mode 100644 index 0000000..ccf5ec6 Binary files /dev/null and b/atu_images/room/at_icon_config_red_bag_btn_bg.png differ diff --git a/atu_images/room/at_icon_config_red_bag_text.png b/atu_images/room/at_icon_config_red_bag_text.png new file mode 100644 index 0000000..ef0e46b Binary files /dev/null and b/atu_images/room/at_icon_config_red_bag_text.png differ diff --git a/atu_images/room/at_icon_cp_gift_floating_bg_lv1.png b/atu_images/room/at_icon_cp_gift_floating_bg_lv1.png new file mode 100644 index 0000000..56956c6 Binary files /dev/null and b/atu_images/room/at_icon_cp_gift_floating_bg_lv1.png differ diff --git a/atu_images/room/at_icon_cp_gift_floating_bg_lv2.png b/atu_images/room/at_icon_cp_gift_floating_bg_lv2.png new file mode 100644 index 0000000..a2874aa Binary files /dev/null and b/atu_images/room/at_icon_cp_gift_floating_bg_lv2.png differ diff --git a/atu_images/room/at_icon_cp_gift_floating_bg_lv3.png b/atu_images/room/at_icon_cp_gift_floating_bg_lv3.png new file mode 100644 index 0000000..6b169da Binary files /dev/null and b/atu_images/room/at_icon_cp_gift_floating_bg_lv3.png differ diff --git a/atu_images/room/at_icon_cp_gift_head_bg_ar.png b/atu_images/room/at_icon_cp_gift_head_bg_ar.png new file mode 100644 index 0000000..e641442 Binary files /dev/null and b/atu_images/room/at_icon_cp_gift_head_bg_ar.png differ diff --git a/atu_images/room/at_icon_cp_gift_head_bg_en.png b/atu_images/room/at_icon_cp_gift_head_bg_en.png new file mode 100644 index 0000000..6210977 Binary files /dev/null and b/atu_images/room/at_icon_cp_gift_head_bg_en.png differ diff --git a/atu_images/room/at_icon_cp_heart_ap.png b/atu_images/room/at_icon_cp_heart_ap.png new file mode 100644 index 0000000..23f645d Binary files /dev/null and b/atu_images/room/at_icon_cp_heart_ap.png differ diff --git a/atu_images/room/at_icon_cp_mic_anm_lv_1.svga b/atu_images/room/at_icon_cp_mic_anm_lv_1.svga new file mode 100644 index 0000000..8f4c1fa Binary files /dev/null and b/atu_images/room/at_icon_cp_mic_anm_lv_1.svga differ diff --git a/atu_images/room/at_icon_cp_mic_anm_lv_2.svga b/atu_images/room/at_icon_cp_mic_anm_lv_2.svga new file mode 100644 index 0000000..234ed75 Binary files /dev/null and b/atu_images/room/at_icon_cp_mic_anm_lv_2.svga differ diff --git a/atu_images/room/at_icon_cp_mic_anm_lv_3.svga b/atu_images/room/at_icon_cp_mic_anm_lv_3.svga new file mode 100644 index 0000000..7db53dc Binary files /dev/null and b/atu_images/room/at_icon_cp_mic_anm_lv_3.svga differ diff --git a/atu_images/room/at_icon_customized_rule.png b/atu_images/room/at_icon_customized_rule.png new file mode 100644 index 0000000..4c38c6c Binary files /dev/null and b/atu_images/room/at_icon_customized_rule.png differ diff --git a/atu_images/room/at_icon_dice_1.png b/atu_images/room/at_icon_dice_1.png new file mode 100644 index 0000000..bc49a6e Binary files /dev/null and b/atu_images/room/at_icon_dice_1.png differ diff --git a/atu_images/room/at_icon_dice_2.png b/atu_images/room/at_icon_dice_2.png new file mode 100644 index 0000000..cb2fa32 Binary files /dev/null and b/atu_images/room/at_icon_dice_2.png differ diff --git a/atu_images/room/at_icon_dice_3.png b/atu_images/room/at_icon_dice_3.png new file mode 100644 index 0000000..b7e42f3 Binary files /dev/null and b/atu_images/room/at_icon_dice_3.png differ diff --git a/atu_images/room/at_icon_dice_4.png b/atu_images/room/at_icon_dice_4.png new file mode 100644 index 0000000..43c26f7 Binary files /dev/null and b/atu_images/room/at_icon_dice_4.png differ diff --git a/atu_images/room/at_icon_dice_5.png b/atu_images/room/at_icon_dice_5.png new file mode 100644 index 0000000..3c6a133 Binary files /dev/null and b/atu_images/room/at_icon_dice_5.png differ diff --git a/atu_images/room/at_icon_dice_6.png b/atu_images/room/at_icon_dice_6.png new file mode 100644 index 0000000..f629caa Binary files /dev/null and b/atu_images/room/at_icon_dice_6.png differ diff --git a/atu_images/room/at_icon_dice_animl.webp b/atu_images/room/at_icon_dice_animl.webp new file mode 100644 index 0000000..e1cd879 Binary files /dev/null and b/atu_images/room/at_icon_dice_animl.webp differ diff --git a/atu_images/room/at_icon_dice_tag.png b/atu_images/room/at_icon_dice_tag.png new file mode 100644 index 0000000..6837627 Binary files /dev/null and b/atu_images/room/at_icon_dice_tag.png differ diff --git a/atu_images/room/at_icon_emoji.png b/atu_images/room/at_icon_emoji.png new file mode 100644 index 0000000..9e07be1 Binary files /dev/null and b/atu_images/room/at_icon_emoji.png differ diff --git a/atu_images/room/at_icon_emoji_vip1_3_bg.png b/atu_images/room/at_icon_emoji_vip1_3_bg.png new file mode 100644 index 0000000..4ae53b7 Binary files /dev/null and b/atu_images/room/at_icon_emoji_vip1_3_bg.png differ diff --git a/atu_images/room/at_icon_emoji_vip4_6_bg.png b/atu_images/room/at_icon_emoji_vip4_6_bg.png new file mode 100644 index 0000000..f6b309b Binary files /dev/null and b/atu_images/room/at_icon_emoji_vip4_6_bg.png differ diff --git a/atu_images/room/at_icon_exit_room.png b/atu_images/room/at_icon_exit_room.png new file mode 100644 index 0000000..c5cb645 Binary files /dev/null and b/atu_images/room/at_icon_exit_room.png differ diff --git a/atu_images/room/at_icon_follow_room_en.png b/atu_images/room/at_icon_follow_room_en.png new file mode 100644 index 0000000..84364d2 Binary files /dev/null and b/atu_images/room/at_icon_follow_room_en.png differ diff --git a/atu_images/room/at_icon_follow_room_un.png b/atu_images/room/at_icon_follow_room_un.png new file mode 100644 index 0000000..6658926 Binary files /dev/null and b/atu_images/room/at_icon_follow_room_un.png differ diff --git a/atu_images/room/at_icon_game_king_day_bg.png b/atu_images/room/at_icon_game_king_day_bg.png new file mode 100644 index 0000000..ddc97b4 Binary files /dev/null and b/atu_images/room/at_icon_game_king_day_bg.png differ diff --git a/atu_images/room/at_icon_game_king_week_bg.png b/atu_images/room/at_icon_game_king_week_bg.png new file mode 100644 index 0000000..9104bd0 Binary files /dev/null and b/atu_images/room/at_icon_game_king_week_bg.png differ diff --git a/atu_images/room/at_icon_gift_all_en.png b/atu_images/room/at_icon_gift_all_en.png new file mode 100644 index 0000000..286762a Binary files /dev/null and b/atu_images/room/at_icon_gift_all_en.png differ diff --git a/atu_images/room/at_icon_gift_all_no.png b/atu_images/room/at_icon_gift_all_no.png new file mode 100644 index 0000000..bcdf570 Binary files /dev/null and b/atu_images/room/at_icon_gift_all_no.png differ diff --git a/atu_images/room/at_icon_gift_cp.png b/atu_images/room/at_icon_gift_cp.png new file mode 100644 index 0000000..7daa1e7 Binary files /dev/null and b/atu_images/room/at_icon_gift_cp.png differ diff --git a/atu_images/room/at_icon_gift_effect.png b/atu_images/room/at_icon_gift_effect.png new file mode 100644 index 0000000..fdce9e3 Binary files /dev/null and b/atu_images/room/at_icon_gift_effect.png differ diff --git a/atu_images/room/at_icon_gift_float_bg.png b/atu_images/room/at_icon_gift_float_bg.png new file mode 100644 index 0000000..4c5962d Binary files /dev/null and b/atu_images/room/at_icon_gift_float_bg.png differ diff --git a/atu_images/room/at_icon_gift_heartbeat.png b/atu_images/room/at_icon_gift_heartbeat.png new file mode 100644 index 0000000..03d1b17 Binary files /dev/null and b/atu_images/room/at_icon_gift_heartbeat.png differ diff --git a/atu_images/room/at_icon_gift_luck.png b/atu_images/room/at_icon_gift_luck.png new file mode 100644 index 0000000..2f6c93b Binary files /dev/null and b/atu_images/room/at_icon_gift_luck.png differ diff --git a/atu_images/room/at_icon_gift_music.png b/atu_images/room/at_icon_gift_music.png new file mode 100644 index 0000000..8caa2b2 Binary files /dev/null and b/atu_images/room/at_icon_gift_music.png differ diff --git a/atu_images/room/at_icon_give_gift_type_bg.png b/atu_images/room/at_icon_give_gift_type_bg.png new file mode 100644 index 0000000..f49e061 Binary files /dev/null and b/atu_images/room/at_icon_give_gift_type_bg.png differ diff --git a/atu_images/room/at_icon_honor_bg.png b/atu_images/room/at_icon_honor_bg.png new file mode 100644 index 0000000..b296a31 Binary files /dev/null and b/atu_images/room/at_icon_honor_bg.png differ diff --git a/atu_images/room/at_icon_honor_content_bg.png b/atu_images/room/at_icon_honor_content_bg.png new file mode 100644 index 0000000..fd3a2c1 Binary files /dev/null and b/atu_images/room/at_icon_honor_content_bg.png differ diff --git a/atu_images/room/at_icon_inv_go_btn.png b/atu_images/room/at_icon_inv_go_btn.png new file mode 100644 index 0000000..2a23af6 Binary files /dev/null and b/atu_images/room/at_icon_inv_go_btn.png differ diff --git a/atu_images/room/at_icon_join_room_member.png b/atu_images/room/at_icon_join_room_member.png new file mode 100644 index 0000000..edd1ebe Binary files /dev/null and b/atu_images/room/at_icon_join_room_member.png differ diff --git a/atu_images/room/at_icon_k.png b/atu_images/room/at_icon_k.png new file mode 100644 index 0000000..cfa0130 Binary files /dev/null and b/atu_images/room/at_icon_k.png differ diff --git a/atu_images/room/at_icon_lab_lv1_en.png b/atu_images/room/at_icon_lab_lv1_en.png new file mode 100644 index 0000000..028dfeb Binary files /dev/null and b/atu_images/room/at_icon_lab_lv1_en.png differ diff --git a/atu_images/room/at_icon_lab_lv1_un.png b/atu_images/room/at_icon_lab_lv1_un.png new file mode 100644 index 0000000..8ff79a0 Binary files /dev/null and b/atu_images/room/at_icon_lab_lv1_un.png differ diff --git a/atu_images/room/at_icon_lab_lv2_en.png b/atu_images/room/at_icon_lab_lv2_en.png new file mode 100644 index 0000000..16ef8bc Binary files /dev/null and b/atu_images/room/at_icon_lab_lv2_en.png differ diff --git a/atu_images/room/at_icon_lab_lv2_un.png b/atu_images/room/at_icon_lab_lv2_un.png new file mode 100644 index 0000000..1e04378 Binary files /dev/null and b/atu_images/room/at_icon_lab_lv2_un.png differ diff --git a/atu_images/room/at_icon_lab_lv3_en.png b/atu_images/room/at_icon_lab_lv3_en.png new file mode 100644 index 0000000..1de870f Binary files /dev/null and b/atu_images/room/at_icon_lab_lv3_en.png differ diff --git a/atu_images/room/at_icon_lab_lv3_un.png b/atu_images/room/at_icon_lab_lv3_un.png new file mode 100644 index 0000000..e5d93d7 Binary files /dev/null and b/atu_images/room/at_icon_lab_lv3_un.png differ diff --git a/atu_images/room/at_icon_luck_fashe.webp b/atu_images/room/at_icon_luck_fashe.webp new file mode 100644 index 0000000..fc64132 Binary files /dev/null and b/atu_images/room/at_icon_luck_fashe.webp differ diff --git a/atu_images/room/at_icon_luck_gift_daiji.webp b/atu_images/room/at_icon_luck_gift_daiji.webp new file mode 100644 index 0000000..a3c4706 Binary files /dev/null and b/atu_images/room/at_icon_luck_gift_daiji.webp differ diff --git a/atu_images/room/at_icon_luck_gift_float_bg1.png b/atu_images/room/at_icon_luck_gift_float_bg1.png new file mode 100644 index 0000000..d820b28 Binary files /dev/null and b/atu_images/room/at_icon_luck_gift_float_bg1.png differ diff --git a/atu_images/room/at_icon_luck_gift_float_bg2.png b/atu_images/room/at_icon_luck_gift_float_bg2.png new file mode 100644 index 0000000..12e1765 Binary files /dev/null and b/atu_images/room/at_icon_luck_gift_float_bg2.png differ diff --git a/atu_images/room/at_icon_luck_gift_float_bg3.png b/atu_images/room/at_icon_luck_gift_float_bg3.png new file mode 100644 index 0000000..66e1984 Binary files /dev/null and b/atu_images/room/at_icon_luck_gift_float_bg3.png differ diff --git a/atu_images/room/at_icon_luck_gift_float_bg4.png b/atu_images/room/at_icon_luck_gift_float_bg4.png new file mode 100644 index 0000000..d917ae7 Binary files /dev/null and b/atu_images/room/at_icon_luck_gift_float_bg4.png differ diff --git a/atu_images/room/at_icon_luck_gift_float_bg5.png b/atu_images/room/at_icon_luck_gift_float_bg5.png new file mode 100644 index 0000000..30edf2e Binary files /dev/null and b/atu_images/room/at_icon_luck_gift_float_bg5.png differ diff --git a/atu_images/room/at_icon_luck_gift_float_n_bg.png b/atu_images/room/at_icon_luck_gift_float_n_bg.png new file mode 100644 index 0000000..f637734 Binary files /dev/null and b/atu_images/room/at_icon_luck_gift_float_n_bg.png differ diff --git a/atu_images/room/at_icon_luck_gift_msg_n_ball.png b/atu_images/room/at_icon_luck_gift_msg_n_ball.png new file mode 100644 index 0000000..0d3233b Binary files /dev/null and b/atu_images/room/at_icon_luck_gift_msg_n_ball.png differ diff --git a/atu_images/room/at_icon_luck_gift_msg_n_bg.png b/atu_images/room/at_icon_luck_gift_msg_n_bg.png new file mode 100644 index 0000000..44c4a83 Binary files /dev/null and b/atu_images/room/at_icon_luck_gift_msg_n_bg.png differ diff --git a/atu_images/room/at_icon_luck_gift_nomore.svga b/atu_images/room/at_icon_luck_gift_nomore.svga new file mode 100644 index 0000000..25df676 Binary files /dev/null and b/atu_images/room/at_icon_luck_gift_nomore.svga differ diff --git a/atu_images/room/at_icon_luck_gift_obt_coins_bg_0.png b/atu_images/room/at_icon_luck_gift_obt_coins_bg_0.png new file mode 100644 index 0000000..ca34726 Binary files /dev/null and b/atu_images/room/at_icon_luck_gift_obt_coins_bg_0.png differ diff --git a/atu_images/room/at_icon_luck_gift_obt_coins_bg_1.png b/atu_images/room/at_icon_luck_gift_obt_coins_bg_1.png new file mode 100644 index 0000000..00344e0 Binary files /dev/null and b/atu_images/room/at_icon_luck_gift_obt_coins_bg_1.png differ diff --git a/atu_images/room/at_icon_luck_gift_obt_coins_bg_2.png b/atu_images/room/at_icon_luck_gift_obt_coins_bg_2.png new file mode 100644 index 0000000..df5618a Binary files /dev/null and b/atu_images/room/at_icon_luck_gift_obt_coins_bg_2.png differ diff --git a/atu_images/room/at_icon_luck_gift_obt_coins_bg_3.png b/atu_images/room/at_icon_luck_gift_obt_coins_bg_3.png new file mode 100644 index 0000000..f05e550 Binary files /dev/null and b/atu_images/room/at_icon_luck_gift_obt_coins_bg_3.png differ diff --git a/atu_images/room/at_icon_luck_gift_obt_coins_bg_4.png b/atu_images/room/at_icon_luck_gift_obt_coins_bg_4.png new file mode 100644 index 0000000..580e1dd Binary files /dev/null and b/atu_images/room/at_icon_luck_gift_obt_coins_bg_4.png differ diff --git a/atu_images/room/at_icon_luck_gift_obt_coins_bg_5.png b/atu_images/room/at_icon_luck_gift_obt_coins_bg_5.png new file mode 100644 index 0000000..30e8f77 Binary files /dev/null and b/atu_images/room/at_icon_luck_gift_obt_coins_bg_5.png differ diff --git a/atu_images/room/at_icon_luck_num_text_ar.png b/atu_images/room/at_icon_luck_num_text_ar.png new file mode 100644 index 0000000..b71a216 Binary files /dev/null and b/atu_images/room/at_icon_luck_num_text_ar.png differ diff --git a/atu_images/room/at_icon_luck_num_text_en.png b/atu_images/room/at_icon_luck_num_text_en.png new file mode 100644 index 0000000..1bc6590 Binary files /dev/null and b/atu_images/room/at_icon_luck_num_text_en.png differ diff --git a/atu_images/room/at_icon_luck_number_tag.png b/atu_images/room/at_icon_luck_number_tag.png new file mode 100644 index 0000000..b9f0706 Binary files /dev/null and b/atu_images/room/at_icon_luck_number_tag.png differ diff --git a/atu_images/room/at_icon_luckgift_coins_anim.webp b/atu_images/room/at_icon_luckgift_coins_anim.webp new file mode 100644 index 0000000..2a51888 Binary files /dev/null and b/atu_images/room/at_icon_luckgift_coins_anim.webp differ diff --git a/atu_images/room/at_icon_lucknumber_0.png b/atu_images/room/at_icon_lucknumber_0.png new file mode 100644 index 0000000..389780e Binary files /dev/null and b/atu_images/room/at_icon_lucknumber_0.png differ diff --git a/atu_images/room/at_icon_lucknumber_1.png b/atu_images/room/at_icon_lucknumber_1.png new file mode 100644 index 0000000..4fc681d Binary files /dev/null and b/atu_images/room/at_icon_lucknumber_1.png differ diff --git a/atu_images/room/at_icon_lucknumber_2.png b/atu_images/room/at_icon_lucknumber_2.png new file mode 100644 index 0000000..d626f4a Binary files /dev/null and b/atu_images/room/at_icon_lucknumber_2.png differ diff --git a/atu_images/room/at_icon_lucknumber_3.png b/atu_images/room/at_icon_lucknumber_3.png new file mode 100644 index 0000000..8a54709 Binary files /dev/null and b/atu_images/room/at_icon_lucknumber_3.png differ diff --git a/atu_images/room/at_icon_lucknumber_4.png b/atu_images/room/at_icon_lucknumber_4.png new file mode 100644 index 0000000..23b143b Binary files /dev/null and b/atu_images/room/at_icon_lucknumber_4.png differ diff --git a/atu_images/room/at_icon_lucknumber_5.png b/atu_images/room/at_icon_lucknumber_5.png new file mode 100644 index 0000000..c8ddb17 Binary files /dev/null and b/atu_images/room/at_icon_lucknumber_5.png differ diff --git a/atu_images/room/at_icon_lucknumber_6.png b/atu_images/room/at_icon_lucknumber_6.png new file mode 100644 index 0000000..c351e46 Binary files /dev/null and b/atu_images/room/at_icon_lucknumber_6.png differ diff --git a/atu_images/room/at_icon_lucknumber_7.png b/atu_images/room/at_icon_lucknumber_7.png new file mode 100644 index 0000000..995bb25 Binary files /dev/null and b/atu_images/room/at_icon_lucknumber_7.png differ diff --git a/atu_images/room/at_icon_lucknumber_8.png b/atu_images/room/at_icon_lucknumber_8.png new file mode 100644 index 0000000..9af7dfd Binary files /dev/null and b/atu_images/room/at_icon_lucknumber_8.png differ diff --git a/atu_images/room/at_icon_lucknumber_9.png b/atu_images/room/at_icon_lucknumber_9.png new file mode 100644 index 0000000..32439cb Binary files /dev/null and b/atu_images/room/at_icon_lucknumber_9.png differ diff --git a/atu_images/room/at_icon_m.png b/atu_images/room/at_icon_m.png new file mode 100644 index 0000000..16bc591 Binary files /dev/null and b/atu_images/room/at_icon_m.png differ diff --git a/atu_images/room/at_icon_magic_gift_head_bg_ar.png b/atu_images/room/at_icon_magic_gift_head_bg_ar.png new file mode 100644 index 0000000..3e6a3f4 Binary files /dev/null and b/atu_images/room/at_icon_magic_gift_head_bg_ar.png differ diff --git a/atu_images/room/at_icon_magic_gift_head_bg_en.png b/atu_images/room/at_icon_magic_gift_head_bg_en.png new file mode 100644 index 0000000..ef1afb8 Binary files /dev/null and b/atu_images/room/at_icon_magic_gift_head_bg_en.png differ diff --git a/atu_images/room/at_icon_menu_mic_model_change.png b/atu_images/room/at_icon_menu_mic_model_change.png new file mode 100644 index 0000000..b98365d Binary files /dev/null and b/atu_images/room/at_icon_menu_mic_model_change.png differ diff --git a/atu_images/room/at_icon_mic_go_up.png b/atu_images/room/at_icon_mic_go_up.png new file mode 100644 index 0000000..a571e4c Binary files /dev/null and b/atu_images/room/at_icon_mic_go_up.png differ diff --git a/atu_images/room/at_icon_mic_leavel.png b/atu_images/room/at_icon_mic_leavel.png new file mode 100644 index 0000000..45709c2 Binary files /dev/null and b/atu_images/room/at_icon_mic_leavel.png differ diff --git a/atu_images/room/at_icon_mic_lock.png b/atu_images/room/at_icon_mic_lock.png new file mode 100644 index 0000000..44419e5 Binary files /dev/null and b/atu_images/room/at_icon_mic_lock.png differ diff --git a/atu_images/room/at_icon_mic_mute.png b/atu_images/room/at_icon_mic_mute.png new file mode 100644 index 0000000..1056e07 Binary files /dev/null and b/atu_images/room/at_icon_mic_mute.png differ diff --git a/atu_images/room/at_icon_mic_open.png b/atu_images/room/at_icon_mic_open.png new file mode 100644 index 0000000..377c342 Binary files /dev/null and b/atu_images/room/at_icon_mic_open.png differ diff --git a/atu_images/room/at_icon_mic_switch_mode.png b/atu_images/room/at_icon_mic_switch_mode.png new file mode 100644 index 0000000..4644a51 Binary files /dev/null and b/atu_images/room/at_icon_mic_switch_mode.png differ diff --git a/atu_images/room/at_icon_mic_unlock.png b/atu_images/room/at_icon_mic_unlock.png new file mode 100644 index 0000000..f12560a Binary files /dev/null and b/atu_images/room/at_icon_mic_unlock.png differ diff --git a/atu_images/room/at_icon_min_room.png b/atu_images/room/at_icon_min_room.png new file mode 100644 index 0000000..f0c7610 Binary files /dev/null and b/atu_images/room/at_icon_min_room.png differ diff --git a/atu_images/room/at_icon_music_delete.png b/atu_images/room/at_icon_music_delete.png new file mode 100644 index 0000000..bf552e8 Binary files /dev/null and b/atu_images/room/at_icon_music_delete.png differ diff --git a/atu_images/room/at_icon_music_to_up.png b/atu_images/room/at_icon_music_to_up.png new file mode 100644 index 0000000..e030b95 Binary files /dev/null and b/atu_images/room/at_icon_music_to_up.png differ diff --git a/atu_images/room/at_icon_number_0.png b/atu_images/room/at_icon_number_0.png new file mode 100644 index 0000000..8f7f38b Binary files /dev/null and b/atu_images/room/at_icon_number_0.png differ diff --git a/atu_images/room/at_icon_number_1.png b/atu_images/room/at_icon_number_1.png new file mode 100644 index 0000000..399e5de Binary files /dev/null and b/atu_images/room/at_icon_number_1.png differ diff --git a/atu_images/room/at_icon_number_2.png b/atu_images/room/at_icon_number_2.png new file mode 100644 index 0000000..dcc30e3 Binary files /dev/null and b/atu_images/room/at_icon_number_2.png differ diff --git a/atu_images/room/at_icon_number_3.png b/atu_images/room/at_icon_number_3.png new file mode 100644 index 0000000..a44e922 Binary files /dev/null and b/atu_images/room/at_icon_number_3.png differ diff --git a/atu_images/room/at_icon_number_4.png b/atu_images/room/at_icon_number_4.png new file mode 100644 index 0000000..3bd9eac Binary files /dev/null and b/atu_images/room/at_icon_number_4.png differ diff --git a/atu_images/room/at_icon_number_5.png b/atu_images/room/at_icon_number_5.png new file mode 100644 index 0000000..daf1162 Binary files /dev/null and b/atu_images/room/at_icon_number_5.png differ diff --git a/atu_images/room/at_icon_number_6.png b/atu_images/room/at_icon_number_6.png new file mode 100644 index 0000000..929ada1 Binary files /dev/null and b/atu_images/room/at_icon_number_6.png differ diff --git a/atu_images/room/at_icon_number_7.png b/atu_images/room/at_icon_number_7.png new file mode 100644 index 0000000..dfe7812 Binary files /dev/null and b/atu_images/room/at_icon_number_7.png differ diff --git a/atu_images/room/at_icon_number_8.png b/atu_images/room/at_icon_number_8.png new file mode 100644 index 0000000..b7d3d3e Binary files /dev/null and b/atu_images/room/at_icon_number_8.png differ diff --git a/atu_images/room/at_icon_number_9.png b/atu_images/room/at_icon_number_9.png new file mode 100644 index 0000000..007b8d9 Binary files /dev/null and b/atu_images/room/at_icon_number_9.png differ diff --git a/atu_images/room/at_icon_online_peple.png b/atu_images/room/at_icon_online_peple.png new file mode 100644 index 0000000..d377280 Binary files /dev/null and b/atu_images/room/at_icon_online_peple.png differ diff --git a/atu_images/room/at_icon_open_card.png b/atu_images/room/at_icon_open_card.png new file mode 100644 index 0000000..6319dd5 Binary files /dev/null and b/atu_images/room/at_icon_open_card.png differ diff --git a/atu_images/room/at_icon_red_envelope_coins_item_bg_en.png b/atu_images/room/at_icon_red_envelope_coins_item_bg_en.png new file mode 100644 index 0000000..6cd321a Binary files /dev/null and b/atu_images/room/at_icon_red_envelope_coins_item_bg_en.png differ diff --git a/atu_images/room/at_icon_red_envelope_coins_item_bg_no.png b/atu_images/room/at_icon_red_envelope_coins_item_bg_no.png new file mode 100644 index 0000000..b0b6961 Binary files /dev/null and b/atu_images/room/at_icon_red_envelope_coins_item_bg_no.png differ diff --git a/atu_images/room/at_icon_red_envelope_config_bg.png b/atu_images/room/at_icon_red_envelope_config_bg.png new file mode 100644 index 0000000..787c332 Binary files /dev/null and b/atu_images/room/at_icon_red_envelope_config_bg.png differ diff --git a/atu_images/room/at_icon_red_envelope_config_bg2.png b/atu_images/room/at_icon_red_envelope_config_bg2.png new file mode 100644 index 0000000..530f6a8 Binary files /dev/null and b/atu_images/room/at_icon_red_envelope_config_bg2.png differ diff --git a/atu_images/room/at_icon_red_envelope_config_tab_bg.png b/atu_images/room/at_icon_red_envelope_config_tab_bg.png new file mode 100644 index 0000000..d6f201c Binary files /dev/null and b/atu_images/room/at_icon_red_envelope_config_tab_bg.png differ diff --git a/atu_images/room/at_icon_red_envelope_config_tab_item_bg.png b/atu_images/room/at_icon_red_envelope_config_tab_item_bg.png new file mode 100644 index 0000000..d431032 Binary files /dev/null and b/atu_images/room/at_icon_red_envelope_config_tab_item_bg.png differ diff --git a/atu_images/room/at_icon_red_envelope_num_item_bg_en.png b/atu_images/room/at_icon_red_envelope_num_item_bg_en.png new file mode 100644 index 0000000..5d3b8e7 Binary files /dev/null and b/atu_images/room/at_icon_red_envelope_num_item_bg_en.png differ diff --git a/atu_images/room/at_icon_red_envelope_num_item_bg_no.png b/atu_images/room/at_icon_red_envelope_num_item_bg_no.png new file mode 100644 index 0000000..f0d7f52 Binary files /dev/null and b/atu_images/room/at_icon_red_envelope_num_item_bg_no.png differ diff --git a/atu_images/room/at_icon_red_envelope_rec_btn.png b/atu_images/room/at_icon_red_envelope_rec_btn.png new file mode 100644 index 0000000..dcfe95b Binary files /dev/null and b/atu_images/room/at_icon_red_envelope_rec_btn.png differ diff --git a/atu_images/room/at_icon_red_envelope_rec_item_bg.png b/atu_images/room/at_icon_red_envelope_rec_item_bg.png new file mode 100644 index 0000000..7f458d1 Binary files /dev/null and b/atu_images/room/at_icon_red_envelope_rec_item_bg.png differ diff --git a/atu_images/room/at_icon_red_envelope_rec_record_bg.png b/atu_images/room/at_icon_red_envelope_rec_record_bg.png new file mode 100644 index 0000000..a976858 Binary files /dev/null and b/atu_images/room/at_icon_red_envelope_rec_record_bg.png differ diff --git a/atu_images/room/at_icon_red_envelope_rew.png b/atu_images/room/at_icon_red_envelope_rew.png new file mode 100644 index 0000000..de508ea Binary files /dev/null and b/atu_images/room/at_icon_red_envelope_rew.png differ diff --git a/atu_images/room/at_icon_red_envelope_title.png b/atu_images/room/at_icon_red_envelope_title.png new file mode 100644 index 0000000..1163bff Binary files /dev/null and b/atu_images/room/at_icon_red_envelope_title.png differ diff --git a/atu_images/room/at_icon_redenvelope_broad_bg.webp b/atu_images/room/at_icon_redenvelope_broad_bg.webp new file mode 100644 index 0000000..182c3b9 Binary files /dev/null and b/atu_images/room/at_icon_redenvelope_broad_bg.webp differ diff --git a/atu_images/room/at_icon_redenvelope_rec_record_list_bg.png b/atu_images/room/at_icon_redenvelope_rec_record_list_bg.png new file mode 100644 index 0000000..0fed021 Binary files /dev/null and b/atu_images/room/at_icon_redenvelope_rec_record_list_bg.png differ diff --git a/atu_images/room/at_icon_remve_block.png b/atu_images/room/at_icon_remve_block.png new file mode 100644 index 0000000..f5f8757 Binary files /dev/null and b/atu_images/room/at_icon_remve_block.png differ diff --git a/atu_images/room/at_icon_room_charm.png b/atu_images/room/at_icon_room_charm.png new file mode 100644 index 0000000..d4912b9 Binary files /dev/null and b/atu_images/room/at_icon_room_charm.png differ diff --git a/atu_images/room/at_icon_room_charm_tag.png b/atu_images/room/at_icon_room_charm_tag.png new file mode 100644 index 0000000..57f5ffd Binary files /dev/null and b/atu_images/room/at_icon_room_charm_tag.png differ diff --git a/atu_images/room/at_icon_room_contribute.png b/atu_images/room/at_icon_room_contribute.png new file mode 100644 index 0000000..f3bd425 Binary files /dev/null and b/atu_images/room/at_icon_room_contribute.png differ diff --git a/atu_images/room/at_icon_room_contribute_rank1.png b/atu_images/room/at_icon_room_contribute_rank1.png new file mode 100644 index 0000000..70c9e60 Binary files /dev/null and b/atu_images/room/at_icon_room_contribute_rank1.png differ diff --git a/atu_images/room/at_icon_room_contribute_rank2.png b/atu_images/room/at_icon_room_contribute_rank2.png new file mode 100644 index 0000000..cd0aa23 Binary files /dev/null and b/atu_images/room/at_icon_room_contribute_rank2.png differ diff --git a/atu_images/room/at_icon_room_contribute_rank3.png b/atu_images/room/at_icon_room_contribute_rank3.png new file mode 100644 index 0000000..59536c1 Binary files /dev/null and b/atu_images/room/at_icon_room_contribute_rank3.png differ diff --git a/atu_images/room/at_icon_room_defaut_bg.png b/atu_images/room/at_icon_room_defaut_bg.png new file mode 100644 index 0000000..a0daaef Binary files /dev/null and b/atu_images/room/at_icon_room_defaut_bg.png differ diff --git a/atu_images/room/at_icon_room_edit.png b/atu_images/room/at_icon_room_edit.png new file mode 100644 index 0000000..68f354f Binary files /dev/null and b/atu_images/room/at_icon_room_edit.png differ diff --git a/atu_images/room/at_icon_room_edit_noti.png b/atu_images/room/at_icon_room_edit_noti.png new file mode 100644 index 0000000..60b71b9 Binary files /dev/null and b/atu_images/room/at_icon_room_edit_noti.png differ diff --git a/atu_images/room/at_icon_room_ext_min.png b/atu_images/room/at_icon_room_ext_min.png new file mode 100644 index 0000000..cb4ce22 Binary files /dev/null and b/atu_images/room/at_icon_room_ext_min.png differ diff --git a/atu_images/room/at_icon_room_follow_no.png b/atu_images/room/at_icon_room_follow_no.png new file mode 100644 index 0000000..faf34fc Binary files /dev/null and b/atu_images/room/at_icon_room_follow_no.png differ diff --git a/atu_images/room/at_icon_room_free_seat.png b/atu_images/room/at_icon_room_free_seat.png new file mode 100644 index 0000000..a190e9e Binary files /dev/null and b/atu_images/room/at_icon_room_free_seat.png differ diff --git a/atu_images/room/at_icon_room_free_sonic.png b/atu_images/room/at_icon_room_free_sonic.png new file mode 100644 index 0000000..a76c541 Binary files /dev/null and b/atu_images/room/at_icon_room_free_sonic.png differ diff --git a/atu_images/room/at_icon_room_fz.png b/atu_images/room/at_icon_room_fz.png new file mode 100644 index 0000000..c5f8501 Binary files /dev/null and b/atu_images/room/at_icon_room_fz.png differ diff --git a/atu_images/room/at_icon_room_game_close.png b/atu_images/room/at_icon_room_game_close.png new file mode 100644 index 0000000..ed1c794 Binary files /dev/null and b/atu_images/room/at_icon_room_game_close.png differ diff --git a/atu_images/room/at_icon_room_game_history_bg.png b/atu_images/room/at_icon_room_game_history_bg.png new file mode 100644 index 0000000..e2191a6 Binary files /dev/null and b/atu_images/room/at_icon_room_game_history_bg.png differ diff --git a/atu_images/room/at_icon_room_game_item_bg_v.png b/atu_images/room/at_icon_room_game_item_bg_v.png new file mode 100644 index 0000000..f4bad2e Binary files /dev/null and b/atu_images/room/at_icon_room_game_item_bg_v.png differ diff --git a/atu_images/room/at_icon_room_game_mic_close.png b/atu_images/room/at_icon_room_game_mic_close.png new file mode 100644 index 0000000..1aedb97 Binary files /dev/null and b/atu_images/room/at_icon_room_game_mic_close.png differ diff --git a/atu_images/room/at_icon_room_game_mic_open.png b/atu_images/room/at_icon_room_game_mic_open.png new file mode 100644 index 0000000..83334e2 Binary files /dev/null and b/atu_images/room/at_icon_room_game_mic_open.png differ diff --git a/atu_images/room/at_icon_room_game_min.png b/atu_images/room/at_icon_room_game_min.png new file mode 100644 index 0000000..8debb25 Binary files /dev/null and b/atu_images/room/at_icon_room_game_min.png differ diff --git a/atu_images/room/at_icon_room_gift_left_no_vip_bg.png b/atu_images/room/at_icon_room_gift_left_no_vip_bg.png new file mode 100644 index 0000000..60b3fa8 Binary files /dev/null and b/atu_images/room/at_icon_room_gift_left_no_vip_bg.png differ diff --git a/atu_images/room/at_icon_room_gift_left_vip3_bg.png b/atu_images/room/at_icon_room_gift_left_vip3_bg.png new file mode 100644 index 0000000..65a6462 Binary files /dev/null and b/atu_images/room/at_icon_room_gift_left_vip3_bg.png differ diff --git a/atu_images/room/at_icon_room_gift_left_vip4_bg.png b/atu_images/room/at_icon_room_gift_left_vip4_bg.png new file mode 100644 index 0000000..b73c4ed Binary files /dev/null and b/atu_images/room/at_icon_room_gift_left_vip4_bg.png differ diff --git a/atu_images/room/at_icon_room_gift_left_vip5_bg.png b/atu_images/room/at_icon_room_gift_left_vip5_bg.png new file mode 100644 index 0000000..130e597 Binary files /dev/null and b/atu_images/room/at_icon_room_gift_left_vip5_bg.png differ diff --git a/atu_images/room/at_icon_room_gly.png b/atu_images/room/at_icon_room_gly.png new file mode 100644 index 0000000..96e2c0d Binary files /dev/null and b/atu_images/room/at_icon_room_gly.png differ diff --git a/atu_images/room/at_icon_room_guest.png b/atu_images/room/at_icon_room_guest.png new file mode 100644 index 0000000..0272148 Binary files /dev/null and b/atu_images/room/at_icon_room_guest.png differ diff --git a/atu_images/room/at_icon_room_hy.png b/atu_images/room/at_icon_room_hy.png new file mode 100644 index 0000000..f8ed661 Binary files /dev/null and b/atu_images/room/at_icon_room_hy.png differ diff --git a/atu_images/room/at_icon_room_input.png b/atu_images/room/at_icon_room_input.png new file mode 100644 index 0000000..051f63f Binary files /dev/null and b/atu_images/room/at_icon_room_input.png differ diff --git a/atu_images/room/at_icon_room_input_t.png b/atu_images/room/at_icon_room_input_t.png new file mode 100644 index 0000000..a7cf9ee Binary files /dev/null and b/atu_images/room/at_icon_room_input_t.png differ diff --git a/atu_images/room/at_icon_room_jiesuo.png b/atu_images/room/at_icon_room_jiesuo.png new file mode 100644 index 0000000..0b1f8fc Binary files /dev/null and b/atu_images/room/at_icon_room_jiesuo.png differ diff --git a/atu_images/room/at_icon_room_luck_gift_tag_vip3_bg.png b/atu_images/room/at_icon_room_luck_gift_tag_vip3_bg.png new file mode 100644 index 0000000..451970d Binary files /dev/null and b/atu_images/room/at_icon_room_luck_gift_tag_vip3_bg.png differ diff --git a/atu_images/room/at_icon_room_luck_gift_tag_vip4_bg.png b/atu_images/room/at_icon_room_luck_gift_tag_vip4_bg.png new file mode 100644 index 0000000..a360830 Binary files /dev/null and b/atu_images/room/at_icon_room_luck_gift_tag_vip4_bg.png differ diff --git a/atu_images/room/at_icon_room_luck_gift_tag_vip5_bg.png b/atu_images/room/at_icon_room_luck_gift_tag_vip5_bg.png new file mode 100644 index 0000000..ecdd8a0 Binary files /dev/null and b/atu_images/room/at_icon_room_luck_gift_tag_vip5_bg.png differ diff --git a/atu_images/room/at_icon_room_menu_entry_vehicle_animation.png b/atu_images/room/at_icon_room_menu_entry_vehicle_animation.png new file mode 100644 index 0000000..8a99ac5 Binary files /dev/null and b/atu_images/room/at_icon_room_menu_entry_vehicle_animation.png differ diff --git a/atu_images/room/at_icon_room_menu_float_win.png b/atu_images/room/at_icon_room_menu_float_win.png new file mode 100644 index 0000000..36b8ad6 Binary files /dev/null and b/atu_images/room/at_icon_room_menu_float_win.png differ diff --git a/atu_images/room/at_icon_room_menu_gift_effect.png b/atu_images/room/at_icon_room_menu_gift_effect.png new file mode 100644 index 0000000..b6f1dab Binary files /dev/null and b/atu_images/room/at_icon_room_menu_gift_effect.png differ diff --git a/atu_images/room/at_icon_room_menu_gift_vibration.png b/atu_images/room/at_icon_room_menu_gift_vibration.png new file mode 100644 index 0000000..ad7530b Binary files /dev/null and b/atu_images/room/at_icon_room_menu_gift_vibration.png differ diff --git a/atu_images/room/at_icon_room_menu_shop.png b/atu_images/room/at_icon_room_menu_shop.png new file mode 100644 index 0000000..267c619 Binary files /dev/null and b/atu_images/room/at_icon_room_menu_shop.png differ diff --git a/atu_images/room/at_icon_room_message_send.png b/atu_images/room/at_icon_room_message_send.png new file mode 100644 index 0000000..48e553e Binary files /dev/null and b/atu_images/room/at_icon_room_message_send.png differ diff --git a/atu_images/room/at_icon_room_mic_model_5.png b/atu_images/room/at_icon_room_mic_model_5.png new file mode 100644 index 0000000..45e359b Binary files /dev/null and b/atu_images/room/at_icon_room_mic_model_5.png differ diff --git a/atu_images/room/at_icon_room_msg_clear.png b/atu_images/room/at_icon_room_msg_clear.png new file mode 100644 index 0000000..465f998 Binary files /dev/null and b/atu_images/room/at_icon_room_msg_clear.png differ diff --git a/atu_images/room/at_icon_room_msg_pic.png b/atu_images/room/at_icon_room_msg_pic.png new file mode 100644 index 0000000..e631e9b Binary files /dev/null and b/atu_images/room/at_icon_room_msg_pic.png differ diff --git a/atu_images/room/at_icon_room_music.png b/atu_images/room/at_icon_room_music.png new file mode 100644 index 0000000..dcaaa23 Binary files /dev/null and b/atu_images/room/at_icon_room_music.png differ diff --git a/atu_images/room/at_icon_room_music_add.png b/atu_images/room/at_icon_room_music_add.png new file mode 100644 index 0000000..d155b3a Binary files /dev/null and b/atu_images/room/at_icon_room_music_add.png differ diff --git a/atu_images/room/at_icon_room_music_empty.png b/atu_images/room/at_icon_room_music_empty.png new file mode 100644 index 0000000..7953872 Binary files /dev/null and b/atu_images/room/at_icon_room_music_empty.png differ diff --git a/atu_images/room/at_icon_room_music_select.png b/atu_images/room/at_icon_room_music_select.png new file mode 100644 index 0000000..c9638c6 Binary files /dev/null and b/atu_images/room/at_icon_room_music_select.png differ diff --git a/atu_images/room/at_icon_room_music_tag.png b/atu_images/room/at_icon_room_music_tag.png new file mode 100644 index 0000000..b3f7dfc Binary files /dev/null and b/atu_images/room/at_icon_room_music_tag.png differ diff --git a/atu_images/room/at_icon_room_redpack_tag.png b/atu_images/room/at_icon_room_redpack_tag.png new file mode 100644 index 0000000..d5aca85 Binary files /dev/null and b/atu_images/room/at_icon_room_redpack_tag.png differ diff --git a/atu_images/room/at_icon_room_report.png b/atu_images/room/at_icon_room_report.png new file mode 100644 index 0000000..1902231 Binary files /dev/null and b/atu_images/room/at_icon_room_report.png differ diff --git a/atu_images/room/at_icon_room_reward_bg.png b/atu_images/room/at_icon_room_reward_bg.png new file mode 100644 index 0000000..7b2ea5e Binary files /dev/null and b/atu_images/room/at_icon_room_reward_bg.png differ diff --git a/atu_images/room/at_icon_room_reward_btn_en.png b/atu_images/room/at_icon_room_reward_btn_en.png new file mode 100644 index 0000000..dbe4872 Binary files /dev/null and b/atu_images/room/at_icon_room_reward_btn_en.png differ diff --git a/atu_images/room/at_icon_room_reward_btn_no.png b/atu_images/room/at_icon_room_reward_btn_no.png new file mode 100644 index 0000000..8d471f0 Binary files /dev/null and b/atu_images/room/at_icon_room_reward_btn_no.png differ diff --git a/atu_images/room/at_icon_room_reward_content_bg.png b/atu_images/room/at_icon_room_reward_content_bg.png new file mode 100644 index 0000000..aa3c7ef Binary files /dev/null and b/atu_images/room/at_icon_room_reward_content_bg.png differ diff --git a/atu_images/room/at_icon_room_reward_countdown_item_bg.png b/atu_images/room/at_icon_room_reward_countdown_item_bg.png new file mode 100644 index 0000000..5a7c6b4 Binary files /dev/null and b/atu_images/room/at_icon_room_reward_countdown_item_bg.png differ diff --git a/atu_images/room/at_icon_room_reward_gb.png b/atu_images/room/at_icon_room_reward_gb.png new file mode 100644 index 0000000..094b6dd Binary files /dev/null and b/atu_images/room/at_icon_room_reward_gb.png differ diff --git a/atu_images/room/at_icon_room_reward_help.png b/atu_images/room/at_icon_room_reward_help.png new file mode 100644 index 0000000..70fd475 Binary files /dev/null and b/atu_images/room/at_icon_room_reward_help.png differ diff --git a/atu_images/room/at_icon_room_reward_rule_content_ar.png b/atu_images/room/at_icon_room_reward_rule_content_ar.png new file mode 100644 index 0000000..a076666 Binary files /dev/null and b/atu_images/room/at_icon_room_reward_rule_content_ar.png differ diff --git a/atu_images/room/at_icon_room_reward_rule_content_en.png b/atu_images/room/at_icon_room_reward_rule_content_en.png new file mode 100644 index 0000000..f08e90e Binary files /dev/null and b/atu_images/room/at_icon_room_reward_rule_content_en.png differ diff --git a/atu_images/room/at_icon_room_reward_rule_title_ar.png b/atu_images/room/at_icon_room_reward_rule_title_ar.png new file mode 100644 index 0000000..8c126c6 Binary files /dev/null and b/atu_images/room/at_icon_room_reward_rule_title_ar.png differ diff --git a/atu_images/room/at_icon_room_reward_rule_title_en.png b/atu_images/room/at_icon_room_reward_rule_title_en.png new file mode 100644 index 0000000..911ed03 Binary files /dev/null and b/atu_images/room/at_icon_room_reward_rule_title_en.png differ diff --git a/atu_images/room/at_icon_room_reward_title.png b/atu_images/room/at_icon_room_reward_title.png new file mode 100644 index 0000000..88e2dff Binary files /dev/null and b/atu_images/room/at_icon_room_reward_title.png differ diff --git a/atu_images/room/at_icon_room_rocket_bg.png b/atu_images/room/at_icon_room_rocket_bg.png new file mode 100644 index 0000000..4b59457 Binary files /dev/null and b/atu_images/room/at_icon_room_rocket_bg.png differ diff --git a/atu_images/room/at_icon_room_rocket_broadcast_anim.webp b/atu_images/room/at_icon_room_rocket_broadcast_anim.webp new file mode 100644 index 0000000..20c060f Binary files /dev/null and b/atu_images/room/at_icon_room_rocket_broadcast_anim.webp differ diff --git a/atu_images/room/at_icon_room_rocket_help.png b/atu_images/room/at_icon_room_rocket_help.png new file mode 100644 index 0000000..d4d452e Binary files /dev/null and b/atu_images/room/at_icon_room_rocket_help.png differ diff --git a/atu_images/room/at_icon_room_rocket_hepl_bg.png b/atu_images/room/at_icon_room_rocket_hepl_bg.png new file mode 100644 index 0000000..52a7d10 Binary files /dev/null and b/atu_images/room/at_icon_room_rocket_hepl_bg.png differ diff --git a/atu_images/room/at_icon_room_rocket_lv1.png b/atu_images/room/at_icon_room_rocket_lv1.png new file mode 100644 index 0000000..74b62d0 Binary files /dev/null and b/atu_images/room/at_icon_room_rocket_lv1.png differ diff --git a/atu_images/room/at_icon_room_rocket_lv1_text.png b/atu_images/room/at_icon_room_rocket_lv1_text.png new file mode 100644 index 0000000..0876001 Binary files /dev/null and b/atu_images/room/at_icon_room_rocket_lv1_text.png differ diff --git a/atu_images/room/at_icon_room_rocket_lv2.png b/atu_images/room/at_icon_room_rocket_lv2.png new file mode 100644 index 0000000..8c769e7 Binary files /dev/null and b/atu_images/room/at_icon_room_rocket_lv2.png differ diff --git a/atu_images/room/at_icon_room_rocket_lv2_text.png b/atu_images/room/at_icon_room_rocket_lv2_text.png new file mode 100644 index 0000000..c6dac34 Binary files /dev/null and b/atu_images/room/at_icon_room_rocket_lv2_text.png differ diff --git a/atu_images/room/at_icon_room_rocket_lv3.png b/atu_images/room/at_icon_room_rocket_lv3.png new file mode 100644 index 0000000..bdd4c99 Binary files /dev/null and b/atu_images/room/at_icon_room_rocket_lv3.png differ diff --git a/atu_images/room/at_icon_room_rocket_lv3_text.png b/atu_images/room/at_icon_room_rocket_lv3_text.png new file mode 100644 index 0000000..0b2e54d Binary files /dev/null and b/atu_images/room/at_icon_room_rocket_lv3_text.png differ diff --git a/atu_images/room/at_icon_room_rocket_progress_lv1_tag.png b/atu_images/room/at_icon_room_rocket_progress_lv1_tag.png new file mode 100644 index 0000000..0e397b3 Binary files /dev/null and b/atu_images/room/at_icon_room_rocket_progress_lv1_tag.png differ diff --git a/atu_images/room/at_icon_room_rocket_progress_lv2_tag.png b/atu_images/room/at_icon_room_rocket_progress_lv2_tag.png new file mode 100644 index 0000000..a25d69d Binary files /dev/null and b/atu_images/room/at_icon_room_rocket_progress_lv2_tag.png differ diff --git a/atu_images/room/at_icon_room_rocket_progress_lv3_tag.png b/atu_images/room/at_icon_room_rocket_progress_lv3_tag.png new file mode 100644 index 0000000..b795b90 Binary files /dev/null and b/atu_images/room/at_icon_room_rocket_progress_lv3_tag.png differ diff --git a/atu_images/room/at_icon_room_rocket_rew_bg.png b/atu_images/room/at_icon_room_rocket_rew_bg.png new file mode 100644 index 0000000..72c211c Binary files /dev/null and b/atu_images/room/at_icon_room_rocket_rew_bg.png differ diff --git a/atu_images/room/at_icon_room_rocket_rew_item_bg.png b/atu_images/room/at_icon_room_rocket_rew_item_bg.png new file mode 100644 index 0000000..bcd1311 Binary files /dev/null and b/atu_images/room/at_icon_room_rocket_rew_item_bg.png differ diff --git a/atu_images/room/at_icon_room_rocket_rule_text_ar.png b/atu_images/room/at_icon_room_rocket_rule_text_ar.png new file mode 100644 index 0000000..367e746 Binary files /dev/null and b/atu_images/room/at_icon_room_rocket_rule_text_ar.png differ diff --git a/atu_images/room/at_icon_room_rocket_rule_text_en.png b/atu_images/room/at_icon_room_rocket_rule_text_en.png new file mode 100644 index 0000000..ff408f9 Binary files /dev/null and b/atu_images/room/at_icon_room_rocket_rule_text_en.png differ diff --git a/atu_images/room/at_icon_room_rocket_title_bg.png b/atu_images/room/at_icon_room_rocket_title_bg.png new file mode 100644 index 0000000..09dd61d Binary files /dev/null and b/atu_images/room/at_icon_room_rocket_title_bg.png differ diff --git a/atu_images/room/at_icon_room_seat_mic_mute.png b/atu_images/room/at_icon_room_seat_mic_mute.png new file mode 100644 index 0000000..b7ee450 Binary files /dev/null and b/atu_images/room/at_icon_room_seat_mic_mute.png differ diff --git a/atu_images/room/at_icon_room_settig_bg.png b/atu_images/room/at_icon_room_settig_bg.png new file mode 100644 index 0000000..18cf68b Binary files /dev/null and b/atu_images/room/at_icon_room_settig_bg.png differ diff --git a/atu_images/room/at_icon_room_special_effects.png b/atu_images/room/at_icon_room_special_effects.png new file mode 100644 index 0000000..1913337 Binary files /dev/null and b/atu_images/room/at_icon_room_special_effects.png differ diff --git a/atu_images/room/at_icon_room_suo.png b/atu_images/room/at_icon_room_suo.png new file mode 100644 index 0000000..c6176eb Binary files /dev/null and b/atu_images/room/at_icon_room_suo.png differ diff --git a/atu_images/room/at_icon_room_switch_mic_model_check.png b/atu_images/room/at_icon_room_switch_mic_model_check.png new file mode 100644 index 0000000..45fdd01 Binary files /dev/null and b/atu_images/room/at_icon_room_switch_mic_model_check.png differ diff --git a/atu_images/room/at_icon_room_task.png b/atu_images/room/at_icon_room_task.png new file mode 100644 index 0000000..dddebe7 Binary files /dev/null and b/atu_images/room/at_icon_room_task.png differ diff --git a/atu_images/room/at_icon_room_task_bg.png b/atu_images/room/at_icon_room_task_bg.png new file mode 100644 index 0000000..b27b2a4 Binary files /dev/null and b/atu_images/room/at_icon_room_task_bg.png differ diff --git a/atu_images/room/at_icon_room_task_bg_com.png b/atu_images/room/at_icon_room_task_bg_com.png new file mode 100644 index 0000000..efefc1d Binary files /dev/null and b/atu_images/room/at_icon_room_task_bg_com.png differ diff --git a/atu_images/room/at_icon_room_task_list_item_act_btn.png b/atu_images/room/at_icon_room_task_list_item_act_btn.png new file mode 100644 index 0000000..ddb51ea Binary files /dev/null and b/atu_images/room/at_icon_room_task_list_item_act_btn.png differ diff --git a/atu_images/room/at_icon_room_task_list_item_bg.png b/atu_images/room/at_icon_room_task_list_item_bg.png new file mode 100644 index 0000000..a4b6209 Binary files /dev/null and b/atu_images/room/at_icon_room_task_list_item_bg.png differ diff --git a/atu_images/room/at_icon_room_task_list_item_complete_btn.png b/atu_images/room/at_icon_room_task_list_item_complete_btn.png new file mode 100644 index 0000000..7ad2718 Binary files /dev/null and b/atu_images/room/at_icon_room_task_list_item_complete_btn.png differ diff --git a/atu_images/room/at_icon_room_task_list_item_go_btn.png b/atu_images/room/at_icon_room_task_list_item_go_btn.png new file mode 100644 index 0000000..759158b Binary files /dev/null and b/atu_images/room/at_icon_room_task_list_item_go_btn.png differ diff --git a/atu_images/room/at_icon_room_task_rule_bg.png b/atu_images/room/at_icon_room_task_rule_bg.png new file mode 100644 index 0000000..82d48df Binary files /dev/null and b/atu_images/room/at_icon_room_task_rule_bg.png differ diff --git a/atu_images/room/at_icon_room_task_tag.png b/atu_images/room/at_icon_room_task_tag.png new file mode 100644 index 0000000..db3bae6 Binary files /dev/null and b/atu_images/room/at_icon_room_task_tag.png differ diff --git a/atu_images/room/at_icon_room_theme.png b/atu_images/room/at_icon_room_theme.png new file mode 100644 index 0000000..69447c7 Binary files /dev/null and b/atu_images/room/at_icon_room_theme.png differ diff --git a/atu_images/room/at_icon_room_user_card_setting.png b/atu_images/room/at_icon_room_user_card_setting.png new file mode 100644 index 0000000..a0bfbb4 Binary files /dev/null and b/atu_images/room/at_icon_room_user_card_setting.png differ diff --git a/atu_images/room/at_icon_room_vip1_seat.png b/atu_images/room/at_icon_room_vip1_seat.png new file mode 100644 index 0000000..0da46c9 Binary files /dev/null and b/atu_images/room/at_icon_room_vip1_seat.png differ diff --git a/atu_images/room/at_icon_room_vip2_seat.png b/atu_images/room/at_icon_room_vip2_seat.png new file mode 100644 index 0000000..f2dbf52 Binary files /dev/null and b/atu_images/room/at_icon_room_vip2_seat.png differ diff --git a/atu_images/room/at_icon_room_vip3_seat.png b/atu_images/room/at_icon_room_vip3_seat.png new file mode 100644 index 0000000..83cd179 Binary files /dev/null and b/atu_images/room/at_icon_room_vip3_seat.png differ diff --git a/atu_images/room/at_icon_room_vip3_sonic.png b/atu_images/room/at_icon_room_vip3_sonic.png new file mode 100644 index 0000000..c6303e3 Binary files /dev/null and b/atu_images/room/at_icon_room_vip3_sonic.png differ diff --git a/atu_images/room/at_icon_room_vip3_sonic_anim.webp b/atu_images/room/at_icon_room_vip3_sonic_anim.webp new file mode 100644 index 0000000..3359e16 Binary files /dev/null and b/atu_images/room/at_icon_room_vip3_sonic_anim.webp differ diff --git a/atu_images/room/at_icon_room_vip4_seat.png b/atu_images/room/at_icon_room_vip4_seat.png new file mode 100644 index 0000000..51fa947 Binary files /dev/null and b/atu_images/room/at_icon_room_vip4_seat.png differ diff --git a/atu_images/room/at_icon_room_vip4_sonic.png b/atu_images/room/at_icon_room_vip4_sonic.png new file mode 100644 index 0000000..f91c90e Binary files /dev/null and b/atu_images/room/at_icon_room_vip4_sonic.png differ diff --git a/atu_images/room/at_icon_room_vip4_sonic_anim.webp b/atu_images/room/at_icon_room_vip4_sonic_anim.webp new file mode 100644 index 0000000..1097c5a Binary files /dev/null and b/atu_images/room/at_icon_room_vip4_sonic_anim.webp differ diff --git a/atu_images/room/at_icon_room_vip5_seat.png b/atu_images/room/at_icon_room_vip5_seat.png new file mode 100644 index 0000000..dc8ea56 Binary files /dev/null and b/atu_images/room/at_icon_room_vip5_seat.png differ diff --git a/atu_images/room/at_icon_room_vip5_sonic.png b/atu_images/room/at_icon_room_vip5_sonic.png new file mode 100644 index 0000000..179444c Binary files /dev/null and b/atu_images/room/at_icon_room_vip5_sonic.png differ diff --git a/atu_images/room/at_icon_room_vip5_sonic_anim.webp b/atu_images/room/at_icon_room_vip5_sonic_anim.webp new file mode 100644 index 0000000..1eb68f9 Binary files /dev/null and b/atu_images/room/at_icon_room_vip5_sonic_anim.webp differ diff --git a/atu_images/room/at_icon_room_vip6_seat.png b/atu_images/room/at_icon_room_vip6_seat.png new file mode 100644 index 0000000..241de41 Binary files /dev/null and b/atu_images/room/at_icon_room_vip6_seat.png differ diff --git a/atu_images/room/at_icon_room_vip6_sonic.png b/atu_images/room/at_icon_room_vip6_sonic.png new file mode 100644 index 0000000..2847983 Binary files /dev/null and b/atu_images/room/at_icon_room_vip6_sonic.png differ diff --git a/atu_images/room/at_icon_room_vip6_sonic_anim.webp b/atu_images/room/at_icon_room_vip6_sonic_anim.webp new file mode 100644 index 0000000..c9073da Binary files /dev/null and b/atu_images/room/at_icon_room_vip6_sonic_anim.webp differ diff --git a/atu_images/room/at_icon_roomgift_rank_back_bg.png b/atu_images/room/at_icon_roomgift_rank_back_bg.png new file mode 100644 index 0000000..b65714d Binary files /dev/null and b/atu_images/room/at_icon_roomgift_rank_back_bg.png differ diff --git a/atu_images/room/at_icon_roomgift_rank_title_bg.png b/atu_images/room/at_icon_roomgift_rank_title_bg.png new file mode 100644 index 0000000..4eea2de Binary files /dev/null and b/atu_images/room/at_icon_roomgift_rank_title_bg.png differ diff --git a/atu_images/room/at_icon_roomgift_rule_back_bg.png b/atu_images/room/at_icon_roomgift_rule_back_bg.png new file mode 100644 index 0000000..9a665d1 Binary files /dev/null and b/atu_images/room/at_icon_roomgift_rule_back_bg.png differ diff --git a/atu_images/room/at_icon_rps_1.png b/atu_images/room/at_icon_rps_1.png new file mode 100644 index 0000000..2a2f662 Binary files /dev/null and b/atu_images/room/at_icon_rps_1.png differ diff --git a/atu_images/room/at_icon_rps_2.png b/atu_images/room/at_icon_rps_2.png new file mode 100644 index 0000000..0b34223 Binary files /dev/null and b/atu_images/room/at_icon_rps_2.png differ diff --git a/atu_images/room/at_icon_rps_3.png b/atu_images/room/at_icon_rps_3.png new file mode 100644 index 0000000..97bdfac Binary files /dev/null and b/atu_images/room/at_icon_rps_3.png differ diff --git a/atu_images/room/at_icon_rps_animal.webp b/atu_images/room/at_icon_rps_animal.webp new file mode 100644 index 0000000..ff1e2b6 Binary files /dev/null and b/atu_images/room/at_icon_rps_animal.webp differ diff --git a/atu_images/room/at_icon_rps_tag.png b/atu_images/room/at_icon_rps_tag.png new file mode 100644 index 0000000..d05b2c7 Binary files /dev/null and b/atu_images/room/at_icon_rps_tag.png differ diff --git a/atu_images/room/at_icon_seat_lock.png b/atu_images/room/at_icon_seat_lock.png new file mode 100644 index 0000000..82512a8 Binary files /dev/null and b/atu_images/room/at_icon_seat_lock.png differ diff --git a/atu_images/room/at_icon_seat_lock_vip1.png b/atu_images/room/at_icon_seat_lock_vip1.png new file mode 100644 index 0000000..d31b54f Binary files /dev/null and b/atu_images/room/at_icon_seat_lock_vip1.png differ diff --git a/atu_images/room/at_icon_seat_lock_vip2.png b/atu_images/room/at_icon_seat_lock_vip2.png new file mode 100644 index 0000000..bbec449 Binary files /dev/null and b/atu_images/room/at_icon_seat_lock_vip2.png differ diff --git a/atu_images/room/at_icon_seat_lock_vip3.png b/atu_images/room/at_icon_seat_lock_vip3.png new file mode 100644 index 0000000..6a1ccfc Binary files /dev/null and b/atu_images/room/at_icon_seat_lock_vip3.png differ diff --git a/atu_images/room/at_icon_seat_lock_vip4.png b/atu_images/room/at_icon_seat_lock_vip4.png new file mode 100644 index 0000000..3b81c0e Binary files /dev/null and b/atu_images/room/at_icon_seat_lock_vip4.png differ diff --git a/atu_images/room/at_icon_seat_lock_vip5.png b/atu_images/room/at_icon_seat_lock_vip5.png new file mode 100644 index 0000000..9ba53ba Binary files /dev/null and b/atu_images/room/at_icon_seat_lock_vip5.png differ diff --git a/atu_images/room/at_icon_seat_lock_vip6.png b/atu_images/room/at_icon_seat_lock_vip6.png new file mode 100644 index 0000000..c0a35c1 Binary files /dev/null and b/atu_images/room/at_icon_seat_lock_vip6.png differ diff --git a/atu_images/room/at_icon_seat_num_bg.png b/atu_images/room/at_icon_seat_num_bg.png new file mode 100644 index 0000000..1ddd935 Binary files /dev/null and b/atu_images/room/at_icon_seat_num_bg.png differ diff --git a/atu_images/room/at_icon_seat_open.png b/atu_images/room/at_icon_seat_open.png new file mode 100644 index 0000000..e3ef7c6 Binary files /dev/null and b/atu_images/room/at_icon_seat_open.png differ diff --git a/atu_images/room/at_icon_send_user_gift.png b/atu_images/room/at_icon_send_user_gift.png new file mode 100644 index 0000000..79072a5 Binary files /dev/null and b/atu_images/room/at_icon_send_user_gift.png differ diff --git a/atu_images/room/at_icon_send_user_message.png b/atu_images/room/at_icon_send_user_message.png new file mode 100644 index 0000000..c27900b Binary files /dev/null and b/atu_images/room/at_icon_send_user_message.png differ diff --git a/atu_images/room/at_icon_sup_fans_bg.png b/atu_images/room/at_icon_sup_fans_bg.png new file mode 100644 index 0000000..8fc1721 Binary files /dev/null and b/atu_images/room/at_icon_sup_fans_bg.png differ diff --git a/atu_images/room/at_icon_task_item_select_bg.png b/atu_images/room/at_icon_task_item_select_bg.png new file mode 100644 index 0000000..c904ca4 Binary files /dev/null and b/atu_images/room/at_icon_task_item_select_bg.png differ diff --git a/atu_images/room/at_icon_task_item_unselect_bg.png b/atu_images/room/at_icon_task_item_unselect_bg.png new file mode 100644 index 0000000..cfebd88 Binary files /dev/null and b/atu_images/room/at_icon_task_item_unselect_bg.png differ diff --git a/atu_images/room/at_icon_times_text_ar.png b/atu_images/room/at_icon_times_text_ar.png new file mode 100644 index 0000000..7306b6d Binary files /dev/null and b/atu_images/room/at_icon_times_text_ar.png differ diff --git a/atu_images/room/at_icon_times_text_en.png b/atu_images/room/at_icon_times_text_en.png new file mode 100644 index 0000000..3d6e821 Binary files /dev/null and b/atu_images/room/at_icon_times_text_en.png differ diff --git a/atu_images/room/at_icon_user_card_copy_id.png b/atu_images/room/at_icon_user_card_copy_id.png new file mode 100644 index 0000000..a5a831d Binary files /dev/null and b/atu_images/room/at_icon_user_card_copy_id.png differ diff --git a/atu_images/room/at_icon_user_card_report.png b/atu_images/room/at_icon_user_card_report.png new file mode 100644 index 0000000..943a7a2 Binary files /dev/null and b/atu_images/room/at_icon_user_card_report.png differ diff --git a/atu_images/room/at_icon_user_card_vip0_bg.png b/atu_images/room/at_icon_user_card_vip0_bg.png new file mode 100644 index 0000000..076c058 Binary files /dev/null and b/atu_images/room/at_icon_user_card_vip0_bg.png differ diff --git a/atu_images/room/at_icon_user_card_vip1_bg.png b/atu_images/room/at_icon_user_card_vip1_bg.png new file mode 100644 index 0000000..abd5fd3 Binary files /dev/null and b/atu_images/room/at_icon_user_card_vip1_bg.png differ diff --git a/atu_images/room/at_icon_user_card_vip2_bg.png b/atu_images/room/at_icon_user_card_vip2_bg.png new file mode 100644 index 0000000..9595f61 Binary files /dev/null and b/atu_images/room/at_icon_user_card_vip2_bg.png differ diff --git a/atu_images/room/at_icon_user_card_vip3_bg.png b/atu_images/room/at_icon_user_card_vip3_bg.png new file mode 100644 index 0000000..1bc593c Binary files /dev/null and b/atu_images/room/at_icon_user_card_vip3_bg.png differ diff --git a/atu_images/room/at_icon_user_card_vip4_bg.png b/atu_images/room/at_icon_user_card_vip4_bg.png new file mode 100644 index 0000000..cb9ce2d Binary files /dev/null and b/atu_images/room/at_icon_user_card_vip4_bg.png differ diff --git a/atu_images/room/at_icon_user_card_vip5_bg.png b/atu_images/room/at_icon_user_card_vip5_bg.png new file mode 100644 index 0000000..cdeebb0 Binary files /dev/null and b/atu_images/room/at_icon_user_card_vip5_bg.png differ diff --git a/atu_images/room/at_icon_user_count_guard.png b/atu_images/room/at_icon_user_count_guard.png new file mode 100644 index 0000000..933a7df Binary files /dev/null and b/atu_images/room/at_icon_user_count_guard.png differ diff --git a/atu_images/room/at_icon_user_count_guard1.png b/atu_images/room/at_icon_user_count_guard1.png new file mode 100644 index 0000000..0614ef9 Binary files /dev/null and b/atu_images/room/at_icon_user_count_guard1.png differ diff --git a/atu_images/room/at_icon_user_count_guard1_user.png b/atu_images/room/at_icon_user_count_guard1_user.png new file mode 100644 index 0000000..c0633da Binary files /dev/null and b/atu_images/room/at_icon_user_count_guard1_user.png differ diff --git a/atu_images/room/at_icon_user_count_guard2.png b/atu_images/room/at_icon_user_count_guard2.png new file mode 100644 index 0000000..478c7f6 Binary files /dev/null and b/atu_images/room/at_icon_user_count_guard2.png differ diff --git a/atu_images/room/at_icon_user_count_guard2_user.png b/atu_images/room/at_icon_user_count_guard2_user.png new file mode 100644 index 0000000..49d0e86 Binary files /dev/null and b/atu_images/room/at_icon_user_count_guard2_user.png differ diff --git a/atu_images/room/at_icon_user_count_guard3.png b/atu_images/room/at_icon_user_count_guard3.png new file mode 100644 index 0000000..82f45b5 Binary files /dev/null and b/atu_images/room/at_icon_user_count_guard3.png differ diff --git a/atu_images/room/at_icon_user_count_guard3_user.png b/atu_images/room/at_icon_user_count_guard3_user.png new file mode 100644 index 0000000..cf003fd Binary files /dev/null and b/atu_images/room/at_icon_user_count_guard3_user.png differ diff --git a/atu_images/room/at_icon_user_follow.png b/atu_images/room/at_icon_user_follow.png new file mode 100644 index 0000000..b4caa80 Binary files /dev/null and b/atu_images/room/at_icon_user_follow.png differ diff --git a/atu_images/room/at_icon_user_un_follow.png b/atu_images/room/at_icon_user_un_follow.png new file mode 100644 index 0000000..c872eff Binary files /dev/null and b/atu_images/room/at_icon_user_un_follow.png differ diff --git a/atu_images/room/at_icon_userson_microphone.png b/atu_images/room/at_icon_userson_microphone.png new file mode 100644 index 0000000..091fe4b Binary files /dev/null and b/atu_images/room/at_icon_userson_microphone.png differ diff --git a/atu_images/room/at_icon_x.png b/atu_images/room/at_icon_x.png new file mode 100644 index 0000000..004cec1 Binary files /dev/null and b/atu_images/room/at_icon_x.png differ diff --git a/atu_images/room/atu_icon_room_sound_open.png b/atu_images/room/atu_icon_room_sound_open.png new file mode 100644 index 0000000..c035f7e Binary files /dev/null and b/atu_images/room/atu_icon_room_sound_open.png differ diff --git a/atu_images/room/entrance/at_icon_room_entrance_no_vip_bg.png b/atu_images/room/entrance/at_icon_room_entrance_no_vip_bg.png new file mode 100644 index 0000000..9bc0c29 Binary files /dev/null and b/atu_images/room/entrance/at_icon_room_entrance_no_vip_bg.png differ diff --git a/atu_images/room/entrance/at_icon_room_entrance_vip1_bg.png b/atu_images/room/entrance/at_icon_room_entrance_vip1_bg.png new file mode 100644 index 0000000..c0ab9ea Binary files /dev/null and b/atu_images/room/entrance/at_icon_room_entrance_vip1_bg.png differ diff --git a/atu_images/room/entrance/at_icon_room_entrance_vip2_bg.png b/atu_images/room/entrance/at_icon_room_entrance_vip2_bg.png new file mode 100644 index 0000000..16dc150 Binary files /dev/null and b/atu_images/room/entrance/at_icon_room_entrance_vip2_bg.png differ diff --git a/atu_images/room/entrance/at_icon_room_entrance_vip3_bg.png b/atu_images/room/entrance/at_icon_room_entrance_vip3_bg.png new file mode 100644 index 0000000..93d19d3 Binary files /dev/null and b/atu_images/room/entrance/at_icon_room_entrance_vip3_bg.png differ diff --git a/atu_images/room/entrance/at_icon_room_entrance_vip4_bg.png b/atu_images/room/entrance/at_icon_room_entrance_vip4_bg.png new file mode 100644 index 0000000..aaccfca Binary files /dev/null and b/atu_images/room/entrance/at_icon_room_entrance_vip4_bg.png differ diff --git a/atu_images/room/entrance/at_icon_room_entrance_vip5_bg.png b/atu_images/room/entrance/at_icon_room_entrance_vip5_bg.png new file mode 100644 index 0000000..533b8a7 Binary files /dev/null and b/atu_images/room/entrance/at_icon_room_entrance_vip5_bg.png differ diff --git a/atu_images/splash/at_icon_splash_icon.png b/atu_images/splash/at_icon_splash_icon.png new file mode 100644 index 0000000..f3f9ca1 Binary files /dev/null and b/atu_images/splash/at_icon_splash_icon.png differ diff --git a/atu_images/splash/at_icon_splash_skip_bg.png b/atu_images/splash/at_icon_splash_skip_bg.png new file mode 100644 index 0000000..73fba16 Binary files /dev/null and b/atu_images/splash/at_icon_splash_skip_bg.png differ diff --git a/atu_images/splash/at_splash.png b/atu_images/splash/at_splash.png new file mode 100644 index 0000000..c15e482 Binary files /dev/null and b/atu_images/splash/at_splash.png differ diff --git a/atu_images/store/at_icon_bag_clock.png b/atu_images/store/at_icon_bag_clock.png new file mode 100644 index 0000000..165f587 Binary files /dev/null and b/atu_images/store/at_icon_bag_clock.png differ diff --git a/atu_images/store/at_icon_bag_shop.png b/atu_images/store/at_icon_bag_shop.png new file mode 100644 index 0000000..880d916 Binary files /dev/null and b/atu_images/store/at_icon_bag_shop.png differ diff --git a/atu_images/store/at_icon_shop_bag.png b/atu_images/store/at_icon_shop_bag.png new file mode 100644 index 0000000..8bade0e Binary files /dev/null and b/atu_images/store/at_icon_shop_bag.png differ diff --git a/atu_images/store/at_icon_shop_item_play.png b/atu_images/store/at_icon_shop_item_play.png new file mode 100644 index 0000000..c2ca14c Binary files /dev/null and b/atu_images/store/at_icon_shop_item_play.png differ diff --git a/atu_images/store/at_icon_shop_item_search.png b/atu_images/store/at_icon_shop_item_search.png new file mode 100644 index 0000000..822223f Binary files /dev/null and b/atu_images/store/at_icon_shop_item_search.png differ diff --git a/atu_images/store/at_icon_store_theme_rev.png b/atu_images/store/at_icon_store_theme_rev.png new file mode 100644 index 0000000..ef50a4a Binary files /dev/null and b/atu_images/store/at_icon_store_theme_rev.png differ diff --git a/atu_images/vip/at_icon_inv_go_btn.png b/atu_images/vip/at_icon_inv_go_btn.png new file mode 100644 index 0000000..4b3884d Binary files /dev/null and b/atu_images/vip/at_icon_inv_go_btn.png differ diff --git a/atu_images/vip/at_icon_room_user_card_vip1_bg.png b/atu_images/vip/at_icon_room_user_card_vip1_bg.png new file mode 100644 index 0000000..7bb21a2 Binary files /dev/null and b/atu_images/vip/at_icon_room_user_card_vip1_bg.png differ diff --git a/atu_images/vip/at_icon_room_user_card_vip2_bg.png b/atu_images/vip/at_icon_room_user_card_vip2_bg.png new file mode 100644 index 0000000..3aee3d9 Binary files /dev/null and b/atu_images/vip/at_icon_room_user_card_vip2_bg.png differ diff --git a/atu_images/vip/at_icon_room_user_card_vip3_bg.png b/atu_images/vip/at_icon_room_user_card_vip3_bg.png new file mode 100644 index 0000000..d10aa0a Binary files /dev/null and b/atu_images/vip/at_icon_room_user_card_vip3_bg.png differ diff --git a/atu_images/vip/at_icon_room_user_card_vip4_bg.png b/atu_images/vip/at_icon_room_user_card_vip4_bg.png new file mode 100644 index 0000000..a4b4b3e Binary files /dev/null and b/atu_images/vip/at_icon_room_user_card_vip4_bg.png differ diff --git a/atu_images/vip/at_icon_room_user_card_vip5_bg.png b/atu_images/vip/at_icon_room_user_card_vip5_bg.png new file mode 100644 index 0000000..507e984 Binary files /dev/null and b/atu_images/vip/at_icon_room_user_card_vip5_bg.png differ diff --git a/atu_images/vip/at_icon_room_user_card_vip6_bg.png b/atu_images/vip/at_icon_room_user_card_vip6_bg.png new file mode 100644 index 0000000..3089c66 Binary files /dev/null and b/atu_images/vip/at_icon_room_user_card_vip6_bg.png differ diff --git a/atu_images/vip/at_icon_tab_vip_text_no_v1.png b/atu_images/vip/at_icon_tab_vip_text_no_v1.png new file mode 100644 index 0000000..724b398 Binary files /dev/null and b/atu_images/vip/at_icon_tab_vip_text_no_v1.png differ diff --git a/atu_images/vip/at_icon_tab_vip_text_no_v2.png b/atu_images/vip/at_icon_tab_vip_text_no_v2.png new file mode 100644 index 0000000..3880f9e Binary files /dev/null and b/atu_images/vip/at_icon_tab_vip_text_no_v2.png differ diff --git a/atu_images/vip/at_icon_tab_vip_text_no_v3.png b/atu_images/vip/at_icon_tab_vip_text_no_v3.png new file mode 100644 index 0000000..590a9da Binary files /dev/null and b/atu_images/vip/at_icon_tab_vip_text_no_v3.png differ diff --git a/atu_images/vip/at_icon_tab_vip_text_no_v4.png b/atu_images/vip/at_icon_tab_vip_text_no_v4.png new file mode 100644 index 0000000..2ca591e Binary files /dev/null and b/atu_images/vip/at_icon_tab_vip_text_no_v4.png differ diff --git a/atu_images/vip/at_icon_tab_vip_text_no_v5.png b/atu_images/vip/at_icon_tab_vip_text_no_v5.png new file mode 100644 index 0000000..447cb02 Binary files /dev/null and b/atu_images/vip/at_icon_tab_vip_text_no_v5.png differ diff --git a/atu_images/vip/at_icon_tab_vip_text_no_v6.png b/atu_images/vip/at_icon_tab_vip_text_no_v6.png new file mode 100644 index 0000000..df9d7ef Binary files /dev/null and b/atu_images/vip/at_icon_tab_vip_text_no_v6.png differ diff --git a/atu_images/vip/at_icon_vip1_badge.png b/atu_images/vip/at_icon_vip1_badge.png new file mode 100644 index 0000000..65bacac Binary files /dev/null and b/atu_images/vip/at_icon_vip1_badge.png differ diff --git a/atu_images/vip/at_icon_vip1_badge_rev.png b/atu_images/vip/at_icon_vip1_badge_rev.png new file mode 100644 index 0000000..2180612 Binary files /dev/null and b/atu_images/vip/at_icon_vip1_badge_rev.png differ diff --git a/atu_images/vip/at_icon_vip1_bg.png b/atu_images/vip/at_icon_vip1_bg.png new file mode 100644 index 0000000..31151d8 Binary files /dev/null and b/atu_images/vip/at_icon_vip1_bg.png differ diff --git a/atu_images/vip/at_icon_vip1_bg2.png b/atu_images/vip/at_icon_vip1_bg2.png new file mode 100644 index 0000000..f5b540d Binary files /dev/null and b/atu_images/vip/at_icon_vip1_bg2.png differ diff --git a/atu_images/vip/at_icon_vip1_buy_bg.png b/atu_images/vip/at_icon_vip1_buy_bg.png new file mode 100644 index 0000000..eeabb34 Binary files /dev/null and b/atu_images/vip/at_icon_vip1_buy_bg.png differ diff --git a/atu_images/vip/at_icon_vip1_chatbox.png b/atu_images/vip/at_icon_vip1_chatbox.png new file mode 100644 index 0000000..a23ea16 Binary files /dev/null and b/atu_images/vip/at_icon_vip1_chatbox.png differ diff --git a/atu_images/vip/at_icon_vip1_entrance_effect.png b/atu_images/vip/at_icon_vip1_entrance_effect.png new file mode 100644 index 0000000..c5471b0 Binary files /dev/null and b/atu_images/vip/at_icon_vip1_entrance_effect.png differ diff --git a/atu_images/vip/at_icon_vip1_entrance_effect_rev.png b/atu_images/vip/at_icon_vip1_entrance_effect_rev.png new file mode 100644 index 0000000..12eb80f Binary files /dev/null and b/atu_images/vip/at_icon_vip1_entrance_effect_rev.png differ diff --git a/atu_images/vip/at_icon_vip1_ic.png b/atu_images/vip/at_icon_vip1_ic.png new file mode 100644 index 0000000..7937191 Binary files /dev/null and b/atu_images/vip/at_icon_vip1_ic.png differ diff --git a/atu_images/vip/at_icon_vip1_item_bg.png b/atu_images/vip/at_icon_vip1_item_bg.png new file mode 100644 index 0000000..301c802 Binary files /dev/null and b/atu_images/vip/at_icon_vip1_item_bg.png differ diff --git a/atu_images/vip/at_icon_vip1_mic_rippl.png b/atu_images/vip/at_icon_vip1_mic_rippl.png new file mode 100644 index 0000000..9fc2082 Binary files /dev/null and b/atu_images/vip/at_icon_vip1_mic_rippl.png differ diff --git a/atu_images/vip/at_icon_vip1_privilege1_en.png b/atu_images/vip/at_icon_vip1_privilege1_en.png new file mode 100644 index 0000000..03dd2a1 Binary files /dev/null and b/atu_images/vip/at_icon_vip1_privilege1_en.png differ diff --git a/atu_images/vip/at_icon_vip1_privilege2_en.png b/atu_images/vip/at_icon_vip1_privilege2_en.png new file mode 100644 index 0000000..259404f Binary files /dev/null and b/atu_images/vip/at_icon_vip1_privilege2_en.png differ diff --git a/atu_images/vip/at_icon_vip1_privilege3_en.png b/atu_images/vip/at_icon_vip1_privilege3_en.png new file mode 100644 index 0000000..0f13e0e Binary files /dev/null and b/atu_images/vip/at_icon_vip1_privilege3_en.png differ diff --git a/atu_images/vip/at_icon_vip1_privilege4_en.png b/atu_images/vip/at_icon_vip1_privilege4_en.png new file mode 100644 index 0000000..fb3ecdb Binary files /dev/null and b/atu_images/vip/at_icon_vip1_privilege4_en.png differ diff --git a/atu_images/vip/at_icon_vip1_privilege5_en.png b/atu_images/vip/at_icon_vip1_privilege5_en.png new file mode 100644 index 0000000..80479c3 Binary files /dev/null and b/atu_images/vip/at_icon_vip1_privilege5_en.png differ diff --git a/atu_images/vip/at_icon_vip1_profile_card.png b/atu_images/vip/at_icon_vip1_profile_card.png new file mode 100644 index 0000000..137957f Binary files /dev/null and b/atu_images/vip/at_icon_vip1_profile_card.png differ diff --git a/atu_images/vip/at_icon_vip1_profile_card_rev.png b/atu_images/vip/at_icon_vip1_profile_card_rev.png new file mode 100644 index 0000000..7e97879 Binary files /dev/null and b/atu_images/vip/at_icon_vip1_profile_card_rev.png differ diff --git a/atu_images/vip/at_icon_vip1_profile_frame.png b/atu_images/vip/at_icon_vip1_profile_frame.png new file mode 100644 index 0000000..979bcdb Binary files /dev/null and b/atu_images/vip/at_icon_vip1_profile_frame.png differ diff --git a/atu_images/vip/at_icon_vip1_profile_rev.png b/atu_images/vip/at_icon_vip1_profile_rev.png new file mode 100644 index 0000000..46571c9 Binary files /dev/null and b/atu_images/vip/at_icon_vip1_profile_rev.png differ diff --git a/atu_images/vip/at_icon_vip1_special_gift_tassel.png b/atu_images/vip/at_icon_vip1_special_gift_tassel.png new file mode 100644 index 0000000..008e420 Binary files /dev/null and b/atu_images/vip/at_icon_vip1_special_gift_tassel.png differ diff --git a/atu_images/vip/at_icon_vip1_special_gift_tassel_rev.png b/atu_images/vip/at_icon_vip1_special_gift_tassel_rev.png new file mode 100644 index 0000000..c4ca1f0 Binary files /dev/null and b/atu_images/vip/at_icon_vip1_special_gift_tassel_rev.png differ diff --git a/atu_images/vip/at_icon_vip1_tag1.png b/atu_images/vip/at_icon_vip1_tag1.png new file mode 100644 index 0000000..b4e769c Binary files /dev/null and b/atu_images/vip/at_icon_vip1_tag1.png differ diff --git a/atu_images/vip/at_icon_vip1_tag2.png b/atu_images/vip/at_icon_vip1_tag2.png new file mode 100644 index 0000000..fc79b79 Binary files /dev/null and b/atu_images/vip/at_icon_vip1_tag2.png differ diff --git a/atu_images/vip/at_icon_vip2_badge.png b/atu_images/vip/at_icon_vip2_badge.png new file mode 100644 index 0000000..f7205cd Binary files /dev/null and b/atu_images/vip/at_icon_vip2_badge.png differ diff --git a/atu_images/vip/at_icon_vip2_badge_rev.png b/atu_images/vip/at_icon_vip2_badge_rev.png new file mode 100644 index 0000000..da2fe06 Binary files /dev/null and b/atu_images/vip/at_icon_vip2_badge_rev.png differ diff --git a/atu_images/vip/at_icon_vip2_bg.png b/atu_images/vip/at_icon_vip2_bg.png new file mode 100644 index 0000000..ace7d34 Binary files /dev/null and b/atu_images/vip/at_icon_vip2_bg.png differ diff --git a/atu_images/vip/at_icon_vip2_bg2.png b/atu_images/vip/at_icon_vip2_bg2.png new file mode 100644 index 0000000..42ebc5d Binary files /dev/null and b/atu_images/vip/at_icon_vip2_bg2.png differ diff --git a/atu_images/vip/at_icon_vip2_chatbox.png b/atu_images/vip/at_icon_vip2_chatbox.png new file mode 100644 index 0000000..7357ba2 Binary files /dev/null and b/atu_images/vip/at_icon_vip2_chatbox.png differ diff --git a/atu_images/vip/at_icon_vip2_entrance_effect.png b/atu_images/vip/at_icon_vip2_entrance_effect.png new file mode 100644 index 0000000..9a18be9 Binary files /dev/null and b/atu_images/vip/at_icon_vip2_entrance_effect.png differ diff --git a/atu_images/vip/at_icon_vip2_entrance_effect_rev.png b/atu_images/vip/at_icon_vip2_entrance_effect_rev.png new file mode 100644 index 0000000..1543b68 Binary files /dev/null and b/atu_images/vip/at_icon_vip2_entrance_effect_rev.png differ diff --git a/atu_images/vip/at_icon_vip2_ic.png b/atu_images/vip/at_icon_vip2_ic.png new file mode 100644 index 0000000..0f8d5ae Binary files /dev/null and b/atu_images/vip/at_icon_vip2_ic.png differ diff --git a/atu_images/vip/at_icon_vip2_mic_rippl.png b/atu_images/vip/at_icon_vip2_mic_rippl.png new file mode 100644 index 0000000..8cde80a Binary files /dev/null and b/atu_images/vip/at_icon_vip2_mic_rippl.png differ diff --git a/atu_images/vip/at_icon_vip2_privilege1_en.png b/atu_images/vip/at_icon_vip2_privilege1_en.png new file mode 100644 index 0000000..4f93926 Binary files /dev/null and b/atu_images/vip/at_icon_vip2_privilege1_en.png differ diff --git a/atu_images/vip/at_icon_vip2_privilege2_en.png b/atu_images/vip/at_icon_vip2_privilege2_en.png new file mode 100644 index 0000000..f40692b Binary files /dev/null and b/atu_images/vip/at_icon_vip2_privilege2_en.png differ diff --git a/atu_images/vip/at_icon_vip2_privilege3_en.png b/atu_images/vip/at_icon_vip2_privilege3_en.png new file mode 100644 index 0000000..2793b3a Binary files /dev/null and b/atu_images/vip/at_icon_vip2_privilege3_en.png differ diff --git a/atu_images/vip/at_icon_vip2_privilege4_en.png b/atu_images/vip/at_icon_vip2_privilege4_en.png new file mode 100644 index 0000000..34677fd Binary files /dev/null and b/atu_images/vip/at_icon_vip2_privilege4_en.png differ diff --git a/atu_images/vip/at_icon_vip2_profile_card.png b/atu_images/vip/at_icon_vip2_profile_card.png new file mode 100644 index 0000000..473b6cb Binary files /dev/null and b/atu_images/vip/at_icon_vip2_profile_card.png differ diff --git a/atu_images/vip/at_icon_vip2_profile_card_rev.png b/atu_images/vip/at_icon_vip2_profile_card_rev.png new file mode 100644 index 0000000..d34c6ec Binary files /dev/null and b/atu_images/vip/at_icon_vip2_profile_card_rev.png differ diff --git a/atu_images/vip/at_icon_vip2_profile_frame.png b/atu_images/vip/at_icon_vip2_profile_frame.png new file mode 100644 index 0000000..29dd64d Binary files /dev/null and b/atu_images/vip/at_icon_vip2_profile_frame.png differ diff --git a/atu_images/vip/at_icon_vip2_profile_rev.png b/atu_images/vip/at_icon_vip2_profile_rev.png new file mode 100644 index 0000000..94932a5 Binary files /dev/null and b/atu_images/vip/at_icon_vip2_profile_rev.png differ diff --git a/atu_images/vip/at_icon_vip2_special_gift_tassel.png b/atu_images/vip/at_icon_vip2_special_gift_tassel.png new file mode 100644 index 0000000..a1ff9fb Binary files /dev/null and b/atu_images/vip/at_icon_vip2_special_gift_tassel.png differ diff --git a/atu_images/vip/at_icon_vip2_special_gift_tassel_rev.png b/atu_images/vip/at_icon_vip2_special_gift_tassel_rev.png new file mode 100644 index 0000000..a403ccf Binary files /dev/null and b/atu_images/vip/at_icon_vip2_special_gift_tassel_rev.png differ diff --git a/atu_images/vip/at_icon_vip2_tag1.png b/atu_images/vip/at_icon_vip2_tag1.png new file mode 100644 index 0000000..b4e769c Binary files /dev/null and b/atu_images/vip/at_icon_vip2_tag1.png differ diff --git a/atu_images/vip/at_icon_vip2_tag2.png b/atu_images/vip/at_icon_vip2_tag2.png new file mode 100644 index 0000000..6a87a64 Binary files /dev/null and b/atu_images/vip/at_icon_vip2_tag2.png differ diff --git a/atu_images/vip/at_icon_vip3_badge.png b/atu_images/vip/at_icon_vip3_badge.png new file mode 100644 index 0000000..3e6b868 Binary files /dev/null and b/atu_images/vip/at_icon_vip3_badge.png differ diff --git a/atu_images/vip/at_icon_vip3_badge_rev.png b/atu_images/vip/at_icon_vip3_badge_rev.png new file mode 100644 index 0000000..eddaa40 Binary files /dev/null and b/atu_images/vip/at_icon_vip3_badge_rev.png differ diff --git a/atu_images/vip/at_icon_vip3_bg.png b/atu_images/vip/at_icon_vip3_bg.png new file mode 100644 index 0000000..fbb7932 Binary files /dev/null and b/atu_images/vip/at_icon_vip3_bg.png differ diff --git a/atu_images/vip/at_icon_vip3_bg2.png b/atu_images/vip/at_icon_vip3_bg2.png new file mode 100644 index 0000000..2e397a3 Binary files /dev/null and b/atu_images/vip/at_icon_vip3_bg2.png differ diff --git a/atu_images/vip/at_icon_vip3_chatbox.png b/atu_images/vip/at_icon_vip3_chatbox.png new file mode 100644 index 0000000..48c11bb Binary files /dev/null and b/atu_images/vip/at_icon_vip3_chatbox.png differ diff --git a/atu_images/vip/at_icon_vip3_chatbox_medal.png b/atu_images/vip/at_icon_vip3_chatbox_medal.png new file mode 100644 index 0000000..7837e6b Binary files /dev/null and b/atu_images/vip/at_icon_vip3_chatbox_medal.png differ diff --git a/atu_images/vip/at_icon_vip3_chatbox_pre.png b/atu_images/vip/at_icon_vip3_chatbox_pre.png new file mode 100644 index 0000000..232d3fa Binary files /dev/null and b/atu_images/vip/at_icon_vip3_chatbox_pre.png differ diff --git a/atu_images/vip/at_icon_vip3_entrance_effect.png b/atu_images/vip/at_icon_vip3_entrance_effect.png new file mode 100644 index 0000000..25c665d Binary files /dev/null and b/atu_images/vip/at_icon_vip3_entrance_effect.png differ diff --git a/atu_images/vip/at_icon_vip3_entrance_effect_rev.png b/atu_images/vip/at_icon_vip3_entrance_effect_rev.png new file mode 100644 index 0000000..592e205 Binary files /dev/null and b/atu_images/vip/at_icon_vip3_entrance_effect_rev.png differ diff --git a/atu_images/vip/at_icon_vip3_exclusive_vehicles.png b/atu_images/vip/at_icon_vip3_exclusive_vehicles.png new file mode 100644 index 0000000..4d1ac16 Binary files /dev/null and b/atu_images/vip/at_icon_vip3_exclusive_vehicles.png differ diff --git a/atu_images/vip/at_icon_vip3_ic.png b/atu_images/vip/at_icon_vip3_ic.png new file mode 100644 index 0000000..1296f46 Binary files /dev/null and b/atu_images/vip/at_icon_vip3_ic.png differ diff --git a/atu_images/vip/at_icon_vip3_mic_rippl.png b/atu_images/vip/at_icon_vip3_mic_rippl.png new file mode 100644 index 0000000..652b47a Binary files /dev/null and b/atu_images/vip/at_icon_vip3_mic_rippl.png differ diff --git a/atu_images/vip/at_icon_vip3_privilege1_en.png b/atu_images/vip/at_icon_vip3_privilege1_en.png new file mode 100644 index 0000000..9b5320a Binary files /dev/null and b/atu_images/vip/at_icon_vip3_privilege1_en.png differ diff --git a/atu_images/vip/at_icon_vip3_privilege2_en.png b/atu_images/vip/at_icon_vip3_privilege2_en.png new file mode 100644 index 0000000..d2dd2ff Binary files /dev/null and b/atu_images/vip/at_icon_vip3_privilege2_en.png differ diff --git a/atu_images/vip/at_icon_vip3_privilege3_en.png b/atu_images/vip/at_icon_vip3_privilege3_en.png new file mode 100644 index 0000000..d7f5c6b Binary files /dev/null and b/atu_images/vip/at_icon_vip3_privilege3_en.png differ diff --git a/atu_images/vip/at_icon_vip3_privilege4_en.png b/atu_images/vip/at_icon_vip3_privilege4_en.png new file mode 100644 index 0000000..65dce04 Binary files /dev/null and b/atu_images/vip/at_icon_vip3_privilege4_en.png differ diff --git a/atu_images/vip/at_icon_vip3_privilege5_en.png b/atu_images/vip/at_icon_vip3_privilege5_en.png new file mode 100644 index 0000000..bd128d4 Binary files /dev/null and b/atu_images/vip/at_icon_vip3_privilege5_en.png differ diff --git a/atu_images/vip/at_icon_vip3_privilege6_en.png b/atu_images/vip/at_icon_vip3_privilege6_en.png new file mode 100644 index 0000000..f201349 Binary files /dev/null and b/atu_images/vip/at_icon_vip3_privilege6_en.png differ diff --git a/atu_images/vip/at_icon_vip3_profile_card.png b/atu_images/vip/at_icon_vip3_profile_card.png new file mode 100644 index 0000000..3f10d23 Binary files /dev/null and b/atu_images/vip/at_icon_vip3_profile_card.png differ diff --git a/atu_images/vip/at_icon_vip3_profile_card_rev.png b/atu_images/vip/at_icon_vip3_profile_card_rev.png new file mode 100644 index 0000000..3c43c3c Binary files /dev/null and b/atu_images/vip/at_icon_vip3_profile_card_rev.png differ diff --git a/atu_images/vip/at_icon_vip3_profile_frame.png b/atu_images/vip/at_icon_vip3_profile_frame.png new file mode 100644 index 0000000..e668894 Binary files /dev/null and b/atu_images/vip/at_icon_vip3_profile_frame.png differ diff --git a/atu_images/vip/at_icon_vip3_profile_rev.png b/atu_images/vip/at_icon_vip3_profile_rev.png new file mode 100644 index 0000000..4fc0eb8 Binary files /dev/null and b/atu_images/vip/at_icon_vip3_profile_rev.png differ diff --git a/atu_images/vip/at_icon_vip3_ripple_theme.png b/atu_images/vip/at_icon_vip3_ripple_theme.png new file mode 100644 index 0000000..7b839c9 Binary files /dev/null and b/atu_images/vip/at_icon_vip3_ripple_theme.png differ diff --git a/atu_images/vip/at_icon_vip3_seat.png b/atu_images/vip/at_icon_vip3_seat.png new file mode 100644 index 0000000..bd1a4e2 Binary files /dev/null and b/atu_images/vip/at_icon_vip3_seat.png differ diff --git a/atu_images/vip/at_icon_vip3_seat_rev.png b/atu_images/vip/at_icon_vip3_seat_rev.png new file mode 100644 index 0000000..ae670a5 Binary files /dev/null and b/atu_images/vip/at_icon_vip3_seat_rev.png differ diff --git a/atu_images/vip/at_icon_vip3_special_gift_tassel.png b/atu_images/vip/at_icon_vip3_special_gift_tassel.png new file mode 100644 index 0000000..c1c5fb4 Binary files /dev/null and b/atu_images/vip/at_icon_vip3_special_gift_tassel.png differ diff --git a/atu_images/vip/at_icon_vip3_special_gift_tassel_rev.png b/atu_images/vip/at_icon_vip3_special_gift_tassel_rev.png new file mode 100644 index 0000000..d5d5c07 Binary files /dev/null and b/atu_images/vip/at_icon_vip3_special_gift_tassel_rev.png differ diff --git a/atu_images/vip/at_icon_vip3_tag1.png b/atu_images/vip/at_icon_vip3_tag1.png new file mode 100644 index 0000000..756493f Binary files /dev/null and b/atu_images/vip/at_icon_vip3_tag1.png differ diff --git a/atu_images/vip/at_icon_vip3_tag2.png b/atu_images/vip/at_icon_vip3_tag2.png new file mode 100644 index 0000000..e15e91d Binary files /dev/null and b/atu_images/vip/at_icon_vip3_tag2.png differ diff --git a/atu_images/vip/at_icon_vip4_badge.png b/atu_images/vip/at_icon_vip4_badge.png new file mode 100644 index 0000000..d673f0c Binary files /dev/null and b/atu_images/vip/at_icon_vip4_badge.png differ diff --git a/atu_images/vip/at_icon_vip4_badge_rev.png b/atu_images/vip/at_icon_vip4_badge_rev.png new file mode 100644 index 0000000..2fe1c1c Binary files /dev/null and b/atu_images/vip/at_icon_vip4_badge_rev.png differ diff --git a/atu_images/vip/at_icon_vip4_bg.png b/atu_images/vip/at_icon_vip4_bg.png new file mode 100644 index 0000000..8579889 Binary files /dev/null and b/atu_images/vip/at_icon_vip4_bg.png differ diff --git a/atu_images/vip/at_icon_vip4_bg2.png b/atu_images/vip/at_icon_vip4_bg2.png new file mode 100644 index 0000000..bbd185b Binary files /dev/null and b/atu_images/vip/at_icon_vip4_bg2.png differ diff --git a/atu_images/vip/at_icon_vip4_chatbox.png b/atu_images/vip/at_icon_vip4_chatbox.png new file mode 100644 index 0000000..da84911 Binary files /dev/null and b/atu_images/vip/at_icon_vip4_chatbox.png differ diff --git a/atu_images/vip/at_icon_vip4_chatbox_medal.png b/atu_images/vip/at_icon_vip4_chatbox_medal.png new file mode 100644 index 0000000..b016e89 Binary files /dev/null and b/atu_images/vip/at_icon_vip4_chatbox_medal.png differ diff --git a/atu_images/vip/at_icon_vip4_chatbox_pre.png b/atu_images/vip/at_icon_vip4_chatbox_pre.png new file mode 100644 index 0000000..12cd9f8 Binary files /dev/null and b/atu_images/vip/at_icon_vip4_chatbox_pre.png differ diff --git a/atu_images/vip/at_icon_vip4_entrance_effect.png b/atu_images/vip/at_icon_vip4_entrance_effect.png new file mode 100644 index 0000000..8a090e0 Binary files /dev/null and b/atu_images/vip/at_icon_vip4_entrance_effect.png differ diff --git a/atu_images/vip/at_icon_vip4_entrance_effect_rev.png b/atu_images/vip/at_icon_vip4_entrance_effect_rev.png new file mode 100644 index 0000000..b5d2a6e Binary files /dev/null and b/atu_images/vip/at_icon_vip4_entrance_effect_rev.png differ diff --git a/atu_images/vip/at_icon_vip4_exclusive_vehicles.png b/atu_images/vip/at_icon_vip4_exclusive_vehicles.png new file mode 100644 index 0000000..4a36c97 Binary files /dev/null and b/atu_images/vip/at_icon_vip4_exclusive_vehicles.png differ diff --git a/atu_images/vip/at_icon_vip4_exclusive_vehicles_rev.png b/atu_images/vip/at_icon_vip4_exclusive_vehicles_rev.png new file mode 100644 index 0000000..a2123dd Binary files /dev/null and b/atu_images/vip/at_icon_vip4_exclusive_vehicles_rev.png differ diff --git a/atu_images/vip/at_icon_vip4_ic.png b/atu_images/vip/at_icon_vip4_ic.png new file mode 100644 index 0000000..cbf97a9 Binary files /dev/null and b/atu_images/vip/at_icon_vip4_ic.png differ diff --git a/atu_images/vip/at_icon_vip4_mic_rippl.png b/atu_images/vip/at_icon_vip4_mic_rippl.png new file mode 100644 index 0000000..aeae105 Binary files /dev/null and b/atu_images/vip/at_icon_vip4_mic_rippl.png differ diff --git a/atu_images/vip/at_icon_vip4_privilege1_en.png b/atu_images/vip/at_icon_vip4_privilege1_en.png new file mode 100644 index 0000000..8a6cbde Binary files /dev/null and b/atu_images/vip/at_icon_vip4_privilege1_en.png differ diff --git a/atu_images/vip/at_icon_vip4_privilege2_en.png b/atu_images/vip/at_icon_vip4_privilege2_en.png new file mode 100644 index 0000000..d546b99 Binary files /dev/null and b/atu_images/vip/at_icon_vip4_privilege2_en.png differ diff --git a/atu_images/vip/at_icon_vip4_privilege3_en.png b/atu_images/vip/at_icon_vip4_privilege3_en.png new file mode 100644 index 0000000..558795f Binary files /dev/null and b/atu_images/vip/at_icon_vip4_privilege3_en.png differ diff --git a/atu_images/vip/at_icon_vip4_privilege4_en.png b/atu_images/vip/at_icon_vip4_privilege4_en.png new file mode 100644 index 0000000..646ce5f Binary files /dev/null and b/atu_images/vip/at_icon_vip4_privilege4_en.png differ diff --git a/atu_images/vip/at_icon_vip4_privilege5_en.png b/atu_images/vip/at_icon_vip4_privilege5_en.png new file mode 100644 index 0000000..d1ce67d Binary files /dev/null and b/atu_images/vip/at_icon_vip4_privilege5_en.png differ diff --git a/atu_images/vip/at_icon_vip4_privilege6_en.png b/atu_images/vip/at_icon_vip4_privilege6_en.png new file mode 100644 index 0000000..4da20c0 Binary files /dev/null and b/atu_images/vip/at_icon_vip4_privilege6_en.png differ diff --git a/atu_images/vip/at_icon_vip4_privilege7_en.png b/atu_images/vip/at_icon_vip4_privilege7_en.png new file mode 100644 index 0000000..43ae5ae Binary files /dev/null and b/atu_images/vip/at_icon_vip4_privilege7_en.png differ diff --git a/atu_images/vip/at_icon_vip4_profile_card.png b/atu_images/vip/at_icon_vip4_profile_card.png new file mode 100644 index 0000000..a3f7bfa Binary files /dev/null and b/atu_images/vip/at_icon_vip4_profile_card.png differ diff --git a/atu_images/vip/at_icon_vip4_profile_card_rev.png b/atu_images/vip/at_icon_vip4_profile_card_rev.png new file mode 100644 index 0000000..44199d2 Binary files /dev/null and b/atu_images/vip/at_icon_vip4_profile_card_rev.png differ diff --git a/atu_images/vip/at_icon_vip4_profile_frame.png b/atu_images/vip/at_icon_vip4_profile_frame.png new file mode 100644 index 0000000..2741fe4 Binary files /dev/null and b/atu_images/vip/at_icon_vip4_profile_frame.png differ diff --git a/atu_images/vip/at_icon_vip4_profile_rev.png b/atu_images/vip/at_icon_vip4_profile_rev.png new file mode 100644 index 0000000..31fa679 Binary files /dev/null and b/atu_images/vip/at_icon_vip4_profile_rev.png differ diff --git a/atu_images/vip/at_icon_vip4_rippl_theme.png b/atu_images/vip/at_icon_vip4_rippl_theme.png new file mode 100644 index 0000000..dc4df29 Binary files /dev/null and b/atu_images/vip/at_icon_vip4_rippl_theme.png differ diff --git a/atu_images/vip/at_icon_vip4_ripple_theme.png b/atu_images/vip/at_icon_vip4_ripple_theme.png new file mode 100644 index 0000000..8c59487 Binary files /dev/null and b/atu_images/vip/at_icon_vip4_ripple_theme.png differ diff --git a/atu_images/vip/at_icon_vip4_room_cover_border_rev.png b/atu_images/vip/at_icon_vip4_room_cover_border_rev.png new file mode 100644 index 0000000..81d0eff Binary files /dev/null and b/atu_images/vip/at_icon_vip4_room_cover_border_rev.png differ diff --git a/atu_images/vip/at_icon_vip4_seat.png b/atu_images/vip/at_icon_vip4_seat.png new file mode 100644 index 0000000..cf0d7fa Binary files /dev/null and b/atu_images/vip/at_icon_vip4_seat.png differ diff --git a/atu_images/vip/at_icon_vip4_seat_rev.png b/atu_images/vip/at_icon_vip4_seat_rev.png new file mode 100644 index 0000000..02d5084 Binary files /dev/null and b/atu_images/vip/at_icon_vip4_seat_rev.png differ diff --git a/atu_images/vip/at_icon_vip4_special_gift_tassel.png b/atu_images/vip/at_icon_vip4_special_gift_tassel.png new file mode 100644 index 0000000..95186f0 Binary files /dev/null and b/atu_images/vip/at_icon_vip4_special_gift_tassel.png differ diff --git a/atu_images/vip/at_icon_vip4_special_gift_tassel_rev.png b/atu_images/vip/at_icon_vip4_special_gift_tassel_rev.png new file mode 100644 index 0000000..c1b7894 Binary files /dev/null and b/atu_images/vip/at_icon_vip4_special_gift_tassel_rev.png differ diff --git a/atu_images/vip/at_icon_vip4_tag1.png b/atu_images/vip/at_icon_vip4_tag1.png new file mode 100644 index 0000000..da7b66e Binary files /dev/null and b/atu_images/vip/at_icon_vip4_tag1.png differ diff --git a/atu_images/vip/at_icon_vip4_tag2.png b/atu_images/vip/at_icon_vip4_tag2.png new file mode 100644 index 0000000..4127e43 Binary files /dev/null and b/atu_images/vip/at_icon_vip4_tag2.png differ diff --git a/atu_images/vip/at_icon_vip5_badge.png b/atu_images/vip/at_icon_vip5_badge.png new file mode 100644 index 0000000..a8eee49 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_badge.png differ diff --git a/atu_images/vip/at_icon_vip5_badge_rev.png b/atu_images/vip/at_icon_vip5_badge_rev.png new file mode 100644 index 0000000..909a770 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_badge_rev.png differ diff --git a/atu_images/vip/at_icon_vip5_bg.png b/atu_images/vip/at_icon_vip5_bg.png new file mode 100644 index 0000000..9ae1467 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_bg.png differ diff --git a/atu_images/vip/at_icon_vip5_bg2.png b/atu_images/vip/at_icon_vip5_bg2.png new file mode 100644 index 0000000..b63ce1b Binary files /dev/null and b/atu_images/vip/at_icon_vip5_bg2.png differ diff --git a/atu_images/vip/at_icon_vip5_chatbox.png b/atu_images/vip/at_icon_vip5_chatbox.png new file mode 100644 index 0000000..a9aac1d Binary files /dev/null and b/atu_images/vip/at_icon_vip5_chatbox.png differ diff --git a/atu_images/vip/at_icon_vip5_chatbox_medal.png b/atu_images/vip/at_icon_vip5_chatbox_medal.png new file mode 100644 index 0000000..bec6386 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_chatbox_medal.png differ diff --git a/atu_images/vip/at_icon_vip5_chatbox_pre.png b/atu_images/vip/at_icon_vip5_chatbox_pre.png new file mode 100644 index 0000000..7054ead Binary files /dev/null and b/atu_images/vip/at_icon_vip5_chatbox_pre.png differ diff --git a/atu_images/vip/at_icon_vip5_entrance_effect.png b/atu_images/vip/at_icon_vip5_entrance_effect.png new file mode 100644 index 0000000..eb9db91 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_entrance_effect.png differ diff --git a/atu_images/vip/at_icon_vip5_entrance_effect_rev.png b/atu_images/vip/at_icon_vip5_entrance_effect_rev.png new file mode 100644 index 0000000..a9a9175 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_entrance_effect_rev.png differ diff --git a/atu_images/vip/at_icon_vip5_exclusive_vehicles.png b/atu_images/vip/at_icon_vip5_exclusive_vehicles.png new file mode 100644 index 0000000..989d7fb Binary files /dev/null and b/atu_images/vip/at_icon_vip5_exclusive_vehicles.png differ diff --git a/atu_images/vip/at_icon_vip5_exclusive_vehicles_rev.png b/atu_images/vip/at_icon_vip5_exclusive_vehicles_rev.png new file mode 100644 index 0000000..843eb62 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_exclusive_vehicles_rev.png differ diff --git a/atu_images/vip/at_icon_vip5_ic.png b/atu_images/vip/at_icon_vip5_ic.png new file mode 100644 index 0000000..0e9b2c1 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_ic.png differ diff --git a/atu_images/vip/at_icon_vip5_mic_rippl.png b/atu_images/vip/at_icon_vip5_mic_rippl.png new file mode 100644 index 0000000..519bb8d Binary files /dev/null and b/atu_images/vip/at_icon_vip5_mic_rippl.png differ diff --git a/atu_images/vip/at_icon_vip5_privilege1_en.png b/atu_images/vip/at_icon_vip5_privilege1_en.png new file mode 100644 index 0000000..6169cf0 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_privilege1_en.png differ diff --git a/atu_images/vip/at_icon_vip5_privilege2_en.png b/atu_images/vip/at_icon_vip5_privilege2_en.png new file mode 100644 index 0000000..5be264e Binary files /dev/null and b/atu_images/vip/at_icon_vip5_privilege2_en.png differ diff --git a/atu_images/vip/at_icon_vip5_privilege3_en.png b/atu_images/vip/at_icon_vip5_privilege3_en.png new file mode 100644 index 0000000..3b2e730 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_privilege3_en.png differ diff --git a/atu_images/vip/at_icon_vip5_privilege4_en.png b/atu_images/vip/at_icon_vip5_privilege4_en.png new file mode 100644 index 0000000..852c232 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_privilege4_en.png differ diff --git a/atu_images/vip/at_icon_vip5_privilege5_en.png b/atu_images/vip/at_icon_vip5_privilege5_en.png new file mode 100644 index 0000000..54a390f Binary files /dev/null and b/atu_images/vip/at_icon_vip5_privilege5_en.png differ diff --git a/atu_images/vip/at_icon_vip5_privilege6_en.png b/atu_images/vip/at_icon_vip5_privilege6_en.png new file mode 100644 index 0000000..3c233e0 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_privilege6_en.png differ diff --git a/atu_images/vip/at_icon_vip5_privilege7_en.png b/atu_images/vip/at_icon_vip5_privilege7_en.png new file mode 100644 index 0000000..ee88929 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_privilege7_en.png differ diff --git a/atu_images/vip/at_icon_vip5_privilege8_en.png b/atu_images/vip/at_icon_vip5_privilege8_en.png new file mode 100644 index 0000000..ad47088 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_privilege8_en.png differ diff --git a/atu_images/vip/at_icon_vip5_privilege9_en.png b/atu_images/vip/at_icon_vip5_privilege9_en.png new file mode 100644 index 0000000..6fce31d Binary files /dev/null and b/atu_images/vip/at_icon_vip5_privilege9_en.png differ diff --git a/atu_images/vip/at_icon_vip5_profile_card.png b/atu_images/vip/at_icon_vip5_profile_card.png new file mode 100644 index 0000000..b47bce9 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_profile_card.png differ diff --git a/atu_images/vip/at_icon_vip5_profile_card_rev.png b/atu_images/vip/at_icon_vip5_profile_card_rev.png new file mode 100644 index 0000000..41d9bfc Binary files /dev/null and b/atu_images/vip/at_icon_vip5_profile_card_rev.png differ diff --git a/atu_images/vip/at_icon_vip5_profile_frame.png b/atu_images/vip/at_icon_vip5_profile_frame.png new file mode 100644 index 0000000..7245507 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_profile_frame.png differ diff --git a/atu_images/vip/at_icon_vip5_profile_rev.png b/atu_images/vip/at_icon_vip5_profile_rev.png new file mode 100644 index 0000000..03323bf Binary files /dev/null and b/atu_images/vip/at_icon_vip5_profile_rev.png differ diff --git a/atu_images/vip/at_icon_vip5_rippl_theme.png b/atu_images/vip/at_icon_vip5_rippl_theme.png new file mode 100644 index 0000000..829b75f Binary files /dev/null and b/atu_images/vip/at_icon_vip5_rippl_theme.png differ diff --git a/atu_images/vip/at_icon_vip5_ripple_theme.png b/atu_images/vip/at_icon_vip5_ripple_theme.png new file mode 100644 index 0000000..f661800 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_ripple_theme.png differ diff --git a/atu_images/vip/at_icon_vip5_room_cover_border_rev.png b/atu_images/vip/at_icon_vip5_room_cover_border_rev.png new file mode 100644 index 0000000..99fa84e Binary files /dev/null and b/atu_images/vip/at_icon_vip5_room_cover_border_rev.png differ diff --git a/atu_images/vip/at_icon_vip5_seat.png b/atu_images/vip/at_icon_vip5_seat.png new file mode 100644 index 0000000..e549c78 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_seat.png differ diff --git a/atu_images/vip/at_icon_vip5_seat_rev.png b/atu_images/vip/at_icon_vip5_seat_rev.png new file mode 100644 index 0000000..a706bfe Binary files /dev/null and b/atu_images/vip/at_icon_vip5_seat_rev.png differ diff --git a/atu_images/vip/at_icon_vip5_special_gift_tassel.png b/atu_images/vip/at_icon_vip5_special_gift_tassel.png new file mode 100644 index 0000000..4d2de56 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_special_gift_tassel.png differ diff --git a/atu_images/vip/at_icon_vip5_special_gift_tassel_rev.png b/atu_images/vip/at_icon_vip5_special_gift_tassel_rev.png new file mode 100644 index 0000000..bc515d5 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_special_gift_tassel_rev.png differ diff --git a/atu_images/vip/at_icon_vip5_tag1.png b/atu_images/vip/at_icon_vip5_tag1.png new file mode 100644 index 0000000..da7b66e Binary files /dev/null and b/atu_images/vip/at_icon_vip5_tag1.png differ diff --git a/atu_images/vip/at_icon_vip5_tag2.png b/atu_images/vip/at_icon_vip5_tag2.png new file mode 100644 index 0000000..5e0dbf9 Binary files /dev/null and b/atu_images/vip/at_icon_vip5_tag2.png differ diff --git a/atu_images/vip/at_icon_vip6_badge.png b/atu_images/vip/at_icon_vip6_badge.png new file mode 100644 index 0000000..a6f38a7 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_badge.png differ diff --git a/atu_images/vip/at_icon_vip6_badge_rev.png b/atu_images/vip/at_icon_vip6_badge_rev.png new file mode 100644 index 0000000..719eefe Binary files /dev/null and b/atu_images/vip/at_icon_vip6_badge_rev.png differ diff --git a/atu_images/vip/at_icon_vip6_bg.png b/atu_images/vip/at_icon_vip6_bg.png new file mode 100644 index 0000000..3b39d49 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_bg.png differ diff --git a/atu_images/vip/at_icon_vip6_buy_bg.png b/atu_images/vip/at_icon_vip6_buy_bg.png new file mode 100644 index 0000000..d21bbd8 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_buy_bg.png differ diff --git a/atu_images/vip/at_icon_vip6_chatbox.png b/atu_images/vip/at_icon_vip6_chatbox.png new file mode 100644 index 0000000..44ca83f Binary files /dev/null and b/atu_images/vip/at_icon_vip6_chatbox.png differ diff --git a/atu_images/vip/at_icon_vip6_chatbox_pre.png b/atu_images/vip/at_icon_vip6_chatbox_pre.png new file mode 100644 index 0000000..e7a2951 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_chatbox_pre.png differ diff --git a/atu_images/vip/at_icon_vip6_entrance_effect.png b/atu_images/vip/at_icon_vip6_entrance_effect.png new file mode 100644 index 0000000..5f18659 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_entrance_effect.png differ diff --git a/atu_images/vip/at_icon_vip6_entrance_effect_rev.png b/atu_images/vip/at_icon_vip6_entrance_effect_rev.png new file mode 100644 index 0000000..98b0a9d Binary files /dev/null and b/atu_images/vip/at_icon_vip6_entrance_effect_rev.png differ diff --git a/atu_images/vip/at_icon_vip6_exclusive_vehicles.png b/atu_images/vip/at_icon_vip6_exclusive_vehicles.png new file mode 100644 index 0000000..aed0667 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_exclusive_vehicles.png differ diff --git a/atu_images/vip/at_icon_vip6_exclusive_vehicles_rev.png b/atu_images/vip/at_icon_vip6_exclusive_vehicles_rev.png new file mode 100644 index 0000000..d6a06ed Binary files /dev/null and b/atu_images/vip/at_icon_vip6_exclusive_vehicles_rev.png differ diff --git a/atu_images/vip/at_icon_vip6_item_bg.png b/atu_images/vip/at_icon_vip6_item_bg.png new file mode 100644 index 0000000..6b2059b Binary files /dev/null and b/atu_images/vip/at_icon_vip6_item_bg.png differ diff --git a/atu_images/vip/at_icon_vip6_mic_rippl_theme.png b/atu_images/vip/at_icon_vip6_mic_rippl_theme.png new file mode 100644 index 0000000..4a0cc00 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_mic_rippl_theme.png differ diff --git a/atu_images/vip/at_icon_vip6_privilege1.png b/atu_images/vip/at_icon_vip6_privilege1.png new file mode 100644 index 0000000..ad74e5a Binary files /dev/null and b/atu_images/vip/at_icon_vip6_privilege1.png differ diff --git a/atu_images/vip/at_icon_vip6_privilege10.png b/atu_images/vip/at_icon_vip6_privilege10.png new file mode 100644 index 0000000..a9bbab1 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_privilege10.png differ diff --git a/atu_images/vip/at_icon_vip6_privilege11.png b/atu_images/vip/at_icon_vip6_privilege11.png new file mode 100644 index 0000000..20215c4 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_privilege11.png differ diff --git a/atu_images/vip/at_icon_vip6_privilege12.png b/atu_images/vip/at_icon_vip6_privilege12.png new file mode 100644 index 0000000..c8e8219 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_privilege12.png differ diff --git a/atu_images/vip/at_icon_vip6_privilege13.png b/atu_images/vip/at_icon_vip6_privilege13.png new file mode 100644 index 0000000..a8246c9 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_privilege13.png differ diff --git a/atu_images/vip/at_icon_vip6_privilege14.png b/atu_images/vip/at_icon_vip6_privilege14.png new file mode 100644 index 0000000..2d7a7b9 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_privilege14.png differ diff --git a/atu_images/vip/at_icon_vip6_privilege2.png b/atu_images/vip/at_icon_vip6_privilege2.png new file mode 100644 index 0000000..71d050e Binary files /dev/null and b/atu_images/vip/at_icon_vip6_privilege2.png differ diff --git a/atu_images/vip/at_icon_vip6_privilege3.png b/atu_images/vip/at_icon_vip6_privilege3.png new file mode 100644 index 0000000..1d3336d Binary files /dev/null and b/atu_images/vip/at_icon_vip6_privilege3.png differ diff --git a/atu_images/vip/at_icon_vip6_privilege4.png b/atu_images/vip/at_icon_vip6_privilege4.png new file mode 100644 index 0000000..327d4d7 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_privilege4.png differ diff --git a/atu_images/vip/at_icon_vip6_privilege5.png b/atu_images/vip/at_icon_vip6_privilege5.png new file mode 100644 index 0000000..5eeef91 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_privilege5.png differ diff --git a/atu_images/vip/at_icon_vip6_privilege6.png b/atu_images/vip/at_icon_vip6_privilege6.png new file mode 100644 index 0000000..9af0cfd Binary files /dev/null and b/atu_images/vip/at_icon_vip6_privilege6.png differ diff --git a/atu_images/vip/at_icon_vip6_privilege7.png b/atu_images/vip/at_icon_vip6_privilege7.png new file mode 100644 index 0000000..ce83d31 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_privilege7.png differ diff --git a/atu_images/vip/at_icon_vip6_privilege8.png b/atu_images/vip/at_icon_vip6_privilege8.png new file mode 100644 index 0000000..0880d70 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_privilege8.png differ diff --git a/atu_images/vip/at_icon_vip6_privilege9.png b/atu_images/vip/at_icon_vip6_privilege9.png new file mode 100644 index 0000000..d37d965 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_privilege9.png differ diff --git a/atu_images/vip/at_icon_vip6_profile_card.png b/atu_images/vip/at_icon_vip6_profile_card.png new file mode 100644 index 0000000..473828d Binary files /dev/null and b/atu_images/vip/at_icon_vip6_profile_card.png differ diff --git a/atu_images/vip/at_icon_vip6_profile_card_rev.png b/atu_images/vip/at_icon_vip6_profile_card_rev.png new file mode 100644 index 0000000..4d728c6 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_profile_card_rev.png differ diff --git a/atu_images/vip/at_icon_vip6_profile_frame.png b/atu_images/vip/at_icon_vip6_profile_frame.png new file mode 100644 index 0000000..417f61a Binary files /dev/null and b/atu_images/vip/at_icon_vip6_profile_frame.png differ diff --git a/atu_images/vip/at_icon_vip6_profile_rev.png b/atu_images/vip/at_icon_vip6_profile_rev.png new file mode 100644 index 0000000..def136a Binary files /dev/null and b/atu_images/vip/at_icon_vip6_profile_rev.png differ diff --git a/atu_images/vip/at_icon_vip6_room_cover_border_rev.png b/atu_images/vip/at_icon_vip6_room_cover_border_rev.png new file mode 100644 index 0000000..759c9f6 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_room_cover_border_rev.png differ diff --git a/atu_images/vip/at_icon_vip6_room_cover_headdress.png b/atu_images/vip/at_icon_vip6_room_cover_headdress.png new file mode 100644 index 0000000..62cf231 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_room_cover_headdress.png differ diff --git a/atu_images/vip/at_icon_vip6_seat_rev.png b/atu_images/vip/at_icon_vip6_seat_rev.png new file mode 100644 index 0000000..3183169 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_seat_rev.png differ diff --git a/atu_images/vip/at_icon_vip6_special_gift_tassel.png b/atu_images/vip/at_icon_vip6_special_gift_tassel.png new file mode 100644 index 0000000..1179b77 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_special_gift_tassel.png differ diff --git a/atu_images/vip/at_icon_vip6_special_gift_tassel_rev.png b/atu_images/vip/at_icon_vip6_special_gift_tassel_rev.png new file mode 100644 index 0000000..5ff3607 Binary files /dev/null and b/atu_images/vip/at_icon_vip6_special_gift_tassel_rev.png differ diff --git a/atu_images/vip/at_icon_vip6_tag1.png b/atu_images/vip/at_icon_vip6_tag1.png new file mode 100644 index 0000000..da7b66e Binary files /dev/null and b/atu_images/vip/at_icon_vip6_tag1.png differ diff --git a/atu_images/vip/at_icon_vip6_tag2.png b/atu_images/vip/at_icon_vip6_tag2.png new file mode 100644 index 0000000..ce2776a Binary files /dev/null and b/atu_images/vip/at_icon_vip6_tag2.png differ diff --git a/atu_images/vip/at_icon_vip_1.png b/atu_images/vip/at_icon_vip_1.png new file mode 100644 index 0000000..4ee86d9 Binary files /dev/null and b/atu_images/vip/at_icon_vip_1.png differ diff --git a/atu_images/vip/at_icon_vip_2.png b/atu_images/vip/at_icon_vip_2.png new file mode 100644 index 0000000..1f9f664 Binary files /dev/null and b/atu_images/vip/at_icon_vip_2.png differ diff --git a/atu_images/vip/at_icon_vip_3.png b/atu_images/vip/at_icon_vip_3.png new file mode 100644 index 0000000..2c05413 Binary files /dev/null and b/atu_images/vip/at_icon_vip_3.png differ diff --git a/atu_images/vip/at_icon_vip_4.png b/atu_images/vip/at_icon_vip_4.png new file mode 100644 index 0000000..9d212b4 Binary files /dev/null and b/atu_images/vip/at_icon_vip_4.png differ diff --git a/atu_images/vip/at_icon_vip_5.png b/atu_images/vip/at_icon_vip_5.png new file mode 100644 index 0000000..a01e571 Binary files /dev/null and b/atu_images/vip/at_icon_vip_5.png differ diff --git a/atu_images/vip/at_icon_vip_6.png b/atu_images/vip/at_icon_vip_6.png new file mode 100644 index 0000000..011f0d8 Binary files /dev/null and b/atu_images/vip/at_icon_vip_6.png differ diff --git a/atu_images/vip/at_icon_vip_buy_btn.png b/atu_images/vip/at_icon_vip_buy_btn.png new file mode 100644 index 0000000..ec5049a Binary files /dev/null and b/atu_images/vip/at_icon_vip_buy_btn.png differ diff --git a/atu_images/vip/at_icon_vip_head_bg_v1.png b/atu_images/vip/at_icon_vip_head_bg_v1.png new file mode 100644 index 0000000..b093ca0 Binary files /dev/null and b/atu_images/vip/at_icon_vip_head_bg_v1.png differ diff --git a/atu_images/vip/at_icon_vip_head_bg_v2.png b/atu_images/vip/at_icon_vip_head_bg_v2.png new file mode 100644 index 0000000..d0413da Binary files /dev/null and b/atu_images/vip/at_icon_vip_head_bg_v2.png differ diff --git a/atu_images/vip/at_icon_vip_head_bg_v3.png b/atu_images/vip/at_icon_vip_head_bg_v3.png new file mode 100644 index 0000000..4f8bfe6 Binary files /dev/null and b/atu_images/vip/at_icon_vip_head_bg_v3.png differ diff --git a/atu_images/vip/at_icon_vip_head_bg_v4.png b/atu_images/vip/at_icon_vip_head_bg_v4.png new file mode 100644 index 0000000..1392a32 Binary files /dev/null and b/atu_images/vip/at_icon_vip_head_bg_v4.png differ diff --git a/atu_images/vip/at_icon_vip_head_bg_v5.png b/atu_images/vip/at_icon_vip_head_bg_v5.png new file mode 100644 index 0000000..7db6c8c Binary files /dev/null and b/atu_images/vip/at_icon_vip_head_bg_v5.png differ diff --git a/atu_images/vip/at_icon_vip_level_bg_1.png b/atu_images/vip/at_icon_vip_level_bg_1.png new file mode 100644 index 0000000..6c528df Binary files /dev/null and b/atu_images/vip/at_icon_vip_level_bg_1.png differ diff --git a/atu_images/vip/at_icon_vip_level_bg_2.png b/atu_images/vip/at_icon_vip_level_bg_2.png new file mode 100644 index 0000000..500be04 Binary files /dev/null and b/atu_images/vip/at_icon_vip_level_bg_2.png differ diff --git a/atu_images/vip/at_icon_vip_level_bg_3.png b/atu_images/vip/at_icon_vip_level_bg_3.png new file mode 100644 index 0000000..e30d962 Binary files /dev/null and b/atu_images/vip/at_icon_vip_level_bg_3.png differ diff --git a/atu_images/vip/at_icon_vip_level_bg_4.png b/atu_images/vip/at_icon_vip_level_bg_4.png new file mode 100644 index 0000000..8c8c7a9 Binary files /dev/null and b/atu_images/vip/at_icon_vip_level_bg_4.png differ diff --git a/atu_images/vip/at_icon_vip_level_bg_5.png b/atu_images/vip/at_icon_vip_level_bg_5.png new file mode 100644 index 0000000..25a4f9f Binary files /dev/null and b/atu_images/vip/at_icon_vip_level_bg_5.png differ diff --git a/atu_images/vip/at_icon_vip_no.png b/atu_images/vip/at_icon_vip_no.png new file mode 100644 index 0000000..2086e52 Binary files /dev/null and b/atu_images/vip/at_icon_vip_no.png differ diff --git a/atu_images/vip/at_icon_vip_privilege3_no.png b/atu_images/vip/at_icon_vip_privilege3_no.png new file mode 100644 index 0000000..a619d3e Binary files /dev/null and b/atu_images/vip/at_icon_vip_privilege3_no.png differ diff --git a/atu_images/vip/at_icon_vip_privilege4_no.png b/atu_images/vip/at_icon_vip_privilege4_no.png new file mode 100644 index 0000000..9d28905 Binary files /dev/null and b/atu_images/vip/at_icon_vip_privilege4_no.png differ diff --git a/atu_images/vip/at_icon_vip_privilege5_no.png b/atu_images/vip/at_icon_vip_privilege5_no.png new file mode 100644 index 0000000..eca45ff Binary files /dev/null and b/atu_images/vip/at_icon_vip_privilege5_no.png differ diff --git a/atu_images/vip/at_icon_vip_privilege6_no.png b/atu_images/vip/at_icon_vip_privilege6_no.png new file mode 100644 index 0000000..4052516 Binary files /dev/null and b/atu_images/vip/at_icon_vip_privilege6_no.png differ diff --git a/atu_images/vip/at_icon_vip_privilege7_no.png b/atu_images/vip/at_icon_vip_privilege7_no.png new file mode 100644 index 0000000..b75bb6f Binary files /dev/null and b/atu_images/vip/at_icon_vip_privilege7_no.png differ diff --git a/atu_images/vip/at_icon_vip_privilege8_no.png b/atu_images/vip/at_icon_vip_privilege8_no.png new file mode 100644 index 0000000..db9cc56 Binary files /dev/null and b/atu_images/vip/at_icon_vip_privilege8_no.png differ diff --git a/atu_images/vip/at_icon_vip_privilege9_no.png b/atu_images/vip/at_icon_vip_privilege9_no.png new file mode 100644 index 0000000..f26a2f5 Binary files /dev/null and b/atu_images/vip/at_icon_vip_privilege9_no.png differ diff --git a/atu_images/vip/at_icon_vip_seat_pre.png b/atu_images/vip/at_icon_vip_seat_pre.png new file mode 100644 index 0000000..a91526e Binary files /dev/null and b/atu_images/vip/at_icon_vip_seat_pre.png differ diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..1dc6cf7 --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 13.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..61f27e0 --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1,5 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" + +APP_DISPLAY_NAME=Aslan +PRODUCT_BUNDLE_IDENTIFIER=com.chat.auu diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..2819793 --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1,5 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" + +APP_DISPLAY_NAME=Aslan +PRODUCT_BUNDLE_IDENTIFIER=com.chat.auu diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..a09ea13 --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,84 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + + target.build_configurations.each do |config| + # 这里的配置会告诉编译器在编译时剔除未使用的权限代码 + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ + '$(inherited)', + + ## 1. 禁用 日历 (NSCalendarsUsageDescription) + 'PERMISSION_EVENTS=0', + 'PERMISSION_EVENTS_FULL_ACCESS=0', + + ## 2. 禁用 提醒事项 + 'PERMISSION_REMINDERS=0', + + ## 3. 禁用 通讯录 (NSContactsUsageDescription) + 'PERMISSION_CONTACTS=0', + + ## 4. 禁用 语音识别 (NSSpeechRecognitionUsageDescription) + 'PERMISSION_SPEECH_RECOGNIZER=0', + + ## 5. 禁用 运动与健身 (NSMotionUsageDescription) + 'PERMISSION_SENSORS=0', + + ## 6. 禁用 Siri/助手 (NSSiriUsageDescription) + 'PERMISSION_ASSISTANT=0', + + ## 7. 禁用 媒体库 (NSAppleMusicUsageDescription) + 'PERMISSION_MEDIA_LIBRARY=1', + + ## 8. 禁用 蓝牙 (如果你没用到蓝牙,建议也关掉) + 'PERMISSION_BLUETOOTH=0', + + ## --- 以下是建议保留的常见权限 (设为 1),如果你的 App 也没用到,也可以改为 0 --- + 'PERMISSION_CAMERA=1', + 'PERMISSION_MICROPHONE=1', + 'PERMISSION_PHOTOS=1', + 'PERMISSION_LOCATION=0', + 'PERMISSION_NOTIFICATIONS=1', + 'PERMISSION_APP_TRACKING_TRANSPARENCY=0', + 'PERMISSION_CRITICAL_ALERTS=0', + ] + end + end +end diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 0000000..cc87936 --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,498 @@ +PODS: + - agora_rtc_engine (6.5.3): + - AgoraIrisRTC_iOS (= 4.5.2-build.1) + - AgoraRtcEngine_iOS (= 4.5.2) + - Flutter + - AgoraInfra_iOS (1.2.13.1) + - AgoraIrisRTC_iOS (4.5.2-build.1) + - AgoraRtcEngine_iOS (4.5.2): + - AgoraRtcEngine_iOS/AIAEC (= 4.5.2) + - AgoraRtcEngine_iOS/AIAECLL (= 4.5.2) + - AgoraRtcEngine_iOS/AINS (= 4.5.2) + - AgoraRtcEngine_iOS/AINSLL (= 4.5.2) + - AgoraRtcEngine_iOS/AudioBeauty (= 4.5.2) + - AgoraRtcEngine_iOS/ClearVision (= 4.5.2) + - AgoraRtcEngine_iOS/ContentInspect (= 4.5.2) + - AgoraRtcEngine_iOS/FaceCapture (= 4.5.2) + - AgoraRtcEngine_iOS/FaceDetection (= 4.5.2) + - AgoraRtcEngine_iOS/LipSync (= 4.5.2) + - AgoraRtcEngine_iOS/ReplayKit (= 4.5.2) + - AgoraRtcEngine_iOS/RtcBasic (= 4.5.2) + - AgoraRtcEngine_iOS/SpatialAudio (= 4.5.2) + - AgoraRtcEngine_iOS/VideoAv1CodecDec (= 4.5.2) + - AgoraRtcEngine_iOS/VideoAv1CodecEnc (= 4.5.2) + - AgoraRtcEngine_iOS/VideoCodecDec (= 4.5.2) + - AgoraRtcEngine_iOS/VideoCodecEnc (= 4.5.2) + - AgoraRtcEngine_iOS/VirtualBackground (= 4.5.2) + - AgoraRtcEngine_iOS/VQA (= 4.5.2) + - AgoraRtcEngine_iOS/AIAEC (4.5.2) + - AgoraRtcEngine_iOS/AIAECLL (4.5.2) + - AgoraRtcEngine_iOS/AINS (4.5.2) + - AgoraRtcEngine_iOS/AINSLL (4.5.2) + - AgoraRtcEngine_iOS/AudioBeauty (4.5.2) + - AgoraRtcEngine_iOS/ClearVision (4.5.2) + - AgoraRtcEngine_iOS/ContentInspect (4.5.2) + - AgoraRtcEngine_iOS/FaceCapture (4.5.2) + - AgoraRtcEngine_iOS/FaceDetection (4.5.2) + - AgoraRtcEngine_iOS/LipSync (4.5.2) + - AgoraRtcEngine_iOS/ReplayKit (4.5.2) + - AgoraRtcEngine_iOS/RtcBasic (4.5.2): + - AgoraInfra_iOS (= 1.2.13.1) + - AgoraRtcEngine_iOS/SpatialAudio (4.5.2) + - AgoraRtcEngine_iOS/VideoAv1CodecDec (4.5.2) + - AgoraRtcEngine_iOS/VideoAv1CodecEnc (4.5.2) + - AgoraRtcEngine_iOS/VideoCodecDec (4.5.2) + - AgoraRtcEngine_iOS/VideoCodecEnc (4.5.2) + - AgoraRtcEngine_iOS/VirtualBackground (4.5.2) + - AgoraRtcEngine_iOS/VQA (4.5.2) + - app_links (6.4.1): + - Flutter + - AppAuth (1.7.6): + - AppAuth/Core (= 1.7.6) + - AppAuth/ExternalUserAgent (= 1.7.6) + - AppAuth/Core (1.7.6) + - AppAuth/ExternalUserAgent (1.7.6): + - AppAuth/Core + - AppCheckCore (11.2.0): + - GoogleUtilities/Environment (~> 8.0) + - GoogleUtilities/UserDefaults (~> 8.0) + - PromisesObjC (~> 2.4) + - audioplayers_darwin (0.0.1): + - Flutter + - FlutterMacOS + - device_info_plus (0.0.1): + - Flutter + - Firebase/Auth (11.15.0): + - Firebase/CoreOnly + - FirebaseAuth (~> 11.15.0) + - Firebase/CoreOnly (11.15.0): + - FirebaseCore (~> 11.15.0) + - Firebase/Crashlytics (11.15.0): + - Firebase/CoreOnly + - FirebaseCrashlytics (~> 11.15.0) + - firebase_auth (5.7.0): + - Firebase/Auth (= 11.15.0) + - firebase_core + - Flutter + - firebase_core (3.15.2): + - Firebase/CoreOnly (= 11.15.0) + - Flutter + - firebase_crashlytics (4.3.10): + - Firebase/Crashlytics (= 11.15.0) + - firebase_core + - Flutter + - FirebaseAppCheckInterop (11.15.0) + - FirebaseAuth (11.15.0): + - FirebaseAppCheckInterop (~> 11.0) + - FirebaseAuthInterop (~> 11.0) + - FirebaseCore (~> 11.15.0) + - FirebaseCoreExtension (~> 11.15.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/Environment (~> 8.1) + - GTMSessionFetcher/Core (< 5.0, >= 3.4) + - RecaptchaInterop (~> 101.0) + - FirebaseAuthInterop (11.15.0) + - FirebaseCore (11.15.0): + - FirebaseCoreInternal (~> 11.15.0) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/Logger (~> 8.1) + - FirebaseCoreExtension (11.15.0): + - FirebaseCore (~> 11.15.0) + - FirebaseCoreInternal (11.15.0): + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - FirebaseCrashlytics (11.15.0): + - FirebaseCore (~> 11.15.0) + - FirebaseInstallations (~> 11.0) + - FirebaseRemoteConfigInterop (~> 11.0) + - FirebaseSessions (~> 11.0) + - GoogleDataTransport (~> 10.0) + - GoogleUtilities/Environment (~> 8.1) + - nanopb (~> 3.30910.0) + - PromisesObjC (~> 2.4) + - FirebaseInstallations (11.15.0): + - FirebaseCore (~> 11.15.0) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/UserDefaults (~> 8.1) + - PromisesObjC (~> 2.4) + - FirebaseRemoteConfigInterop (11.15.0) + - FirebaseSessions (11.15.0): + - FirebaseCore (~> 11.15.0) + - FirebaseCoreExtension (~> 11.15.0) + - FirebaseInstallations (~> 11.0) + - GoogleDataTransport (~> 10.0) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/UserDefaults (~> 8.1) + - nanopb (~> 3.30910.0) + - PromisesSwift (~> 2.1) + - Flutter (1.0.0) + - flutter_foreground_task (0.0.1): + - Flutter + - flutter_image_compress_common (1.0.0): + - Flutter + - Mantle + - SDWebImage + - SDWebImageWebPCoder + - flutter_inappwebview_ios (0.0.1): + - Flutter + - flutter_inappwebview_ios/Core (= 0.0.1) + - OrderedSet (~> 6.0.3) + - flutter_inappwebview_ios/Core (0.0.1): + - Flutter + - OrderedSet (~> 6.0.3) + - fluttertoast (0.0.2): + - Flutter + - google_sign_in_ios (0.0.1): + - AppAuth (>= 1.7.4) + - Flutter + - FlutterMacOS + - GoogleSignIn (~> 8.0) + - GTMSessionFetcher (>= 3.4.0) + - GoogleDataTransport (10.1.0): + - nanopb (~> 3.30910.0) + - PromisesObjC (~> 2.4) + - GoogleSignIn (8.0.0): + - AppAuth (< 2.0, >= 1.7.3) + - AppCheckCore (~> 11.0) + - GTMAppAuth (< 5.0, >= 4.1.1) + - GTMSessionFetcher/Core (~> 3.3) + - GoogleUtilities/AppDelegateSwizzler (8.1.0): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Privacy + - GoogleUtilities/Environment (8.1.0): + - GoogleUtilities/Privacy + - GoogleUtilities/Logger (8.1.0): + - GoogleUtilities/Environment + - GoogleUtilities/Privacy + - GoogleUtilities/Network (8.1.0): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Privacy + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (8.1.0)": + - GoogleUtilities/Privacy + - GoogleUtilities/Privacy (8.1.0) + - GoogleUtilities/Reachability (8.1.0): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GoogleUtilities/UserDefaults (8.1.0): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GTMAppAuth (4.1.1): + - AppAuth/Core (~> 1.7) + - GTMSessionFetcher/Core (< 4.0, >= 3.3) + - GTMSessionFetcher (3.5.0): + - GTMSessionFetcher/Full (= 3.5.0) + - GTMSessionFetcher/Core (3.5.0) + - GTMSessionFetcher/Full (3.5.0): + - GTMSessionFetcher/Core + - HydraAsync (2.0.6) + - image_cropper (0.0.4): + - Flutter + - TOCropViewController (~> 2.6.1) + - image_picker_ios (0.0.1): + - Flutter + - in_app_purchase_storekit (0.0.1): + - Flutter + - FlutterMacOS + - iris_method_channel (0.0.1): + - Flutter + - libwebp (1.5.0): + - libwebp/demux (= 1.5.0) + - libwebp/mux (= 1.5.0) + - libwebp/sharpyuv (= 1.5.0) + - libwebp/webp (= 1.5.0) + - libwebp/demux (1.5.0): + - libwebp/webp + - libwebp/mux (1.5.0): + - libwebp/demux + - libwebp/sharpyuv (1.5.0) + - libwebp/webp (1.5.0): + - libwebp/sharpyuv + - loading_indicator_view_plus (0.0.1): + - Flutter + - Mantle (2.2.0): + - Mantle/extobjc (= 2.2.0) + - Mantle/extobjc (2.2.0) + - nanopb (3.30910.0): + - nanopb/decode (= 3.30910.0) + - nanopb/encode (= 3.30910.0) + - nanopb/decode (3.30910.0) + - nanopb/encode (3.30910.0) + - on_audio_query_ios (0.0.1): + - Flutter + - SwiftyBeaver + - OrderedSet (6.0.3) + - package_info_plus (0.4.5): + - Flutter + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - permission_handler_apple (9.3.0): + - Flutter + - PromisesObjC (2.4.0) + - PromisesSwift (2.4.0): + - PromisesObjC (= 2.4.0) + - QGVAPlayer (1.0.19) + - RecaptchaInterop (101.0.0) + - SDWebImage (5.21.7): + - SDWebImage/Core (= 5.21.7) + - SDWebImage/Core (5.21.7) + - SDWebImageWebPCoder (0.15.0): + - libwebp (~> 1.0) + - SDWebImage/Core (~> 5.17) + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - sign_in_with_apple (0.0.1): + - Flutter + - social_sharing_plus (0.0.1): + - Flutter + - sqflite_darwin (0.0.4): + - Flutter + - FlutterMacOS + - store_redirect (0.0.1): + - Flutter + - SwiftyBeaver (1.9.5) + - tancent_vap (0.0.1): + - Flutter + - QGVAPlayer (= 1.0.19) + - tencent_cloud_chat_sdk (8.0.0): + - Flutter + - HydraAsync + - TXIMSDK_Plus_iOS_XCFramework (= 8.3.6498) + - TOCropViewController (2.6.1) + - TXIMSDK_Plus_iOS_XCFramework (8.3.6498) + - url_launcher_ios (0.0.1): + - Flutter + - video_player_avfoundation (0.0.1): + - Flutter + - FlutterMacOS + - video_thumbnail (0.0.1): + - Flutter + - libwebp + - wakelock_plus (0.0.1): + - Flutter + - webview_flutter_wkwebview (0.0.1): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - agora_rtc_engine (from `.symlinks/plugins/agora_rtc_engine/ios`) + - app_links (from `.symlinks/plugins/app_links/ios`) + - audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`) + - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) + - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) + - firebase_core (from `.symlinks/plugins/firebase_core/ios`) + - firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`) + - Flutter (from `Flutter`) + - flutter_foreground_task (from `.symlinks/plugins/flutter_foreground_task/ios`) + - flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`) + - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) + - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) + - google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/darwin`) + - image_cropper (from `.symlinks/plugins/image_cropper/ios`) + - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) + - in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/darwin`) + - iris_method_channel (from `.symlinks/plugins/iris_method_channel/ios`) + - loading_indicator_view_plus (from `.symlinks/plugins/loading_indicator_view_plus/ios`) + - on_audio_query_ios (from `.symlinks/plugins/on_audio_query_ios/ios`) + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`) + - social_sharing_plus (from `.symlinks/plugins/social_sharing_plus/ios`) + - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) + - store_redirect (from `.symlinks/plugins/store_redirect/ios`) + - tancent_vap (from `.symlinks/plugins/tancent_vap/ios`) + - tencent_cloud_chat_sdk (from `.symlinks/plugins/tencent_cloud_chat_sdk/ios`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) + - video_thumbnail (from `.symlinks/plugins/video_thumbnail/ios`) + - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) + - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`) + +SPEC REPOS: + trunk: + - AgoraInfra_iOS + - AgoraIrisRTC_iOS + - AgoraRtcEngine_iOS + - AppAuth + - AppCheckCore + - Firebase + - FirebaseAppCheckInterop + - FirebaseAuth + - FirebaseAuthInterop + - FirebaseCore + - FirebaseCoreExtension + - FirebaseCoreInternal + - FirebaseCrashlytics + - FirebaseInstallations + - FirebaseRemoteConfigInterop + - FirebaseSessions + - GoogleDataTransport + - GoogleSignIn + - GoogleUtilities + - GTMAppAuth + - GTMSessionFetcher + - HydraAsync + - libwebp + - Mantle + - nanopb + - OrderedSet + - PromisesObjC + - PromisesSwift + - QGVAPlayer + - RecaptchaInterop + - SDWebImage + - SDWebImageWebPCoder + - SwiftyBeaver + - TOCropViewController + - TXIMSDK_Plus_iOS_XCFramework + +EXTERNAL SOURCES: + agora_rtc_engine: + :path: ".symlinks/plugins/agora_rtc_engine/ios" + app_links: + :path: ".symlinks/plugins/app_links/ios" + audioplayers_darwin: + :path: ".symlinks/plugins/audioplayers_darwin/darwin" + device_info_plus: + :path: ".symlinks/plugins/device_info_plus/ios" + firebase_auth: + :path: ".symlinks/plugins/firebase_auth/ios" + firebase_core: + :path: ".symlinks/plugins/firebase_core/ios" + firebase_crashlytics: + :path: ".symlinks/plugins/firebase_crashlytics/ios" + Flutter: + :path: Flutter + flutter_foreground_task: + :path: ".symlinks/plugins/flutter_foreground_task/ios" + flutter_image_compress_common: + :path: ".symlinks/plugins/flutter_image_compress_common/ios" + flutter_inappwebview_ios: + :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" + fluttertoast: + :path: ".symlinks/plugins/fluttertoast/ios" + google_sign_in_ios: + :path: ".symlinks/plugins/google_sign_in_ios/darwin" + image_cropper: + :path: ".symlinks/plugins/image_cropper/ios" + image_picker_ios: + :path: ".symlinks/plugins/image_picker_ios/ios" + in_app_purchase_storekit: + :path: ".symlinks/plugins/in_app_purchase_storekit/darwin" + iris_method_channel: + :path: ".symlinks/plugins/iris_method_channel/ios" + loading_indicator_view_plus: + :path: ".symlinks/plugins/loading_indicator_view_plus/ios" + on_audio_query_ios: + :path: ".symlinks/plugins/on_audio_query_ios/ios" + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + permission_handler_apple: + :path: ".symlinks/plugins/permission_handler_apple/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + sign_in_with_apple: + :path: ".symlinks/plugins/sign_in_with_apple/ios" + social_sharing_plus: + :path: ".symlinks/plugins/social_sharing_plus/ios" + sqflite_darwin: + :path: ".symlinks/plugins/sqflite_darwin/darwin" + store_redirect: + :path: ".symlinks/plugins/store_redirect/ios" + tancent_vap: + :path: ".symlinks/plugins/tancent_vap/ios" + tencent_cloud_chat_sdk: + :path: ".symlinks/plugins/tencent_cloud_chat_sdk/ios" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + video_player_avfoundation: + :path: ".symlinks/plugins/video_player_avfoundation/darwin" + video_thumbnail: + :path: ".symlinks/plugins/video_thumbnail/ios" + wakelock_plus: + :path: ".symlinks/plugins/wakelock_plus/ios" + webview_flutter_wkwebview: + :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" + +SPEC CHECKSUMS: + agora_rtc_engine: 0c7d50312967c4dc31c3c45e50589ce48f57e08a + AgoraInfra_iOS: 3691b2b277a1712a35ae96de25af319de0d73d08 + AgoraIrisRTC_iOS: eab58c126439adf5ec99632828a558ea216860da + AgoraRtcEngine_iOS: 97e2398a2addda9057815a2a583a658e36796ff6 + app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a + AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73 + AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f + audioplayers_darwin: 835ced6edd4c9fc8ebb0a7cc9e294a91d99917d5 + device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896 + Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e + firebase_auth: 50af8366c87bb88c80ebeae62eb60189c7246b9b + firebase_core: 995454a784ff288be5689b796deb9e9fa3601818 + firebase_crashlytics: 30dcd6dfd2fe895c0848af46722a4227346c19aa + FirebaseAppCheckInterop: 06fe5a3799278ae4667e6c432edd86b1030fa3df + FirebaseAuth: a6575e5fbf46b046c58dc211a28a5fbdd8d4c83b + FirebaseAuthInterop: 7087d7a4ee4bc4de019b2d0c240974ed5d89e2fd + FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e + FirebaseCoreExtension: edbd30474b5ccf04e5f001470bdf6ea616af2435 + FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4 + FirebaseCrashlytics: e09d0bc19aa54a51e45b8039c836ef73f32c039a + FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843 + FirebaseRemoteConfigInterop: 1c6135e8a094cc6368949f5faeeca7ee8948b8aa + FirebaseSessions: b9a92c1c51bbb81e78fc3142cda6d925d700f8e7 + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + flutter_foreground_task: a159d2c2173b33699ddb3e6c2a067045d7cebb89 + flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1 + flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 + fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 + google_sign_in_ios: b48bb9af78576358a168361173155596c845f0b9 + GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 + GoogleSignIn: ce8c89bb9b37fb624b92e7514cc67335d1e277e4 + GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 + GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de + GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 + HydraAsync: 8d589bd725b0224f899afafc9a396327405f8063 + image_cropper: 655b3ba703c9e15e3111e79151624d6154288774 + image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a + in_app_purchase_storekit: d1a48cb0f8b29dbf5f85f782f5dd79b21b90a5e6 + iris_method_channel: 7d661cf3259b3009ae423508470dbeb9374446ee + libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 + loading_indicator_view_plus: 33a5d3527e9c6a7b470c79878613d01c4fa1fb65 + Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d + nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 + on_audio_query_ios: 28a780e2d0d85d92d500ba6e12c6c8167022b2fa + OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d + PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 + PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 + QGVAPlayer: a0bca68c9bd6f1c8de5ac2d10ddf98be6038cce9 + RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba + SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf + SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377 + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418 + social_sharing_plus: e6024862e5a4be59ef8c97a93558cba1043628bf + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + store_redirect: 55fd455802ceab09803b2df6e68f19a58815266a + SwiftyBeaver: 84069991dd5dca07d7069100985badaca7f0ce82 + tancent_vap: 4917210cc7e916023fd112655170d8e3d074f482 + tencent_cloud_chat_sdk: 55e5fffe20f6b7937a26a674ccccb639563a9790 + TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863 + TXIMSDK_Plus_iOS_XCFramework: 5d1933192fb3b7ef2fe933f1623de4a0486a7fe2 + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b + video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140 + wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 + webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2 + +PODFILE CHECKSUM: a6f49a93e5f85201a2efdadcd7cf184b0e310894 + +COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..6c6f7a1 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,780 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3AA5D06ED1878042C8461CFA /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5787BE1D8BF9BB6960F7B587 /* Pods_Runner.framework */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 46D37906416913B201350BFF /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8CD6B2D6477A38E56A72C61 /* Pods_RunnerTests.framework */; }; + 525B8C722ED013520007B725 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 525B8C712ED013520007B725 /* GoogleService-Info.plist */; }; + 52DF38A12F110B0B0086FBE4 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52DF38A02F110B0B0086FBE4 /* StoreKit.framework */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 2DBA1462EA0F13259D54391C /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 3144A376A151695EA2D1A4FE /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3E47BBEC2076B0DEF3312C28 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 525B8C712ED013520007B725 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 52DF389D2F0E79020086FBE4 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; + 52DF38A02F110B0B0086FBE4 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; + 52DF38A22F110EC10086FBE4 /* RunnerRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerRelease.entitlements; sourceTree = ""; }; + 5787BE1D8BF9BB6960F7B587 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 628A4C79D49FE8929E845244 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 6FE5DAD7939E244362D1F7DA /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 7147A3F76DA0C2CFC820ED1C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D8CD6B2D6477A38E56A72C61 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 52DF38A12F110B0B0086FBE4 /* StoreKit.framework in Frameworks */, + 3AA5D06ED1878042C8461CFA /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A0E84D3ADAE5F6DDD480F35D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 46D37906416913B201350BFF /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 03AFDC00C5136405689DA84C /* Frameworks */ = { + isa = PBXGroup; + children = ( + 52DF38A02F110B0B0086FBE4 /* StoreKit.framework */, + 5787BE1D8BF9BB6960F7B587 /* Pods_Runner.framework */, + D8CD6B2D6477A38E56A72C61 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 3B8051ED51CBDB3CF1DF0F5B /* Pods */ = { + isa = PBXGroup; + children = ( + 7147A3F76DA0C2CFC820ED1C /* Pods-Runner.debug.xcconfig */, + 6FE5DAD7939E244362D1F7DA /* Pods-Runner.release.xcconfig */, + 3E47BBEC2076B0DEF3312C28 /* Pods-Runner.profile.xcconfig */, + 3144A376A151695EA2D1A4FE /* Pods-RunnerTests.debug.xcconfig */, + 628A4C79D49FE8929E845244 /* Pods-RunnerTests.release.xcconfig */, + 2DBA1462EA0F13259D54391C /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 3B8051ED51CBDB3CF1DF0F5B /* Pods */, + 03AFDC00C5136405689DA84C /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 52DF38A22F110EC10086FBE4 /* RunnerRelease.entitlements */, + 52DF389D2F0E79020086FBE4 /* RunnerDebug.entitlements */, + 525B8C712ED013520007B725 /* GoogleService-Info.plist */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + CFD1FCCAE92AFBF15E8B607E /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + A0E84D3ADAE5F6DDD480F35D /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9AAFF935C9514CBA898ADFC8 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + F1AFD9E9E8DBA4F1609CE22F /* [CP] Embed Pods Frameworks */, + 5DF67264ADC5268AC3021CED /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 525B8C722ED013520007B725 /* GoogleService-Info.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 5DF67264ADC5268AC3021CED /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + 9AAFF935C9514CBA898ADFC8 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + CFD1FCCAE92AFBF15E8B607E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + F1AFD9E9E8DBA4F1609CE22F /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPat_icon_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 35; + DEVELOPMENT_TEAM = S9X2AJ2US9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.2.0; + PRODUCT_BUNDLE_IDENTIFIER = com.smartteam.atu; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3144A376A151695EA2D1A4FE /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.atu.atu.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 628A4C79D49FE8929E845244 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.atu.atu.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2DBA1462EA0F13259D54391C /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.atu.atu.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPat_icon_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 35; + DEVELOPMENT_TEAM = F33K8VUZ62; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.2.0; + PRODUCT_BUNDLE_IDENTIFIER = com.smartteam.atu; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPat_icon_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 35; + DEVELOPMENT_TEAM = F33K8VUZ62; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.2.0; + PRODUCT_BUNDLE_IDENTIFIER = com.smartteam.atu; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..e3773d4 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,5 @@ + + + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..f04e007 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,79 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + // 获取Flutter引擎 + let controller : FlutterViewController = window?.rootViewController as! FlutterViewController + // 创建MethodChannel + let channel = FlutterMethodChannel(name: "com.chat.auu.snapchat_share/intent", binaryMessenger: controller.binaryMessenger) + + // 设置方法调用处理器 + channel.setMethodCallHandler { [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in + // 处理shareToSnapchat方法调用 + if call.method == "shareToSnapchat" { + // 获取Flutter传递的text参数 + let text = call.arguments as? [String: Any]? ?? [:] + let shareText = text?["text"] as? String ?? "" + // 调用分享方法 + self?.shareToSnapchat(text: shareText, result: result) + } else { + // 方法未实现 + result(FlutterMethodNotImplemented) + } + } + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + private func shareToSnapchat(text: String, result: @escaping FlutterResult) { + // 1. 构建Snapchat的分享URL + guard let snapchatUrl = URL(string: "snapchat://") else { + result(FlutterError(code: "INVALID_URL", message: "Snapchat URL无效", details: nil)) + return + } + + // 2. 检查设备是否安装了Snapchat + if UIApplication.shared.canOpenURL(snapchatUrl) { + // 构建分享参数(根据Snapchat的URL Scheme文档调整) + let shareParams = [ + "text": text + ] + + // 3. 拼接分享URL(示例,具体参数请参考Snapchat官方文档) + var components = URLComponents(url: snapchatUrl, resolvingAgainstBaseURL: true) + components?.queryItems = shareParams.map { URLQueryItem(name: $0.key, value: $0.value) } + + guard let finalUrl = components?.url else { + result(FlutterError(code: "URL_BUILD_FAILED", message: "Open Snapchat Fail", details: nil)) + return + } + + // 4. 打开Snapchat进行分享 + if #available(iOS 10.0, *) { + UIApplication.shared.open(finalUrl, options: [:]) { success in + if success { + result(nil) // 分享成功 + } else { + result(FlutterError(code: "SHARE_FAILED", message: "Open Snapchat Fail", details: nil)) + } + } + } else { + // iOS 9及以下兼容 + let success = UIApplication.shared.openURL(finalUrl) + if success { + result(nil) + } else { + result(FlutterError(code: "SHARE_FAILED", message: "Open Snapchat Fail", details: nil)) + } + } + } else { + // 设备未安装Snapchat + result(FlutterError(code: "SNAPCHAT_NOT_INSTALLED", message: "Snapchat is not installed.", details: nil)) + } + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d0d98aa --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1 @@ +{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dd24b58 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..ab830be Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..1956eaa Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..ed20948 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..80d515a Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..ae0652a Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..bb9f081 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..1956eaa Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..8cce04e Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..57e5d48 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png new file mode 100644 index 0000000..af7cecf Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png new file mode 100644 index 0000000..c19090f Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png new file mode 100644 index 0000000..a62e389 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png new file mode 100644 index 0000000..dd051fa Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..57e5d48 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..f572d4e Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png new file mode 100644 index 0000000..f667e54 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png new file mode 100644 index 0000000..e509773 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..e51633e Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..a5d914a Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..4d6132e Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +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. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f9d920d --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist new file mode 100644 index 0000000..0e57c82 --- /dev/null +++ b/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,36 @@ + + + + + CLIENT_ID + 52796773508-dk441c9e7tdmqv47mbdt2vpbn2clrii8.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.52796773508-dk441c9e7tdmqv47mbdt2vpbn2clrii8 + ANDROID_CLIENT_ID + 52796773508-2ruai8tqhp0knuofo02ob1cmheu76u2d.apps.googleusercontent.com + API_KEY + AIzaSyAeeeuw_zKUQmdUUiOXD4u_BvQqaV3w9Gk + GCM_SENDER_ID + 52796773508 + PLIST_VERSION + 1 + BUNDLE_ID + com.smartteam.atu + PROJECT_ID + atu-7b503 + STORAGE_BUCKET + atu-7b503.firebasestorage.app + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:52796773508:ios:7dfa56d19e1711ab65ded1 + + \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..6d14594 --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,94 @@ + + + + +LSApplicationQueriesSchemes + + fb + fbapi + fb-messenger-api + whatsapp + snapchat + paytmmp + phonepe + upi + + CADisableMinimumFrameDurationOnPhone + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Aslan + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Aslan + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + + + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + NSAppleMusicUsageDescription + We need access to the media library so that you can play your favorite local music files in the voice background. + NSCameraUsageDescription + We need access to your camera so you can take a photo and make it a profile picture, or take and share a picture in real time in a voice chat room. + NSMicrophoneUsageDescription + We need access to your microphone so that you can make live calls, record, and send voice messages with other users in the voice dating room. + NSPhotoLibraryAddUsageDescription + We need your permission to save beautiful images you take or edit in the app to your phone album. + NSPhotoLibraryUsageDescription + We need access to your photo album so you can choose photos from it to update your profile picture or share moments from your life in chat. + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UIViewControllerBasedStatusBarAppearance + + BGTaskSchedulerPermittedIdentifiers + + com.pravera.flutter_foreground_task.refresh + + UIBackgroundModes + + audio + + com.apple.developer.applesignin + + Default + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/ios/Runner/RunnerDebug.entitlements b/ios/Runner/RunnerDebug.entitlements new file mode 100644 index 0000000..a812db5 --- /dev/null +++ b/ios/Runner/RunnerDebug.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.applesignin + + Default + + + diff --git a/ios/Runner/RunnerRelease.entitlements b/ios/Runner/RunnerRelease.entitlements new file mode 100644 index 0000000..a812db5 --- /dev/null +++ b/ios/Runner/RunnerRelease.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.applesignin + + Default + + + diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/lib/app_localizations.dart b/lib/app_localizations.dart new file mode 100644 index 0000000..4a6135a --- /dev/null +++ b/lib/app_localizations.dart @@ -0,0 +1,1673 @@ +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; + +class ATAppLocalizations { + final Locale locale; + + ATAppLocalizations(this.locale); + + static ATAppLocalizations? of(BuildContext context) { + return Localizations.of(context, ATAppLocalizations); + } + + static const LocalizationsDelegate delegate = + _ATAppLocalizationsDelegate(); + + Map? _localizedStrings; + + Future load() async { + final jsonString = await rootBundle.loadString( + 'assets/l10n/intl_${locale.languageCode}.json', + ); + + final Map jsonMap = json.decode(jsonString); + + _localizedStrings = jsonMap.map((key, value) { + return MapEntry(key, value.toString()); + }); + + return true; + } + + String translate(String key) { + return _localizedStrings?[key] ?? key; + } + + // 添加具体翻译键的getter方法 + String get signInWithGoogle => translate('signInWithGoogle'); + + String get or => translate('or'); + + String get rechargeRecords => translate('rechargeRecords'); + + String get dailyTasksTips => translate('dailyTasksTips'); + + String get shop => translate('shop'); + + String get selectCountry => translate('selectCountry'); + + String get hot => translate('hot'); + + String get sound2 => translate('sound2'); + + String get giftEffect => translate('giftEffect'); + + String get winFloat => translate('winFloat'); + + String get giftVibration => translate('giftVibration'); + + String get transactionReceived => translate('transactionReceived'); + + String get other => translate('other'); + + String get balance => translate('balance'); + + String get list => translate('list'); + + String get inviteToBecomeAHost => translate('inviteToBecomeAHost'); + + String get uploadGifAvatar => translate('uploadGifAvatar'); + + String get uploadProfilePicture => translate('uploadProfilePicture'); + + String get maliciousHarassment => translate('maliciousHarassment'); + + String get roomEdit => translate('roomEdit'); + + String get searchCountry => translate('searchCountry'); + + String get permissionSettings => translate('permissionSettings'); + + String get roomOwner => translate('roomOwner'); + + String get levelIcon => translate('levelIcon'); + + String get idIcon => translate('idIcon'); + + String get luckGiftRuleTips => translate('luckGiftRuleTips'); + + String get levelMedal => translate('levelMedal'); + + String get searchNoDataTips => translate('searchNoDataTips'); + + String get preventBeingBlocked => translate('preventBeingBlocked'); + + String get enableRankIncognitoMode => translate('enableRankIncognitoMode'); + + String get vipPrivilege => translate('vipPrivilege'); + + String get expirationTime => translate('expirationTime'); + + String get inviteNewUsersToEarnCoins => translate('inviteNewUsersToEarnCoins'); + + String get avoidBeingKicked => translate('avoidBeingKicked'); + + String get vipEntranceEffect => translate('vipEntranceEffect'); + + String get vipSpecialGiftTassel => translate('vipSpecialGiftTassel'); + + String get youHaveNotHadVIPYet => translate('youHaveNotHadVIPYet'); + + String get games => translate('games'); + + String get gifProfileUpload => translate('gifProfileUpload'); + + String get pleaseEnterContent => translate('pleaseEnterContent'); + + String get profilePhoto => translate('profilePhoto'); + + String get aboutMe => translate('aboutMe'); + + String get numberOfMic => translate('numberOfMic'); + + String get noHistoricalRecordsAvailable => translate('noHistoricalRecordsAvailable'); + + String get myRoom => translate('myRoom'); + + String get biggestDiscount => translate('biggestDiscount'); + + String get googlePay => translate('googlePay'); + + String get appStore => translate('appStore'); + + String get selectACountry => translate('selectACountry'); + + String get officialRechargeAgent => translate('officialRechargeAgent'); + + String get cancelRoomPassword => translate('cancelRoomPassword'); + + String get roomMemberFee => translate('roomMemberFee'); + + String get roomPassword => translate('roomPassword'); + + String get roomTheme2 => translate('roomTheme2'); + + String get blockedList2 => translate('blockedList2'); + + String get crateMyRoom => translate('crateMyRoom'); + + String get exclusiveEmojiWillBeReleasedAfterBecoming => translate('exclusiveEmojiWillBeReleasedAfterBecoming'); + + String get socialPrivilege => translate('socialPrivilege'); + + String get mysteriousInvisibility => translate('mysteriousInvisibility'); + + String get antiBlock => translate('antiBlock'); + + String get goToUpgrade => translate('goToUpgrade'); + + String get playLog => translate('playLog'); + + String get createFamilyForFree => translate('createFamilyForFree'); + + String get privateChat => translate('privateChat'); + + String get everyone => translate('everyone'); + + String get basicPermissions => translate('basicPermissions'); + + String get vipBirthdayGift => translate('vipBirthdayGift'); + + String get pleaseUpgradeYourVipLevel => translate('pleaseUpgradeYourVipLevel'); + + String get membershipFreeChatSpeak => translate('membershipFreeChatSpeak'); + + String get priorityRoomSorting => translate('priorityRoomSorting'); + + String get userColoredID => translate('userColoredID'); + + String get casualInteraction => translate('casualInteraction'); + + String get haveGamePlayingTips => translate('haveGamePlayingTips'); + + String get historicalTour => translate('historicalTour'); + + String get gameCenter => translate('gameCenter'); + + String get returnToVoiceChat => translate('returnToVoiceChat'); + + String get exitGameMode => translate('exitGameMode'); + + String get enterTheRoom => translate('enterTheRoom'); + + String get taskNamePersonalGameConsume => translate('taskNamePersonalGameConsume'); + + String get taskNamePersonalMicInRoom => translate('taskNamePersonalMicInRoom'); + + String get taskNamePersonalActiveInRoom => translate('taskNamePersonalActiveInRoom'); + + String get taskNameRoomOwnerMicTime => translate('taskNameRoomOwnerMicTime'); + + String get taskNamePersonalLuckyGiftGold => translate('taskNamePersonalLuckyGiftGold'); + + String get taskNamePersonalMagicGiftGold => translate('taskNamePersonalMagicGiftGold'); + + String get taskNameRoomOwnerSendGiftGold => translate('taskNameRoomOwnerSendGiftGold'); + + String get taskNameRoomUserSendGiftGold => translate('taskNameRoomUserSendGiftGold'); + + String get taskNameRoomOwnerInviteMic => translate('taskNameRoomOwnerInviteMic'); + + String get taskNameRoomOnlineUserCount => translate('taskNameRoomOnlineUserCount'); + + String get taskNamePersonalSendGift => translate('taskNamePersonalSendGift'); + + String get taskNameRoomMicUser30Min => translate('taskNameRoomMicUser30Min'); + + String get taskNameRoomMicUser60Min => translate('taskNameRoomMicUser60Min'); + + String get taskNameRoomMicUser120Min => translate('taskNameRoomMicUser120Min'); + + String get taskNameRoomOwnerSendGiftUser => translate('taskNameRoomOwnerSendGiftUser'); + + String get taskNameRoomOwnerSendRedPacket => translate('taskNameRoomOwnerSendRedPacket'); + + String get taskNameRoomNewMember => translate('taskNameRoomNewMember'); + + String get copyLink => translate('copyLink'); + + String get shareTo => translate('shareTo'); + + String get invite => translate('invite'); + + String get faceBook => translate('faceBook'); + + String get whatsApp => translate('whatsApp'); + + String get snapChat => translate('snapChat'); + + String get complete => translate('complete'); + + String get claim => translate('claim'); + + String get inviteGoRoomTips => translate('inviteGoRoomTips'); + + String get dailyCoinBonanzaRules => translate('dailyCoinBonanzaRules'); + + String get dailyCoinBonanzaRulesDetail => translate('dailyCoinBonanzaRulesDetail'); + + String get personalTasks => translate('personalTasks'); + + String get roomOwnerTasks => translate('roomOwnerTasks'); + + String get getPaidToRefer => translate('getPaidToRefer'); + + String get weekStart => translate('weekStart'); + + String get kingQuuen => translate('kingQuuen'); + + String get rechargeSuccessful => translate('rechargeSuccessful'); + + String get operationFail => translate('operationFail'); + + String get forMoreRewardsPleaseCheckTheTaskCenter => translate('forMoreRewardsPleaseCheckTheTaskCenter'); + + String get likedYourComment => translate('likedYourComment'); + + String get deleteAccountTips2 => translate('deleteAccountTips2'); + + String get likedYourDynamic => translate('likedYourDynamic'); + + String get doYouWantToKeepTheDraft => translate('doYouWantToKeepTheDraft'); + + String get createDynamicSuccess => translate('createDynamicSuccess'); + + String get like => translate('like'); + + String get noPromptsToday => translate('noPromptsToday'); + + String get currentVip => translate('currentVip'); + + String get ramadan => translate('ramadan'); + + String get deleteDynamicTips => translate('deleteDynamicTips'); + + String get deleteCommentTips => translate('deleteCommentTips'); + + String get deleteSuccessful => translate('deleteSuccessful'); + + String get itemsLeft => translate('itemsLeft'); + + String get replySucc => translate('replySucc'); + + String get basicFeatures => translate('basicFeatures'); + + String get areYouSureYouWantToDeleteYourAccount => translate('areYouSureYouWantToDeleteYourAccount'); + + String get game => translate('game'); + + String get accountDeletionNotice => translate('accountDeletionNotice'); + + String get deleteAccountTips => translate('deleteAccountTips'); + + String get thisUserHasBeenBlacklisted => translate('thisUserHasBeenBlacklisted'); + + String get comment => translate('comment'); + + String get cantSendDynamicTips => translate('cantSendDynamicTips'); + + String get customizedGiftRules => translate('customizedGiftRules'); + + String get customizedGiftRulesContent => translate('customizedGiftRulesContent'); + + String get clearCache => translate('clearCache'); + + String get saySomething => translate('saySomething'); + + String get catchFirstComment => translate('catchFirstComment'); + + String get dynamicT => translate('dynamic'); + + String get more => translate('more'); + + String get roomAnnouncement => translate('roomAnnouncement'); + + String get reply => translate('reply'); + + String get importantReminder => translate('importantReminder'); + + String get enterThisVoiceChatRoom => translate('enterThisVoiceChatRoom'); + + String get swipeLeftOnTheFloatingScreenAreaToQuicklyCloseIt => translate('swipeLeftOnTheFloatingScreenAreaToQuicklyCloseIt'); + + String get areYouSureYouWantToClearLocalCache => translate('areYouSureYouWantToClearLocalCache'); + + String get multiple => translate('multiple'); + + String get posting => translate('posting'); + + String get showMore => translate('showMore'); + + String get showLess => translate('showLess'); + + String get clearCacheSuccessfully => translate('clearCacheSuccessfully'); + + String get successfullyAddedToTheBlacklist => translate('successfullyAddedToTheBlacklist'); + + String get successfullyRemovedFromTheBlacklist => translate('successfullyRemovedFromTheBlacklist'); + + String get successfullyRemovedFromTheDynamicBlacklist => translate('successfullyRemovedFromTheDynamicBlacklist'); + + String get successfullyAddedToTheDynamicBlacklist => translate('successfullyAddedToTheDynamicBlacklist'); + + String get areYouSureToCancelBlacklist => + translate('areYouSureToCancelBlacklist'); + + String get areYouSureYouWantToBlockThisUser => + translate('areYouSureYouWantToBlockThisUser'); + + String get areYouSureYouWantToDynamicBlockThisUser => + translate('areYouSureYouWantToDynamicBlockThisUser'); + + String get removeFromBlacklist => translate('removeFromBlacklist'); + + String get unblockUserDynamic => translate('unblockUserDynamic'); + + String get blockUserDynamic => translate('blockUserDynamic'); + + String get moveToBlacklist => translate('moveToBlacklist'); + + String get userBlacklist => translate('userBlacklist'); + + String get areYouSureToCancelDynamicBlacklist => translate('areYouSureToCancelDynamicBlacklist'); + + String get thisFeatureIsCurrentlyUnavailable => + translate('thisFeatureIsCurrentlyUnavailable'); + + String get pleaseSelectTheRecipient => translate('pleaseSelectTheRecipient'); + + String get searchMemberIdHint => translate('searchMemberIdHint'); + + String get win => translate('win'); + + String get luckGiftSpecialEffects => translate('luckGiftSpecialEffects'); + + String get receivedFromALuckyGift => translate('receivedFromALuckyGift'); + + String get entryVehicleAnimation => translate('entryVehicleAnimation'); + + String get entryVehicleAnimation2 => translate('entryVehicleAnimation2'); + + String get floatingAnimationInGlobal => + translate('floatingAnimationInGlobal'); + + String get theRedEnvelopeHasExpired => translate('theRedEnvelopeHasExpired'); + + String get wishingYouHappinessEveryDay => + translate('wishingYouHappinessEveryDay'); + + String get newMessage => translate('newMessage'); + + String get familyNews => translate('familyNews'); + + String get familyName => translate('familyName'); + + String get help => translate('help'); + + String get open => translate('open'); + + String get canSendMsgTips => translate('canSendMsgTips'); + + String get systemAnnouncement => translate('systemAnnouncement'); + + String get systemAnnouncementTips1 => translate('systemAnnouncementTips1'); + + String get contactUs => translate('contactUs'); + + String get sentARedEnvelope => translate('sentARedEnvelope'); + + String get systemAnnouncementTips => translate('systemAnnouncementTips'); + + String get doNotClickUnfamiliarTips => translate('doNotClickUnfamiliarTips'); + + String get disbandTheFamilyTips => translate('disbandTheFamilyTips'); + + String get familyHelpTips => translate('familyHelpTips'); + + String get familyInfo => translate('familyInfo'); + + String get joinRequest => translate('joinRequest'); + + String get createRoomSuccsess => translate('createRoomSuccsess'); + + String get enterFamilyInfo => translate('enterFamilyInfo'); + + String get searchFamilyIdHint => translate('searchFamilyIdHint'); + + String get enterFamilyName => translate('enterFamilyName'); + + String get cpList => translate('cpList'); + + String get redEnvelopeNotYetClaimed => translate('redEnvelopeNotYetClaimed'); + + String get refuse => translate('refuse'); + + String get sayHi2 => translate('sayHi2'); + + String get rulesUpload => translate('rulesUpload'); + + String get agree => translate('agree'); + + String get coins3 => translate('coins3'); + + String get trend => translate('trend'); + + String get discard => translate('discard'); + + String get atTag => translate('atTag'); + + String get keep => translate('keep'); + + String get magic => translate('magic'); + + String get customized => translate('customized'); + + String get unclaimedRedEnvelopes => translate('unclaimedRedEnvelopes'); + + String get boxContributeTips => translate('boxContributeTips'); + + String get supporterWeeklyRank => translate('supporterWeeklyRank'); + + String get hostWeeklyRank => translate('hostWeeklyRank'); + + String get noMatchedCP => translate('noMatchedCP'); + + String get giftSpecialEffects => translate('giftSpecialEffects'); + + String get familyLevel => translate('familyLevel'); + + String get applyToJoin => translate('applyToJoin'); + + String get msgSendRedEnvelopeTips => translate('msgSendRedEnvelopeTips'); + + String get ownerSendTheRedEnvelope => translate('ownerSendTheRedEnvelope'); + + String get hotGames => translate('hotGames'); + + String get levelPrivileges => translate('levelPrivileges'); + + String get cpRequest => translate('cpRequest'); + + String get areYouSureYouWantToSpend3 => + translate('areYouSureYouWantToSpend3'); + + String get areYouSureYouWantToSpend => translate('areYouSureYouWantToSpend'); + + String get areYouSureYouWantToSpend2 => + translate('areYouSureYouWantToSpend2'); + + String get allGames => translate('allGames'); + + String get restorePurchases => translate('restorePurchases'); + + String get createFamily => translate('createFamily'); + + String get agreeJoinFamilyTips => translate('agreeJoinFamilyTips'); + + String get refuseJoinFamilyTips => translate('refuseJoinFamilyTips'); + + String get welcomeToMyFamily => translate('welcomeToMyFamily'); + + String get slotsClass => translate('slotsClass'); + + String get greedyClass => translate('greedyClass'); + + String get raceSeries => translate('raceSeries'); + + String get fishClass => translate('fishClass'); + + String get supporterList => translate('supporterList'); + + String get hostList => translate('hostList'); + + String get pending => translate('pending'); + + String get approved => translate('approved'); + + String get restorePurchasesTips => translate('restorePurchasesTips'); + + String get rejected => translate('rejected'); + + String get applicationRecord => translate('applicationRecord'); + + String get joinFamily => translate('joinFamily'); + + String get received => translate('received'); + + String get others => translate('others'); + + String get cpSexTips => translate('cpSexTips'); + + String get editProfile => translate('editProfile'); + + String get myPhoto => translate('myPhoto'); + + String get balanceNotEnough => translate('balanceNotEnough'); + + String get chooseFromAblum => translate('chooseFromAblum'); + + String get information => translate('information'); + + String get memberList => translate('memberList'); + + String get supporter => translate('supporter'); + + String get family => translate('family'); + + String get spaceBackground => translate('spaceBackground'); + + String get currentProgress => translate('currentProgress'); + + String get redEnvelopeTips2 => translate('redEnvelopeTips2'); + + String get openTheTreasureChest => translate('openTheTreasureChest'); + + String get number2 => translate('number2'); + + String get underReview => translate('underReview'); + + String get createAFamily => translate('createAFamily'); + + String get doYouWantToDeleteIt => translate('doYouWantToDeleteIt'); + + String get lastWeekProgress => translate('lastWeekProgress'); + + String get roomReward => translate('roomReward'); + + String get goToRecharge => translate('goToRecharge'); + + String get insufhcientGoldsGoToRecharge => + translate('insufhcientGoldsGoToRecharge'); + + String get redEnvelopeRecTips2 => translate('redEnvelopeRecTips2'); + + String get redEnvelopeRecTips3 => translate('redEnvelopeRecTips3'); + + String get sendARedEnvelope => translate('sendARedEnvelope'); + + String get redEnvelopeSendingRecords => + translate('redEnvelopeSendingRecords'); + + String get countdownMinutes => translate('countdownMinutes'); + + String get spam => translate('spam'); + + String get sendRedPackConfirmTips => translate('sendRedPackConfirmTips'); + + String get redEnvelopeTips1 => translate('redEnvelopeTips1'); + + String get canOnlyBeShown => translate('canOnlyBeShown'); + + String get walletBalance => translate('walletBalance'); + + String get roomTools => translate('roomTools'); + + String get fraud => translate('fraud'); + + String get redEnvelope => translate('redEnvelope'); + + String get dice => translate('dice'); + + String get rps => translate('rps'); + + String get operationsAreTooFrequent => translate('operationsAreTooFrequent'); + + String get luckNumber => translate('luckNumber'); + + String get entertainment => translate('entertainment'); + + String get reportSucc => translate('reportSucc'); + + String get reportInputTips => translate('reportInputTips'); + + String get pornography => translate('pornography'); + + String get redEnvelopeRecTips1 => translate('redEnvelopeRecTips1'); + + String get inappropriateContent => translate('inappropriateContent'); + + String get personalAttack => translate('personalAttack'); + + String get illegalInformation => translate('illegalInformation'); + + String get pleaseSelectTheTypeContent => + translate('pleaseSelectTheTypeContent'); + + String get searchUserId => translate('searchUserId'); + + String get sendUser => translate('sendUser'); + + String get userName => translate('userName'); + + String get identity => translate('identity'); + + String get roomProfilePicture => translate('roomProfilePicture'); + + String get userProfilePicture => translate('userProfilePicture'); + + String get roomNotice => translate('roomNotice'); + + String get roomTheme => translate('roomTheme'); + + String get warning => translate('warning'); + + String get adjust => translate('adjust'); + + String get inputDesHint => translate('inputDesHint'); + + String get screenshotTips => translate('screenshotTips'); + + String get description => translate('description'); + + String get pleaseSelectTheTypeToProcess => + translate('pleaseSelectTheTypeToProcess'); + + String get enterTheRoomId => translate('enterTheRoomId'); + + String get enterTheUserId => translate('enterTheUserId'); + + String get roomEditing => translate('roomEditing'); + + String get userEditing => translate('userEditing'); + + String get wearHonor => translate('wearHonor'); + + String get youDontHaveAnyHonorYet => translate('youDontHaveAnyHonorYet'); + + String get activityHonor => translate('activityHonor'); + + String get achievementHonor => translate('achievementHonor'); + + String get letGoToWatch => translate('letGoToWatch'); + + String get launchedARocket => translate('launchedARocket'); + + String get useCoupontips => translate('useCoupontips'); + + String get joinMemberTips2 => translate('joinMemberTips2'); + + String get leaveRoomIdentityTips => translate('leaveRoomIdentityTips'); + + String get followSucc => translate('followSucc'); + + String get searchCouponHint => translate('searchCouponHint'); + + String get signInWithYourAccount => translate('signInWithYourAccount'); + + String get signInWithApple => translate('signInWithApple'); + + String get deleteConversationTips => translate('deleteConversationTips'); + + String get loginRepresentsAgreementTo => + translate('loginRepresentsAgreementTo'); + + String get termsofService => translate('termsofService'); + + String get theImageSizeCannotExceed => translate('theImageSizeCannotExceed'); + + String get privaceyPolicy => translate('privaceyPolicy'); + + String get youHaventFollowed => translate('youHaventFollowed'); + + String get tips => translate('tips'); + + String get inviteYouToBecomeBD => translate('inviteYouToBecomeBD'); + + String get bDLeaderInviteYouToBecomeBDLeader => + translate('bDLeaderInviteYouToBecomeBDLeader'); + + String get adminInviteRechargeAgent => translate('adminInviteRechargeAgent'); + + String get task => translate('task'); + + String get honor => translate('honor'); + + String get badge => translate('badge'); + + String get glory => translate('glory'); + + String get bio => translate('bio'); + + String get hobby => translate('hobby'); + + String get search => translate('search'); + + String get sendCoupontips => translate('sendCoupontips'); + + String get coupon => translate('coupon'); + + String get done => translate('done'); + + String get join => translate('join'); + + String get roomRocketHelpTips => translate('roomRocketHelpTips'); + + String get giveUpIdentity => translate('giveUpIdentity'); + + String get bdLeader => translate('bdLeader'); + + String get picture => translate('picture'); + + String get signedin => translate('signedin'); + + String get couponRecord => translate('couponRecord'); + + String get improvementTasks => translate('improvementTasks'); + + String get image => translate('image'); + + String get inRocket => translate('inRocket'); + + String get video => translate('video'); + + String get get => translate('get'); + + String get sound => translate('sound'); + + String get wearMedal => translate('wearMedal'); + + String get sureUnfollowThisRoom => translate('sureUnfollowThisRoom'); + + String get camera => translate('camera'); + + String get crop => translate('crop'); + + String get inRoom => translate('inRoom'); + + String get youDontHaveAnyCouponsYet => translate('youDontHaveAnyCouponsYet'); + + String get finish => translate('finish'); + + String get visitorList => translate('visitorList'); + + String get gift2 => translate('gift2'); + + String get receiveSucc => translate('receiveSucc'); + + String get album => translate('album'); + + String get dailyTasks => translate('dailyTasks'); + + String get reject => translate('reject'); + + String get messageHasBeenRecalled => translate('messageHasBeenRecalled'); + + String get accept => translate('accept'); + + String get go => translate('go'); + + String get copy => translate('copy'); + + String get openUserProfleCard => translate('openUserProfleCard'); + + String get deleteOnAllDevices => translate('deleteOnAllDevices'); + + String get deleteFromMyDevice => translate('deleteFromMyDevice'); + + String get recallThisMessage => translate('recallThisMessage'); + + String get recall => translate('recall'); + + String get followedYou => translate('followedYou'); + + String get confirmAcceptTheInvitation => + translate('confirmAcceptTheInvitation'); + + String get confirmDeclineTheInvitation => + translate('confirmDeclineTheInvitation'); + + String get propMessagePrompt => translate('propMessagePrompt'); + + String get chats => translate('chats'); + + String get delete => translate('delete'); + + String get enterRoomConfirmTips => translate('enterRoomConfirmTips'); + + String get inputUserId => translate('inputUserId'); + + String get enterNickname => translate('enterNickname'); + + String get inviteCode => translate('inviteCode'); + + String get notifcation => translate('notifcation'); + + String get gameRules => translate('gameRules'); + + String get male => translate('male'); + + String get familyNotifcations => translate('familyNotifcations'); + + String get female => translate('female'); + + String get pullToLoadMore => translate('pullToLoadMore'); + + String get loadingFailedClickToRetry => + translate('loadingFailedClickToRetry'); + + String get releaseToLoadMore => translate('releaseToLoadMore'); + + String get haveMyLimits => translate('haveMyLimits'); + + String get becomeAgent => translate('becomeAgent'); + + String get setAccount => translate('setAccount'); + + String get deleteAccount => translate('deleteAccount'); + + String get backTheRoom => translate('backTheRoom'); + + String get spendCoinsToGainExperiencePoints => + translate('spendCoinsToGainExperiencePoints'); + + String get enterRoomName => translate('enterRoomName'); + + String get selectYourCountry => translate('selectYourCountry'); + + String get theMembershipFee => translate('theMembershipFee'); + + String get termsOfServicePrivacyPolicyTips => + translate('termsOfServicePrivacyPolicyTips'); + + String get and => translate('and'); + + String get inviteYouToBecomeHost => translate('inviteYouToBecomeHost'); + + String get inviteYouToBecomeAgent => translate('inviteYouToBecomeAgent'); + + String get theVideoSizeCannotExceed => translate('theVideoSizeCannotExceed'); + + String get clickHereToStartChatting => translate('clickHereToStartChatting'); + + String get music => translate('music'); + + String get unread => translate('unread'); + + String get checkInSuccessful => translate('checkInSuccessful'); + + String get sginTips => translate('sginTips'); + + String get adminCenter => translate('adminCenter'); + + String get confirm => translate('confirm'); + + String get cancel => translate('cancel'); + + String get reapply => translate('reapply'); + + String get takeTheMic => translate('takeTheMic'); + + String get unlockTheMic => translate('unlockTheMic'); + + String get lockTheMic => translate('lockTheMic'); + + String get removeTheMic => translate('removeTheMic'); + + String get leavelTheMic => translate('leavelTheMic'); + + String get muteTheMic => translate('muteTheMic'); + + String get openTheMic => translate('openTheMic'); + + String get leavFamilyTips => translate('leavFamilyTips'); + + String get leavingTheFamily => translate('leavingTheFamily'); + + String get inviteToTheMicrophone => translate('inviteToTheMicrophone'); + + String get toConsume => translate('toConsume'); + + String get userLevel => translate('userLevel'); + + String get settings => translate('settings'); + + String get receive => translate('receive'); + + String get sendTheCpRequest => translate('sendTheCpRequest'); + + String get addCp => translate('addCp'); + + String get scrollToTheBottom => translate('scrollToTheBottom'); + + String get host => translate('host'); + + String get read => translate('read'); + + String get system => translate('system'); + + String get receivedAMessage => translate('receivedAMessage'); + + String get stop => translate('stop'); + + String get familyOwner2 => translate('familyOwner2'); + + String get agent => translate('agent'); + + String get obtain => translate('obtain'); + + String get apple => translate('apple'); + + String get google => translate('google'); + + String get wins => translate('wins'); + + String get ra => translate('ra'); + + String get bd => translate('bd'); + + String get charm => translate('charm'); + + String get specialEffectsManagement => translate('specialEffectsManagement'); + + String get giftCounter => translate('giftCounter'); + + String get resetLoginPasswordtTips2 => translate('resetLoginPasswordtTips2'); + + String get enterYourOldPassword => translate('enterYourOldPassword'); + + String get inputYourOldPassword => translate('inputYourOldPassword'); + + String get localMusic => translate('localMusic'); + + String get pleaseUpgradeYourVipLevelFirst => + translate('pleaseUpgradeYourVipLevelFirst'); + + String get howToUpgrade => translate('howToUpgrade'); + + String get higherLevelFancierAvatarFrame => + translate('higherLevelFancierAvatarFrame'); + + String get touristsTakeToTheMic => translate('touristsTakeToTheMic'); + + String get pleaseChatFfriendly => translate('pleaseChatFfriendly'); + + String get startVoiceParty => translate('startVoiceParty'); + + String get mountains => translate('mountains'); + + String get account => translate('account'); + + String get fromLuckyGifts => translate('fromLuckyGifts'); + + String get setLoginPassword => translate('setLoginPassword'); + + String get theTwoPasswordsDoNotMatch => + translate('theTwoPasswordsDoNotMatch'); + + String get confirmYourPassword => translate('confirmYourPassword'); + + String get resetLoginPasswordtTips1 => translate('resetLoginPasswordtTips1'); + + String get medalAndAvatarFrameRewards => + translate('medalAndAvatarFrameRewards'); + + String get theModificationsMade => translate('theModificationsMade'); + + String get touristsSendText => translate('touristsSendText'); + + String get membershipFeeTips1 => translate('membershipFeeTips1'); + + String get membershipFeeTips2 => translate('membershipFeeTips2'); + + String get membershipFee => translate('membershipFee'); + + String get setYourPassword => translate('setYourPassword'); + + String get vip => translate('vip'); + + String get vip1 => translate('vip1'); + + String get vip2 => translate('vip2'); + + String get vip3 => translate('vip3'); + + String get vip4 => translate('vip4'); + + String get vip5 => translate('vip5'); + + String get vip6 => translate('vip6'); + + String get vipChatBox => translate('vipChatBox'); + + String get sendTo => translate('sendTo'); + + String get level => translate('level'); + + String get badgeHonor => translate('badgeHonor'); + + String get add => translate('add'); + + String get free => translate('free'); + + String get openRedPackDialogTip => translate('openRedPackDialogTip'); + + String get start => translate('start'); + + String get vipUseThisThemeTips => translate('vipUseThisThemeTips'); + + String get charmGameRulesTips => translate('charmGameRulesTips'); + + + String get enterYourNewPassword => translate('enterYourNewPassword'); + + String get confirmSwitchMicThemeTips => + translate('confirmSwitchMicThemeTips'); + + String get confirmSwitchMicModelTips => + translate('confirmSwitchMicModelTips'); + + String get micTheme => translate('micTheme'); + + String get conntinue => translate('conntinue'); + + String get renewVip => translate('renewVip'); + + String get buyVip => translate('buyVip'); + + String get couple2 => translate('couple2'); + + String get personal2 => translate('personal2'); + + String get family2 => translate('family2'); + + String get password => translate('password'); + + String get days => translate('days'); + + String get vipMicSoundWave => translate('vipMicSoundWave'); + + String get myMusic => translate('myMusic'); + + String get partWays => translate('partWays'); + + String get reconcileInvitationTips => translate('reconcileInvitationTips'); + + String get reconcileInvitation => translate('reconcileInvitation'); + + String get reconcile => translate('reconcile'); + + String get areYouSureYouWantToSpend4 => + translate('areYouSureYouWantToSpend4'); + + String get partWaysTips => translate('partWaysTips'); + + String get areYouSureYouWantToPartWaysWithYourCP => + translate('areYouSureYouWantToPartWaysWithYourCP'); + + String get separated => translate('separated'); + + String classicMic(String name) => + translate('classicMic').replaceAll('{1}', name); + + String currentLevelPrivilegesAndCostumes(String name) => + translate('currentLevelPrivilegesAndCostumes').replaceAll('{1}', name); + + String redEnvelopeAmount(String name) => + translate('redEnvelopeAmount').replaceAll('{1}', name); + + String yesterday(String name) => + translate('yesterday').replaceAll('{1}', name); + + String monday(String name) => translate('monday').replaceAll('{1}', name); + + String tuesday(String name) => translate('tuesday').replaceAll('{1}', name); + + String wednesday(String name) => + translate('wednesday').replaceAll('{1}', name); + + String vipEmoticon(String name) => + translate('vipEmoticon').replaceAll('{1}', name); + + String family3(String name) => + translate('family3').replaceAll('{1}', name); + + String onlineUsers(String name1, String name2) => + translate( + 'onlineUsers', + ).replaceAll('{1}', name1).replaceAll('{2}', name2); + + String timeSpentTogether(String name) => + translate('timeSpentTogether').replaceAll('{1}', name); + + String firstDay(String name) => translate('firstDay').replaceAll('{1}', name); + + String thursday(String name) => translate('thursday').replaceAll('{1}', name); + + String friday(String name) => translate('friday').replaceAll('{1}', name); + + String saturday(String name) => translate('saturday').replaceAll('{1}', name); + + String sunday(String name) => translate('sunday').replaceAll('{1}', name); + + String win2(String name) => translate('win2').replaceAll('{1}', name); + + String numberOfMyCPs(String name1, String name2) => + translate( + 'numberOfMyCPs', + ).replaceAll('{1}', name1).replaceAll('{2}', name2); + + String sendUserId(String name) => + translate('sendUserId').replaceAll('{1}', name); + + String youAccepted(String name) => + translate('youAccepted').replaceAll('{1}', name); + + String acceptedYour(String name) => + translate('acceptedYour').replaceAll('{1}', name); + + String hasBeenACoinAgencyForDays(String name) => + translate('hasBeenACoinAgencyForDays').replaceAll('{1}', name); + + String successfulTransaction(String name) => + translate('successfulTransaction').replaceAll('{1}', name); + + String couple(String name) => translate('couple').replaceAll('{1}', name); + + String areYouSureYouWantToSpend5(String name) => + translate('areYouSureYouWantToSpend5').replaceAll('{1}', name); + + String areYouSureYouWantToSpend6(String name) => + translate('areYouSureYouWantToSpend6').replaceAll('{1}', name); + + String get micManagement => translate('micManagement'); + + String get logIn => translate('logIn'); + + String get sendMessage => translate('sendMessage'); + + String get availableCountries => translate('availableCountries'); + + String get currentBalance => translate('currentBalance'); + + String get sayHi => translate('sayHi'); + + String get permanent => translate('permanent'); + + String get enterAccount => translate('enterAccount'); + + String get cantBuyVip => translate('cantBuyVip'); + + String get vipRoomCoverBorder => translate('vipRoomCoverBorder'); + + String get bdCenter => translate('bdCenter'); + + String get number => translate('number'); + + String get successfulWear => translate('successfulWear'); + + String get successfullyUnloaded => translate('successfullyUnloaded'); + + String get common => translate('common'); + + String get wealthLevel => translate('wealthLevel'); + + String get roomName => translate('roomName'); + + String get enterPassword => translate('enterPassword'); + + String get goToUpload => translate('goToUpload'); + + String get language => translate('language'); + + String get feedback => translate('feedback'); + + String get about => translate('about'); + + String get unFollow => translate('unFollow'); + + String get vipBadge => translate('vipBadge'); + + String get vipProfileFrame => translate('vipProfileFrame'); + + String get vipProfileCard => translate('vipProfileCard'); + + String get profile => translate('profile'); + + String get props => translate('props'); + + String get relationShip => translate('relationShip'); + + String get medal => translate('medal'); + + String get blockedList => translate('blockedList'); + + String get renewal => translate('renewal'); + + String get daily => translate('daily'); + + String get vipExclusiveVehicles => translate('vipExclusiveVehicles'); + + String get vipRippleTheme => translate('vipRippleTheme'); + + String get kickPrevention => translate('kickPrevention'); + + String get freeChatSpeak => translate('freeChatSpeak'); + + String get weekly => translate('weekly'); + + String get monthly => translate('monthly'); + + String get resetLoginPassword => translate('resetLoginPassword'); + + String get confirmUseTips => translate('confirmUseTips'); + + String get confirmUnUseTips => translate('confirmUnUseTips'); + + String get inUse => translate('inUse'); + + String get themeGoToUploadTips => translate('themeGoToUploadTips'); + + String get myItems => translate('myItems'); + + String get items => translate('items'); + + String get admin => translate('admin'); + + String get medals => translate('medals'); + + String get fansList => translate('fansList'); + + String get message => translate('message'); + + String get friends => translate('friends'); + + String get rechargeAgency => translate('rechargeAgency'); + + String get announcement => translate('announcement'); + + String get aboutUs => translate('aboutUs'); + + String get logout => translate('logout'); + + String get home => translate('home'); + + String get followList => translate('followList'); + + String get country2 => translate('country2'); + + String get giftwall => translate('giftwall'); + + String get purchaseIsSuccessful => translate('purchaseIsSuccessful'); + + String get member => translate('member'); + + String get explore => translate('explore'); + + String get me => translate('me'); + + String get expired => translate('expired'); + + String get confirmBuyTips => translate('confirmBuyTips'); + + String get buy => translate('buy'); + + String get use => translate('use'); + + String get unUse => translate('unUse'); + + String get day => translate('day'); + + String get popular => translate('popular'); + + String get recommend => translate('recommend'); + + String get follow => translate('follow'); + + String get followed => translate('followed'); + + String get following => translate('following'); + + String get history => translate('history'); + + String get hotRooms => translate('hotRooms'); + + String get viewMore => translate('viewMore'); + + String get noData => translate('noData'); + + String get freePrice => translate('freePrice'); + + String get purchase => translate('purchase'); + + String get wallet => translate('wallet'); + + String get viewFrame => translate('viewFrame'); + + String get touristsAreNotAllowedToGoOnTheMic => + translate('touristsAreNotAllowedToGoOnTheMic'); + + String get inputRoomPassword => translate('inputRoomPassword'); + + String get users => translate('users'); + + String get rooms => translate('rooms'); + + String get store => translate('store'); + + String get coins => translate('coins'); + + String get coins4 => translate('coins4'); + + String get custom => translate('custom'); + + String get touristsCannotSendMessages => + translate('touristsCannotSendMessages'); + + String get setRoomPassword => translate('setRoomPassword'); + + String get goldList => translate('goldList'); + + String get submit => translate('submit'); + + String get theme => translate('theme'); + + String get chatBox => translate('chatBox'); + + String get pleaseUploadUserAvatar => translate('pleaseUploadUserAvatar'); + + String get headdress => translate('headdress'); + + String get searchInputHint => translate('searchInputHint'); + + String get kickRoomTips => translate('kickRoomTips'); + + String get joinRoomTips => translate('joinRoomTips'); + + String get youAreCurrentlyCPRelationshipPleaseDissolve => + translate('youAreCurrentlyCPRelationshipPleaseDissolve'); + + String get joinMemberTips => translate('joinMemberTips'); + + String get systemRoomTips => translate('systemRoomTips'); + + String get roomSetting => translate('roomSetting'); + + String get roomDetails => translate('roomDetails'); + + String get copiedToClipboard => translate('copiedToClipboard'); + + String get recharge => translate('recharge'); + + String get enter => translate('enter'); + + String get roomMember => translate('roomMember'); + + String get alreadyAnAdministrator => translate('alreadyAnAdministrator'); + + String get alreadyAnMember => translate('alreadyAnMember'); + + String get alreadyAnTourist => translate('alreadyAnTourist'); + + String get operationSuccessful => translate('operationSuccessful'); + + String get setUpAnIdentity => translate('setUpAnIdentity'); + + String get kickedOutOfRoom => translate('kickedOutOfRoom'); + + String get playGiftMusicAndDynamicMusic => + translate('playGiftMusicAndDynamicMusic'); + + String get agentCenter => translate('agentCenter'); + + String get becomeHost => translate('becomeHost'); + + String get report => translate('report'); + + String get goldListort => translate('goldListort'); + + String get rechargeList => translate('rechargeList'); + + String get edit => translate('edit'); + + String get save => translate('save'); + + String get guest => translate('guest'); + + String get nickName => translate('nickName'); + + String get gender => translate('gender'); + + String get superFans => translate('superFans'); + + String get dateOfBirth => translate('dateOfBirth'); + + String get country => translate('country'); + + String get activity => translate('activity'); + + String get luck => translate('luck'); + + String get cp => translate('cp'); + + String get treasureChest => translate('treasureChest'); + + String get countryRegion => translate('countryRegion'); + + String get birthday => translate('birthday'); + + String get man => translate('man'); + + String get woman => translate('woman'); + + String get mute => translate('mute'); + + String get exit => translate('exit'); + + String get owner => translate('owner'); + + String get adminByHomeowner => translate('adminByHomeowner'); + + String get memberByHomeowner => translate('memberByHomeowner'); + + String get touristByHomeowner => translate('touristByHomeowner'); + + String get allInTheRoom => translate('allInTheRoom'); + + String get mInimize => translate('mInimize'); + + String get hostCenter => translate('hostCenter'); + + String get allOnMicrophone => translate('allOnMicrophone'); + + String get pleaseEnterNickname => translate('pleaseEnterNickname'); + + String get pleaseSelectYourCountry => translate('pleaseSelectYourCountry'); + + String get pleaseSelectYourGender => translate('pleaseSelectYourGender'); + + String get usersOnMicrophone => translate('usersOnMicrophone'); + + String get send => translate('send'); + + String get familyAnnouncement => translate('familyAnnouncement'); + + String get enterFamilyAnnouncement => translate('enterFamilyAnnouncement'); + + String get disbandTheFamily => translate('disbandTheFamily'); + + String get editFamily => translate('editFamily'); + + String get createFamilySuccess => translate('createFamilySuccess'); + + String get sent => translate('sent'); + + String get mine => translate('mine'); + + String get party => translate('party'); + + String get event => translate('event'); + + String get vistors => translate('vistors'); + + String get fans => translate('fans'); + + String get lockTheRoom => translate('lockTheRoom'); + + String get unLockTheRoom => translate('unLockTheRoom'); + + String get all => translate('all'); + + String get chat => translate('chat'); + + String get gift => translate('gift'); + + String get activityMedal => translate('activityMedal'); + + String get achievementMedal => translate('achievementMedal'); + + String get knapsack => translate('knapsack'); + + String get cancelRequest => translate('cancelRequest'); + + String get kickOutOfFamily => translate('kickOutOfFamily'); + + String get kickFamilyUserTips => translate('kickFamilyUserTips'); + + String get cancelFamilyAdmin => translate('cancelFamilyAdmin'); + + String get setAsFamilyAdmin => translate('setAsFamilyAdmin'); + + String get pleaseGetOnTheMicFirst => translate('pleaseGetOnTheMicFirst'); + + String get special => translate('special'); + + String get skip2 => translate('skip2'); + + String get updateNow => translate('updateNow'); + + String get clearMessage => translate('clearMessage'); + + String get pleaseSelectaItem => translate('pleaseSelectaItem'); + + String get areYouRureRoRecharge => translate('areYouRureRoRecharge'); + + String get giftGivingSuccessful => translate('giftGivingSuccessful'); + + String get theAccountPasswordCannotBeEmpty => + translate('theAccountPasswordCannotBeEmpty'); + + String invitesYouToTheMicrophone(String name) => + translate('invitesYouToTheMicrophone').replaceAll('{1}', name); + + String enterRoomTips(String name) => + translate('enterRoomTips').replaceAll('{1}', name); + + String userLevelXPBoost(String name) => + translate('userLevelXPBoost').replaceAll('{1}', name); + + String dailyTaskRewardBonus(String name) => + translate('dailyTaskRewardBonus').replaceAll('{1}', name); + + String storeDiscount(String name) => + translate('storeDiscount').replaceAll('{1}', name); + + String confirmInviteThisUserToTheRoom(String name) => + translate('confirmInviteThisUserToTheRoom').replaceAll('{1}', name); + + String upToAdmins(String name) => + translate('upToAdmins').replaceAll('{1}', name); + + String upToMembers(String name) => + translate('upToMembers').replaceAll('{1}', name); + + String xxfamily(String name) => translate('xxfamily').replaceAll('{1}', name); + + String collectionTimeTips(String time, + String remainCount, + String totalCount,) => + translate('collectionTimeTips') + .replaceAll('{1}', time) + .replaceAll("{2}", remainCount) + .replaceAll("{3}", totalCount); + + String yourVipWillExpire(String name) => + translate('yourVipWillExpire').replaceAll('{1}', name); + + String andAboveUsers(String name) => + translate('andAboveUsers').replaceAll('{1}', name); + + String privileges(String name) => + translate('privileges').replaceAll('{1}', name); + + String remainingNumberTips(String name1, String name2) => + translate( + 'remainingNumberTips', + ).replaceAll('{1}', name1).replaceAll("{2}", name2); + + String coins2(String name) => translate('coins2').replaceAll('{1}', name); + + String credits(String name) => translate('credits').replaceAll('{1}', name); + + String currentStage(String name) => + translate('currentStage').replaceAll('{1}', name); + + String roomReward2(String name) => + translate('roomReward2').replaceAll('{1}', name); + + String ownerIncomeCoins(String name) => + translate('ownerIncomeCoins').replaceAll('{1}', name); + + String rewardCoins(String name) => + translate('rewardCoins').replaceAll('{1}', name); + + String deleteAccount2(String name) => + translate('deleteAccount2').replaceAll('{1}', name); + + String skip(String name) => translate('skip').replaceAll('{1}', name); + + String familyMember2(String name1, String name2) => + translate( + 'familyMember2', + ).replaceAll('{1}', name1).replaceAll('{2}', name2); + + String familyMember3(String name1) => + translate('familyMember3').replaceAll('{1}', name1); + + String familyAdmin2(String name1, String name2) => + translate( + 'familyAdmin2', + ).replaceAll('{1}', name1).replaceAll('{2}', name2); + + String follow2(String name) => translate('follow2').replaceAll('{1}', name); + + String fans2(String name) => translate('fans2').replaceAll('{1}', name); + + String vistors2(String name) => translate('vistors2').replaceAll('{1}', name); + + String duration2(String name) => + translate('duration2').replaceAll('{1}', name); + + String cancelRequestFamilyMsg(String name) => + translate('cancelRequestFamilyMsg').replaceAll('{1}', name); + + String appUpdateTip(String name) => + translate('appUpdateTip').replaceAll('{1}', name); + + String numberOfSign(String name) => + translate('numberOfSign').replaceAll('{1}', name); + + String vipAccelerating(String name) => + translate('vipAccelerating').replaceAll('{1}', name); +} + +class _ATAppLocalizationsDelegate + extends LocalizationsDelegate { + const _ATAppLocalizationsDelegate(); + + @override + bool isSupported(Locale locale) => + ['en', 'zh', 'ar','bn','tr'].contains(locale.languageCode); + + @override + Future load(Locale locale) async { + final localizations = ATAppLocalizations(locale); + await localizations.load(); + return localizations; + } + + @override + bool shouldReload(covariant LocalizationsDelegate old) => + false; +} diff --git a/lib/chatvibe_core/at_lk_event_bus.dart b/lib/chatvibe_core/at_lk_event_bus.dart new file mode 100644 index 0000000..c7b8cd2 --- /dev/null +++ b/lib/chatvibe_core/at_lk_event_bus.dart @@ -0,0 +1,23 @@ +import 'package:event_bus/event_bus.dart'; + +EventBus eventBus = EventBus(); + +class ATGiveRoomLuckPageDisposeEvent {} + +class GiveRoomLuckWithOtherEvent { + String giftPic = ""; + List acceptUserIds = []; + + GiveRoomLuckWithOtherEvent(this.giftPic, this.acceptUserIds); +} + +class UpdateDynamicEvent { +} + +class OpenDrawerEvent { + final bool highlightMyRoom; + + OpenDrawerEvent({this.highlightMyRoom = false}); +} + +class RefreshCPEvent {} \ No newline at end of file diff --git a/lib/chatvibe_core/config/app_config.dart b/lib/chatvibe_core/config/app_config.dart new file mode 100644 index 0000000..70c414b --- /dev/null +++ b/lib/chatvibe_core/config/app_config.dart @@ -0,0 +1,156 @@ +import 'package:flutter/foundation.dart'; +import 'package:aslan/chatvibe_core/config/configs/variant1_config.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; + +/// 应用配置基类 +/// 定义单一应用配置接口 +abstract class AppConfig { + /// 应用显示名称 + String get appName; + + /// 包名/应用ID + String get packageName; + + /// API服务器地址 + String get apiHost; + + /// 图片服务器地址 + String get imgHost; + + /// 隐私协议URL + String get privacyAgreementUrl; + + /// 用户协议URL + String get userAgreementUrl; + + /// 应用下载链接(Google Play) + String get appDownloadUrlGoogle; + + /// 应用下载链接(App Store) + String get appDownloadUrlApple; + + /// 第三方支付ID + String get payApplicationId; + + /// 主播代理URL + String get anchorAgentUrl; + + /// 主持人中心URL + String get hostCenterUrl; + + /// BD中心URL + String get bdCenterUrl; + + /// BD领导中心URL + String get bdLeaderUrl; + + /// 代币销售URL + String get coinSellerUrl; + + /// 管理员URL + String get adminUrl; + + /// 代理中心URL + String get agencyCenterUrl; + + /// 游戏国王URL + String get gamesKingUrl; + + /// 充值URL + String get rechargeUrl; + + /// CP奖励URL + String get cpRewardUrl; + + /// 财富榜URL + String get wealthRankUrl; + + /// 魅力榜URL + String get charmRankUrl; + + /// 房间榜URL + String get roomRankUrl; + + /// 邀请新用户URL + String get inviteNewUserUrl; + + /// 主题主颜色 + int get primaryColor; + + /// 腾讯IM App ID + String get tencentImAppid; + + /// Agora RTC App ID + String get agoraRtcAppid; + + /// 游戏App ID + num get gameAppid; + + /// 游戏渠道 + String get gameAppChannel; + + /// 全服广播大群ID + String get bigBroadcastGroup; + + /// 管理账号 + String get imAdmin; + + /// 是否审核模式 + bool get isReview; + + /// 礼物特效开关 + bool get isGiftSpecialEffects; + + /// 入场秀开关 + bool get isEntryVehicleAnimation; + + /// 全局飘屏开关 + bool get isFloatingAnimationInGlobal; + + /// 幸运礼物特效开关 + bool get isLuckGiftSpecialEffects; + + /// 获取功能开关 + Map get featureFlags; + + /// 获取业务逻辑策略 + BusinessLogicStrategy get businessLogicStrategy; + + /// 获取所有配置的Map(用于调试) + Map toMap(); + + /// 验证配置是否有效 + /// 检查必要的配置项是否已正确设置 + /// 如果配置无效,抛出 [StateError] + void validate(); + + /// 当前应用的配置实例 + static AppConfig? _current; + + /// 获取当前配置实例 + static AppConfig get current { + if (_current == null) { + throw StateError( + 'AppConfig not initialized. Call AppConfig.initialize() first.', + ); + } + return _current!; + } + + /// 初始化应用配置 + /// 使用variant1(马甲包)配置 + static void initialize() { + _current = Variant1Config(); + debugPrint('AppConfig initialized for variant1'); + + // 验证配置是否有效 + try { + _current!.validate(); + } catch (e) { + // validate方法在调试模式下不会抛出异常 + // 只有在发布模式下验证失败才会抛出异常 + debugPrint('应用配置验证失败: $e'); + rethrow; + } + } +} diff --git a/lib/chatvibe_core/config/asset_loader.dart b/lib/chatvibe_core/config/asset_loader.dart new file mode 100644 index 0000000..3f78970 --- /dev/null +++ b/lib/chatvibe_core/config/asset_loader.dart @@ -0,0 +1,28 @@ +/// 资源加载器 +class AssetLoader { + /// 获取资源路径 + /// [path] 相对路径,如 'atu_images/icon.png' + static String getAssetPath(String path) { + return 'assets/$path'; + } + + /// 获取图片资源路径 + static String image(String path) { + return getAssetPath('atu_images/$path'); + } + + /// 获取本地化文件路径 + static String localization(String path) { + return getAssetPath('l10n/$path'); + } + + /// 获取配置文件路径 + static String config(String path) { + return getAssetPath('config/$path'); + } + + /// 获取共享资源路径 + static String shared(String path) { + return 'assets/shared/$path'; + } +} \ No newline at end of file diff --git a/lib/chatvibe_core/config/business_logic_strategy.dart b/lib/chatvibe_core/config/business_logic_strategy.dart new file mode 100644 index 0000000..b32a811 --- /dev/null +++ b/lib/chatvibe_core/config/business_logic_strategy.dart @@ -0,0 +1,1639 @@ +import 'package:flutter/material.dart'; + +/// 业务逻辑策略接口 +/// 定义页面类可用的差异化业务逻辑方法 +abstract class BusinessLogicStrategy { + /// 获取首页Tab页配置 + /// 返回: List<Widget> - 首页的页面组件列表 + List getHomeTabPages(BuildContext context); + + /// 获取首页Tab标签配置 + /// 返回: List<Widget> - 首页的Tab标签列表 + List getHomeTabLabels(BuildContext context); + + /// 判断是否显示家族Tab + /// 返回: bool - 是否显示家族Tab + bool shouldShowFamilyTab(); + + /// 获取首页初始Tab索引 + /// 返回: int - 初始Tab索引 + int getHomeInitialTabIndex(); + + /// 处理头像点击事件 + /// 参数: context - BuildContext + void onAvatarTap(BuildContext context); + + /// 判断是否显示首充提示 + /// 返回: bool - 是否显示首充提示 + bool shouldShowFirstRechargePrompt(); + + /// 获取首充提示位置配置 + /// 返回: Map<String, double?> - 包含top/bottom/start/end位置,未设置的键返回null + Map getFirstRechargePosition(); + + /// 处理首充提示点击事件 + /// 参数: context - BuildContext + void onFirstRechargeTap(BuildContext context); + + /// === 登录页面差异化方法 === + + /// 获取登录页面背景图像路径 + /// 返回: String - 背景图像资源路径 + String getLoginBackgroundImage(); + + /// 获取登录页面应用图标路径 + /// 返回: String - 应用图标资源路径 + String getLoginAppIcon(); + + /// 获取登录按钮背景颜色 + /// 返回: Color - 按钮背景颜色 + Color getLoginButtonColor(); + + /// 获取登录按钮渐变颜色(可选) + /// 返回: List<Color>? - 渐变颜色列表,null表示使用纯色 + List? getLoginButtonGradient(); + + /// 获取登录按钮文本颜色 + /// 返回: Color - 按钮文本颜色 + Color getLoginButtonTextColor(); + + /// 获取登录标签文本颜色 + /// 返回: Color - 标签文本颜色 + Color getLoginLabelTextColor(); + + /// 获取登录输入框装饰 + /// 返回: BoxDecoration - 输入框装饰样式 + BoxDecoration getLoginInputDecoration(); + + /// 获取登录页面主要内边距 + /// 返回: EdgeInsets - 页面内边距 + EdgeInsets getLoginPagePadding(); + + /// 是否显示登录页面状态栏 + /// 返回: bool - 是否显示状态栏 + bool shouldShowLoginStatusBar(); + + /// 获取登录输入框提示文本颜色 + /// 返回: Color - 提示文本颜色 + Color getLoginHintTextColor(); + + /// 获取登录输入框文本颜色 + /// 返回: Color - 输入框文本颜色 + Color getLoginInputTextColor(); + + /// === 家族页面差异化方法 === + + /// 获取家族等级显示值 + /// 参数: level - 原始等级值 + /// 返回: num - 处理后的显示等级 + num getFamilyDisplayLevel(num level); + + /// 获取家族等级背景图片路径 + /// 参数: level - 显示等级值 + /// 返回: String - 背景图片资源路径 + String getFamilyLevelBackgroundImage(num level); + + /// 获取家族通知显示文本 + /// 参数: familyNotice - 原始家族通知文本 + /// 返回: String - 处理后的显示文本 + String getFamilyNoticeDisplayText(String? familyNotice, BuildContext context); + + /// 获取在线用户列表显示数量 + /// 返回: int - 显示的最大在线用户数量 + int getFamilyOnlineUserDisplayCount(); + + /// 获取房间列表网格列数 + /// 返回: int - 网格列数 + int getFamilyRoomGridCrossAxisCount(); + + /// 是否显示密码房间图标 + /// 返回: bool - 是否显示密码图标 + bool shouldShowPasswordRoomIcon(); + + /// === 探索页面差异化方法 === + + /// 获取探索页面房间显示阈值 + /// 返回: int - 显示在抽屉上方的房间数量阈值 + int getExploreRoomDisplayThreshold(); + + /// 获取探索页面网格视图图标路径 + /// 返回: String - 网格视图图标资源路径 + String getExploreGridIcon(); + + /// 获取探索页面列表视图图标路径 + /// 返回: String - 列表视图图标资源路径 + String getExploreListIcon(); + + /// 获取探索页面排名图标路径模式 + /// 参数: isGrid - 是否为网格视图 + /// 参数: rank - 排名(1-based) + /// 返回: String - 排名图标资源路径 + String getExploreRankIconPattern(bool isGrid, int rank); + + /// 获取探索页面房间边框颜色 + /// 返回: Color - 房间边框颜色 + Color getExploreRoomBorderColor(); + + /// 获取探索页面房间边框宽度 + /// 返回: double - 房间边框宽度 + double getExploreRoomBorderWidth(); + + /// 获取探索页面抽屉内容网格列数 + /// 返回: int - 网格列数 + int getExploreDrawerGridCrossAxisCount(); + + /// 获取探索页面房间名称滚动阈值 + /// 返回: int - 房间名称长度超过此值时显示滚动效果 + int getExploreRoomNameMarqueeThreshold(); + + /// 获取探索页面房间描述滚动阈值 + /// 返回: int - 房间描述长度超过此值时显示滚动效果 + int getExploreRoomDescMarqueeThreshold(); + + /// === 个人主页差异化方法 === + + /// 获取个人主页默认背景图像路径 + /// 返回: String - 默认背景图像资源路径 + String getMePageDefaultBackgroundImage(); + + /// 获取个人主页性别背景图像路径 + /// 参数: isFemale - 是否为女性用户 + /// 返回: String - 性别背景图像资源路径 + String getMePageGenderBackgroundImage(bool isFemale); + + /// 获取个人主页CP对话框头像图像路径 + /// 返回: String - CP对话框头像图像资源路径 + String getMePageCpDialogHeadImage(); + + /// 获取个人主页默认头像图像路径 + /// 返回: String - 默认头像图像资源路径 + String getMePageDefaultAvatarImage(); + + /// 获取个人主页ID背景图像路径 + /// 参数: hasSpecialId - 是否有特殊ID + /// 返回: String - ID背景图像资源路径 + String getMePageIdBackgroundImage(bool hasSpecialId); + + /// 获取个人主页复制ID图标路径 + /// 返回: String - 复制ID图标资源路径 + String getMePageCopyIdIcon(); + + /// 获取个人主页性别年龄背景图像路径 + /// 参数: isFemale - 是否为女性用户 + /// 返回: String - 性别年龄背景图像资源路径 + String getMePageGenderAgeBackgroundImage(bool isFemale); + + /// 获取个人主页访客关注粉丝背景图像路径 + /// 参数: isFemale - 是否为女性用户 + /// 返回: String - 访客关注粉丝背景图像资源路径 + String getMePageVisitorsFollowFansBackgroundImage(bool isFemale); + + /// 获取个人主页编辑用户信息图标路径 + /// 返回: String - 编辑用户信息图标资源路径 + String getMePageEditUserInfoIcon(); + + /// 获取个人主页闪耀文本颜色 + /// 返回: Color - 闪耀文本颜色 + Color getMePageShineColor(); + + /// 获取个人主页渐变颜色 + /// 参数: isFemale - 是否为女性用户 + /// 返回: List<Color> - 渐变颜色列表 + List getMePageGradientColors(bool isFemale); + + /// 获取个人主页Tab指示器渐变颜色 + /// 参数: isFemale - 是否为女性用户 + /// 返回: List<Color> - Tab指示器渐变颜色列表 + List getMePageTabIndicatorGradient(bool isFemale); + + /// 获取个人主页访客关注粉丝文本颜色 + /// 返回: Color - 访客关注粉丝文本颜色 + Color getMePageVisitorsFollowFansTextColor(); + + /// 获取个人主页初始Tab索引 + /// 返回: int - 初始Tab索引 + int getMePageInitialTabIndex(); + + /// 获取个人主页滚动透明度计算阈值 + /// 返回: double - 滚动透明度计算阈值 + double getMePageScrollOpacityThreshold(); + + /// 获取个人主页昵称滚动显示阈值 + /// 返回: int - 昵称长度超过此值时显示滚动效果 + int getMePageNicknameScrollThreshold(); + + /// 获取个人主页年龄显示宽度阈值 + /// 返回: int - 年龄超过此值时调整显示宽度 + int getMePageAgeDisplayWidthThreshold(); + + /// 获取个人主页头像显示透明度阈值 + /// 返回: double - 透明度超过此值时显示头像 + double getMePageOpacityThresholdForAvatarDisplay(); + + /// === 热门页面差异化方法 === + + /// 获取热门页面排行榜背景图像路径 + /// 参数: leaderboardType - 排行榜类型 ('room', 'wealth', 'charm') + /// 返回: String - 背景图像资源路径 + String getPopularLeaderboardBackgroundImage(String leaderboardType); + + /// === 游戏页面差异化方法 === + + /// 获取游戏页面排名背景图像路径 + /// 参数: rank - 排名 (1, 2, 3, 4) + /// 返回: String - 排名背景图像资源路径 + String getGameRankBackgroundImage(int rank); + + /// 获取游戏页面排名图标路径 + /// 参数: rank - 排名 (1, 2, 3, 4) + /// 返回: String - 排名图标资源路径 + String getGameRankIcon(int rank); + + /// 获取游戏页面主题颜色 + /// 返回: Color - 游戏页面主题颜色 + Color getGamePageThemeColor(); + + /// 获取游戏页面背景颜色 + /// 返回: Color - 游戏页面背景颜色 + Color getGamePageBackgroundColor(); + + /// 获取游戏页面列数配置 + /// 返回: int - 网格列数 + int getGamePageGridCrossAxisCount(); + + /// 获取游戏页面网格高度配置 + /// 返回: double - 网格高度比例 + double getGamePageGridChildAspectRatio(); + + /// 获取游戏页面排名文本颜色 + /// 参数: rank - 排名 (1, 2, 3, 4) + /// 返回: Color - 排名文本颜色 + Color getGameRankTextColor(int rank); + + /// 获取游戏页面渐变第二颜色 + /// 返回: Color - 渐变第二颜色 + Color getGamePageGradientSecondColor(); + + /// 获取游戏页面热门标签背景图像路径 + /// 返回: String - 热门标签背景图像资源路径 + String getGameHotTagBackgroundImage(); + + /// 获取游戏页面新游戏标签图像路径 + /// 返回: String - 新游戏标签图像资源路径 + String getGameNewTagImage(); + + /// 获取游戏页面遮罩图像路径 + /// 返回: String - 游戏页面遮罩图像资源路径 + String getGamePageMaskImage(); + + /// === VIP页面差异化方法 === + + /// 获取VIP页面背景图像路径 + /// 参数: vipLevel - VIP等级 (1-9) + /// 返回: String - VIP背景图像资源路径 + String getVipPageBackgroundImage(int vipLevel); + + /// 获取VIP页面图标路径 + /// 参数: vipLevel - VIP等级 (1-9) + /// 返回: String - VIP图标资源路径 + String getVipPageIcon(int vipLevel); + + /// 获取VIP页面按钮渐变颜色 + /// 返回: List<Color> - 按钮渐变颜色列表 + List getVipPageButtonGradient(); + + /// 获取VIP页面网格列数配置 + /// 返回: int - 网格列数 + int getVipPageGridCrossAxisCount(); + + /// 获取VIP页面网格高度配置 + /// 返回: double - 网格高度比例 + double getVipPageGridChildAspectRatio(); + + /// 获取VIP页面头部背景图像路径 + /// 参数: vipLevel - VIP等级 (1-9) + /// 返回: String - VIP头部背景图像资源路径 + String getVipPageHeadBackgroundImage(int vipLevel); + + /// 获取VIP页面Tab选中状态图标路径 + /// 参数: vipLevel - VIP等级 (1-9) + /// 返回: String - Tab选中状态图标资源路径 + String getVipPageTabSelectedIcon(int vipLevel); + + /// 获取VIP页面Tab未选中状态图标路径 + /// 参数: vipLevel - VIP等级 (1-9) + /// 返回: String - Tab未选中状态图标资源路径 + String getVipPageTabUnselectedIcon(int vipLevel); + + /// 获取VIP页面大图标路径 + /// 参数: vipLevel - VIP等级 (1-9) + /// 返回: String - 大图标资源路径 + String getVipPageLargeIcon(int vipLevel); + + /// 获取VIP页面标题图像路径 + /// 参数: vipLevel - VIP等级 (1-9) + /// 返回: String - 标题图像资源路径 + String getVipPageTitleImage(int vipLevel); + + /// 获取VIP页面标签图像路径 + /// 参数: vipLevel - VIP等级 (1-9) + /// 参数: tagIndex - 标签索引 (1或2) + /// 返回: String - 标签图像资源路径 + String getVipPageTagImage(int vipLevel, int tagIndex); + + /// 获取VIP页面功能项背景图像路径 + /// 参数: vipLevel - VIP等级 (1-9) + /// 返回: String - 功能项背景图像资源路径 + String getVipPageItemBackgroundImage(int vipLevel); + + /// 获取VIP页面特权图标路径 + /// 参数: vipLevel - VIP等级 (1-9) + /// 参数: privilegeIndex - 特权索引 (1-14) + /// 返回: String - 特权图标资源路径 + String getVipPagePrivilegeIcon(int vipLevel, int privilegeIndex,bool isSelected); + + /// 获取VIP页面功能图标路径 + /// 参数: vipLevel - VIP等级 (1-9) + /// 参数: featureName - 功能名称 ('badge', 'profile_frame', 'profile_card', 'entrance_effect', 'special_gift_tassel', 'mic_rippl_theme', 'chatbox', 'room_cover_headdress', 'exclusive_vehicles') + /// 返回: String - 功能图标资源路径 + String getVipPageFeatureIcon(int vipLevel, String featureName); + + /// 获取VIP页面预览图像路径 + /// 参数: vipLevel - VIP等级 (1-9) + /// 参数: previewType - 预览类型 ('rev', 'pre') + /// 参数: featureName - 功能名称 + /// 返回: String - 预览图像资源路径 + String getVipPagePreviewImage( + int vipLevel, + String previewType, + String featureName, + ); + + /// 获取VIP页面购买背景图像路径 + /// 参数: vipLevel - VIP等级 (1-9) + /// 返回: String - 购买背景图像资源路径 + String getVipPagePurchaseBackgroundImage(int vipLevel); + + /// 获取VIP页面购买按钮图像路径 + /// 返回: String - 购买按钮图像资源路径 + String getVipPagePurchaseButtonImage(); + + /// 获取VIP页面文本颜色 + /// 参数: vipLevel - VIP等级 (1-9) + /// 返回: Color - 文本颜色 + Color getVipPageTextColor(int vipLevel,bool isSelected); + + /// === 通用页面差异化方法 === + + /// 获取通用页面排名背景图像路径模式 + /// 参数: pageType - 页面类型 ('top', 'local', 'global', 'today') + /// 参数: rank - 排名 (1-based) + /// 返回: String - 排名背景图像资源路径 + String getCommonRankBackgroundImage(String pageType, int rank); + + /// 获取通用页面排名图标路径模式 + /// 参数: pageType - 页面类型 ('top', 'local', 'global', 'today') + /// 参数: rank - 排名 (1-based) + /// 返回: String - 排名图标资源路径 + String getCommonRankIcon(String pageType, int rank); + + /// 获取通用页面网格列数配置 + /// 参数: pageType - 页面类型 ('top', 'local', 'global', 'today') + /// 返回: int - 网格列数 + int getCommonPageGridCrossAxisCount(String pageType); + + /// 获取通用页面网格高度配置 + /// 参数: pageType - 页面类型 ('top', 'local', 'global', 'today') + /// 返回: double - 网格高度比例 + double getCommonPageGridChildAspectRatio(String pageType); + + /// === 个人主页2 (MePage2) 差异化方法 === + + /// 获取MePage2钱包区域背景配置 + /// 返回: Map - 包含渐变颜色、圆角、阴影等配置 + Map getMePage2WalletBackground(); + + /// 获取MePage2统计信息颜色配置 + /// 返回: Map - 包含数字颜色、标签颜色、分割线颜色 + Map getMePage2StatisticsColors(); + + /// 获取MePage2功能卡片图标路径映射 + /// 返回: Map - 功能名称到图标路径的映射 + Map getMePage2FunctionIcons(); + + /// 获取MePage2卡片样式配置 + /// 返回: Map - 包含圆角、阴影、边框等配置 + Map getMePage2CardStyles(); + + /// === Admin编辑页面差异化方法 === + + /// 获取Admin编辑页面背景图像路径 + /// 返回: String - 背景图像资源路径 + String getAdminEditingBackgroundImage(); + + /// 获取Admin编辑页面按钮渐变颜色 + /// 参数: buttonType - 按钮类型 ('warning', 'adjust') + /// 返回: List - 渐变颜色列表 + List getAdminEditingButtonGradient(String buttonType); + + /// 获取Admin编辑页面图标路径 + /// 参数: iconType - 图标类型 ('specialIdBg', 'normalIdBg', 'copyId', 'addPic', 'closePic', 'checked') + /// 返回: String - 图标资源路径 + String getAdminEditingIcon(String iconType); + + /// 获取Admin编辑页面输入框装饰 + /// 返回: BoxDecoration - 输入框装饰样式 + BoxDecoration getAdminEditingInputDecoration(); + + /// 获取Admin编辑页面最大图片上传数量 + /// 返回: int - 最大图片上传数量 + int getAdminEditingMaxImageUploadCount(); + + /// 获取Admin编辑页面描述最大长度 + /// 返回: int - 描述文本框最大字符数 + int getAdminEditingDescriptionMaxLength(); + + /// 获取Admin编辑页面违规类型映射 + /// 参数: targetType - 目标类型 ('User', 'Room') + /// 返回: Map - 违规类型名称到类型ID的映射 + Map getAdminEditingViolationTypeMapping(String targetType); + + /// === Admin搜索页面差异化方法 === + + /// 获取Admin搜索页面背景图像路径 + /// 参数: pageType - 页面类型 ('userSearch', 'roomSearch') + /// 返回: String - 背景图像资源路径 + String getAdminSearchBackgroundImage(String pageType); + + /// 获取Admin搜索页面搜索按钮渐变颜色 + /// 参数: pageType - 页面类型 ('userSearch', 'roomSearch') + /// 返回: List - 渐变颜色列表 + List getAdminSearchButtonGradient(String pageType); + + /// 获取Admin搜索页面搜索按钮文本颜色 + /// 参数: pageType - 页面类型 ('userSearch', 'roomSearch') + /// 返回: Color - 文本颜色 + Color getAdminSearchButtonTextColor(String pageType); + + /// 获取Admin搜索页面编辑图标路径 + /// 参数: pageType - 页面类型 ('userSearch', 'roomSearch') + /// 返回: String - 编辑图标资源路径 + String getAdminSearchEditIcon(String pageType); + + /// 获取Admin搜索页面搜索框边框颜色 + /// 参数: pageType - 页面类型 ('userSearch', 'roomSearch') + /// 返回: Color - 边框颜色 + Color getAdminSearchInputBorderColor(String pageType); + + /// 获取Admin搜索页面搜索框文本颜色 + /// 参数: pageType - 页面类型 ('userSearch', 'roomSearch') + /// 返回: Color - 文本颜色 + Color getAdminSearchInputTextColor(String pageType); + + /// 获取Admin搜索页面用户信息图标路径 + /// 参数: iconType - 图标类型 ('specialIdBg', 'normalIdBg', 'copyId') + /// 返回: String - 图标资源路径 + String getAdminSearchUserInfoIcon(String iconType); + + /// === Auth页面差异化方法 === + + /// === 主登录页面 (LoginPage) 方法 === + + /// 获取主登录页面背景图像路径 + /// 返回: String - 背景图像资源路径 + String getLoginMainBackgroundImage(); + + /// 获取主登录页面应用图标路径 + /// 返回: String - 应用图标资源路径 + String getLoginMainAppIcon(); + + /// 获取主登录页面Google登录图标路径 + /// 返回: String - Google登录图标资源路径 + String getLoginMainGoogleIcon(); + + /// 获取主登录页面Apple登录图标路径 + /// 返回: String - Apple登录图标资源路径 + String getLoginMainAppleIcon(); + + /// 获取主登录页面账户登录图标路径 + /// 返回: String - 账户登录图标资源路径 + String getLoginMainAccountIcon(); + + /// 获取主登录页面协议选择图标路径 + /// 参数: isSelected - 是否选中状态 + /// 返回: String - 协议选择图标资源路径 + String getLoginMainAgreementIcon(bool isSelected); + + /// 获取主登录页面分隔线颜色 + /// 返回: Color - 分隔线颜色 + Color getLoginMainDividerColor(); + + /// 获取主登录页面按钮边框颜色 + /// 返回: Color - 按钮边框颜色 + Color getLoginMainButtonBorderColor(); + + /// 获取主登录页面按钮背景颜色 + /// 返回: Color - 按钮背景颜色 + Color getLoginMainButtonBackgroundColor(); + + /// 获取主登录页面链接文本颜色 + /// 返回: Color - 链接文本颜色 + Color getLoginMainLinkColor(); + + /// 获取主登录页面按钮文本颜色 + /// 返回: Color - 按钮文本颜色 + Color getLoginMainButtonTextColor(); + + /// === 编辑个人资料页面 (EditProfilePage) 方法 === + + /// 获取编辑个人资料页面背景图像路径 + /// 返回: String - 背景图像资源路径 + String getEditProfileBackgroundImage(); + + /// 获取编辑个人资料页面默认头像图像路径 + /// 返回: String - 默认头像图像资源路径 + String getEditProfileDefaultAvatarImage(); + + /// 获取编辑个人资料页面性别图标路径 + /// 参数: isMale - 是否为男性图标 + /// 返回: String - 性别图标资源路径 + String getEditProfileGenderIcon(bool isMale); + + /// 获取编辑个人资料页面性别按钮渐变颜色 + /// 参数: isMale - 是否为男性按钮 + /// 参数: isSelected - 是否选中状态 + /// 返回: List - 渐变颜色列表 + List getEditProfileGenderButtonGradient(bool isMale, bool isSelected); + + /// 获取编辑个人资料页面输入框背景颜色 + /// 返回: Color - 输入框背景颜色 + Color getEditProfileInputBackgroundColor(); + + /// 获取编辑个人资料页面日期选择确认按钮颜色 + /// 返回: Color - 日期选择确认按钮颜色 + Color getEditProfileDatePickerConfirmColor(); + + /// 获取编辑个人资料页面继续按钮颜色 + /// 返回: Color - 继续按钮颜色 + Color getEditProfileContinueButtonColor(); + + /// === Country页面差异化方法 === + + /// 获取Country页面选择确认图标路径 + /// 返回: String - 选择确认图标资源路径 + String getCountrySelectOkIcon(); + + /// 获取Country页面选择未确认图标路径 + /// 返回: String - 选择未确认图标资源路径 + String getCountrySelectUnOkIcon(); + + /// 获取Country页面单选选中图标路径 + /// 返回: String - 单选选中图标资源路径 + String getCountryRadioSelectedIcon(); + + /// 获取Country页面单选未选中图标路径 + /// 返回: String - 单选未选中图标资源路径 + String getCountryRadioUnselectedIcon(); + + /// 获取Country页面国家列表项边框颜色 + /// 返回: Color - 国家列表项边框颜色 + Color getCountryItemBorderColor(); + + /// 获取Country页面Scaffold背景颜色 + /// 返回: Color - Scaffold背景颜色 + Color getCountryPageScaffoldBackgroundColor(); + + /// 获取Country页面图标颜色 + /// 返回: Color - 图标颜色 + Color getCountryPageIconColor(); + + /// 获取Country页面主要文本颜色 + /// 返回: Color - 主要文本颜色 + Color getCountryPagePrimaryTextColor(); + + /// 获取Country页面次要文本颜色 + /// 返回: Color - 次要文本颜色 + Color getCountryPageSecondaryTextColor(); + + /// 获取Country页面容器背景颜色 + /// 返回: Color - 容器背景颜色 + Color getCountryPageContainerBackgroundColor(); + + /// === Chat页面差异化方法 === + + /// === 消息页面 (ATMessagePage) 方法 === + + /// 获取消息页面索引遮罩图标路径 + /// 返回: String - 索引遮罩图标资源路径 + String getMessagePageIndexMaskIcon(); + + /// 获取消息页面公告标签图标路径 + /// 返回: String - 公告标签图标资源路径 + String getMessagePageAnnouncementTagIcon(); + + /// 获取消息页面活动消息图标路径 + /// 返回: String - 活动消息图标资源路径 + String getMessagePageActivityMessageIcon(); + + /// 获取消息页面活动消息标题背景图标路径 + /// 参数: isRtl - 是否为RTL布局(从右到左) + /// 返回: String - 活动消息标题背景图标资源路径 + String getMessagePageActivityTitleBackground(bool isRtl); + + /// 获取消息页面系统消息图标路径 + /// 返回: String - 系统消息图标资源路径 + String getMessagePageSystemMessageIcon(); + + /// 获取消息页面系统消息标题背景图标路径 + /// 参数: isRtl - 是否为RTL布局(从右到左) + /// 返回: String - 系统消息标题背景图标资源路径 + String getMessagePageSystemTitleBackground(bool isRtl); + + /// 获取消息页面通知消息图标路径 + /// 返回: String - 通知消息图标资源路径 + String getMessagePageNotificationMessageIcon(); + + /// 获取消息页面通知消息标题背景图标路径 + /// 参数: isRtl - 是否为RTL布局(从右到左) + /// 返回: String - 通知消息标题背景图标资源路径 + String getMessagePageNotificationTitleBackground(bool isRtl); + + /// === 聊天页面 (ATMessageChatPage) 方法 === + + /// 获取聊天页面房间设置背景图像路径 + /// 返回: String - 房间设置背景图像资源路径 + String getATMessageChatPageRoomSettingBackground(); + + /// 获取聊天页面表情图标路径 + /// 返回: String - 表情图标资源路径 + String getATMessageChatPageEmojiIcon(); + + /// 获取聊天页面聊天键盘图标路径 + /// 返回: String - 聊天键盘图标资源路径 + String getATMessageChatPageChatKeyboardIcon(); + + /// 获取聊天页面添加图标路径 + /// 返回: String - 添加图标资源路径 + String getATMessageChatPageAddIcon(); + + /// 获取聊天页面发送消息图标路径 + /// 返回: String - 发送消息图标资源路径 + String getATMessageChatPageSendMessageIcon(); + + /// 获取聊天页面相机图标路径 + /// 返回: String - 相机图标资源路径 + String getATMessageChatPageCameraIcon(); + + /// 获取聊天页面图片图标路径 + /// 返回: String - 图片图标资源路径 + String getATMessageChatPagePictureIcon(); + + /// 获取聊天页面红包图标路径 + /// 返回: String - 红包图标资源路径 + String getATMessageChatPageRedEnvelopeIcon(); + + /// 获取聊天页面红包消息背景图像路径 + /// 返回: String - 红包消息背景图像资源路径 + String getATMessageChatPageRedEnvelopeMessageBackground(); + + /// 获取聊天页面红包配置标签按钮图标路径 + /// 返回: String - 红包配置标签按钮图标资源路径 + String getATMessageChatPageRedEnvelopeConfigTabButtonIcon(); + + /// 获取聊天页面红包消息项背景图像路径 + /// 返回: String - 红包消息项背景图像资源路径 + String getATMessageChatPageRedEnvelopeMessageItemBackground(); + + /// 获取聊天页面红包接收包图标路径 + /// 返回: String - 红包接收包图标资源路径 + String getATMessageChatPageRedEnvelopeReceivePackageIcon(); + + /// 获取聊天页面红包打开背景图像路径 + /// 返回: String - 红包打开背景图像资源路径 + String getATMessageChatPageRedEnvelopeOpenBackground(); + + /// 获取聊天页面红包已打开背景图像路径 + /// 返回: String - 红包已打开背景图像资源路径 + String getATMessageChatPageRedEnvelopeOpenedBackground(); + + /// 获取聊天页面金币图标路径 + /// 返回: String - 金币图标资源路径 + String getATMessageChatPageGoldCoinIcon(); + + /// 获取聊天页面消息菜单删除图标路径 + /// 返回: String - 消息菜单删除图标资源路径 + String getATMessageChatPageMessageMenuDeleteIcon(); + + /// 获取聊天页面消息菜单复制图标路径 + /// 返回: String - 消息菜单复制图标资源路径 + String getATMessageChatPageMessageMenuCopyIcon(); + + /// 获取聊天页面消息菜单撤回图标路径 + /// 返回: String - 消息菜单撤回图标资源路径 + String getATMessageChatPageMessageMenuRecallIcon(); + + /// 获取聊天页面加载图标路径 + /// 返回: String - 加载图标资源路径 + String getATMessageChatPageLoadingIcon(); + + /// 获取聊天页面系统头像图像路径 + /// 返回: String - 系统头像图像资源路径 + String getATMessageChatPageSystemHeadImage(); + + /// === 通用图标差异化方法 === + + /// 获取性别背景图像路径 + /// 参数: isFemale - 是否为女性 + /// 返回: String - 性别背景图像资源路径 + String getGenderBackgroundImage(bool isFemale); + + /// 获取通用ID背景图像路径 + /// 参数: hasSpecialId - 是否有特殊ID + /// 返回: String - ID背景图像资源路径 + String getIdBackgroundImage(bool hasSpecialId); + + /// 获取通用复制ID图标路径 + /// 返回: String - 复制ID图标资源路径 + String getCopyIdIcon(); + + /// 获取通用选择图标路径 + /// 返回: String - 选择图标资源路径 + String getCommonSelectIcon(); + + /// 获取通用未选择图标路径 + /// 返回: String - 未选择图标资源路径 + String getCommonUnselectIcon(); + + /// === Gift页面差异化方法 === + + /// 获取礼物页面首充房间标签图标路径 + /// 返回: String - 首充房间标签图标资源路径 + String getGiftPageFirstRechargeRoomTagIcon(); + + /// 获取礼物页面金币图标路径 + /// 返回: String - 金币图标资源路径 + String getGiftPageGoldCoinIcon(); + + /// 获取礼物页面赠送类型背景图像路径 + /// 返回: String - 赠送类型背景图像资源路径 + String getGiftPageGiveTypeBackground(); + + /// 获取礼物页面活动礼物头部背景图像路径 + /// 参数: giftType - 礼物类型 ('ACTIVITY', 'LUCK', 'CP', 'MAGIC') + /// 返回: String - 活动礼物头部背景图像资源路径 + String getGiftPageActivityGiftHeadBackground(String giftType); + + /// 获取礼物页面自定义规则图标路径 + /// 返回: String - 自定义规则图标资源路径 + String getGiftPageCustomizedRuleIcon(); + + /// 获取礼物页面特效礼物图标路径 + /// 参数: giftType - 礼物类型标识符 + /// 返回: String - 特效礼物图标资源路径 + String getGiftPageGiftEffectIcon(String giftType); + + /// 获取礼物页面音乐礼物图标路径 + /// 参数: giftType - 礼物类型标识符 + /// 返回: String - 音乐礼物图标资源路径 + String getGiftPageGiftMusicIcon(String giftType); + + /// 获取礼物页面幸运礼物图标路径 + /// 参数: giftType - 礼物类型标识符 + /// 返回: String - 幸运礼物图标资源路径 + String getGiftPageGiftLuckIcon(String giftType); + + /// 获取礼物页面CP礼物图标路径 + /// 参数: giftType - 礼物类型标识符 + /// 返回: String - CP礼物图标资源路径 + String getGiftPageGiftCpIcon(String giftType); + + /// 获取礼物页面首充文本图标路径 + /// 参数: languageCode - 语言代码 ('ar'为阿拉伯语, 'en'为英语) + /// 返回: String - 首充文本图标资源路径 + String getGiftPageFirstRechargeTextIcon(String languageCode); + + /// 获取礼物页面"全开麦"图标路径 + /// 返回: String - "全开麦"图标资源路径 + String getGiftPageAllOnMicrophoneIcon(); + + /// 获取礼物页面"用户开麦"图标路径 + /// 返回: String - "用户开麦"图标资源路径 + String getGiftPageUsersOnMicrophoneIcon(); + + /// 获取礼物页面"房间所有人"图标路径 + /// 返回: String - "房间所有人"图标资源路径 + String getGiftPageAllInTheRoomIcon(); + + /// === Media页面差异化方法 === + + /// 获取图片预览页面背景颜色 + /// 返回: Color - 页面背景颜色 + Color getImagePreviewBackgroundColor(); + + /// 获取图片预览画廊背景颜色 + /// 返回: Color - 画廊背景颜色 + Color getImagePreviewGalleryBackgroundColor(); + + /// 获取图片预览AppBar背景颜色 + /// 返回: Color - AppBar背景颜色 + Color getImagePreviewAppBarBackgroundColor(); + + /// 获取图片预览返回图标颜色 + /// 返回: Color - 返回图标颜色 + Color getImagePreviewBackIconColor(); + + /// 获取图片预览文本颜色 + /// 返回: Color - 文本颜色 + Color getImagePreviewTextColor(); + + /// 获取图片预览加载指示器颜色 + /// 返回: Color - 加载指示器颜色 + Color getImagePreviewLoadingIndicatorColor(); + + /// 获取图片预览AppBar透明度 + /// 返回: double - AppBar透明度值 + double getImagePreviewAppBarOpacity(); + + /// 获取视频播放页面背景颜色 + /// 返回: Color - 页面背景颜色 + Color getVideoPlayerBackgroundColor(); + + /// 获取视频播放控制界面背景颜色 + /// 返回: Color - 控制界面背景颜色 + Color getVideoPlayerControlsBackgroundColor(); + + /// 获取视频播放图标颜色 + /// 返回: Color - 图标颜色 + Color getVideoPlayerIconColor(); + + /// 获取视频播放底部控制栏背景颜色 + /// 返回: Color - 底部控制栏背景颜色 + Color getVideoPlayerBottomControlsBackgroundColor(); + + /// 获取视频播放进度条活动颜色 + /// 返回: Color - 进度条活动部分颜色 + Color getVideoPlayerProgressBarActiveColor(); + + /// 获取视频播放进度条非活动颜色 + /// 返回: Color - 进度条非活动部分颜色 + Color getVideoPlayerProgressBarInactiveColor(); + + /// 获取视频播放文本颜色 + /// 返回: Color - 文本颜色 + Color getVideoPlayerTextColor(); + + /// 获取视频播放加载指示器颜色 + /// 返回: Color - 加载指示器颜色 + Color getVideoPlayerLoadingIndicatorColor(); + + /// 获取视频播放控制界面透明度 + /// 返回: double - 控制界面透明度值 + double getVideoPlayerControlsOpacity(); + + /// 获取视频播放底部控制栏透明度 + /// 返回: double - 底部控制栏透明度值 + double getVideoPlayerBottomControlsOpacity(); + + /// 获取视频播放控制界面显示持续时间(秒) + /// 返回: int - 控制界面显示持续时间(秒) + int getVideoPlayerControlsDisplayDuration(); + + /// === 举报页面差异化方法 === + + /// 获取举报页面背景图像路径 + /// 返回: String - 背景图像资源路径 + String getReportPageBackgroundImage(); + + /// 获取举报页面提示文本颜色 + /// 返回: Color - 提示文本颜色 + Color getReportPageHintTextColor(); + + /// 获取举报页面容器背景颜色 + /// 返回: Color - 容器背景颜色 + Color getReportPageContainerBackgroundColor(); + + /// 获取举报页面边框颜色 + /// 返回: Color - 边框颜色 + Color getReportPageBorderColor(); + + /// 获取举报页面主要文本颜色 + /// 返回: Color - 主要文本颜色 + Color getReportPagePrimaryTextColor(); + + /// 获取举报页面次级提示文本颜色 + /// 返回: Color - 次级提示文本颜色 + Color getReportPageSecondaryHintTextColor(); + + /// 获取举报页面未选中边框颜色 + /// 返回: Color - 未选中边框颜色 + Color getReportPageUnselectedBorderColor(); + + /// 获取举报页面提交按钮背景颜色 + /// 返回: Color - 提交按钮背景颜色 + Color getReportPageSubmitButtonBackgroundColor(); + + /// 获取举报页面提交按钮文本颜色 + /// 返回: Color - 提交按钮文本颜色 + Color getReportPageSubmitButtonTextColor(); + + /// 获取举报页面图标路径 + /// 参数: iconType - 图标类型 ('addPic', 'closePic', 'checked') + /// 返回: String - 图标资源路径 + String getReportPageIcon(String iconType); + + /// === 语音房间页面差异化方法 === + + /// 获取语音房间页面背景颜色 + /// 返回: Color - 页面背景颜色 + Color getVoiceRoomBackgroundColor(); + + /// 获取语音房间页面Tab标签选中颜色 + /// 返回: Color - Tab标签选中颜色 + Color getVoiceRoomTabLabelColor(); + + /// 获取语音房间页面Tab标签未选中颜色 + /// 返回: Color - Tab标签未选中颜色 + Color getVoiceRoomTabUnselectedLabelColor(); + + /// 获取语音房间页面Tab指示器颜色 + /// 返回: Color - Tab指示器颜色 + Color getVoiceRoomTabIndicatorColor(); + + /// 获取语音房间页面Tab分割线颜色 + /// 返回: Color - Tab分割线颜色 + Color getVoiceRoomTabDividerColor(); + + /// 获取语音房间页面Tab标签内边距 + /// 返回: EdgeInsets - Tab标签内边距 + EdgeInsets getVoiceRoomTabLabelPadding(); + + /// 获取语音房间页面聊天容器外边距 + /// 返回: EdgeInsets - 聊天容器外边距 + EdgeInsetsDirectional getVoiceRoomChatContainerMargin(); + + /// 获取语音房间页面Tab标签选中文本样式 + /// 返回: TextStyle - Tab标签选中文本样式 + TextStyle getVoiceRoomTabLabelStyle(); + + /// 获取语音房间页面Tab标签未选中文本样式 + /// 返回: TextStyle - Tab标签未选中文本样式 + TextStyle getVoiceRoomTabUnselectedLabelStyle(); + + /// 获取语音房间默认背景图像路径 + /// 返回: String - 默认背景图像资源路径 + String getVoiceRoomDefaultBackgroundImage(); + + /// === 钱包页面差异化方法 === + + /// === 充值页面差异化方法 === + + /// 获取充值页面背景图像路径 + /// 返回: String - 背景图像资源路径 + String getRechargePageBackgroundImage(); + + /// 获取充值页面Scaffold背景颜色 + /// 返回: Color - Scaffold背景颜色 + Color getRechargePageScaffoldBackgroundColor(); + + /// 获取充值页面对话框屏障颜色 + /// 返回: Color - 对话框屏障颜色 + Color getRechargePageDialogBarrierColor(); + + /// 获取充值页面对话框容器背景颜色 + /// 返回: Color - 对话框容器背景颜色 + Color getRechargePageDialogContainerBackgroundColor(); + + /// 获取充值页面对话框文本颜色 + /// 返回: Color - 对话框文本颜色 + Color getRechargePageDialogTextColor(); + + /// 获取充值页面主容器背景颜色 + /// 返回: Color - 主容器背景颜色 + Color getRechargePageMainContainerBackgroundColor(); + + /// 获取充值页面按钮背景颜色 + /// 返回: Color - 按钮背景颜色 + Color getRechargePageButtonBackgroundColor(); + + /// 获取充值页面按钮文本颜色 + /// 返回: Color - 按钮文本颜色 + Color getRechargePageButtonTextColor(); + + /// 获取充值页面钱包背景图像路径 + /// 返回: String - 钱包背景图像资源路径 + String getRechargePageWalletBackgroundImage(); + + /// 获取充值页面钱包图标路径 + /// 返回: String - 钱包图标资源路径 + String getRechargePageWalletIcon(); + + /// 获取充值页面钱包文本颜色 + /// 返回: Color - 钱包文本颜色 + Color getRechargePageWalletTextColor(); + + /// 获取充值页面金币图标路径(默认图标) + /// 返回: String - 金币图标资源路径 + String getRechargePageGoldIcon(); + + /// 获取充值页面选中项背景颜色 + /// 返回: Color - 选中项背景颜色 + Color getRechargePageSelectedItemBackgroundColor(); + + /// 获取充值页面选中项边框颜色 + /// 返回: Color - 选中项边框颜色 + Color getRechargePageSelectedItemBorderColor(); + + /// 获取充值页面未选中项边框颜色 + /// 返回: Color - 未选中项边框颜色 + Color getRechargePageUnselectedItemBorderColor(); + + /// 获取充值页面未选中项背景颜色 + /// 返回: Color - 未选中项背景颜色 + Color getRechargePageUnselectedItemBackgroundColor(); + + /// 获取充值页面项目文本颜色 + /// 返回: Color - 项目文本颜色 + Color getRechargePageItemTextColor(); + + /// 获取充值页面价格文本颜色 + /// 返回: Color - 价格文本颜色 + Color getRechargePageItemPriceTextColor(); + + /// 根据索引获取充值页面金币图标路径 + /// 参数: index - 项目索引 (0, 1, 2) + /// 返回: String - 金币图标资源路径 + String getRechargePageGoldIconByIndex(int index); + + /// 获取充值页面Apple产品项背景颜色 + /// 返回: Color - Apple产品项背景颜色 + Color getRechargePageAppleItemBackgroundColor(); + + /// === 金币记录页面差异化方法 === + + /// 获取金币记录页面背景图像路径 + /// 返回: String - 背景图像资源路径 + String getGoldRecordPageBackgroundImage(); + + /// 获取金币记录页面Scaffold背景颜色 + /// 返回: Color - Scaffold背景颜色 + Color getGoldRecordPageScaffoldBackgroundColor(); + + /// 获取金币记录页面列表背景颜色 + /// 返回: Color - 列表背景颜色 + Color getGoldRecordATPageListBackgroundColor(); + + /// 获取金币记录页面容器背景颜色 + /// 返回: Color - 容器背景颜色 + Color getGoldRecordPageContainerBackgroundColor(); + + /// 获取金币记录页面边框颜色 + /// 返回: Color - 边框颜色 + Color getGoldRecordPageBorderColor(); + + /// 获取金币记录页面主要文本颜色 + /// 返回: Color - 主要文本颜色 + Color getGoldRecordPagePrimaryTextColor(); + + /// 获取金币记录页面次要文本颜色 + /// 返回: Color - 次要文本颜色 + Color getGoldRecordPageSecondaryTextColor(); + + /// 获取充值页面记录图标路径 + /// 返回: String - 充值记录图标资源路径 + String getRechargePageRecordIcon(); + + /// === Store页面差异化方法 === + + /// 获取Store页面背景图像路径 + /// 返回: String - 背景图像资源路径 + String getStorePageBackgroundImage(); + + /// 获取Store页面购物袋图标路径 + /// 返回: String - 购物袋图标资源路径 + String getStorePageShoppingBagIcon(); + + /// 获取Store页面购物袋图标外边距 + /// 返回: EdgeInsetsDirectional - 购物袋图标外边距 + EdgeInsetsDirectional getStorePageShoppingBagMargin(); + + /// 获取Store页面Tab标签内边距 + /// 返回: EdgeInsets - Tab标签内边距 + EdgeInsets getStorePageTabLabelPadding(); + + /// 获取Store页面Tab标签选中文本样式 + /// 返回: TextStyle - Tab标签选中文本样式 + TextStyle getStorePageTabLabelStyle(); + + /// 获取Store页面Tab标签未选中文本样式 + /// 返回: TextStyle - Tab标签未选中文本样式 + TextStyle getStorePageTabUnselectedLabelStyle(); + + /// 获取Store页面Tab指示器颜色 + /// 返回: Color - Tab指示器颜色 + Color getStorePageTabIndicatorColor(); + + /// 获取Store页面Tab分割线颜色 + /// 返回: Color - Tab分割线颜色 + Color getStorePageTabDividerColor(); + + /// 获取Store页面底部Divider颜色 + /// 返回: Color - 底部Divider颜色 + Color getStorePageBottomDividerColor(); + + /// 获取Store页面底部Divider厚度 + /// 返回: double - 底部Divider厚度 + double getStorePageBottomDividerThickness(); + + /// 获取Store页面底部Divider高度 + /// 返回: double - 底部Divider高度 + double getStorePageBottomDividerHeight(); + + /// 获取Store页面金币图标路径 + /// 返回: String - 金币图标资源路径 + String getStorePageGoldIcon(); + + /// 获取Store页面金币文本颜色 + /// 返回: Color - 金币文本颜色 + Color getStorePageGoldTextColor(); + + /// 获取Store页面金币图标颜色 + /// 返回: Color - 金币图标颜色 + Color getStorePageGoldIconColor(); + + /// === Store子页面(商品项)差异化方法 === + + /// 获取Store商品项背景颜色 + /// 返回: Color - 商品项背景颜色 + Color getStoreItemBackgroundColor(); + + /// 获取Store商品项未选中边框颜色 + /// 返回: Color - 未选中边框颜色 + Color getStoreItemUnselectedBorderColor(); + + /// 获取Store商品项选中边框颜色 + /// 返回: Color - 选中边框颜色 + Color getStoreItemSelectedBorderColor(); + + /// 获取Store商品项金币图标路径 + /// 返回: String - 金币图标资源路径 + String getStoreItemGoldIcon(); + + /// 获取Store商品项价格文本颜色 + /// 返回: Color - 价格文本颜色 + Color getStoreItemPriceTextColor(); + + /// 获取Store商品项购买按钮背景颜色 + /// 返回: Color - 购买按钮背景颜色 + Color getStoreItemBuyButtonColor(); + + /// 获取Store商品项购买按钮文本颜色 + /// 返回: Color - 购买按钮文本颜色 + Color getStoreItemBuyButtonTextColor(); + + /// 获取Store商品项操作图标路径 + /// 参数: itemType - 商品类型 ('headdress', 'mountains', 'theme', 'chatbox') + /// 返回: String - 操作图标资源路径 + String getStoreItemActionIcon(String itemType); + + /// === 动态页面差异化方法 === + + /// 获取动态页面背景图像路径 + /// 返回: String - 背景图像资源路径 + String getDynamicPageBackgroundImage(); + + /// 获取动态页面Scaffold背景颜色 + /// 返回: Color - Scaffold背景颜色 + Color getDynamicPageScaffoldBackgroundColor(); + + /// 获取动态页面AppBar背景颜色 + /// 返回: Color - AppBar背景颜色 + Color getDynamicPageAppBarBackgroundColor(); + + /// 获取动态页面Tab标签选中颜色 + /// 返回: Color - Tab标签选中颜色 + Color getDynamicPageTabLabelColor(); + + /// 获取动态页面Tab标签未选中颜色 + /// 返回: Color - Tab标签未选中颜色 + Color getDynamicPageTabUnselectedLabelColor(); + + /// 获取动态页面Tab指示器颜色 + /// 返回: Color - Tab指示器颜色 + Color getDynamicPageTabIndicatorColor(); + + /// 获取动态页面Tab分割线颜色 + /// 返回: Color - Tab分割线颜色 + Color getDynamicPageTabDividerColor(); + + /// 获取动态页面添加动态按钮图标路径 + /// 返回: String - 添加动态按钮图标资源路径 + String getDynamicPageAddButtonIcon(); + + /// 是否启用动态页面Tab标签滚动 + /// 返回: bool - 是否启用Tab标签滚动 + bool shouldDynamicPageTabScrollable(); + + /// 获取动态页面Tab标签选中文本样式 + /// 返回: TextStyle - Tab标签选中文本样式 + TextStyle getDynamicPageTabLabelStyle(); + + /// 获取动态页面Tab标签未选中文本样式 + /// 返回: TextStyle - Tab标签未选中文本样式 + TextStyle getDynamicPageTabUnselectedLabelStyle(); + + /// === 启动页面差异化方法 === + + /// 获取启动页面背景图像路径 + /// 返回: String - 启动背景图像资源路径 + String getSplashPageBackgroundImage(); + + /// 获取启动页面图标路径 + /// 返回: String - 启动图标资源路径 + String getSplashPageIcon(); + + /// 获取启动页面跳过按钮背景图像路径 + /// 返回: String - 跳过按钮背景图像资源路径 + String getSplashPageSkipButtonBackground(); + + /// 获取启动页面跳过按钮文本颜色 + /// 返回: Color - 跳过按钮文本颜色 + Color getSplashPageSkipButtonTextColor(); + + /// 获取启动页面游戏名称背景图像路径 + /// 返回: String - 游戏名称背景图像资源路径 + String getSplashPageKingGamesNameBackground(); + + /// 获取启动页面游戏名称文本颜色 + /// 返回: Color - 游戏名称文本颜色 + Color getSplashPageKingGamesTextColor(); + + /// 获取启动页面CP名称背景图像路径 + /// 返回: String - CP名称背景图像资源路径 + String getSplashPageCpNameBackground(); + + /// 获取启动页面CP名称文本颜色 + /// 返回: Color - CP名称文本颜色 + Color getSplashPageCpTextColor(); + + /// === 搜索页面差异化方法 === + + /// 获取搜索页面Scaffold背景颜色 + /// 返回: Color - Scaffold背景颜色 + Color getSearchPageScaffoldBackgroundColor(); + + /// 获取搜索页面返回图标颜色 + /// 返回: Color - 返回图标颜色 + Color getSearchPageBackIconColor(); + + /// 获取搜索页面搜索框边框颜色 + /// 返回: Color - 搜索框边框颜色 + Color getSearchPageInputBorderColor(); + + /// 获取搜索页面搜索框文本颜色 + /// 返回: Color - 搜索框文本颜色 + Color getSearchPageInputTextColor(); + + /// 获取搜索页面搜索按钮文本颜色 + /// 返回: Color - 搜索按钮文本颜色 + Color getSearchPageButtonTextColor(); + + /// 获取搜索页面搜索按钮渐变颜色 + /// 返回: List - 搜索按钮渐变颜色列表 + List getSearchPageButtonGradient(); + + /// 获取搜索页面历史记录标题文本颜色 + /// 返回: Color - 历史记录标题文本颜色 + Color getSearchPageHistoryTitleTextColor(); + + /// 获取搜索页面历史项文本颜色 + /// 返回: Color - 历史项文本颜色 + Color getSearchPageHistoryItemTextColor(); + + /// 获取搜索页面历史项背景颜色 + /// 返回: Color - 历史项背景颜色 + Color getSearchPageHistoryItemBackgroundColor(); + + /// 获取搜索页面Tab指示器渐变开始颜色 + /// 返回: Color - Tab指示器渐变开始颜色 + Color getSearchPageTabIndicatorGradientStartColor(); + + /// 获取搜索页面Tab指示器渐变结束颜色 + /// 返回: Color - Tab指示器渐变结束颜色 + Color getSearchPageTabIndicatorGradientEndColor(); + + /// 获取搜索页面Tab标签选中颜色 + /// 返回: Color - Tab标签选中颜色 + Color getSearchPageTabSelectedLabelColor(); + + /// 获取搜索页面Tab分割线颜色 + /// 返回: Color - Tab分割线颜色 + Color getSearchPageTabDividerColor(); + + /// 获取搜索页面Tab标签未选中颜色 + /// 返回: Color - Tab标签未选中颜色 + Color getSearchPageTabUnselectedLabelColor(); + + /// 获取搜索页面分隔容器背景颜色 + /// 返回: Color - 分隔容器背景颜色 + Color getSearchPageDividerContainerBackgroundColor(); + + /// 获取搜索页面Tab标签选中文本样式 + /// 返回: TextStyle - Tab标签选中文本样式 + TextStyle getSearchPageTabSelectedLabelStyle(); + + /// 获取搜索页面Tab标签未选中文本样式 + /// 返回: TextStyle - Tab标签未选中文本样式 + TextStyle getSearchPageTabUnselectedLabelStyle(); + + /// 获取搜索页面搜索结果Tab标签内边距 + /// 返回: EdgeInsets - Tab标签内边距 + EdgeInsets getSearchPageResultTabLabelPadding(); + + /// 获取搜索页面搜索结果Tab标签选中颜色 + /// 返回: Color - Tab标签选中颜色 + Color getSearchPageResultTabLabelColor(); + + /// 获取搜索页面搜索结果Tab标签未选中颜色 + /// 返回: Color - Tab标签未选中颜色 + Color getSearchPageResultTabUnselectedLabelColor(); + + /// 获取搜索页面搜索结果Tab标签未选中文本样式 + /// 返回: TextStyle - Tab标签未选中文本样式 + TextStyle getSearchPageResultTabUnselectedLabelStyle(); + + /// 获取搜索页面搜索结果Tab标签选中文本样式 + /// 返回: TextStyle - Tab标签选中文本样式 + TextStyle getSearchPageResultTabLabelStyle(); + + /// 获取搜索页面搜索结果Tab指示器颜色(渐变) + /// 返回: LinearGradient - Tab指示器渐变颜色 + LinearGradient getSearchPageResultTabIndicatorColor(); + + /// 获取搜索页面搜索结果Tab指示器宽度 + /// 返回: double - Tab指示器宽度 + double getSearchPageResultTabIndicatorWidth(); + + /// 获取搜索页面搜索结果背景颜色 + /// 返回: Color - 搜索结果背景颜色 + Color getSearchPageResultBackgroundColor(); + + /// 获取搜索页面清除历史记录图标路径 + /// 返回: String - 清除历史记录图标资源路径 + String getSearchPageClearHistoryIcon(); + + /// 获取搜索页面空数据图标路径 + /// 返回: String - 空数据图标资源路径 + String getSearchPageEmptyDataIcon(); + + /// === 任务页面差异化方法 === + + /// 获取任务页面背景图像路径 + /// 返回: String - 背景图像资源路径 + String getTaskPageBackgroundImage(); + + /// 获取任务页面返回按钮颜色 + /// 返回: Color - 返回按钮颜色 + Color getTaskPageBackButtonColor(); + + /// 获取任务页面Scaffold背景颜色 + /// 返回: Color - Scaffold背景颜色 + Color getTaskPageScaffoldBackgroundColor(); + + /// 获取任务页面容器背景颜色 + /// 返回: Color - 容器背景颜色 + Color getTaskPageContainerBackgroundColor(); + + /// 获取任务页面边框颜色(普通边框) + /// 返回: Color - 普通边框颜色 + Color getTaskPageBorderColor(); + + /// 获取任务页面边框颜色(特殊边框,如任务项边框) + /// 返回: Color - 特殊边框颜色 + Color getTaskPageSpecialBorderColor(); + + /// 获取任务页面主要文本颜色 + /// 返回: Color - 主要文本颜色 + Color getTaskPagePrimaryTextColor(); + + /// 获取任务页面金币文本颜色(折扣前价格) + /// 返回: Color - 金币文本颜色 + Color getTaskPageGoldTextColor(); + + /// 获取任务页面经验值文本颜色 + /// 返回: Color - 经验值文本颜色 + Color getTaskPageExpTextColor(); + + /// 获取任务页面主题颜色(用于ChatVibeTheme.primaryColor) + /// 返回: Color - 主题颜色 + Color getTaskPageThemeColor(); + + /// 获取任务页面主题浅色(用于ChatVibeTheme.primaryLight) + /// 返回: Color - 主题浅色 + Color getTaskPageThemeLightColor(); + + /// 获取任务页面可领取按钮渐变颜色 + /// 返回: List - 可领取按钮渐变颜色列表 + List getTaskPageReceivableButtonGradient(); + + /// 获取任务页面已完成按钮渐变颜色 + /// 返回: List - 已完成按钮渐变颜色列表 + List getTaskPageCompletedButtonGradient(); + + /// 获取任务页面按钮文本颜色 + /// 返回: Color - 按钮文本颜色 + Color getTaskPageButtonTextColor(); + + /// 获取任务页面遮罩颜色 + /// 返回: Color - 遮罩颜色 + Color getTaskPageMaskColor(); + + /// 获取任务页面头部背景图像路径 + /// 返回: String - 头部背景图像资源路径 + String getTaskPageHeadBackgroundImage(); + + /// 获取任务页面金币图标路径 + /// 返回: String - 金币图标资源路径 + String getTaskPageGoldIcon(); + + /// 获取任务页面经验值图标路径 + /// 返回: String - 经验值图标资源路径 + String getTaskPageExpIcon(); + + /// 获取任务页面邀请奖励背景图像路径 + /// 返回: String - 邀请奖励背景图像资源路径 + String getTaskPageInvitationRewardBackgroundImage(); + + /// 获取任务页面礼品袋文本颜色 + /// 返回: Color - 礼品袋文本颜色 + Color getTaskPageGiftBagTextColor(); + + /// 获取任务页面AppBar背景颜色 + /// 返回: Color - AppBar背景颜色 + Color getTaskPageAppBarBackgroundColor(); + + /// 获取任务页面透明容器颜色 + /// 返回: Color - 透明容器颜色 + Color getTaskPageTransparentContainerColor(); + + /// === WebView页面差异化方法 === + + /// === 普通WebView页面方法 === + + /// 获取WebView页面AppBar背景颜色 + /// 返回: Color - AppBar背景颜色 + Color getWebViewPageAppBarBackgroundColor(); + + /// 获取WebView页面标题文本颜色 + /// 返回: Color - 标题文本颜色 + Color getWebViewPageTitleTextColor(); + + /// 获取WebView页面返回箭头图标颜色 + /// 返回: Color - 返回箭头图标颜色 + Color getWebViewPageBackArrowColor(); + + /// 获取WebView页面进度条背景颜色 + /// 返回: Color - 进度条背景颜色 + Color getWebViewPageProgressBarBackgroundColor(); + + /// 获取WebView页面进度条活动颜色 + /// 返回: Color - 进度条活动颜色 + Color getWebViewPageProgressBarActiveColor(); + + /// === 游戏WebView页面方法 === + + /// 获取游戏WebView页面关闭图标颜色 + /// 返回: Color - 关闭图标颜色 + Color getGameWebViewCloseIconColor(); + + /// 获取游戏WebView页面Scaffold背景颜色 + /// 返回: Color - Scaffold背景颜色 + Color getGameWebViewScaffoldBackgroundColor(); + + /// 获取游戏WebView页面加载指示器颜色 + /// 返回: Color - 加载指示器颜色 + Color getGameWebViewLoadingIndicatorColor(); + + /// 获取游戏WebView页面进度条背景颜色 + /// 返回: Color - 进度条背景颜色 + Color getGameWebViewProgressBarBackgroundColor(); + + /// === 设置页面差异化方法 === + + /// 获取设置页面背景图像路径 + /// 返回: String - 背景图像资源路径 + String getSettingsPageBackgroundImage(); + + /// 获取设置页面主容器背景颜色 + /// 返回: Color - 主容器背景颜色 + Color getSettingsPageMainContainerBackgroundColor(); + + /// 获取设置页面主容器边框颜色 + /// 返回: Color - 主容器边框颜色 + Color getSettingsPageMainContainerBorderColor(); + + /// 获取设置页面主文本颜色 + /// 返回: Color - 主文本颜色 + Color getSettingsPagePrimaryTextColor(); + + /// 获取设置页面次要文本颜色 + /// 返回: Color - 次要文本颜色 + Color getSettingsPageSecondaryTextColor(); + + /// 获取设置页面图标颜色 + /// 返回: Color - 图标颜色 + Color getSettingsPageIconColor(); + + /// 获取设置页面常见文本颜色 + /// 返回: Color - 常见文本颜色 + Color getSettingsPageCommonTextColor(); + + /// 获取设置页面关于文本颜色 + /// 返回: Color - 关于文本颜色 + Color getSettingsPageAboutTextColor(); + + /// 获取设置页面容器边框颜色 + /// 返回: Color - 容器边框颜色 + Color getSettingsPageContainerBorderColor(); + + /// 获取设置页面容器背景颜色 + /// 返回: Color - 容器背景颜色 + Color getSettingsPageContainerBackgroundColor(); + + /// 获取设置页面按钮背景颜色 + /// 返回: Color - 按钮背景颜色 + Color getSettingsPageButtonBackgroundColor(); + + /// 获取设置页面按钮文本颜色 + /// 返回: Color - 按钮文本颜色 + Color getSettingsPageButtonTextColor(); + + /// === 语言页面差异化方法 === + + /// 获取语言页面背景图像路径 + /// 返回: String - 背景图像资源路径 + String getLanguagePageBackgroundImage(); + + /// 获取语言页面复选框选中颜色 + /// 返回: Color - 复选框选中颜色 + Color getLanguagePageCheckboxActiveColor(); + + /// 获取语言页面复选框边框颜色 + /// 返回: Color - 复选框边框颜色 + Color getLanguagePageCheckboxBorderColor(); + + /// 获取语言页面主文本颜色 + /// 返回: Color - 主文本颜色 + Color getLanguagePagePrimaryTextColor(); + + /// 获取语言页面复选框对勾颜色 + /// 返回: Color - 复选框对勾颜色 + Color getLanguagePageCheckColor(); + + /// 获取语言页面复选框边框圆角 + /// 返回: double - 边框圆角值 + double getLanguagePageCheckboxBorderRadius(); + + /// 获取语言页面复选框边框宽度 + /// 返回: double - 边框宽度值 + double getLanguagePageCheckboxBorderWidth(); + + /// 获取语言页面Scaffold背景颜色 + /// 返回: Color - Scaffold背景颜色 + Color getLanguagePageScaffoldBackgroundColor(); + + /// === 账户页面差异化方法 === + + /// 获取账户页面背景图像路径 + /// 返回: String - 背景图像资源路径 + String getAccountPageBackgroundImage(); + + /// 获取账户页面主容器背景颜色 + /// 返回: Color - 主容器背景颜色 + Color getAccountPageMainContainerBackgroundColor(); + + /// 获取账户页面主容器边框颜色 + /// 返回: Color - 主容器边框颜色 + Color getAccountPageMainContainerBorderColor(); + + /// 获取账户页面主文本颜色 + /// 返回: Color - 主文本颜色 + Color getAccountPagePrimaryTextColor(); + + /// 获取账户页面次要文本颜色 + /// 返回: Color - 次要文本颜色 + Color getAccountPageSecondaryTextColor(); + + /// 获取账户页面图标颜色 + /// 返回: Color - 图标颜色 + Color getAccountPageIconColor(); + + /// 获取账户页面分隔线颜色 + /// 返回: Color - 分隔线颜色 + Color getAccountPageDividerColor(); + + /// 获取账户页面Scaffold背景颜色 + /// 返回: Color - Scaffold背景颜色 + Color getAccountPageScaffoldBackgroundColor(); + + Color getGoldRecordPageListBackgroundColor(); +} diff --git a/lib/chatvibe_core/config/configs/variant1_config.dart b/lib/chatvibe_core/config/configs/variant1_config.dart new file mode 100644 index 0000000..a292817 --- /dev/null +++ b/lib/chatvibe_core/config/configs/variant1_config.dart @@ -0,0 +1,179 @@ +import 'package:flutter/foundation.dart'; +import 'package:aslan/chatvibe_core/config/app_config.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; +import 'package:aslan/chatvibe_core/config/strategies/variant1_business_logic_strategy.dart'; + +/// 马甲包配置 +class Variant1Config implements AppConfig { + BusinessLogicStrategy? _businessLogicStrategy; + + @override + String get appName => 'Aslan'; // 马甲包应用名称 + + @override + String get packageName => 'com.chat.auu'; + + @override + String get apiHost => 'https://api.atuchat.com/'; // 独立API服务器,需要部署 + + @override + String get imgHost => 'https://img.atuchat.com/'; // 独立图片服务器,需要部署 + + @override + String get privacyAgreementUrl => 'https://www.atuchat.com/privacy.html'; // 独立隐私政策页面 + + @override + String get userAgreementUrl => 'https://www.atuchat.com/service.html'; // 独立用户协议页面 + + @override + String get appDownloadUrlGoogle => 'https://play.google.com/store/apps/details?id=$packageName'; + + @override + String get rechargeUrl => 'https://h5.atuchat.com/#/recharge-agency-recruit'; + + @override + String get cpRewardUrl => 'https://h5.atuchat.com/#/cp-reward'; + + @override + String get payApplicationId => '1963531459019739137'; + + @override + String get appDownloadUrlApple => 'https://apps.apple.com/us/app/chatvibe/id1234567890'; // 需要更新为真实App Store ID + + @override + String get anchorAgentUrl => 'https://h5.atuchat.com/#/apply'; // 独立H5页面,需要部署 + + @override + String get hostCenterUrl => 'https://h5.atuchat.com/#/host-center'; // 独立H5页面,需要部署 + + @override + String get bdCenterUrl => 'https://h5.atuchat.com/#/bd-center'; // 独立H5页面,需要部署 + + @override + String get bdLeaderUrl => 'http://h5.atuchat.com/#/bd-leader-center'; // 独立H5页面,需要部署 + + @override + String get coinSellerUrl => 'https://h5.atuchat.com/#/coin-seller'; // 独立H5页面,需要部署 + + @override + String get adminUrl => 'https://h5.atuchat.com/#/admin-center'; // 独立H5页面,需要部署 + + @override + String get agencyCenterUrl => 'https://h5.atuchat.com/#/agency-center'; // 独立H5页面,需要部署 + + @override + String get gamesKingUrl => 'https://h5.atuchat.com/#/games-king'; // 独立H5页面,需要部署 + + @override + String get wealthRankUrl => 'https://h5.atuchat.com/#/ranking?first=Wealth'; // 独立H5页面,需要部署 + + @override + String get charmRankUrl => 'https://h5.atuchat.com/#/ranking?first=Charm'; // 独立H5页面,需要部署 + + @override + String get roomRankUrl => 'https://h5.atuchat.com/#/ranking?first=Room'; // 独立H5页面,需要部署 + + @override + String get inviteNewUserUrl => 'https://h5.atuchat.com/#/invitation/invite-new-user'; // 独立H5页面,需要部署 + + @override + int get primaryColor => 0xffFF5722; // 不同主色(橙色) + + @override + String get tencentImAppid => '20034801'; // 马甲包腾讯IM App ID,需要替换为实际值 + + @override + String get agoraRtcAppid => 'f3d2cd414a5f4ee6a08819aa4c72e17a'; // 马甲包Agora RTC App ID,需要替换为实际值 + + @override + num get gameAppid => 2412086832; // 马甲包游戏App ID,需要替换为实际值 + + @override + String get gameAppChannel => 'aslan'; + + @override + String get bigBroadcastGroup => '@TGS#aTJCOGK5CM'; // 独立的广播群组ID + + @override + String get imAdmin => 'c2c_atyou-admin'; // 独立的管理账号 + + @override + bool get isReview => true; + + @override + bool get isGiftSpecialEffects => true; + + @override + bool get isEntryVehicleAnimation => true; + + @override + bool get isFloatingAnimationInGlobal => true; + + @override + bool get isLuckGiftSpecialEffects => true; + + @override + Map get featureFlags => { + 'giftSpecialEffects': isGiftSpecialEffects, + 'entryVehicleAnimation': isEntryVehicleAnimation, + 'floatingAnimationInGlobal': isFloatingAnimationInGlobal, + 'luckGiftSpecialEffects': isLuckGiftSpecialEffects, + }; + + @override + BusinessLogicStrategy get businessLogicStrategy { + return _businessLogicStrategy ??= Variant1BusinessLogicStrategy(); + } + + @override + Map toMap() { + return { + 'appName': appName, + 'packageName': packageName, + 'apiHost': apiHost, + 'imgHost': imgHost, + 'privacyAgreementUrl': privacyAgreementUrl, + 'userAgreementUrl': userAgreementUrl, + 'primaryColor': primaryColor, + 'tencentImAppid': tencentImAppid, + 'agoraRtcAppid': agoraRtcAppid, + 'gameAppid': gameAppid, + 'gameAppChannel': gameAppChannel, + 'bigBroadcastGroup': bigBroadcastGroup, + 'imAdmin': imAdmin, + 'isReview': isReview, + 'isGiftSpecialEffects': isGiftSpecialEffects, + 'isEntryVehicleAnimation': isEntryVehicleAnimation, + 'isFloatingAnimationInGlobal': isFloatingAnimationInGlobal, + 'isLuckGiftSpecialEffects': isLuckGiftSpecialEffects, + 'featureFlags': featureFlags, + 'businessLogicStrategy': 'Variant1BusinessLogicStrategy', + }; + } + + @override + void validate() { + final errors = []; + + // 检查API主机地址 + if (apiHost.isEmpty || !apiHost.startsWith('http')) { + errors.add('API主机地址无效或未设置'); + } + + // 如果有错误,根据模式处理 + if (errors.isNotEmpty) { + final errorMessage = '应用配置验证失败:\n${errors.join('\n')}'; + + if (kDebugMode) { + // 调试模式下只输出警告,不阻止应用启动(方便开发) + debugPrint('⚠️ 警告: $errorMessage'); + debugPrint('⚠️ 在调试模式下,应用将继续启动,但某些功能可能无法正常工作'); + } else { + // 发布模式下抛出异常,阻止应用启动 + throw StateError(errorMessage); + } + } else { + debugPrint('应用配置验证通过'); + } + } +} \ No newline at end of file diff --git a/lib/chatvibe_core/config/strategies/base_business_logic_strategy.dart b/lib/chatvibe_core/config/strategies/base_business_logic_strategy.dart new file mode 100644 index 0000000..9587912 --- /dev/null +++ b/lib/chatvibe_core/config/strategies/base_business_logic_strategy.dart @@ -0,0 +1,2629 @@ +import 'package:flutter/material.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_features/home/popular/home_popular_page.dart'; +import 'package:aslan/chatvibe_features/family/index_family_page.dart'; +import 'package:aslan/chatvibe_features/home/explore/explore_page2.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_core/utilities/at_dialog_utils.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:provider/provider.dart'; + +/// 基础业务逻辑策略实现 +/// 提供原始应用的默认业务逻辑 +class BaseBusinessLogicStrategy implements BusinessLogicStrategy { + + @override + List getHomeTabPages(BuildContext context) { + final List pages = []; + if (shouldShowFamilyTab()) { + pages.add(IndexFamilyPage()); + } + pages.add(HomePopularPage()); + pages.add(ExplorePage2()); + return pages; + } + + @override + List getHomeTabLabels(BuildContext context) { + final List tabs = []; + if (shouldShowFamilyTab()) { + tabs.add(Tab(text: ATAppLocalizations.of(context)!.family)); + } + tabs.add(Tab(text: ATAppLocalizations.of(context)!.popular)); + tabs.add(Tab(text: ATAppLocalizations.of(context)!.explore)); + return tabs; + } + + @override + bool shouldShowFamilyTab() { + return UserManager().getCurrentUser()?.userProfile?.familyId != null; + } + + @override + int getHomeInitialTabIndex() { + return shouldShowFamilyTab() ? 1 : 0; + } + + @override + void onAvatarTap(BuildContext context) { + Provider.of(context, listen: false).openDrawer(context); + } + + @override + bool shouldShowFirstRechargePrompt() { + return UserManager().getCurrentUser()?.userProfile?.firstRecharge ?? false; + } + + @override + Map getFirstRechargePosition() { + return {'bottom': 40.0, 'end': 15.0, 'top': null, 'start': null}; + } + + @override + void onFirstRechargeTap(BuildContext context) { + ATDialogUtils.showFirstRechargeDialog(context); + } + + /// === 登录页面差异化方法实现 === + + @override + String getLoginBackgroundImage() { + // 原始应用默认背景图片 + return "atu_images/login/login_account.png"; + } + + @override + String getLoginAppIcon() { + // 原始应用默认图标 + return "atu_images/splash/at_icon_splash_icon.png"; + } + + @override + Color getLoginButtonColor() { + // 原始应用默认按钮颜色 - 使用马甲包主题的primaryLight + return ChatVibeTheme.primaryLight; + } + + @override + List? getLoginButtonGradient() { + // 原始应用默认不使用渐变 + return null; + } + + @override + Color getLoginButtonTextColor() { + // 原始应用默认按钮文本颜色(白色) + return Colors.white; + } + + @override + Color getLoginLabelTextColor() { + // 原始应用默认标签文本颜色(黑色) + return Colors.black; + } + + @override + BoxDecoration getLoginInputDecoration() { + // 原始应用输入框装饰:透明背景,黑色边框 + return BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.all(Radius.circular(8)), + border: Border.all(color: Colors.black45, width: 0.5), + ); + } + + @override + EdgeInsets getLoginPagePadding() { + // 原始应用默认内边距 + return const EdgeInsets.only(top: 100); + } + + @override + bool shouldShowLoginStatusBar() { + // 原始应用默认显示状态栏 + return true; + } + + @override + Color getLoginHintTextColor() { + // 原始应用默认提示文本颜色(黑色45%透明度) + return Colors.black45; + } + + @override + Color getLoginInputTextColor() { + // 原始应用默认输入文本颜色(黑色) + return Colors.black; + } + + /// === 家族页面差异化方法实现 === + + @override + num getFamilyDisplayLevel(num level) { + // 原始应用的等级分段逻辑 + if (level > 0 && level < 4) { + return 1; + } else if (level > 3 && level < 7) { + return 2; + } else if (level > 6 && level < 10) { + return 3; + } else { + return level; + } + } + + @override + String getFamilyLevelBackgroundImage(num level) { + // 原始应用背景图片路径 + return "atu_images/family/at_icon_family_info_item_bg_$level.9.png"; + } + + @override + String getFamilyNoticeDisplayText(String? familyNotice, BuildContext context) { + // 原始应用通知处理逻辑 + String processedNotice = (familyNotice ?? "").replaceAll(RegExp(r'\s+'), ' '); + if (processedNotice.isEmpty) { + return ATAppLocalizations.of(context)!.welcomeToMyFamily; + } + return processedNotice; + } + + @override + int getFamilyOnlineUserDisplayCount() { + // 原始应用:显示所有在线用户(由数据源控制显示数量) + // 这里返回一个较大的值,实际显示数量由数据源决定 + return 20; // 假设显示最多20个 + } + + @override + int getFamilyRoomGridCrossAxisCount() { + // 原始应用:2列网格 + return 2; + } + + @override + bool shouldShowPasswordRoomIcon() { + // 原始应用:显示密码房间图标 + return true; + } + + /// === 探索页面差异化方法实现 === + + @override + int getExploreRoomDisplayThreshold() { + // 原始应用:显示前6个房间在抽屉上方 + return 6; + } + + @override + String getExploreGridIcon() { + // 原始应用:网格视图图标 + return "atu_images/index/at_icon_index_room_model_1.png"; + } + + @override + String getExploreListIcon() { + // 原始应用:列表视图图标 + return "atu_images/index/at_icon_index_room_model_2.png"; + } + + @override + String getExploreRankIconPattern(bool isGrid, int rank) { + // 原始应用:排名图标路径模式 + if (isGrid) { + return "atu_images/index/at_icon_explore_room_model_1_rank_$rank.png"; + } else { + return "atu_images/index/at_icon_explore_room_model_2_rank_$rank.png"; + } + } + + @override + Color getExploreRoomBorderColor() { + // 原始应用:使用主题色作为边框颜色 + return ChatVibeTheme.primaryLight; + } + + @override + double getExploreRoomBorderWidth() { + // 原始应用:边框宽度 + return 1.3; + } + + @override + int getExploreDrawerGridCrossAxisCount() { + // 原始应用:抽屉内容网格列数 + return 3; + } + + @override + int getExploreRoomNameMarqueeThreshold() { + // 原始应用:房间名称长度超过10时显示滚动效果 + return 10; + } + + @override + int getExploreRoomDescMarqueeThreshold() { + // 原始应用:房间描述长度超过12时显示滚动效果 + return 12; + } + + /// === 个人主页差异化方法实现 === + + @override + String getMePageDefaultBackgroundImage() { + // 原始应用默认背景图片 + return "atu_images/person/at_icon_my_head_bg_defalt.png"; + } + + @override + String getMePageGenderBackgroundImage(bool isFemale) { + // 原始应用性别背景图片 + return isFemale + ? "atu_images/person/at_icon_my_head_bg_woman.png" + : "atu_images/person/at_icon_my_head_bg_man.png"; + } + + @override + String getMePageCpDialogHeadImage() { + // 原始应用CP对话框头像图片 + return "atu_images/person/at_icon_send_cp_requst_dialog_head.png"; + } + + @override + String getMePageDefaultAvatarImage() { + // 原始应用默认头像图片 + return "atu_images/general/at_icon_avar_defalt.png"; + } + + @override + String getMePageIdBackgroundImage(bool hasSpecialId) { + // 原始应用ID背景图片 + return hasSpecialId + ? "atu_images/general/at_icon_special_id_bg.png" + : "atu_images/general/at_icon_id_bg.png"; + } + + @override + String getMePageCopyIdIcon() { + // 原始应用复制ID图标 + return "atu_images/room/at_icon_user_card_copy_id.png"; + } + + @override + String getMePageGenderAgeBackgroundImage(bool isFemale) { + // 原始应用性别年龄背景图片 + return isFemale + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png"; + } + + @override + String getMePageVisitorsFollowFansBackgroundImage(bool isFemale) { + // 原始应用访客关注粉丝背景图片 + return isFemale + ? "atu_images/person/at_icon_vistors_follow_fans_bg_woman.png" + : "atu_images/person/at_icon_vistors_follow_fans_bg_man.png"; + } + + @override + String getMePageEditUserInfoIcon() { + // 原始应用编辑用户信息图标 + return "atu_images/person/at_icon_edit_user_info2.png"; + } + + @override + Color getMePageShineColor() { + // 原始应用闪耀文本颜色 + return const Color(0xffe6e6e6); + } + + @override + List getMePageGradientColors(bool isFemale) { + // 原始应用渐变颜色 + if (isFemale) { + return [ + const Color(0xFF3F1016), + const Color(0xFF1E0B0E), + const Color(0xFF3F0810), + ]; + } else { + return [ + const Color(0xFF666666), + const Color(0xFF000000), + ]; + } + } + + @override + List getMePageTabIndicatorGradient(bool isFemale) { + // 原始应用Tab指示器渐变 + if (isFemale) { + return [ + const Color(0xffC62548), + const Color(0xff9C0322), + ]; + } else { + return [ + const Color(0xff141414), + const Color(0xffE4E4E4), + ]; + } + } + + @override + Color getMePageVisitorsFollowFansTextColor() { + // 原始应用访客关注粉丝文本颜色 + return const Color(0xffD0D0D0); + } + + @override + int getMePageInitialTabIndex() { + // 原始应用初始Tab索引 + return 1; // profile页 + } + + @override + double getMePageScrollOpacityThreshold() { + // 原始应用滚动透明度计算阈值 + return 320.0; + } + + @override + int getMePageNicknameScrollThreshold() { + // 原始应用昵称滚动显示阈值 + return 13; + } + + @override + int getMePageAgeDisplayWidthThreshold() { + // 原始应用年龄显示宽度阈值 + return 999; + } + + @override + double getMePageOpacityThresholdForAvatarDisplay() { + // 原始应用头像显示透明度阈值 + return 0.5; + } + + /// === 热门页面差异化方法实现 === + + @override + String getPopularLeaderboardBackgroundImage(String leaderboardType) { + // 原始应用:使用现有的图像资源路径 + switch (leaderboardType) { + case 'room': + return "atu_images/index/at_icon_leader_spinner_room_bg.png"; + case 'wealth': + return "atu_images/index/at_icon_leader_spinner_wealth_bg.png"; + case 'charm': + return "atu_images/index/at_icon_leader_spinner_charm_bg.png"; + default: + return "atu_images/index/at_icon_leader_spinner_room_bg.png"; + } + } + + /// === 游戏页面差异化方法实现 === + + @override + String getGameRankBackgroundImage(int rank) { + // 原始应用:使用现有的排名背景图像模式 + return "atu_images/game/at_icon_game_list_item_bg_$rank.png"; + } + + @override + String getGameRankIcon(int rank) { + // 原始应用:使用现有的排名图标模式 + return "atu_images/game/at_icon_game_rank_$rank.png"; + } + + @override + Color getGamePageThemeColor() { + // 原始应用:默认主题颜色 + return const Color(0xffFF5722); + } + + @override + Color getGamePageBackgroundColor() { + // 原始应用:默认背景颜色 + return const Color(0xfff5f5f5); + } + + @override + int getGamePageGridCrossAxisCount() { + // 原始应用:3列网格 + return 3; + } + + @override + double getGamePageGridChildAspectRatio() { + // 原始应用:默认高度比例 + return 1.3; + } + + @override + Color getGameRankTextColor(int rank) { + // 原始应用:根据排名返回不同颜色 + switch (rank) { + case 1: + return const Color(0xffFFD700); // 金色 + case 2: + return const Color(0xffC0C0C0); // 银色 + case 3: + return const Color(0xffCD7F32); // 铜色 + default: + return Colors.black87; + } + } + + @override + Color getGamePageGradientSecondColor() { + // 原始应用:渐变第二颜色 + return const Color(0xffEAD4FF); + } + + @override + String getGameHotTagBackgroundImage() { + // 原始应用:热门游戏标签背景图片 + return "atu_images/index/at_icon_hotgames_tag_bg.png"; + } + + @override + String getGameNewTagImage() { + // 原始应用:新游戏标签图片 + return "atu_images/index/at_icon_game_new_tag.png"; + } + + @override + String getGamePageMaskImage() { + // 原始应用:游戏页面遮罩图片 + return "atu_images/index/at_icon_index_mask.png"; + } + + /// === VIP页面差异化方法实现 === + + @override + String getVipPageBackgroundImage(int vipLevel) { + // 原始应用:使用现有的VIP背景图像模式 + return "atu_images/vip/at_icon_vip_level_bg_$vipLevel.png"; + } + + @override + String getVipPageIcon(int vipLevel) { + // 原始应用:使用现有的VIP图标模式 + return "atu_images/vip/at_icon_vip_level_$vipLevel.png"; + } + + @override + List getVipPageButtonGradient() { + // 原始应用:VIP按钮渐变颜色 + return [ + const Color(0xffFF5722), + const Color(0xffFEB219), + ]; + } + + @override + int getVipPageGridCrossAxisCount() { + // 原始应用:3列网格 + return 3; + } + + @override + double getVipPageGridChildAspectRatio() { + // 原始应用:默认高度比例 + return 1.5; + } + + @override + String getVipPageHeadBackgroundImage(int vipLevel) { + // 原始应用:VIP头部背景图像模式 + return "atu_images/vip/at_icon_vip_head_bg_v$vipLevel.png"; + } + + @override + String getVipPageTabSelectedIcon(int vipLevel) { + // 原始应用:Tab选中状态图标模式 + return "atu_images/vip/at_icon_tab_vip_text_on_v$vipLevel.png"; + } + + @override + String getVipPageTabUnselectedIcon(int vipLevel) { + // 原始应用:Tab未选中状态图标模式 + return "atu_images/vip/at_icon_tab_vip_text_no_v$vipLevel.png"; + } + + @override + String getVipPageLargeIcon(int vipLevel) { + // 原始应用:VIP页面大图标路径模式 + return "atu_images/vip/at_icon_vip${vipLevel}_ic.webp"; + } + + @override + String getVipPageTitleImage(int vipLevel) { + // 原始应用:VIP页面标题图像路径模式 + return "atu_images/vip/at_icon_vip${vipLevel}_t1.png"; + } + + @override + String getVipPageTagImage(int vipLevel, int tagIndex) { + // 原始应用:VIP页面标签图像路径模式 + return "atu_images/vip/at_icon_vip${vipLevel}_tag$tagIndex.png"; + } + + @override + String getVipPageItemBackgroundImage(int vipLevel) { + // 原始应用:VIP页面功能项背景图像路径模式 + return "atu_images/vip/at_icon_vip${vipLevel}_item_bg.png"; + } + + @override + String getVipPagePrivilegeIcon(int vipLevel, int privilegeIndex,bool isSelected) { + // 原始应用:VIP页面特权图标路径模式 + return "atu_images/vip/at_icon_vip${vipLevel}_privilege$privilegeIndex.png"; + } + + @override + String getVipPageFeatureIcon(int vipLevel, String featureName) { + // 原始应用:VIP页面功能图标路径模式 + return "atu_images/vip/at_icon_vip${vipLevel}_$featureName.png"; + } + + @override + String getVipPagePreviewImage(int vipLevel, String previewType, String featureName) { + // 原始应用:VIP页面预览图像路径模式 + if (featureName == 'profile_frame') { + return "atu_images/vip/at_icon_vip${vipLevel}_profile_rev.png"; + } else if (featureName == 'profile_card') { + return "atu_images/vip/at_icon_vip${vipLevel}_profile_card_rev.png"; + } else if (featureName == 'room_cover_headdress') { + return "atu_images/vip/at_icon_vip${vipLevel}_room_cover_border_rev.png"; + } else if (featureName == 'mic_rippl_theme') { + return "atu_images/vip/at_icon_vip${vipLevel}_seat_rev.png"; + } else if (featureName == 'chatbox') { + return "atu_images/vip/at_icon_vip${vipLevel}_chatbox_pre.png"; + } else { + return "atu_images/vip/at_icon_vip${vipLevel}_${featureName}_$previewType.png"; + } + } + + @override + String getVipPagePurchaseBackgroundImage(int vipLevel) { + // 原始应用:VIP页面购买背景图像路径模式 + return "atu_images/vip/at_icon_vip${vipLevel}_buy_bg.png"; + } + + @override + String getVipPagePurchaseButtonImage() { + // 原始应用:VIP页面购买按钮图像路径 + return "atu_images/vip/at_icon_vip_buy_btn.png"; + } + + @override + Color getVipPageTextColor(int vipLevel,bool isSelected) { + // 原始应用:根据VIP等级返回不同的文本颜色 + switch (vipLevel) { + case 1: + return const Color(0xFF71D671); + case 2: + return const Color(0xFF7179D6); + case 3: + return const Color(0xFFAA71D6); + case 4: + return const Color(0xFF71D6B3); + case 5: + return const Color(0xFFD67171); + case 6: + return const Color(0xFFFEE5BB); + default: + return Colors.white; + } + } + + /// === 通用页面差异化方法实现 === + + @override + String getCommonRankBackgroundImage(String pageType, int rank) { + // 原始应用:通用的排名背景图像模式 + return "atu_images/$pageType/at_icon_${pageType}_rank_bg_$rank.png"; + } + + @override + String getCommonRankIcon(String pageType, int rank) { + // 原始应用:通用的排名图标模式 + return "atu_images/$pageType/at_icon_${pageType}_rank_$rank.png"; + } + + @override + int getCommonPageGridCrossAxisCount(String pageType) { + // 原始应用:通用的2列网格 + return 2; + } + + @override + double getCommonPageGridChildAspectRatio(String pageType) { + // 原始应用:通用的高度比例 + return 1.4; + } + + /// === 个人主页2 (MePage2) 差异化方法实现 === + + @override + Map getMePage2WalletBackground() { + // 原始应用默认钱包背景配置 + return { + 'gradient': [ + Color(0xFFFF9500), + Color(0xCCFF9500), // withOpacity(0.8) + ], + 'borderRadius': 12.0, + 'shadow': { + 'color': Color(0x1A000000), // Colors.black.withOpacity(0.1) + 'blurRadius': 12.0, + 'offset': Offset(0, 4.0), + }, + }; + } + + @override + Map getMePage2StatisticsColors() { + // 原始应用统计信息颜色配置 + return { + 'numberColor': Color(0xFF333333), + 'labelColor': Color(0xFFB1B1B1), + 'dividerColor': Color(0xFFE6E6E6), + }; + } + + @override + Map getMePage2FunctionIcons() { + // 原始应用功能卡片图标路径映射 + return { + 'invite': 'atu_images/general/at_icon_invite.png', + 'task': 'atu_images/general/at_icon_task.png', + 'store': 'atu_images/general/at_icon_shop.png', + 'items': 'atu_images/general/at_icon_items.png', + 'level': 'atu_images/general/at_icon_level.png', + 'badge': 'atu_images/general/at_icon_badge.png', + 'host': 'atu_images/general/at_icon_host.png', + 'bd': 'atu_images/general/at_icon_bd.png', + 'feedback': 'atu_images/general/at_icon_feedback.png', + 'settings': 'atu_images/general/at_icon_settings_card.png', + }; + } + + @override + Map getMePage2CardStyles() { + // 原始应用卡片样式配置 + return { + 'borderRadius': 8.0, + 'shadow': { + 'color': Color(0x0D000000), // Colors.black.withOpacity(0.05) + 'blurRadius': 8.0, + 'offset': Offset(0, 2.0), + }, + 'border': null, // 无边框 + }; + } + + /// === Admin编辑页面差异化方法实现 === + + @override + String getAdminEditingBackgroundImage() { + // 原始应用:编辑页面背景图片 + return "atu_images/room/at_icon_room_settig_bg.png"; + } + + @override + List getAdminEditingButtonGradient(String buttonType) { + // 原始应用:编辑页面按钮渐变颜色,两个按钮使用相同的渐变 + return [ + const Color(0xffC670FF), + const Color(0xff7726FF), + ]; + } + + @override + String getAdminEditingIcon(String iconType) { + // 原始应用:编辑页面图标路径 + switch (iconType) { + case 'specialIdBg': + return "atu_images/general/at_icon_special_id_bg.png"; + case 'normalIdBg': + return "atu_images/general/at_icon_id_bg.png"; + case 'copyId': + return "atu_images/room/at_icon_user_card_copy_id.png"; + case 'addPic': + return "atu_images/general/at_icon_add_pic.png"; + case 'closePic': + return "atu_images/general/at_icon_pic_close.png"; + case 'checked': + return "atu_images/general/at_icon_checked.png"; + default: + return ""; + } + } + + @override + BoxDecoration getAdminEditingInputDecoration() { + // 原始应用:编辑页面输入框装饰样式 + return BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: const Color(0xffE6E6E6), + width: 1.0, + ), + ); + } + + @override + int getAdminEditingMaxImageUploadCount() { + // 原始应用:最大图片上传数量(pic01, pic02, pic03) + return 3; + } + + @override + int getAdminEditingDescriptionMaxLength() { + // 原始应用:描述文本框最大字符数 + return 200; + } + + @override + Map getAdminEditingViolationTypeMapping(String targetType) { + // 原始应用:违规类型ID映射 + if (targetType == 'User') { + return { + 'Pornography': 1, + 'Illegal information': 2, + }; + } else if (targetType == 'Room') { + return { + 'Pornography': 3, + 'Illegal information': 4, + 'Fraud': 5, + 'Other': 6, + }; + } + return {}; + } + + /// === Admin搜索页面差异化方法实现 === + + @override + String getAdminSearchBackgroundImage(String pageType) { + // 原始应用:搜索页面背景图片 + return "atu_images/index/at_icon_coupon_head_bg.png"; + } + + @override + List getAdminSearchButtonGradient(String pageType) { + // 原始应用:搜索按钮渐变颜色 - 透明渐变 + return [ + Colors.transparent, + Colors.transparent, + ]; + } + + @override + Color getAdminSearchButtonTextColor(String pageType) { + // 原始应用:搜索按钮文本颜色 + return Colors.black; + } + + @override + String getAdminSearchEditIcon(String pageType) { + // 原始应用:编辑图标路径 + return "atu_images/room/at_icon_room_edit.png"; + } + + @override + Color getAdminSearchInputBorderColor(String pageType) { + // 原始应用:搜索框边框颜色 + return Colors.black12; + } + + @override + Color getAdminSearchInputTextColor(String pageType) { + // 原始应用:搜索框文本颜色 + return Colors.grey; + } + + @override + String getAdminSearchUserInfoIcon(String iconType) { + // 原始应用:用户信息图标路径 + switch (iconType) { + case 'specialIdBg': + return "atu_images/general/at_icon_special_id_bg.png"; + case 'normalIdBg': + return "atu_images/general/at_icon_id_bg.png"; + case 'copyId': + return "atu_images/room/at_icon_user_card_copy_id.png"; + default: + return ""; + } + } + + /// === 主登录页面差异化方法实现 === + + @override + String getLoginMainBackgroundImage() { + // 原始应用默认背景图片 + return "atu_images/login/at_login.png"; + } + + @override + String getLoginMainAppIcon() { + // 原始应用默认应用图标 + return "atu_images/splash/at_icon_splash_icon.png"; + } + + @override + String getLoginMainGoogleIcon() { + // 原始应用默认Google图标 + return "atu_images/login/at_icon_google.png"; + } + + @override + String getLoginMainAppleIcon() { + // 原始应用默认Apple图标 + return "atu_images/login/at_icon_iphone.png"; + } + + @override + String getLoginMainAccountIcon() { + // 原始应用默认账号图标 + return "atu_images/login/at_icon_account.png"; + } + + @override + String getLoginMainAgreementIcon(bool isSelected) { + // 原始应用默认协议图标 + return isSelected + ? "atu_images/login/at_icon_login_ser_select.png" + : "atu_images/login/at_icon_login_ser_select_un.png"; + } + + @override + Color getLoginMainDividerColor() { + // 原始应用默认分割线颜色 + return const Color(0xFFCCCCCC); + } + + @override + Color getLoginMainButtonBorderColor() { + // 原始应用默认按钮边框颜色 + return const Color(0xFFE6E6E6); + } + + @override + Color getLoginMainButtonBackgroundColor() { + // 原始应用默认按钮背景颜色 + return Colors.white; + } + + @override + Color getLoginMainLinkColor() { + // 原始应用默认链接颜色 + return const Color(0xFFFF9500); + } + + @override + Color getLoginMainButtonTextColor() { + // 原始应用默认按钮文本颜色 + return Colors.black; + } + + /// === 编辑个人资料页面差异化方法实现 === + + @override + String getEditProfileBackgroundImage() { + // 原始应用默认背景图片 + return "atu_images/login/edit_profile_bg.png"; + } + + @override + String getEditProfileDefaultAvatarImage() { + // 原始应用默认头像图片 + return "atu_images/login/at_icon_profile_head.png"; + } + + @override + String getEditProfileGenderIcon(bool isMale) { + // 原始应用默认性别图标 + return isMale + ? "atu_images/login/at_icon_sex_man.png" + : "atu_images/login/at_icon_sex_woman.png"; + } + + @override + List getEditProfileGenderButtonGradient(bool isMale, bool isSelected) { + // 原始应用默认性别按钮渐变颜色 + return [ + const Color(0xffFF9326), + const Color(0xffFEB219), + ]; + } + + @override + Color getEditProfileInputBackgroundColor() { + // 原始应用默认输入框背景颜色 + return Colors.white38; + } + + @override + Color getEditProfileDatePickerConfirmColor() { + // 原始应用默认日期选择器确认按钮颜色 + return const Color(0xff7726FF); + } + + @override + Color getEditProfileContinueButtonColor() { + // 原始应用默认继续按钮颜色 + return const Color(0xff7726FF); + } + + /// === Country页面差异化方法实现 === + + @override + String getCountrySelectOkIcon() { + // 原始应用默认选择确认图标 + return "atu_images/general/at_icon_select_ok.png"; + } + + @override + String getCountrySelectUnOkIcon() { + // 原始应用默认选择未确认图标 + return "atu_images/general/at_icon_select_un_ok.png"; + } + + @override + String getCountryRadioSelectedIcon() { + // 原始应用默认单选选中图标 + return "atu_images/login/at_icon_login_ser_select.png"; + } + + @override + String getCountryRadioUnselectedIcon() { + // 原始应用默认单选未选中图标 + return "atu_images/login/at_icon_login_ser_select_un.png"; + } + + @override + Color getCountryItemBorderColor() { + // 原始应用默认国家列表项边框颜色 + return const Color(0xffE6E6E6); + } + + @override + Color getCountryPageScaffoldBackgroundColor() { + // 原始应用默认Scaffold背景颜色(白色) + return Colors.white; + } + + @override + Color getCountryPageIconColor() { + // 原始应用默认图标颜色(黑色) + return Colors.black; + } + + @override + Color getCountryPagePrimaryTextColor() { + // 原始应用默认主要文本颜色(黑色) + return Colors.black; + } + + @override + Color getCountryPageSecondaryTextColor() { + // 原始应用默认次要文本颜色(黑色45%透明度) + return Colors.black45; + } + + @override + Color getCountryPageContainerBackgroundColor() { + // 原始应用默认容器背景颜色(白色) + return Colors.white; + } + + /// === Chat页面差异化方法实现 === + + /// === 消息页面 (ATMessagePage) 方法实现 === + + @override + String getMessagePageIndexMaskIcon() { + // 原始应用:消息页面索引遮罩图标 + return "atu_images/index/at_icon_index_mask.png"; + } + + @override + String getMessagePageAnnouncementTagIcon() { + // 原始应用:消息页面公告标签图标 + return "atu_images/family/at_icon_announcement_tag.png"; + } + + @override + String getMessagePageActivityMessageIcon() { + // 原始应用:消息页面活动消息图标 + return "atu_images/msg/at_icon_message_activity.png"; + } + + @override + String getMessagePageActivityTitleBackground(bool isRtl) { + // 原始应用:消息页面活动消息标题背景图标 + return isRtl + ? "atu_images/msg/at_icon_activity_title_bg_rtl.png" + : "atu_images/msg/at_icon_message_activity.png"; + } + + @override + String getMessagePageSystemMessageIcon() { + // 原始应用:消息页面系统消息图标 + return "atu_images/msg/at_icon_message_system.png"; + } + + @override + String getMessagePageSystemTitleBackground(bool isRtl) { + // 原始应用:消息页面系统消息标题背景图标 + return isRtl + ? "atu_images/msg/at_icon_system_title_bg_rtl.png" + : "atu_images/msg/at_icon_system_title_bg.png"; + } + + @override + String getMessagePageNotificationMessageIcon() { + // 原始应用:消息页面通知消息图标 + return "atu_images/msg/at_icon_message_noti.png"; + } + + @override + String getMessagePageNotificationTitleBackground(bool isRtl) { + // 原始应用:消息页面通知消息标题背景图标 + return isRtl + ? "atu_images/msg/at_icon_notifcation_title_bg_rtl.png" + : "atu_images/msg/at_icon_notifcation_title_bg.png"; + } + + /// === 聊天页面 (ATMessageChatPage) 方法实现 === + + @override + String getATMessageChatPageRoomSettingBackground() { + // 原始应用:聊天页面房间设置背景图像 + return "atu_images/room/at_icon_room_settig_bg.png"; + } + + @override + String getATMessageChatPageEmojiIcon() { + // 原始应用:聊天页面表情图标 + return "atu_images/msg/at_icon_emoji.png"; + } + + @override + String getATMessageChatPageChatKeyboardIcon() { + // 原始应用:聊天页面聊天键盘图标 + return "atu_images/msg/at_icon_chat_key.png"; + } + + @override + String getATMessageChatPageAddIcon() { + // 原始应用:聊天页面添加图标 + return "atu_images/msg/at_icon_add.png"; + } + + @override + String getATMessageChatPageSendMessageIcon() { + // 原始应用:聊天页面发送消息图标 + return "atu_images/msg/at_icon_chat_message_send.png"; + } + + @override + String getATMessageChatPageCameraIcon() { + // 原始应用:聊天页面相机图标 + return "atu_images/msg/at_icon_xiangji.png"; + } + + @override + String getATMessageChatPagePictureIcon() { + // 原始应用:聊天页面图片图标 + return "atu_images/msg/at_icon_tupian.png"; + } + + @override + String getATMessageChatPageRedEnvelopeIcon() { + // 原始应用:聊天页面红包图标 + return "atu_images/msg/at_icon_hongbao.png"; + } + + @override + String getATMessageChatPageRedEnvelopeMessageBackground() { + // 原始应用:聊天页面红包消息背景图像 + return "atu_images/msg/at_icon_msg_send_hongbao_bg.png"; + } + + @override + String getATMessageChatPageRedEnvelopeConfigTabButtonIcon() { + // 原始应用:聊天页面红包配置标签按钮图标 + return "atu_images/room/at_icon_red_envelope_config_tab_btn.png"; + } + + @override + String getATMessageChatPageRedEnvelopeMessageItemBackground() { + // 原始应用:聊天页面红包消息项背景图像 + return "atu_images/msg/at_icon_red_envelopes_msg_item_bg.png"; + } + + @override + String getATMessageChatPageRedEnvelopeReceivePackageIcon() { + // 原始应用:聊天页面红包接收包图标 + return "atu_images/room/at_icon_red_envelope_rec_pack.png"; + } + + @override + String getATMessageChatPageRedEnvelopeOpenBackground() { + // 原始应用:聊天页面红包打开背景图像 + return "atu_images/msg/at_icon_red_envelopes_msg_open_bg.png"; + } + + @override + String getATMessageChatPageRedEnvelopeOpenedBackground() { + // 原始应用:聊天页面红包已打开背景图像 + return "atu_images/msg/at_icon_red_envelopes_msg_opened_bg.png"; + } + + @override + String getATMessageChatPageGoldCoinIcon() { + // 原始应用:聊天页面金币图标 + return "atu_images/general/at_icon_jb.png"; + } + + @override + String getATMessageChatPageMessageMenuDeleteIcon() { + // 原始应用:聊天页面消息菜单删除图标 + return "atu_images/msg/at_icon_msg_menu_delete.png"; + } + + @override + String getATMessageChatPageMessageMenuCopyIcon() { + // 原始应用:聊天页面消息菜单复制图标 + return "atu_images/msg/at_icon_msg_menu_copy.png"; + } + + @override + String getATMessageChatPageMessageMenuRecallIcon() { + // 原始应用:聊天页面消息菜单撤回图标 + return "atu_images/msg/at_icon_msg_menu_recall.png"; + } + + @override + String getATMessageChatPageLoadingIcon() { + // 原始应用:聊天页面加载图标 + return "atu_images/general/at_icon_loading.png"; + } + + @override + String getATMessageChatPageSystemHeadImage() { + // 原始应用:聊天页面系统头像图像 + return "atu_images/msg/head_system.png"; + } + + @override + String getGenderBackgroundImage(bool isFemale) { + // 原始应用:性别背景图片 + return isFemale + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png"; + } + + @override + String getIdBackgroundImage(bool hasSpecialId) { + // 原始应用:ID背景图片 + return hasSpecialId + ? "atu_images/general/at_icon_special_id_bg.png" + : "atu_images/general/at_icon_id_bg.png"; + } + + @override + String getCopyIdIcon() { + // 原始应用:复制ID图标 + return "atu_images/room/at_icon_user_card_copy_id.png"; + } + + /// === Gift页面差异化方法实现 === + + @override + String getGiftPageFirstRechargeRoomTagIcon() { + // 原始应用:首充房间标签图标 + return "atu_images/room/at_icon_first_recharge_room_tag.png"; + } + + @override + String getGiftPageGoldCoinIcon() { + // 原始应用:金币图标 + return "atu_images/general/at_icon_jb.png"; + } + + @override + String getGiftPageGiveTypeBackground() { + // 原始应用:赠送类型背景图像 + return "atu_images/room/at_icon_give_type_bg.png"; + } + + @override + String getGiftPageActivityGiftHeadBackground(String giftType) { + // 原始应用:活动礼物头部背景图像,根据礼物类型返回不同路径 + switch (giftType) { + case 'ACTIVITY': + return "atu_images/room/at_icon_activity_gift_head_bg.png"; + case 'LUCK': + return "atu_images/room/at_icon_luck_gift_head_bg.png"; + case 'CP': + return "atu_images/room/at_icon_cp_gift_head_bg.png"; + case 'MAGIC': + return "atu_images/room/at_icon_magic_gift_head_bg.png"; + default: + return "atu_images/room/at_icon_activity_gift_head_bg.png"; + } + } + + @override + String getGiftPageCustomizedRuleIcon() { + // 原始应用:自定义规则图标 + return "atu_images/room/at_icon_customized_rule.png"; + } + + @override + String getGiftPageGiftEffectIcon(String giftType) { + // 原始应用:特效礼物图标 + return "atu_images/room/at_icon_gift_effect.png"; + } + + @override + String getGiftPageGiftMusicIcon(String giftType) { + // 原始应用:音乐礼物图标 + return "atu_images/room/at_icon_gift_music.png"; + } + + @override + String getGiftPageGiftLuckIcon(String giftType) { + // 原始应用:幸运礼物图标 + return "atu_images/room/at_icon_gift_luck.png"; + } + + @override + String getGiftPageGiftCpIcon(String giftType) { + // 原始应用:CP礼物图标 + return "atu_images/room/at_icon_gift_cp.png"; + } + + @override + String getGiftPageFirstRechargeTextIcon(String languageCode) { + // 原始应用:首充文本图标(根据语言显示不同图像) + if (languageCode == 'ar') { + return "atu_images/index/at_icon_first_recharge_ar_text.png"; + } else { + // 默认为英语版本 + return "atu_images/index/at_icon_first_recharge_en_text.png"; + } + } + + @override + String getGiftPageAllOnMicrophoneIcon() { + // 原始应用:"全开麦"图标 + return "atu_images/room/at_icon_all_on_microphone.png"; + } + + @override + String getGiftPageUsersOnMicrophoneIcon() { + // 原始应用:"用户开麦"图标 + return "atu_images/room/at_icon_userson_microphone.png"; + } + + @override + String getGiftPageAllInTheRoomIcon() { + // 原始应用:"房间所有人"图标 + return "atu_images/room/at_icon_all_in_the_room.png"; + } + + @override + String getCommonSelectIcon() { + // 原始应用:通用选择图标 + return "atu_images/general/at_icon_checked.png"; + } + + @override + String getCommonUnselectIcon() { + // 原始应用:通用未选择图标 + return "atu_images/general/at_icon_unchecked.png"; + } + + /// === Media页面差异化方法实现 === + + @override + Color getImagePreviewBackgroundColor() { + // 原始应用:图片预览页面背景颜色 - 黑色 + return Colors.black; + } + + @override + Color getImagePreviewGalleryBackgroundColor() { + // 原始应用:图片预览画廊背景颜色 - 黑色 + return Colors.black; + } + + @override + Color getImagePreviewAppBarBackgroundColor() { + // 原始应用:图片预览AppBar背景颜色 - 黑色54%透明度 + return Colors.black54; + } + + @override + Color getImagePreviewBackIconColor() { + // 原始应用:图片预览返回图标颜色 - 白色 + return Colors.white; + } + + @override + Color getImagePreviewTextColor() { + // 原始应用:图片预览文本颜色 - 白色 + return Colors.white; + } + + @override + Color getImagePreviewLoadingIndicatorColor() { + // 原始应用:图片预览加载指示器颜色 - 默认主题色 + // CircularProgressIndicator默认使用主题色 + return const Color(0xFFFF9500); // 应用主色调 + } + + @override + double getImagePreviewAppBarOpacity() { + // 原始应用:图片预览AppBar透明度 - 0.54 (Colors.black54) + return 0.54; + } + + @override + Color getVideoPlayerBackgroundColor() { + // 原始应用:视频播放页面背景颜色 - 黑色 + return Colors.black; + } + + @override + Color getVideoPlayerControlsBackgroundColor() { + // 原始应用:视频播放控制界面背景颜色 - 黑色38%透明度 + return Colors.black38; + } + + @override + Color getVideoPlayerIconColor() { + // 原始应用:视频播放图标颜色 - 白色 + return Colors.white; + } + + @override + Color getVideoPlayerBottomControlsBackgroundColor() { + // 原始应用:视频播放底部控制栏背景颜色 - 黑色54%透明度 + return Colors.black54; + } + + @override + Color getVideoPlayerProgressBarActiveColor() { + // 原始应用:视频播放进度条活动颜色 - 红色 + return Colors.red; + } + + @override + Color getVideoPlayerProgressBarInactiveColor() { + // 原始应用:视频播放进度条非活动颜色 - 灰色 + return Colors.grey; + } + + @override + Color getVideoPlayerTextColor() { + // 原始应用:视频播放文本颜色 - 白色 + return Colors.white; + } + + @override + Color getVideoPlayerLoadingIndicatorColor() { + // 原始应用:视频播放加载指示器颜色 - 红色 + return Colors.red; + } + + @override + double getVideoPlayerControlsOpacity() { + // 原始应用:视频播放控制界面透明度 - 0.38 (Colors.black38) + return 0.38; + } + + @override + double getVideoPlayerBottomControlsOpacity() { + // 原始应用:视频播放底部控制栏透明度 - 0.54 (Colors.black54) + return 0.54; + } + + @override + int getVideoPlayerControlsDisplayDuration() { + // 原始应用:视频播放控制界面显示持续时间 - 3秒 + return 3; + } + + /// === 举报页面差异化方法实现 === + + @override + String getReportPageBackgroundImage() { + // 原始应用:举报页面背景图片 - 与房间设置背景相同 + return "atu_images/room/at_icon_room_settig_bg.png"; + } + + @override + Color getReportPageHintTextColor() { + // 原始应用:举报页面提示文本颜色 - Colors.black38 + return Colors.black38; + } + + @override + Color getReportPageContainerBackgroundColor() { + // 原始应用:举报页面容器背景颜色 - Colors.white + return Colors.white; + } + + @override + Color getReportPageBorderColor() { + // 原始应用:举报页面边框颜色 - Color(0xffE6E6E6) + return const Color(0xffE6E6E6); + } + + @override + Color getReportPagePrimaryTextColor() { + // 原始应用:举报页面主要文本颜色 - Colors.black + return Colors.black; + } + + @override + Color getReportPageSecondaryHintTextColor() { + // 原始应用:举报页面次级提示文本颜色 - Colors.black45 + return Colors.black45; + } + + @override + Color getReportPageUnselectedBorderColor() { + // 原始应用:举报页面未选中边框颜色 - Colors.grey + return Colors.grey; + } + + @override + Color getReportPageSubmitButtonBackgroundColor() { + // 原始应用:举报页面提交按钮背景颜色 - 使用主题主色 + // 注意:在实际使用时,页面应通过ChatVibeTheme.primaryColor获取 + // 这里返回Colors.blue作为占位符,实际页面应该使用主题颜色 + // 或者可以考虑返回null,让页面使用默认主题颜色 + return Colors.blue; // 临时值,需要从主题获取 + } + + @override + Color getReportPageSubmitButtonTextColor() { + // 原始应用:举报页面提交按钮文本颜色 - Colors.white + return Colors.white; + } + + @override + String getReportPageIcon(String iconType) { + // 原始应用:举报页面图标路径 + switch (iconType) { + case 'addPic': + return "atu_images/general/at_icon_add_pic.png"; + case 'closePic': + return "atu_images/general/at_icon_pic_close.png"; + case 'checked': + return "atu_images/general/at_icon_checked.png"; + default: + return ""; // 未知图标类型返回空字符串 + } + } + + /// === 语音房间页面差异化方法实现 === + + @override + Color getVoiceRoomBackgroundColor() { + // 原始应用:语音房间页面背景颜色 - Colors.transparent + return Colors.transparent; + } + + @override + Color getVoiceRoomTabLabelColor() { + // 原始应用:语音房间页面Tab标签选中颜色 - Colors.white + return Colors.white; + } + + @override + Color getVoiceRoomTabUnselectedLabelColor() { + // 原始应用:语音房间页面Tab标签未选中颜色 - Colors.white54 + return Colors.white54; + } + + @override + Color getVoiceRoomTabIndicatorColor() { + // 原始应用:语音房间页面Tab指示器颜色 - Colors.transparent + return Colors.transparent; + } + + @override + Color getVoiceRoomTabDividerColor() { + // 原始应用:语音房间页面Tab分割线颜色 - Colors.transparent + return Colors.transparent; + } + + @override + EdgeInsets getVoiceRoomTabLabelPadding() { + // 原始应用:语音房间页面Tab标签内边距 - EdgeInsets.symmetric(horizontal: 12) + // 注意:页面需要将数值乘以ScreenUtil().w + return const EdgeInsets.symmetric(horizontal: 12); + } + + @override + EdgeInsetsDirectional getVoiceRoomChatContainerMargin() { + // 原始应用:语音房间页面聊天容器外边距 - EdgeInsets.only(right: 52) + // 注意:页面需要将数值乘以ScreenUtil().w + return const EdgeInsetsDirectional.only(end: 52); + } + + @override + TextStyle getVoiceRoomTabLabelStyle() { + // 原始应用:语音房间页面Tab标签选中文本样式 + // 字体大小15,字体族"MyCustomFont" + // 注意:页面需要将字体大小乘以ScreenUtil().sp + return const TextStyle( + fontSize: 15, + fontFamily: "MyCustomFont", + ); + } + + @override + TextStyle getVoiceRoomTabUnselectedLabelStyle() { + // 原始应用:语音房间页面Tab标签未选中文本样式 + // 字体大小13,字体族"MyCustomFont" + // 注意:页面需要将字体大小乘以ScreenUtil().sp + return const TextStyle( + fontSize: 13, + fontFamily: "MyCustomFont", + ); + } + + @override + String getVoiceRoomDefaultBackgroundImage() { + // 原始应用:语音房间默认背景图像路径 + return "atu_images/room/at_icon_room_defaut_bg.png"; + } + + /// === 钱包页面差异化方法实现 === + + /// === 充值页面差异化方法实现 === + + @override + String getRechargePageBackgroundImage() { + // 原始应用:充值页面背景图像路径 - "atu_images/room/at_icon_room_settig_bg.png" + return "atu_images/room/at_icon_room_settig_bg.png"; + } + + @override + Color getRechargePageScaffoldBackgroundColor() { + // 原始应用:充值页面Scaffold背景颜色 - Colors.transparent + return Colors.transparent; + } + + @override + Color getRechargePageDialogBarrierColor() { + // 原始应用:充值页面对话框屏障颜色 - Colors.transparent + return Colors.transparent; + } + + @override + Color getRechargePageDialogContainerBackgroundColor() { + // 原始应用:充值页面对话框容器背景颜色 - Colors.white30 + return Colors.white30; + } + + @override + Color getRechargePageDialogTextColor() { + // 原始应用:充值页面对话框文本颜色 - Colors.black38 + return Colors.black38; + } + + @override + Color getRechargePageMainContainerBackgroundColor() { + // 原始应用:充值页面主容器背景颜色 - Colors.white + return Colors.white; + } + + @override + Color getRechargePageButtonBackgroundColor() { + // 原始应用:充值页面按钮背景颜色 - 使用主题颜色 + // 注意:页面应使用ChatVibeTheme.primaryLight + return const Color(0xFFE6F7FF); // 近似主题浅色 + } + + @override + Color getRechargePageButtonTextColor() { + // 原始应用:充值页面按钮文本颜色 - Colors.white + return Colors.white; + } + + @override + String getRechargePageWalletBackgroundImage() { + // 原始应用:充值页面钱包背景图像路径 - 'atu_images/index/at_icon_wallet_bg.png' + return "atu_images/index/at_icon_wallet_bg.png"; + } + + @override + String getRechargePageWalletIcon() { + // 原始应用:充值页面钱包图标路径 - 'atu_images/index/at_icon_wallet_icon.png' + return "atu_images/index/at_icon_wallet_icon.png"; + } + + @override + Color getRechargePageWalletTextColor() { + // 原始应用:充值页面钱包文本颜色 - Colors.white + return Colors.white; + } + + @override + String getRechargePageGoldIcon() { + // 原始应用:充值页面金币图标路径(默认图标)- 'atu_images/general/at_icon_jb.png' + return "atu_images/general/at_icon_jb.png"; + } + + @override + Color getRechargePageSelectedItemBackgroundColor() { + // 原始应用:充值页面选中项背景颜色 - 使用主题浅色 + // 注意:页面应使用ChatVibeTheme.primaryLight + return const Color(0xFFE6F7FF); // 近似主题浅色 + } + + @override + Color getRechargePageSelectedItemBorderColor() { + // 原始应用:充值页面选中项边框颜色 - 使用主题主色 + // 注意:页面应使用ChatVibeTheme.primaryColor + return const Color(0xFF1890FF); // 近似主题主色 + } + + @override + Color getRechargePageUnselectedItemBorderColor() { + // 原始应用:充值页面未选中项边框颜色 - Color(0xffE6E6E6) + return const Color(0xffE6E6E6); + } + + @override + Color getRechargePageUnselectedItemBackgroundColor() { + // 原始应用:充值页面未选中项背景颜色 - Colors.white + return Colors.white; + } + + @override + Color getRechargePageItemTextColor() { + // 原始应用:充值页面项目文本颜色 - Colors.black54 + return Colors.black54; + } + + @override + Color getRechargePageItemPriceTextColor() { + // 原始应用:充值页面价格文本颜色 - Colors.black + return Colors.black; + } + + @override + String getRechargePageGoldIconByIndex(int index) { + // 原始应用:根据索引获取充值页面金币图标路径 + switch (index) { + case 0: + return "atu_images/general/at_icon_jb.png"; + case 1: + return "atu_images/general/at_icon_jb2.png"; + case 2: + return "atu_images/general/at_icon_jb3.png"; + default: + return "atu_images/general/at_icon_jb.png"; // 默认图标 + } + } + + @override + String getRechargePageRecordIcon() { + // 原始应用:充值页面记录图标路径 - "atu_images/index/at_icon_recharge_recod.png" + return "atu_images/index/at_icon_recharge_recod.png"; + } + + @override + Color getRechargePageAppleItemBackgroundColor() { + // 原始应用:充值页面Apple产品项背景颜色 - Colors.white + return Colors.white; + } + + /// === 金币记录页面差异化方法实现 === + + @override + String getGoldRecordPageBackgroundImage() { + // 原始应用:金币记录页面背景图像路径 - "atu_images/room/at_icon_room_settig_bg.png" + return "atu_images/room/at_icon_room_settig_bg.png"; + } + + @override + Color getGoldRecordPageScaffoldBackgroundColor() { + // 原始应用:金币记录页面Scaffold背景颜色 - Colors.transparent + return Colors.transparent; + } + + @override + Color getGoldRecordATPageListBackgroundColor() { + // 原始应用:金币记录页面列表背景颜色 - Colors.transparent + return Colors.transparent; + } + + @override + Color getGoldRecordPageContainerBackgroundColor() { + // 原始应用:金币记录页面容器背景颜色 - Colors.white + return Colors.white; + } + + @override + Color getGoldRecordPageBorderColor() { + // 原始应用:金币记录页面边框颜色 - Color(0xffE6E6E6) + return const Color(0xffE6E6E6); + } + + @override + Color getGoldRecordPagePrimaryTextColor() { + // 原始应用:金币记录页面主要文本颜色 - Colors.black + return Colors.black; + } + + @override + Color getGoldRecordPageSecondaryTextColor() { + // 原始应用:金币记录页面次要文本颜色 - Colors.black38 + return Colors.black38; + } + + /// === Store页面差异化方法实现 === + + @override + String getStorePageBackgroundImage() { + // 原始应用:Store页面背景图像路径 - "atu_images/room/at_icon_room_settig_bg.png" + return "atu_images/room/at_icon_room_settig_bg.png"; + } + + @override + String getStorePageShoppingBagIcon() { + // 原始应用:Store页面购物袋图标路径 - "atu_images/store/at_icon_shop_bag.png" + return "atu_images/store/at_icon_shop_bag.png"; + } + + @override + EdgeInsetsDirectional getStorePageShoppingBagMargin() { + // 原始应用:Store页面购物袋图标外边距 - EdgeInsetsDirectional.only(end: 10.w) + // 注意:这里无法使用.w单位,需要在调用处使用ScreenUtil() + return const EdgeInsetsDirectional.only(end: 10); + } + + @override + EdgeInsets getStorePageTabLabelPadding() { + // 原始应用:Store页面Tab标签内边距 - EdgeInsets.symmetric(horizontal: 12.w) + // 注意:这里无法使用.w单位,需要在调用处使用ScreenUtil() + return const EdgeInsets.symmetric(horizontal: 12); + } + + @override + TextStyle getStorePageTabLabelStyle() { + // 原始应用:Store页面Tab标签选中文本样式 + // 原始值:TextStyle(fontSize: 15.sp, color: Colors.black87, fontWeight: FontWeight.w600) + // 注意:这里无法使用.sp单位,需要在调用处使用ScreenUtil() + return const TextStyle( + fontSize: 15, + color: Color(0xDD000000), // Colors.black87 对应 #DD000000 + fontWeight: FontWeight.w600, + ); + } + + @override + TextStyle getStorePageTabUnselectedLabelStyle() { + // 原始应用:Store页面Tab标签未选中文本样式 + // 原始值:TextStyle(fontSize: 13.sp, color: Colors.black38, fontWeight: FontWeight.w500) + // 注意:这里无法使用.sp单位,需要在调用处使用ScreenUtil() + return const TextStyle( + fontSize: 13, + color: Color(0x61000000), // Colors.black38 对应 #61000000 + fontWeight: FontWeight.w500, + ); + } + + @override + Color getStorePageTabIndicatorColor() { + // 原始应用:Store页面Tab指示器颜色 - ChatVibeTheme.primaryColor + // 需要查看ChatVibeTheme.primaryColor的值 + // 从chatvibe_theme.dart中查看到primaryColor = Color(0xffFF5722); + return const Color(0xffFF5722); // ChatVibeTheme.primaryColor + } + + @override + Color getStorePageTabDividerColor() { + // 原始应用:Store页面Tab分割线颜色 - Colors.transparent + return Colors.transparent; + } + + @override + Color getStorePageBottomDividerColor() { + // 原始应用:Store页面底部Divider颜色 - Colors.black12 + return const Color(0x1F000000); // Colors.black12 对应 #1F000000 + } + + @override + double getStorePageBottomDividerThickness() { + // 原始应用:Store页面底部Divider厚度 - 0.5 + // 注意:原始代码中使用0.5,没有.w单位 + return 0.5; + } + + @override + double getStorePageBottomDividerHeight() { + // 原始应用:Store页面底部Divider高度 - 24.w + // 注意:这里无法使用.w单位,需要在调用处使用ScreenUtil() + return 24; + } + + @override + String getStorePageGoldIcon() { + // 原始应用:Store页面金币图标路径 - "atu_images/general/at_icon_jb.png" + return "atu_images/general/at_icon_jb.png"; + } + + @override + Color getStorePageGoldTextColor() { + // 原始应用:Store页面金币文本颜色 - Color(0xFFFFE134) + return const Color(0xFFFFE134); + } + + @override + Color getStorePageGoldIconColor() { + // 原始应用:Store页面金币图标颜色 - Color(0xFFFFE134) + return const Color(0xFFFFE134); + } + + /// === Store子页面(商品项)差异化方法实现 === + + @override + Color getStoreItemBackgroundColor() { + // 原始应用:Store商品项背景颜色 - Colors.white + return Colors.white; + } + + @override + Color getStoreItemUnselectedBorderColor() { + // 原始应用:Store商品项未选中边框颜色 - Colors.black12 + return const Color(0x1F000000); // Colors.black12 对应 #1F000000 + } + + @override + Color getStoreItemSelectedBorderColor() { + // 原始应用:Store商品项选中边框颜色 - ChatVibeTheme.primaryLight (Color(0xffFF9500)) + return const Color(0xffFF9500); + } + + @override + String getStoreItemGoldIcon() { + // 原始应用:Store商品项金币图标路径 - "atu_images/general/at_icon_jb.png" + return "atu_images/general/at_icon_jb.png"; + } + + @override + Color getStoreItemPriceTextColor() { + // 原始应用:Store商品项价格文本颜色 - Colors.black + return Colors.black; + } + + @override + Color getStoreItemBuyButtonColor() { + // 原始应用:Store商品项购买按钮背景颜色 - ChatVibeTheme.primaryLight (Color(0xffFF9500)) + return const Color(0xffFF9500); + } + + @override + Color getStoreItemBuyButtonTextColor() { + // 原始应用:Store商品项购买按钮文本颜色 - Colors.white + return Colors.white; + } + + @override + String getStoreItemActionIcon(String itemType) { + // 原始应用:Store商品项操作图标路径 + // 根据商品类型返回不同的图标路径 + switch (itemType) { + case 'headdress': + return "atu_images/store/at_icon_shop_item_search.png"; // 头饰页面使用搜索图标 + case 'mountains': + return "atu_images/store/at_icon_shop_item_play.png"; // 山脉页面使用播放图标 + case 'theme': + return "atu_images/store/at_icon_store_theme_rev.png"; // 主题页面使用主题图标 + case 'chatbox': + return "atu_images/store/at_icon_shop_item_search.png"; // 聊天气泡页面使用搜索图标 + default: + return "atu_images/store/at_icon_store_theme_rev.png"; // 默认返回theme图标 + } + } + + /// === 动态页面差异化方法实现 === + + @override + String getDynamicPageBackgroundImage() { + // 原始应用:动态页面背景图像路径 - "atu_images/index/at_icon_index_mask.png" + return "atu_images/index/at_icon_index_mask.png"; + } + + @override + Color getDynamicPageScaffoldBackgroundColor() { + // 原始应用:动态页面Scaffold背景颜色 - Colors.transparent + return Colors.transparent; + } + + @override + Color getDynamicPageAppBarBackgroundColor() { + // 原始应用:动态页面AppBar背景颜色 - Colors.transparent + return Colors.transparent; + } + + @override + Color getDynamicPageTabLabelColor() { + // 原始应用:动态页面Tab标签选中颜色 - Colors.black87 + return Colors.black87; + } + + @override + Color getDynamicPageTabUnselectedLabelColor() { + // 原始应用:动态页面Tab标签未选中颜色 - Colors.black38 + return Colors.black38; + } + + @override + Color getDynamicPageTabIndicatorColor() { + // 原始应用:动态页面Tab指示器颜色 - Colors.transparent + return Colors.transparent; + } + + @override + Color getDynamicPageTabDividerColor() { + // 原始应用:动态页面Tab分割线颜色 - Colors.transparent + return Colors.transparent; + } + + @override + String getDynamicPageAddButtonIcon() { + // 原始应用:动态页面添加动态按钮图标路径 - "atu_images/dynamic/at_icon_add_dynamic_btn.png" + return "atu_images/dynamic/at_icon_add_dynamic_btn.png"; + } + + @override + bool shouldDynamicPageTabScrollable() { + // 原始应用:动态页面Tab标签是否可滚动 - true + return true; + } + + @override + TextStyle getDynamicPageTabLabelStyle() { + // 原始应用:动态页面Tab标签选中文本样式 - TextStyle(fontWeight: FontWeight.bold, fontSize: 16.sp) + // 注意:这里无法使用.sp单位,需要在调用处使用ScreenUtil() + return const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ); + } + + @override + TextStyle getDynamicPageTabUnselectedLabelStyle() { + // 原始应用:动态页面Tab标签未选中文本样式 - TextStyle(fontWeight: FontWeight.normal, fontSize: 15.sp) + // 注意:这里无法使用.sp单位,需要在调用处使用ScreenUtil() + return const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 15, + ); + } + + /// === 启动页面差异化方法实现 === + + @override + String getSplashPageBackgroundImage() { + // 原始应用:启动页面背景图像路径 - "atu_images/splash/splash.png" + return "atu_images/splash/at_splash.png"; + } + + @override + String getSplashPageIcon() { + // 原始应用:启动页面图标路径 - "atu_images/splash/at_icon_splash_icon.png" + return "atu_images/splash/at_icon_splash_icon.png"; + } + + @override + String getSplashPageSkipButtonBackground() { + // 原始应用:启动页面跳过按钮背景图像路径 - "atu_images/splash/at_icon_splash_skip_bg.png" + return "atu_images/splash/at_icon_splash_skip_bg.png"; + } + + @override + Color getSplashPageSkipButtonTextColor() { + // 原始应用:启动页面跳过按钮文本颜色 - Colors.white + return Colors.white; + } + + @override + String getSplashPageKingGamesNameBackground() { + // 原始应用:启动页面游戏名称背景图像路径 - "atu_images/index/at_icon_splash_king_games_name_bg.png" + return "atu_images/index/at_icon_splash_king_games_name_bg.png"; + } + + @override + Color getSplashPageKingGamesTextColor() { + // 原始应用:启动页面游戏名称文本颜色 - Colors.white + return Colors.white; + } + + @override + String getSplashPageCpNameBackground() { + // 原始应用:启动页面CP名称背景图像路径 - "atu_images/index/at_icon_splash_cp_name_bg.png" + return "atu_images/index/at_icon_splash_cp_name_bg.png"; + } + + @override + Color getSplashPageCpTextColor() { + // 原始应用:启动页面CP名称文本颜色 - Colors.white + return Colors.white; + } + + /// === WebView页面差异化方法实现 === + + @override + Color getWebViewPageAppBarBackgroundColor() { + // 原始应用:WebView页面AppBar背景颜色 - Colors.white + return Colors.white; + } + + @override + Color getWebViewPageTitleTextColor() { + // 原始应用:WebView页面标题文本颜色 - Color(0xff333333) + return const Color(0xff333333); + } + + @override + Color getWebViewPageBackArrowColor() { + // 原始应用:WebView页面返回箭头图标颜色 - 使用默认图标颜色 + return const Color(0xff333333); + } + + @override + Color getWebViewPageProgressBarBackgroundColor() { + // 原始应用:WebView页面进度条背景颜色 - Colors.white70.withOpacity(0) + return Colors.white70.withOpacity(0); + } + + @override + Color getWebViewPageProgressBarActiveColor() { + // 原始应用:WebView页面进度条活动颜色 - Color(0xffFF6000) + return const Color(0xffFF6000); + } + + @override + Color getGameWebViewCloseIconColor() { + // 原始应用:游戏WebView页面关闭图标颜色 - Colors.white + return Colors.white; + } + + @override + Color getGameWebViewScaffoldBackgroundColor() { + // 原始应用:游戏WebView页面Scaffold背景颜色 - Colors.transparent + return Colors.transparent; + } + + @override + Color getGameWebViewProgressBarBackgroundColor() { + // 原始应用:游戏WebView页面进度条背景颜色 - Colors.white70.withOpacity(0) + return Colors.white70.withOpacity(0); + } + + @override + Color getGameWebViewLoadingIndicatorColor() { + // 原始应用:游戏WebView页面加载指示器颜色 - Color(0xffFF6000) + return const Color(0xffFF6000); + } + + /// === 搜索页面差异化方法实现 === + + @override + Color getSearchPageScaffoldBackgroundColor() { + // 原始应用:搜索页面Scaffold背景颜色 - Colors.white + return Colors.white; + } + + @override + Color getSearchPageBackIconColor() { + // 原始应用:搜索页面返回图标颜色 - Color(0xffBBBBBB) + return const Color(0xffBBBBBB); + } + + @override + Color getSearchPageInputBorderColor() { + // 原始应用:搜索页面搜索框边框颜色 - Colors.transparent + return Colors.transparent; + } + + @override + Color getSearchPageInputTextColor() { + // 原始应用:搜索页面搜索框文本颜色 - Colors.grey + return Colors.grey; + } + + @override + Color getSearchPageButtonTextColor() { + // 原始应用:搜索页面搜索按钮文本颜色 - Colors.black + return Colors.black; + } + + @override + List getSearchPageButtonGradient() { + // 原始应用:搜索页面搜索按钮渐变颜色 - [Colors.transparent, Colors.transparent] + return [Colors.transparent, Colors.transparent]; + } + + @override + Color getSearchPageHistoryTitleTextColor() { + // 原始应用:搜索页面历史记录标题文本颜色 - Colors.black + return Colors.black; + } + + @override + Color getSearchPageHistoryItemTextColor() { + // 原始应用:搜索页面历史项文本颜色 - Colors.white + return Colors.white; + } + + @override + Color getSearchPageHistoryItemBackgroundColor() { + // 原始应用:搜索页面历史项背景颜色 - ChatVibeTheme.primaryLight (Color(0xffFF9500)) + return const Color(0xffFF9500); + } + + @override + Color getSearchPageTabIndicatorGradientStartColor() { + // 原始应用:搜索页面Tab指示器渐变开始颜色 - Color(0xffFFA500) + return const Color(0xffFFA500); + } + + @override + Color getSearchPageTabIndicatorGradientEndColor() { + // 原始应用:搜索页面Tab指示器渐变结束颜色 - Color(0xffFFD700) + return const Color(0xffFFD700); + } + + @override + Color getSearchPageTabSelectedLabelColor() { + // 原始应用:搜索页面Tab标签选中颜色 - Colors.black + return Colors.black; + } + + @override + Color getSearchPageTabDividerColor() { + // 原始应用:搜索页面Tab分割线颜色 - Colors.transparent + return Colors.transparent; + } + + @override + Color getSearchPageTabUnselectedLabelColor() { + // 原始应用:搜索页面Tab标签未选中颜色 - Colors.black26 + return Colors.black26; + } + + @override + Color getSearchPageDividerContainerBackgroundColor() { + // 原始应用:搜索页面分隔容器背景颜色 - Color(0xffFFF8DA) + return const Color(0xffFFF8DA); + } + + @override + TextStyle getSearchPageTabSelectedLabelStyle() { + // 原始应用:搜索页面Tab标签选中文本样式 - TextStyle(fontSize: 18.sp, fontWeight: FontWeight.w600) + // 注意:这里无法使用.sp单位,需要在调用处使用ScreenUtil() + return const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Colors.black, + ); + } + + @override + TextStyle getSearchPageTabUnselectedLabelStyle() { + // 原始应用:搜索页面Tab标签未选中文本样式 - TextStyle(fontSize: 17.sp, fontWeight: FontWeight.w400) + // 注意:这里无法使用.sp单位,需要在调用处使用ScreenUtil() + return const TextStyle( + fontSize: 17, + fontWeight: FontWeight.w400, + color: Colors.black26, + ); + } + + @override + EdgeInsets getSearchPageResultTabLabelPadding() { + // 原始应用:搜索页面搜索结果Tab标签内边距 - EdgeInsets.symmetric(horizontal: 15) + return const EdgeInsets.symmetric(horizontal: 15); + } + + @override + Color getSearchPageResultTabLabelColor() { + // 原始应用:搜索页面搜索结果Tab标签选中颜色 - Colors.black + return Colors.black; + } + + @override + Color getSearchPageResultTabUnselectedLabelColor() { + // 原始应用:搜索页面搜索结果Tab标签未选中颜色 - Colors.black26 + return Colors.black26; + } + + @override + TextStyle getSearchPageResultTabUnselectedLabelStyle() { + // 原始应用:搜索页面搜索结果Tab标签未选中文本样式 - TextStyle(fontSize: 17, fontWeight: FontWeight.w400, color: Colors.black26) + return const TextStyle( + fontSize: 17, + fontWeight: FontWeight.w400, + color: Colors.black26, + ); + } + + @override + TextStyle getSearchPageResultTabLabelStyle() { + // 原始应用:搜索页面搜索结果Tab标签选中文本样式 - TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Colors.black87) + return const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Colors.black87, + ); + } + + @override + LinearGradient getSearchPageResultTabIndicatorColor() { + // 原始应用:搜索页面搜索结果Tab指示器颜色 - LinearGradient(colors: [Colors.transparent, Colors.transparent]) + return const LinearGradient( + colors: [Colors.transparent, Colors.transparent], + ); + } + + @override + double getSearchPageResultTabIndicatorWidth() { + // 原始应用:搜索页面搜索结果Tab指示器宽度 - 15.0 + return 15.0; + } + + @override + Color getSearchPageResultBackgroundColor() { + // 原始应用:搜索页面搜索结果背景颜色 - Color(0xffFFF8DA) + return const Color(0xffFFF8DA); + } + + @override + String getSearchPageClearHistoryIcon() { + // 原始应用:搜索页面清除历史记录图标路径 + return "atu_images/general/at_icon_delete.png"; + } + + @override + String getSearchPageEmptyDataIcon() { + // 原始应用:搜索页面空数据图标路径 + return "atu_images/room/at_icon_room_defaut_bg.png"; + } + + // === 任务页面差异化方法 === + + @override + String getTaskPageBackgroundImage() { + // 原始应用:任务页面背景图像 - "atu_images/room/at_icon_room_settig_bg.png" + return "atu_images/room/at_icon_room_settig_bg.png"; + } + + @override + Color getTaskPageBackButtonColor() { + // 原始应用:任务页面返回按钮颜色 - Colors.black + return Colors.black; + } + + @override + Color getTaskPageScaffoldBackgroundColor() { + // 原始应用:任务页面Scaffold背景颜色 - Colors.white + return Colors.white; + } + + @override + Color getTaskPageContainerBackgroundColor() { + // 原始应用:任务页面容器背景颜色 - Colors.white + return Colors.white; + } + + @override + Color getTaskPageBorderColor() { + // 原始应用:任务页面边框颜色 - Color(0xffE6E6E6) + return const Color(0xffE6E6E6); + } + + @override + Color getTaskPageSpecialBorderColor() { + // 原始应用:任务页面特殊边框颜色 - Color(0xffBB92FF) + return const Color(0xffBB92FF); + } + + @override + Color getTaskPagePrimaryTextColor() { + // 原始应用:任务页面主要文本颜色 - Colors.white + return Colors.white; + } + + @override + Color getTaskPageGoldTextColor() { + // 原始应用:任务页面金币文本颜色 - Color(0xffFFB627) + return const Color(0xffFFB627); + } + + @override + Color getTaskPageExpTextColor() { + // 原始应用:任务页面经验文本颜色 - Color(0xff52FF90) + return const Color(0xff52FF90); + } + + @override + Color getTaskPageThemeColor() { + // 原始应用:任务页面主题颜色 - ChatVibeTheme.primaryColor + return const Color(0xffFF5722); // ChatVibeTheme.primaryColor + } + + @override + Color getTaskPageThemeLightColor() { + // 原始应用:任务页面浅主题颜色 - ChatVibeTheme.primaryLight + return const Color(0xffFF9500); // ChatVibeTheme.primaryLight + } + + @override + List getTaskPageReceivableButtonGradient() { + // 原始应用:任务页面可领取按钮渐变颜色 - [Color(0xffBB92FF), Color(0xff8B45FF)] + return [const Color(0xffBB92FF), const Color(0xff8B45FF)]; + } + + @override + List getTaskPageCompletedButtonGradient() { + // 原始应用:任务页面已完成按钮渐变颜色 - [Color(0xffDADADA), Color(0xff585859)] + return [const Color(0xffDADADA), const Color(0xff585859)]; + } + + @override + Color getTaskPageButtonTextColor() { + // 原始应用:任务页面按钮文本颜色 - Colors.white + return Colors.white; + } + + @override + Color getTaskPageMaskColor() { + // 原始应用:任务页面遮罩颜色 - Colors.black + return Colors.black; + } + + @override + String getTaskPageHeadBackgroundImage() { + // 原始应用:任务页面头部背景图像 - "atu_images/index/at_icon_task_head_bg.png" + return "atu_images/index/at_icon_task_head_bg.png"; + } + + @override + String getTaskPageGoldIcon() { + // 原始应用:任务页面金币图标 - "atu_images/general/at_icon_jb.png" + return "atu_images/general/at_icon_jb.png"; + } + + @override + String getTaskPageExpIcon() { + // 原始应用:任务页面经验图标 - "atu_images/index/at_icon_task_exp.png" + return "atu_images/index/at_icon_task_exp.png"; + } + + @override + String getTaskPageInvitationRewardBackgroundImage() { + // 原始应用:任务页面邀请奖励背景图像 - "atu_images/index/at_icon_invitation_bg.png" + return "atu_images/index/at_icon_invitation_bg.png"; + } + + @override + Color getTaskPageGiftBagTextColor() { + // 原始应用:任务页面礼包文本颜色 - Colors.white + return Colors.white; + } + + @override + Color getTaskPageAppBarBackgroundColor() { + // 原始应用:任务页面AppBar背景颜色 - Colors.transparent + return Colors.transparent; + } + + @override + Color getTaskPageTransparentContainerColor() { + // 原始应用:任务页面透明容器颜色 - Colors.transparent + return Colors.transparent; + } + + /// === 设置页面差异化方法实现 === + + @override + String getSettingsPageBackgroundImage() { + // 原始应用:设置页面背景图像路径 - "atu_images/person/at_icon_edit_userinfo_bg.png" + return "atu_images/person/at_icon_edit_userinfo_bg.png"; + } + + @override + Color getSettingsPageMainContainerBackgroundColor() { + // 原始应用:设置页面主容器背景颜色 - Colors.white + return Colors.white; + } + + @override + Color getSettingsPageMainContainerBorderColor() { + // 原始应用:设置页面主容器边框颜色 - Color(0xffE6E6E6) + return const Color(0xffE6E6E6); + } + + @override + Color getSettingsPagePrimaryTextColor() { + // 原始应用:设置页面主文本颜色 - Colors.black + return Colors.black; + } + + @override + Color getSettingsPageSecondaryTextColor() { + // 原始应用:设置页面次要文本颜色 - Colors.black38 + return Colors.black38; + } + + @override + Color getSettingsPageIconColor() { + // 原始应用:设置页面图标颜色 - Colors.black26 + return Colors.black26; + } + + @override + Color getSettingsPageCommonTextColor() { + // 原始应用:设置页面常见文本颜色 - Colors.black38 + return Colors.black38; + } + + @override + Color getSettingsPageAboutTextColor() { + // 原始应用:设置页面关于文本颜色 - Colors.black38 + return Colors.black38; + } + + @override + Color getSettingsPageContainerBorderColor() { + // 原始应用:设置页面容器边框颜色 - Color(0xffE6E6E6) + return const Color(0xffE6E6E6); + } + + @override + Color getSettingsPageContainerBackgroundColor() { + // 原始应用:设置页面容器背景颜色 - Colors.white + return Colors.white; + } + + @override + Color getSettingsPageButtonBackgroundColor() { + // 原始应用:设置页面按钮背景颜色 - Color(0xffBBBBBB) + return const Color(0xffBBBBBB); + } + + @override + Color getSettingsPageButtonTextColor() { + // 原始应用:设置页面按钮文本颜色 - Colors.white + return Colors.white; + } + + /// === 语言页面差异化方法实现 === + + @override + String getLanguagePageBackgroundImage() { + // 原始应用:语言页面背景图像路径 - "atu_images/person/at_icon_edit_userinfo_bg.png" + return "atu_images/person/at_icon_edit_userinfo_bg.png"; + } + + @override + Color getLanguagePageCheckboxActiveColor() { + // 原始应用:语言页面复选框选中颜色 - ChatVibeTheme.primaryColor + return const Color(0xffFF5722); // ChatVibeTheme.primaryColor + } + + @override + Color getLanguagePageCheckboxBorderColor() { + // 原始应用:语言页面复选框边框颜色 - Color(0xffD4D4D4) + return const Color(0xffD4D4D4); + } + + @override + Color getLanguagePagePrimaryTextColor() { + // 原始应用:语言页面主文本颜色 - Colors.black + return Colors.black; + } + + @override + Color getLanguagePageCheckColor() { + // 原始应用:语言页面复选框对勾颜色 - Colors.transparent + return Colors.transparent; + } + + @override + double getLanguagePageCheckboxBorderRadius() { + // 原始应用:语言页面复选框边框圆角 - 15 + return 15; + } + + @override + double getLanguagePageCheckboxBorderWidth() { + // 原始应用:语言页面复选框边框宽度 - 2 + return 2; + } + + @override + Color getLanguagePageScaffoldBackgroundColor() { + // 原始应用:语言页面Scaffold背景颜色 - Colors.transparent + return Colors.transparent; + } + + /// === 账户页面差异化方法实现 === + + @override + String getAccountPageBackgroundImage() { + // 原始应用:账户页面背景图像路径 - "atu_images/person/at_icon_edit_userinfo_bg.png" + return "atu_images/person/at_icon_edit_userinfo_bg.png"; + } + + @override + Color getAccountPageMainContainerBackgroundColor() { + // 原始应用:账户页面主容器背景颜色 - Colors.white + return Colors.white; + } + + @override + Color getAccountPageMainContainerBorderColor() { + // 原始应用:账户页面主容器边框颜色 - Color(0xffE6E6E6) + return const Color(0xffE6E6E6); + } + + @override + Color getAccountPagePrimaryTextColor() { + // 原始应用:账户页面主文本颜色 - Colors.black + return Colors.black; + } + + @override + Color getAccountPageSecondaryTextColor() { + // 原始应用:账户页面次要文本颜色 - Colors.black38 + return Colors.black38; + } + + @override + Color getAccountPageIconColor() { + // 原始应用:账户页面图标颜色 - Colors.black26 + return Colors.black26; + } + + @override + Color getAccountPageDividerColor() { + // 原始应用:账户页面分隔线颜色 - Colors.black12 + return Colors.black12; + } + + @override + Color getAccountPageScaffoldBackgroundColor() { + // 原始应用:账户页面Scaffold背景颜色 - Colors.transparent + return Colors.transparent; + } + @override + Color getGoldRecordPageListBackgroundColor() { + // 马甲包策略:金币记录页面列表背景颜色 - 深灰色 + return const Color(0xff1a1a1a); // #1a1a1a + } +} \ No newline at end of file diff --git a/lib/chatvibe_core/config/strategies/variant1_business_logic_strategy.dart b/lib/chatvibe_core/config/strategies/variant1_business_logic_strategy.dart new file mode 100644 index 0000000..2645401 --- /dev/null +++ b/lib/chatvibe_core/config/strategies/variant1_business_logic_strategy.dart @@ -0,0 +1,2617 @@ +import 'package:flutter/material.dart'; +import 'package:aslan/chatvibe_core/config/strategies/base_business_logic_strategy.dart'; +import 'package:aslan/chatvibe_features/home/popular/home_popular_page.dart'; +import 'package:aslan/chatvibe_features/family/index_family_page.dart'; +import 'package:aslan/chatvibe_features/home/explore/explore_page2.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/utilities/at_dialog_utils.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; + +import '../../../chatvibe_features/home/popular/event/home_event_page.dart'; +import '../../../chatvibe_features/home/popular/mine/home_mine_page.dart'; +import '../../../chatvibe_features/home/popular/party/home_party_page.dart'; + +/// 马甲包业务逻辑策略实现 +/// 提供与原始应用不同的业务逻辑 +class Variant1BusinessLogicStrategy extends BaseBusinessLogicStrategy { + /// 马甲包版本:始终显示家族Tab(即使没有加入家族) + @override + bool shouldShowFamilyTab() { + // 马甲包策略:始终显示家族Tab以鼓励用户加入 + return true; + } + + /// 马甲包版本:调整Tab顺序,家族放在最后 + @override + List getHomeTabPages(BuildContext context) { + final List pages = []; + pages.add(HomeMinePage()); + pages.add(HomePartyPage()); + // 马甲包:家族页放在最后 + pages.add(HomeEventPage()); + return pages; + } + + @override + List getHomeTabLabels(BuildContext context) { + final List tabs = []; + tabs.add(Tab(text: ATAppLocalizations.of(context)!.mine)); + tabs.add(Tab(text: ATAppLocalizations.of(context)!.party)); + // 马甲包:家族标签放在最后 + tabs.add(Tab(text: ATAppLocalizations.of(context)!.event)); + return tabs; + } + + @override + int getHomeInitialTabIndex() { + // 马甲包:从探索页开始 + return 1; // explore页 + } + + /// 马甲包版本:不同的首充提示位置 + @override + Map getFirstRechargePosition() { + // 马甲包:调整到左上角,提供完整的四个键值对 + return {'top': null, 'start': null, 'bottom': 100, 'end': 15.0}; + } + + /// 马甲包版本:增强的首充提示点击处理 + @override + void onFirstRechargeTap(BuildContext context) { + // 马甲包:显示更详细的首充对话框 + ATDialogUtils.showFirstRechargeDialog(context); + // 额外逻辑:记录点击事件或触发其他行为 + debugPrint('马甲包首充提示被点击'); + } + + /// === 登录页面差异化方法实现(马甲包专属样式)=== + + @override + String getLoginBackgroundImage() { + // 马甲包专属背景图片 + // TODO: 根据Figma设计添加马甲包专属背景图片 + return "atu_images/login/at_login_account.png"; + } + + @override + String getLoginAppIcon() { + // 马甲包专属应用图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/splash/at_icon_splash_icon.png"; + } + + @override + Color getLoginButtonColor() { + // 马甲包按钮颜色 - 使用主题色 + return const Color(0xffFF5722); // ChatVibeTheme.primaryColor + } + + @override + List? getLoginButtonGradient() { + // 马甲包按钮渐变:从Figma设计中提取的橙色渐变 + return [ + const Color(0xffFEB219), // rgb(254, 178, 25) + const Color(0xffFF9326), // rgb(255, 147, 38) + ]; + } + + @override + Color getLoginButtonTextColor() { + // 马甲包按钮文本颜色(白色,与渐变背景形成对比) + return Colors.white; + } + + @override + Color getLoginLabelTextColor() { + // 马甲包标签文本颜色(黑色) + return Colors.black; + } + + @override + BoxDecoration getLoginInputDecoration() { + // 马甲包输入框装饰:浅灰色背景,圆形边框 + return BoxDecoration( + color: const Color(0xfff2f2f2), // #f2f2f2 + borderRadius: BorderRadius.circular(56), // 56px圆角 + border: Border.all(color: Colors.transparent, width: 0), + ); + } + + @override + EdgeInsets getLoginPagePadding() { + // 马甲包页面内边距:根据Figma设计调整 + return const EdgeInsets.only(top: 120); // 顶部319px间距 + } + + @override + bool shouldShowLoginStatusBar() { + // 马甲包:显示黑色状态栏 + return true; + } + + @override + Color getLoginHintTextColor() { + // 马甲包:提示文本颜色(黑色45%透明度) + return Colors.black45; + } + + @override + Color getLoginInputTextColor() { + // 马甲包:输入文本颜色(黑色) + return Colors.black; + } + + /// === 家族页面差异化方法实现(马甲包专属) === + + @override + num getFamilyDisplayLevel(num level) { + // 马甲包策略:简化等级显示,所有等级直接显示 + return level; + } + + @override + String getFamilyLevelBackgroundImage(num level) { + // 马甲包策略:使用统一的背景图片,不区分等级 + // TODO: 根据Figma设计添加马甲包专属背景图片 + return "atu_images/family/at_icon_family_info_item_bg_1.9.png"; + } + + @override + String getFamilyNoticeDisplayText( + String? familyNotice, + BuildContext context, + ) { + // 马甲包策略:简化通知处理,不进行空白字符替换 + if (familyNotice == null || familyNotice.isEmpty) { + // 马甲包专属默认欢迎语 + return "Welcome to our family! Join us today!"; + } + return familyNotice; + } + + @override + int getFamilyOnlineUserDisplayCount() { + // 马甲包策略:显示更多在线用户以鼓励互动 + return 30; // 显示最多30个在线用户 + } + + @override + int getFamilyRoomGridCrossAxisCount() { + // 马甲包策略:3列网格,更紧凑的布局 + return 3; + } + + @override + bool shouldShowPasswordRoomIcon() { + // 马甲包策略:不显示密码房间图标,简化UI + return false; + } + + /// === 探索页面差异化方法实现(马甲包专属) === + + @override + int getExploreRoomDisplayThreshold() { + // 马甲包策略:显示更多房间在抽屉上方,减少抽屉使用 + return 9; + } + + @override + String getExploreGridIcon() { + // 马甲包策略:使用马甲包专属网格视图图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/index/at_icon_index_room_model_1.png"; + } + + @override + String getExploreListIcon() { + // 马甲包策略:使用马甲包专属列表视图图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/index/at_icon_index_room_model_2.png"; + } + + @override + String getExploreRankIconPattern(bool isGrid, int rank) { + // 马甲包策略:使用统一的排名图标,不区分网格/列表视图 + // TODO: 根据Figma设计添加马甲包专属排名图标 + if (isGrid) { + return "atu_images/index/at_icon_explore_room_model_1_rank_$rank.png"; + } else { + return "atu_images/index/at_icon_explore_room_model_2_rank_$rank.png"; + } + } + + @override + Color getExploreRoomBorderColor() { + // 马甲包策略:使用马甲包主题色作为边框颜色 + return ChatVibeTheme.primaryLight; // 马甲包主题色 + } + + @override + double getExploreRoomBorderWidth() { + // 马甲包策略:更细的边框,更简洁的UI + return 1.0; + } + + @override + int getExploreDrawerGridCrossAxisCount() { + // 马甲包策略:4列网格,更紧凑的布局 + return 4; + } + + @override + int getExploreRoomNameMarqueeThreshold() { + // 马甲包策略:更长的房间名称才显示滚动效果,减少不必要的动画 + return 15; + } + + @override + int getExploreRoomDescMarqueeThreshold() { + // 马甲包策略:更长的房间描述才显示滚动效果 + return 18; + } + + /// === 个人主页差异化方法实现(马甲包专属) === + + @override + String getMePageDefaultBackgroundImage() { + // 马甲包策略:使用马甲包专属默认背景图片 + // TODO: 根据Figma设计添加马甲包专属背景图片 + return "atu_images/person/at_icon_my_head_bg_defalt.png"; + } + + @override + String getMePageGenderBackgroundImage(bool isFemale) { + // 马甲包策略:使用统一的背景图片,不区分性别 + // TODO: 根据Figma设计添加马甲包专属背景图片 + return "atu_images/person/at_icon_my_head_bg_defalt.png"; + } + + @override + String getMePageCpDialogHeadImage() { + // 马甲包策略:使用马甲包专属CP对话框头像图片 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/person/at_icon_send_cp_requst_dialog_head.png"; + } + + @override + String getMePageDefaultAvatarImage() { + // 马甲包策略:使用马甲包专属默认头像图片 + // TODO: 根据Figma设计添加马甲包专属头像图标 + return "atu_images/general/at_icon_avar_defalt.png"; + } + + @override + String getMePageIdBackgroundImage(bool hasSpecialId) { + // 马甲包策略:使用统一的ID背景图片,简化UI + // TODO: 根据Figma设计添加马甲包专属ID背景图片 + return "atu_images/general/at_icon_id_bg.png"; + } + + @override + String getMePageCopyIdIcon() { + // 马甲包策略:使用马甲包专属复制ID图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/room/at_icon_user_card_copy_id.png"; + } + + @override + String getMePageGenderAgeBackgroundImage(bool isFemale) { + // 马甲包策略:使用统一的性别年龄背景图片,不区分性别 + // TODO: 根据Figma设计添加马甲包专属背景图片 + return "atu_images/login/at_icon_sex_woman_bg.png"; + } + + @override + String getMePageVisitorsFollowFansBackgroundImage(bool isFemale) { + // 马甲包策略:使用统一的访客关注粉丝背景图片,不区分性别 + // TODO: 根据Figma设计添加马甲包专属背景图片 + return "atu_images/person/at_icon_vistors_follow_fans_bg_woman.png"; + } + + @override + String getMePageEditUserInfoIcon() { + // 马甲包策略:使用马甲包专属编辑用户信息图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/person/at_icon_edit_user_info2.png"; + } + + @override + Color getMePageShineColor() { + // 马甲包策略:使用马甲包主题色作为闪耀文本颜色 + return const Color(0xffFF5722); // 马甲包主题色 + } + + @override + List getMePageGradientColors(bool isFemale) { + // 马甲包策略:使用统一的渐变颜色,不区分性别 + return [ + const Color(0xffFF5722), // 马甲包主题色 + const Color(0xffFEB219), // 马甲包辅助色 + const Color(0xffFF9326), // 马甲包辅助色 + ]; + } + + @override + List getMePageTabIndicatorGradient(bool isFemale) { + // 马甲包策略:使用统一的Tab指示器渐变,不区分性别 + return [ + const Color(0xffFF5722), // 马甲包主题色 + const Color(0xffFEB219), // 马甲包辅助色 + ]; + } + + @override + Color getMePageVisitorsFollowFansTextColor() { + // 马甲包策略:使用更亮的文本颜色,提高可读性 + return const Color(0xffFFFFFF); // 白色 + } + + @override + int getMePageInitialTabIndex() { + // 马甲包策略:从动态页开始,鼓励用户发布内容 + return 0; // dynamic页 + } + + @override + double getMePageScrollOpacityThreshold() { + // 马甲包策略:降低滚动阈值,更快显示AppBar效果 + return 200.0; + } + + @override + int getMePageNicknameScrollThreshold() { + // 马甲包策略:提高昵称滚动阈值,减少不必要的动画 + return 18; + } + + @override + int getMePageAgeDisplayWidthThreshold() { + // 马甲包策略:降低年龄显示宽度阈值,更早调整布局 + return 99; + } + + @override + double getMePageOpacityThresholdForAvatarDisplay() { + // 马甲包策略:降低头像显示透明度阈值,更早显示头像 + return 0.3; + } + + /// === 热门页面差异化方法实现(马甲包专属) === + + @override + String getPopularLeaderboardBackgroundImage(String leaderboardType) { + // 马甲包策略:使用马甲包专属的排行榜背景图片 + // 原始应用:使用现有的图像资源路径 + switch (leaderboardType) { + case 'room': + return "atu_images/index/at_icon_leader_spinner_room_bg.png"; + case 'wealth': + return "atu_images/index/at_icon_leader_spinner_wealth_bg.png"; + case 'charm': + return "atu_images/index/at_icon_leader_spinner_charm_bg.png"; + default: + return "atu_images/index/at_icon_leader_spinner_room_bg.png"; + } + } + + /// === 游戏页面差异化方法实现(马甲包专属) === + + @override + String getGameRankBackgroundImage(int rank) { + // 马甲包策略:使用统一的背景图片,简化UI + return "atu_images/game/at_icon_game_list_item_bg_variant1.png"; + } + + @override + String getGameRankIcon(int rank) { + // 马甲包策略:使用统一的排名图标 + return "atu_images/game/at_icon_game_rank_variant1.png"; + } + + @override + Color getGamePageThemeColor() { + // 马甲包策略:使用马甲包主题色 + return const Color(0xffFF9326); + } + + @override + Color getGamePageBackgroundColor() { + // 马甲包策略:更亮的背景颜色 + return const Color(0xfffafafa); + } + + @override + int getGamePageGridCrossAxisCount() { + // 马甲包策略:4列网格,更紧凑的布局 + return 4; + } + + @override + double getGamePageGridChildAspectRatio() { + // 马甲包策略:更高的高度比例 + return 1.5; + } + + @override + Color getGameRankTextColor(int rank) { + // 马甲包策略:使用统一的文本颜色,简化UI + return const Color(0xffFF5722); + } + + @override + Color getGamePageGradientSecondColor() { + // 马甲包策略:使用马甲包风格的渐变第二颜色 + return const Color(0xffFFE0B2); + } + + @override + String getGameHotTagBackgroundImage() { + // 马甲包策略:使用马甲包风格的热门游戏标签背景图片 + return "atu_images/index/at_icon_hotgames_tag_bg_variant1.png"; + } + + @override + String getGameNewTagImage() { + // 马甲包策略:使用马甲包风格的新游戏标签图片 + return "atu_images/index/at_icon_game_new_tag.png"; + } + + @override + String getGamePageMaskImage() { + // 马甲包策略:使用马甲包风格的游戏页面遮罩图片 + return "atu_images/index/at_icon_index_mask_variant1.png"; + } + + /// === VIP页面差异化方法实现(马甲包专属) === + + @override + String getVipPageBackgroundImage(int vipLevel) { + // 原始应用:使用现有的VIP背景图像模式 + return "atu_images/vip/at_icon_vip_level_bg_$vipLevel.png"; + } + + @override + String getVipPageIcon(int vipLevel) { + // 原始应用:使用现有的VIP图标模式 + return "atu_images/vip/at_icon_vip_level_$vipLevel.png"; + } + + @override + List getVipPageButtonGradient() { + // 原始应用:VIP按钮渐变颜色 + return [const Color(0xffFF5722), const Color(0xffFEB219)]; + } + + @override + int getVipPageGridCrossAxisCount() { + // 原始应用:3列网格 + return 3; + } + + @override + double getVipPageGridChildAspectRatio() { + // 原始应用:默认高度比例 + return 1.5; + } + + @override + String getVipPageHeadBackgroundImage(int vipLevel) { + // 原始应用:VIP头部背景图像模式 + return "atu_images/vip/at_icon_vip_head_bg_v$vipLevel.png"; + } + + @override + String getVipPageTabSelectedIcon(int vipLevel) { + // 原始应用:Tab选中状态图标模式 + return "atu_images/vip/at_icon_tab_vip_text_on_v$vipLevel.png"; + } + + @override + String getVipPageTabUnselectedIcon(int vipLevel) { + // 原始应用:Tab未选中状态图标模式 + return "atu_images/vip/at_icon_tab_vip_text_no_v$vipLevel.png"; + } + + @override + String getVipPageLargeIcon(int vipLevel) { + // 原始应用:VIP页面大图标路径模式 + return "atu_images/vip/at_icon_vip${vipLevel}_ic.png"; + } + + @override + String getVipPageTitleImage(int vipLevel) { + // 原始应用:VIP页面标题图像路径模式 + return "atu_images/vip/at_icon_vip${vipLevel}_t1.png"; + } + + @override + String getVipPageTagImage(int vipLevel, int tagIndex) { + // 原始应用:VIP页面标签图像路径模式 + return "atu_images/vip/at_icon_vip${vipLevel}_tag$tagIndex.png"; + } + + @override + String getVipPageItemBackgroundImage(int vipLevel) { + // 原始应用:VIP页面功能项背景图像路径模式 + return "atu_images/vip/at_icon_vip1_item_bg.png"; + } + + @override + String getVipPagePrivilegeIcon( + int vipLevel, + int privilegeIndex, + bool isSelected, + ) { + if (isSelected) { + return "atu_images/vip/at_icon_vip${vipLevel}_privilege${privilegeIndex}_en.png"; + } else { + return "atu_images/vip/at_icon_vip_privilege${privilegeIndex}_no.png"; + } + } + + @override + String getVipPageFeatureIcon(int vipLevel, String featureName) { + // 原始应用:VIP页面功能图标路径模式 + return "atu_images/vip/at_icon_vip${vipLevel}_$featureName.png"; + } + + @override + String getVipPagePreviewImage( + int vipLevel, + String previewType, + String featureName, + ) { + // 原始应用:VIP页面预览图像路径模式 + if (featureName == 'profile_frame') { + return "atu_images/vip/at_icon_vip${vipLevel}_profile_rev.png"; + } else if (featureName == 'profile_card') { + return "atu_images/vip/at_icon_vip${vipLevel}_profile_card_rev.png"; + } else if (featureName == 'room_cover_headdress') { + return "atu_images/vip/at_icon_vip${vipLevel}_room_cover_border_rev.png"; + } else if (featureName == 'mic_rippl_theme') { + return "atu_images/vip/at_icon_vip${vipLevel}_seat_rev.png"; + } else if (featureName == 'chatbox') { + return "atu_images/vip/at_icon_vip${vipLevel}_chatbox_pre.png"; + } else { + return "atu_images/vip/at_icon_vip${vipLevel}_${featureName}_$previewType.png"; + } + } + + @override + String getVipPagePurchaseBackgroundImage(int vipLevel) { + // 原始应用:VIP页面购买背景图像路径模式 + return "atu_images/vip/at_icon_vip1_buy_bg.png"; + } + + @override + String getVipPagePurchaseButtonImage() { + // 原始应用:VIP页面购买按钮图像路径 + return "atu_images/vip/at_icon_vip_buy_btn.png"; + } + + @override + Color getVipPageTextColor(int vipLevel, bool isSelected) { + if (!isSelected) { + return const Color(0xff646464); + } + // 原始应用:根据VIP等级返回不同的文本颜色 + switch (vipLevel) { + case 1: + return const Color(0xFFFADD5E); + case 2: + return const Color(0xFF5E9AFA); + case 3: + return const Color(0xFF1CC483); + case 4: + return const Color(0xFFAF5EFA); + case 5: + return const Color(0xFFFF4144); + case 6: + return const Color(0xFFFEE5BB); + default: + return Colors.white; + } + } + + /// === 通用页面差异化方法实现 === + + @override + String getCommonRankBackgroundImage(String pageType, int rank) { + // 原始应用:通用的排名背景图像模式 + return "atu_images/$pageType/at_icon_${pageType}_rank_bg_$rank.png"; + } + + @override + String getCommonRankIcon(String pageType, int rank) { + // 原始应用:通用的排名图标模式 + return "atu_images/$pageType/at_icon_${pageType}_rank_$rank.png"; + } + + @override + int getCommonPageGridCrossAxisCount(String pageType) { + // 原始应用:通用的2列网格 + return 2; + } + + @override + double getCommonPageGridChildAspectRatio(String pageType) { + // 原始应用:通用的高度比例 + return 1.4; + } + + /// === Admin编辑页面差异化方法实现(马甲包专属) === + + @override + String getAdminEditingBackgroundImage() { + // 马甲包策略:使用马甲包专属背景图片 + // TODO: 根据Figma设计添加马甲包专属背景图片 + return "atu_images/room/at_icon_room_settig_bg.png"; + } + + @override + List getAdminEditingButtonGradient(String buttonType) { + // 马甲包策略:使用马甲包主题渐变 + return [ + const Color(0xffFF9326), // 马甲包主题色 + const Color(0xffFEB219), // 马甲包辅助色 + ]; + } + + @override + String getAdminEditingIcon(String iconType) { + // 马甲包策略:使用统一的图标,简化UI + // TODO: 根据Figma设计添加马甲包专属图标 + switch (iconType) { + case 'specialIdBg': + return "atu_images/general/at_icon_special_id_bg.png"; + case 'normalIdBg': + return "atu_images/general/at_icon_id_bg.png"; + case 'copyId': + return "atu_images/room/at_icon_user_card_copy_id.png"; + case 'addPic': + return "atu_images/general/at_icon_add_pic.png"; + case 'closePic': + return "atu_images/general/at_icon_pic_close.png"; + case 'checked': + return "atu_images/general/at_icon_checked.png"; + default: + return ""; + } + } + + @override + BoxDecoration getAdminEditingInputDecoration() { + // 马甲包策略:使用马甲包风格的输入框装饰 + return BoxDecoration( + color: const Color(0xfff2f2f2), // #f2f2f2 + borderRadius: BorderRadius.circular(56), // 56px圆角,与登录页保持一致 + border: Border.all(color: Colors.transparent, width: 0), + ); + } + + @override + int getAdminEditingMaxImageUploadCount() { + // 马甲包策略:增加图片上传数量,支持更多图片 + return 5; // 允许上传最多5张图片 + } + + @override + int getAdminEditingDescriptionMaxLength() { + // 马甲包策略:增加描述最大长度 + return 300; // 允许更长的描述 + } + + @override + Map getAdminEditingViolationTypeMapping(String targetType) { + // 马甲包策略:使用相同的违规类型映射,或可自定义 + // 暂时使用原始应用的映射,后续可根据需求调整 + if (targetType == 'User') { + return {'Pornography': 1, 'Illegal information': 2}; + } else if (targetType == 'Room') { + return { + 'Pornography': 3, + 'Illegal information': 4, + 'Fraud': 5, + 'Other': 6, + }; + } + return {}; + } + + /// === Admin搜索页面差异化方法实现(马甲包专属) === + + @override + String getAdminSearchBackgroundImage(String pageType) { + // 马甲包策略:使用马甲包专属背景图片 + // TODO: 根据Figma设计添加马甲包专属背景图片 + return "atu_images/index/at_icon_coupon_head_bg.png"; + } + + @override + List getAdminSearchButtonGradient(String pageType) { + // 马甲包策略:使用马甲包主题渐变 + return [ + const Color(0xffFF9326), // 马甲包主题色 + const Color(0xffFEB219), // 马甲包辅助色 + ]; + } + + @override + Color getAdminSearchButtonTextColor(String pageType) { + // 马甲包策略:使用白色文本,提高可读性 + return Colors.white; + } + + @override + String getAdminSearchEditIcon(String pageType) { + // 马甲包策略:使用马甲包专属编辑图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/room/at_icon_room_edit.png"; + } + + @override + Color getAdminSearchInputBorderColor(String pageType) { + // 马甲包策略:使用马甲包主题色作为边框 + return const Color(0xffFF5722); // 马甲包主题色 + } + + @override + Color getAdminSearchInputTextColor(String pageType) { + // 马甲包策略:使用更深的文本颜色 + return Colors.black87; + } + + @override + String getAdminSearchUserInfoIcon(String iconType) { + // 马甲包策略:使用统一的图标 + // TODO: 根据Figma设计添加马甲包专属图标 + switch (iconType) { + case 'specialIdBg': + return "atu_images/general/at_icon_special_id_bg.png"; + case 'normalIdBg': + return "atu_images/general/at_icon_id_bg.png"; + case 'copyId': + return "atu_images/room/at_icon_user_card_copy_id.png"; + default: + return ""; + } + } + + /// === 主登录页面差异化方法实现(马甲包专属) === + + @override + String getLoginMainBackgroundImage() { + // 马甲包策略:使用马甲包专属背景图片 + // TODO: 根据Figma设计添加马甲包专属背景图片 + return "atu_images/login/at_login.png"; + } + + @override + String getLoginMainAppIcon() { + // 马甲包策略:使用马甲包专属应用图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/splash/at_icon_splash_icon.png"; + } + + @override + String getLoginMainGoogleIcon() { + // 马甲包策略:使用马甲包专属Google图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/login/at_icon_google.png"; + } + + @override + String getLoginMainAppleIcon() { + // 马甲包策略:使用马甲包专属Apple图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/login/at_icon_iphone.png"; + } + + @override + String getLoginMainAccountIcon() { + // 马甲包策略:使用马甲包专属账号图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/login/at_icon_account.png"; + } + + @override + String getLoginMainAgreementIcon(bool isSelected) { + // 马甲包策略:使用马甲包专属协议图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return isSelected + ? "atu_images/login/at_icon_login_ser_select.png" + : "atu_images/login/at_icon_login_ser_select_un.png"; + } + + @override + Color getLoginMainDividerColor() { + // 马甲包策略:使用马甲包主题色作为分割线颜色 + return const Color(0xffE6E6E6); // 马甲包主题色 + } + + @override + Color getLoginMainButtonBorderColor() { + // 马甲包策略:使用马甲包主题色作为按钮边框颜色 + return const Color(0xffE6E6E6); // 马甲包主题色 + } + + @override + Color getLoginMainButtonBackgroundColor() { + // 马甲包策略:使用浅色背景,提高可读性 + return const Color(0xffffffff); // 浅灰色 + } + + @override + Color getLoginMainButtonTextColor() { + // 马甲包策略:按钮文本颜色(黑色) + return Colors.black; + } + + @override + Color getLoginMainLinkColor() { + // 马甲包策略:使用马甲包主题色作为链接颜色 + return const Color(0xffFF5722); // 马甲包主题色 + } + + /// === 编辑个人资料页面差异化方法实现(马甲包专属) === + + @override + String getEditProfileBackgroundImage() { + // 马甲包策略:使用马甲包专属背景图片 + // TODO: 根据Figma设计添加马甲包专属背景图片 + return "atu_images/login/at_edit_profile_bg.png"; + } + + @override + String getEditProfileDefaultAvatarImage() { + // 马甲包策略:使用马甲包专属默认头像图片 + // TODO: 根据Figma设计添加马甲包专属头像图标 + return "atu_images/general/at_icon_avar_defalt.png"; + } + + @override + String getEditProfileGenderIcon(bool isMale) { + // 马甲包策略:使用马甲包专属性别图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return isMale + ? "atu_images/login/at_icon_sex_man.png" + : "atu_images/login/at_icon_sex_woman.png"; + } + + @override + List getEditProfileGenderButtonGradient(bool isMale, bool isSelected) { + // 马甲包策略:使用马甲包主题渐变 + return [ + const Color(0xffFF9326), // 马甲包主题色 + const Color(0xffFEB219), // 马甲包辅助色 + ]; + } + + @override + Color getEditProfileInputBackgroundColor() { + // 马甲包策略:使用更亮的背景颜色,提高可读性 + return Colors.white30; + } + + @override + Color getEditProfileDatePickerConfirmColor() { + // 马甲包策略:使用马甲包主题色 + return const Color(0xffFF5722); // 马甲包主题色 + } + + @override + Color getEditProfileContinueButtonColor() { + // 马甲包策略:使用马甲包主题色 + return const Color(0xffFF5722); // 马甲包主题色 + } + + /// === Country页面差异化方法实现(马甲包专属) === + + @override + String getCountrySelectOkIcon() { + // 马甲包策略:使用马甲包专属选择确认图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/general/at_icon_select_ok.png"; + } + + @override + String getCountrySelectUnOkIcon() { + // 马甲包策略:使用马甲包专属选择未确认图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/general/at_icon_select_un_ok.png"; + } + + @override + String getCountryRadioSelectedIcon() { + // 马甲包策略:使用马甲包专属单选选中图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/login/at_icon_login_ser_select.png"; + } + + @override + String getCountryRadioUnselectedIcon() { + // 马甲包策略:使用马甲包专属单选未选中图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/login/at_icon_login_ser_select_un.png"; + } + + @override + Color getCountryItemBorderColor() { + // 马甲包策略:使用马甲包主题色作为边框颜色 + return const Color(0xffFF5722); // 马甲包主题色 + } + + @override + Color getCountryPageScaffoldBackgroundColor() { + // 马甲包策略:使用浅灰色背景 + return const Color(0xfff5f5f5); + } + + @override + Color getCountryPageIconColor() { + // 马甲包策略:使用深灰色图标 + return const Color(0xff333333); + } + + @override + Color getCountryPagePrimaryTextColor() { + // 马甲包策略:使用深灰色文本 + return const Color(0xff212121); + } + + @override + Color getCountryPageSecondaryTextColor() { + // 马甲包策略:使用中等灰色文本 + return const Color(0xff757575); + } + + @override + Color getCountryPageContainerBackgroundColor() { + // 马甲包策略:使用白色容器背景 + return Colors.white; + } + + /// === Chat页面差异化方法实现(马甲包专属) === + + /// === 消息页面 (ATMessagePage) 方法实现 === + + @override + String getMessagePageIndexMaskIcon() { + // 马甲包策略:使用马甲包专属索引遮罩图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/index/at_icon_index_bg.png"; + } + + @override + String getMessagePageAnnouncementTagIcon() { + // 马甲包策略:使用马甲包专属公告标签图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/family/at_icon_announcement_tag.png"; + } + + @override + String getMessagePageActivityMessageIcon() { + // 马甲包策略:使用马甲包专属活动消息图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/msg/at_icon_message_activity.png"; + } + + @override + String getMessagePageActivityTitleBackground(bool isRtl) { + // 马甲包策略:使用马甲包专属活动消息标题背景图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return isRtl + ? "atu_images/msg/at_icon_message_activity.png" + : "atu_images/msg/at_icon_message_activity.png"; + } + + @override + String getMessagePageSystemMessageIcon() { + // 马甲包策略:使用马甲包专属系统消息图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/msg/at_icon_message_system.png"; + } + + @override + String getMessagePageSystemTitleBackground(bool isRtl) { + // 马甲包策略:使用马甲包专属系统消息标题背景图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return isRtl + ? "atu_images/msg/at_icon_system_title_bg_rtl.png" + : "atu_images/msg/at_icon_system_title_bg.png"; + } + + @override + String getMessagePageNotificationMessageIcon() { + // 马甲包策略:使用马甲包专属通知消息图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/msg/at_icon_message_noti.png"; + } + + @override + String getMessagePageNotificationTitleBackground(bool isRtl) { + // 马甲包策略:使用马甲包专属通知消息标题背景图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return isRtl + ? "atu_images/msg/at_icon_notifcation_title_bg_rtl.png" + : "atu_images/msg/at_icon_notifcation_title_bg.png"; + } + + /// === 聊天页面 (ATMessageChatPage) 方法实现 === + + @override + String getATMessageChatPageRoomSettingBackground() { + // 马甲包策略:使用马甲包专属房间设置背景图像 + // TODO: 根据Figma设计添加马甲包专属背景图片 + return "atu_images/person/at_icon_edit_userinfo_bg.png"; + } + + @override + String getATMessageChatPageEmojiIcon() { + // 马甲包策略:使用马甲包专属表情图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/msg/at_icon_emoji.png"; + } + + @override + String getATMessageChatPageChatKeyboardIcon() { + // 马甲包策略:使用马甲包专属聊天键盘图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/msg/at_icon_chat_key.png"; + } + + @override + String getATMessageChatPageAddIcon() { + // 马甲包策略:使用马甲包专属添加图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/msg/at_icon_add.png"; + } + + @override + String getATMessageChatPageSendMessageIcon() { + // 马甲包策略:使用马甲包专属发送消息图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/msg/at_icon_chat_message_send.png"; + } + + @override + String getATMessageChatPageCameraIcon() { + // 马甲包策略:使用马甲包专属相机图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/msg/at_icon_xiangji.png"; + } + + @override + String getATMessageChatPagePictureIcon() { + // 马甲包策略:使用马甲包专属图片图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/msg/at_icon_tupian.png"; + } + + @override + String getATMessageChatPageRedEnvelopeIcon() { + // 马甲包策略:使用马甲包专属红包图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/msg/at_icon_hongbao.png"; + } + + @override + String getATMessageChatPageRedEnvelopeMessageBackground() { + // 马甲包策略:使用马甲包专属红包消息背景图像 + // TODO: 根据Figma设计添加马甲包专属背景图片 + return "atu_images/msg/at_icon_msg_send_hongbao_bg.png"; + } + + @override + String getATMessageChatPageRedEnvelopeConfigTabButtonIcon() { + // 马甲包策略:使用马甲包专属红包配置标签按钮图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/room/at_icon_red_envelope_config_tab_btn.png"; + } + + @override + String getATMessageChatPageRedEnvelopeMessageItemBackground() { + // 马甲包策略:使用马甲包专属红包消息项背景图像 + // TODO: 根据Figma设计添加马甲包专属背景图片 + return "atu_images/msg/at_icon_red_envelopes_msg_item_bg.png"; + } + + @override + String getATMessageChatPageRedEnvelopeReceivePackageIcon() { + // 马甲包策略:使用马甲包专属红包接收包图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/room/at_icon_red_envelope_rec_pack.png"; + } + + @override + String getATMessageChatPageRedEnvelopeOpenBackground() { + // 马甲包策略:使用马甲包专属红包打开背景图像 + // TODO: 根据Figma设计添加马甲包专属背景图片 + return "atu_images/msg/at_icon_red_envelopes_msg_open_bg.png"; + } + + @override + String getATMessageChatPageRedEnvelopeOpenedBackground() { + // 马甲包策略:使用马甲包专属红包已打开背景图像 + // TODO: 根据Figma设计添加马甲包专属背景图片 + return "atu_images/msg/at_icon_red_envelopes_msg_opened_bg.png"; + } + + @override + String getATMessageChatPageGoldCoinIcon() { + // 马甲包策略:使用马甲包专属金币图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/general/at_icon_jb.png"; + } + + @override + String getATMessageChatPageMessageMenuDeleteIcon() { + // 马甲包策略:使用马甲包专属消息菜单删除图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/msg/at_icon_msg_menu_delete.png"; + } + + @override + String getATMessageChatPageMessageMenuCopyIcon() { + // 马甲包策略:使用马甲包专属消息菜单复制图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/msg/at_icon_msg_menu_copy.png"; + } + + @override + String getATMessageChatPageMessageMenuRecallIcon() { + // 马甲包策略:使用马甲包专属消息菜单撤回图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/msg/at_icon_msg_menu_recall.png"; + } + + @override + String getATMessageChatPageLoadingIcon() { + // 马甲包策略:使用马甲包专属加载图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/general/at_icon_loading.png"; + } + + @override + String getGenderBackgroundImage(bool isFemale) { + // 马甲包策略:使用马甲包专属性别背景图片 + // TODO: 根据Figma设计添加马甲包专属性别背景图片 + return isFemale + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png"; + } + + @override + String getIdBackgroundImage(bool hasSpecialId) { + // 马甲包策略:使用马甲包专属ID背景图片 + // TODO: 根据Figma设计添加马甲包专属ID背景图片 + return hasSpecialId + ? "atu_images/general/at_icon_special_id_bg.png" + : "atu_images/general/at_icon_id_bg.png"; + } + + @override + String getCopyIdIcon() { + // 马甲包策略:使用马甲包专属复制ID图标 + // TODO: 根据Figma设计添加马甲包专属复制ID图标 + return "atu_images/room/at_icon_user_card_copy_id.png"; + } + + @override + String getATMessageChatPageSystemHeadImage() { + // 马甲包策略:使用马甲包专属系统头像图像 + // TODO: 根据Figma设计添加马甲包专属头像图片 + return "atu_images/msg/head_system.png"; + } + + /// === Gift页面差异化方法实现(马甲包专属) === + + @override + String getGiftPageFirstRechargeRoomTagIcon() { + // 马甲包策略:使用马甲包专属首充房间标签图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/room/at_icon_first_recharge_room_tag.png"; + } + + @override + String getGiftPageGoldCoinIcon() { + // 马甲包策略:使用马甲包专属金币图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/general/at_icon_jb.png"; + } + + @override + String getGiftPageGiveTypeBackground() { + // 马甲包策略:使用马甲包专属赠送类型背景图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/room/at_icon_give_type_bg.png"; + } + + @override + String getGiftPageActivityGiftHeadBackground(String giftType) { + // 马甲包策略:使用马甲包专属活动礼物头部背景图标 + // TODO: 根据Figma设计添加马甲包专属图标 + switch (giftType) { + case 'ACTIVITY': + return "atu_images/room/at_icon_activity_gift_head_bg.png"; + case 'LUCK': + return "atu_images/room/at_icon_luck_gift_head_bg.png"; + case 'CP': + return "atu_images/room/at_icon_cp_gift_head_bg.png"; + case 'MAGIC': + return "atu_images/room/at_icon_magic_gift_head_bg.png"; + default: + return "atu_images/room/at_icon_activity_gift_head_bg.png"; + } + } + + @override + String getGiftPageCustomizedRuleIcon() { + // 马甲包策略:使用马甲包专属定制规则图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/room/at_icon_customized_rule.png"; + } + + @override + String getGiftPageGiftEffectIcon(String giftType) { + // 马甲包策略:使用马甲包专属礼物效果图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/room/at_icon_gift_effect.png"; + } + + @override + String getGiftPageGiftMusicIcon(String giftType) { + // 马甲包策略:使用马甲包专属礼物音乐图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/room/at_icon_gift_music.png"; + } + + @override + String getGiftPageGiftLuckIcon(String giftType) { + // 马甲包策略:使用马甲包专属礼物幸运图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/room/at_icon_gift_luck.png"; + } + + @override + String getGiftPageGiftCpIcon(String giftType) { + // 马甲包策略:使用马甲包专属礼物CP图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/room/at_icon_gift_cp.png"; + } + + @override + String getGiftPageFirstRechargeTextIcon(String languageCode) { + // 马甲包策略:使用马甲包专属首充文本图标 + // TODO: 根据Figma设计添加马甲包专属图标 + if (languageCode == 'ar') { + return "atu_images/index/at_icon_first_recharge_ar_text.png"; + } else { + // 默认为英语版本 + return "atu_images/index/at_icon_first_recharge_en_text.png"; + } + } + + @override + String getGiftPageAllOnMicrophoneIcon() { + // 马甲包策略:使用马甲包专属"全开麦"图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/room/at_icon_all_on_microphone.png"; + } + + @override + String getGiftPageUsersOnMicrophoneIcon() { + // 马甲包策略:使用马甲包专属"用户开麦"图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/room/at_icon_userson_microphone.png"; + } + + @override + String getGiftPageAllInTheRoomIcon() { + // 马甲包策略:使用马甲包专属"房间所有人"图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/room/at_icon_all_in_the_room.png"; + } + + @override + String getCommonSelectIcon() { + // 马甲包策略:使用马甲包专属选择图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/general/at_icon_checked.png"; + } + + @override + String getCommonUnselectIcon() { + // 马甲包策略:使用马甲包专属未选择图标 + // TODO: 根据Figma设计添加马甲包专属图标 + return "atu_images/general/at_icon_unchecked.png"; + } + + /// === Media页面差异化方法实现(马甲包专属) === + + @override + Color getImagePreviewBackgroundColor() { + // 马甲包策略:图片预览页面背景颜色 - 深灰色 + return const Color(0xff1a1a1a); // #1a1a1a + } + + @override + Color getImagePreviewGalleryBackgroundColor() { + // 马甲包策略:图片预览画廊背景颜色 - 深灰色 + return const Color(0xff1a1a1a); // #1a1a1a + } + + @override + Color getImagePreviewAppBarBackgroundColor() { + // 马甲包策略:图片预览AppBar背景颜色 - 深灰色70%透明度 + return const Color(0xB31a1a1a); // withOpacity(0.7) + } + + @override + Color getImagePreviewBackIconColor() { + // 马甲包策略:图片预览返回图标颜色 - 浅灰色 + return const Color(0xffe6e6e6); // #e6e6e6 + } + + @override + Color getImagePreviewTextColor() { + // 马甲包策略:图片预览文本颜色 - 浅灰色 + return const Color(0xffe6e6e6); // #e6e6e6 + } + + @override + Color getImagePreviewLoadingIndicatorColor() { + // 马甲包策略:图片预览加载指示器颜色 - 马甲包主题色(橙色) + return const Color(0xffFF5722); // 马甲包主题色 + } + + @override + double getImagePreviewAppBarOpacity() { + // 马甲包策略:图片预览AppBar透明度 - 0.7 + return 0.7; + } + + @override + Color getVideoPlayerBackgroundColor() { + // 马甲包策略:视频播放页面背景颜色 - 深灰色 + return const Color(0xff1a1a1a); // #1a1a1a + } + + @override + Color getVideoPlayerControlsBackgroundColor() { + // 马甲包策略:视频播放控制界面背景颜色 - 深灰色50%透明度 + return const Color(0x801a1a1a); // withOpacity(0.5) + } + + @override + Color getVideoPlayerIconColor() { + // 马甲包策略:视频播放图标颜色 - 浅灰色 + return const Color(0xffe6e6e6); // #e6e6e6 + } + + @override + Color getVideoPlayerBottomControlsBackgroundColor() { + // 马甲包策略:视频播放底部控制栏背景颜色 - 深灰色60%透明度 + return const Color(0x991a1a1a); // withOpacity(0.6) + } + + @override + Color getVideoPlayerProgressBarActiveColor() { + // 马甲包策略:视频播放进度条活动颜色 - 马甲包主题色(橙色) + return const Color(0xffFF5722); // 马甲包主题色 + } + + @override + Color getVideoPlayerProgressBarInactiveColor() { + // 马甲包策略:视频播放进度条非活动颜色 - 深灰色 + return const Color(0xff666666); // 深灰色 + } + + @override + Color getVideoPlayerTextColor() { + // 马甲包策略:视频播放文本颜色 - 浅灰色 + return const Color(0xffe6e6e6); // #e6e6e6 + } + + @override + Color getVideoPlayerLoadingIndicatorColor() { + // 马甲包策略:视频播放加载指示器颜色 - 马甲包主题色(橙色) + return const Color(0xffFF5722); // 马甲包主题色 + } + + @override + double getVideoPlayerControlsOpacity() { + // 马甲包策略:视频播放控制界面透明度 - 0.5 + return 0.5; + } + + @override + double getVideoPlayerBottomControlsOpacity() { + // 马甲包策略:视频播放底部控制栏透明度 - 0.6 + return 0.6; + } + + @override + int getVideoPlayerControlsDisplayDuration() { + // 马甲包策略:视频播放控制界面显示持续时间 - 5秒 + return 5; + } + + /// === 举报页面差异化方法实现 === + + @override + String getReportPageBackgroundImage() { + // 马甲包策略:举报页面背景图片 - 与房间设置背景相同(与原始应用相同) + return "atu_images/person/at_icon_edit_userinfo_bg.png"; + } + + @override + Color getReportPageHintTextColor() { + // 马甲包策略:举报页面提示文本颜色 - 深灰色 + return const Color(0xff666666); // 深灰色 + } + + @override + Color getReportPageContainerBackgroundColor() { + // 马甲包策略:举报页面容器背景颜色 - 白色(与原始应用相同) + return Color(0xffF2F2F2); + } + + @override + Color getReportPageBorderColor() { + // 马甲包策略:举报页面边框颜色 - 浅灰色 + return const Color(0xffCCCCCC); // 浅灰色 + } + + @override + Color getReportPagePrimaryTextColor() { + // 马甲包策略:举报页面主要文本颜色 - 黑色(与原始应用相同) + return Colors.black; // 黑色 + } + + @override + Color getReportPageSecondaryHintTextColor() { + // 马甲包策略:举报页面次级提示文本颜色 - 深灰色 + return const Color(0xff666666); // 深灰色 + } + + @override + Color getReportPageUnselectedBorderColor() { + // 马甲包策略:举报页面未选中边框颜色 - 灰色 + return Colors.grey; // 灰色 + } + + @override + Color getReportPageSubmitButtonBackgroundColor() { + // 马甲包策略:举报页面提交按钮背景颜色 - 马甲包主题色(橙色) + return const Color(0xffFF5722); // 马甲包主题色 + } + + @override + Color getReportPageSubmitButtonTextColor() { + // 马甲包策略:举报页面提交按钮文本颜色 - 白色(与原始应用相同) + return Colors.white; + } + + @override + String getReportPageIcon(String iconType) { + // 马甲包策略:举报页面图标路径(目前使用与原始应用相同的图标) + switch (iconType) { + case 'addPic': + return "atu_images/general/at_icon_add_pic.png"; + case 'closePic': + return "atu_images/general/at_icon_pic_close.png"; + case 'checked': + return "atu_images/login/at_icon_login_ser_select.png"; + default: + return ""; // 未知图标类型返回空字符串 + } + } + + /// === 语音房间页面差异化方法实现 === + + @override + Color getVoiceRoomBackgroundColor() { + // 马甲包策略:语音房间页面背景颜色 - 深灰色 + return const Color(0xff1a1a1a); // #1a1a1a + } + + @override + Color getVoiceRoomTabLabelColor() { + // 马甲包策略:语音房间页面Tab标签选中颜色 - 浅灰色 + return const Color(0xffe6e6e6); // #e6e6e6 + } + + @override + Color getVoiceRoomTabUnselectedLabelColor() { + // 马甲包策略:语音房间页面Tab标签未选中颜色 - 灰色 + return const Color(0xff999999); // 灰色 + } + + @override + Color getVoiceRoomTabIndicatorColor() { + // 马甲包策略:语音房间页面Tab指示器颜色 - 马甲包主题色(橙色) + return const Color(0xffFF5722); // 马甲包主题色 + } + + @override + Color getVoiceRoomTabDividerColor() { + // 马甲包策略:语音房间页面Tab分割线颜色 - 深灰色 + return Colors.transparent; // 深灰色 + } + + @override + EdgeInsets getVoiceRoomTabLabelPadding() { + // 马甲包策略:语音房间页面Tab标签内边距 - 与原始应用相同 + return const EdgeInsets.symmetric(horizontal: 12); + } + + @override + EdgeInsetsDirectional getVoiceRoomChatContainerMargin() { + // 马甲包策略:语音房间页面聊天容器外边距 - 与原始应用相同 + return const EdgeInsetsDirectional.only(end: 52); + } + + @override + TextStyle getVoiceRoomTabLabelStyle() { + // 马甲包策略:语音房间页面Tab标签选中文本样式 - 与原始应用相同 + return const TextStyle(fontSize: 15, fontFamily: "MyCustomFont"); + } + + @override + TextStyle getVoiceRoomTabUnselectedLabelStyle() { + // 马甲包策略:语音房间页面Tab标签未选中文本样式 - 与原始应用相同 + return const TextStyle(fontSize: 13, fontFamily: "MyCustomFont"); + } + + @override + String getVoiceRoomDefaultBackgroundImage() { + // 马甲包策略:语音房间默认背景图像路径 - 与原始应用相同 + return "atu_images/room/at_icon_room_defaut_bg.png"; + } + + /// === 钱包页面差异化方法实现 === + + /// === 充值页面差异化方法实现 === + + @override + String getRechargePageBackgroundImage() { + // 马甲包策略:充值页面背景图像路径 - 与原始应用相同 + return "atu_images/room/at_icon_room_settig_bg.png"; + } + + @override + Color getRechargePageScaffoldBackgroundColor() { + // 马甲包策略:充值页面Scaffold背景颜色 - 深灰色 + return Colors.transparent; // #1a1a1a + } + + @override + Color getRechargePageDialogBarrierColor() { + // 马甲包策略:充值页面对话框屏障颜色 - 深灰色半透明 + return Colors.black12; // withOpacity(0.8) + } + + @override + Color getRechargePageDialogContainerBackgroundColor() { + // 马甲包策略:充值页面对话框容器背景颜色 - 深灰色 + return Colors.black26; // 深灰色 + } + + @override + Color getRechargePageDialogTextColor() { + // 马甲包策略:充值页面对话框文本颜色 - 浅灰色 + return const Color(0xffe6e6e6); // #e6e6e6 + } + + @override + Color getRechargePageMainContainerBackgroundColor() { + // 马甲包策略:充值页面主容器背景颜色 - 深灰色 + return Colors.transparent; // #1a1a1a + } + + @override + Color getRechargePageButtonBackgroundColor() { + // 马甲包策略:充值页面按钮背景颜色 - 马甲包主题色(橙色) + return const Color(0xffFF9326); // 马甲包主题色 + } + + @override + Color getRechargePageButtonTextColor() { + // 马甲包策略:充值页面按钮文本颜色 - 白色(与原始应用相同) + return Colors.white; + } + + @override + String getRechargePageWalletBackgroundImage() { + // 马甲包策略:充值页面钱包背景图像路径 - 与原始应用相同 + return "atu_images/index/at_icon_wallet_bg.png"; + } + + @override + String getRechargePageWalletIcon() { + // 马甲包策略:充值页面钱包图标路径 - 与原始应用相同 + return "atu_images/index/at_icon_wallet_icon.png"; + } + + @override + Color getRechargePageWalletTextColor() { + // 马甲包策略:充值页面钱包文本颜色 - 浅灰色 + return Colors.white; // #e6e6e6 + } + + @override + String getRechargePageGoldIcon() { + // 马甲包策略:充值页面金币图标路径(默认图标)- 与原始应用相同 + return "atu_images/general/at_icon_jb.png"; + } + + @override + Color getRechargePageSelectedItemBackgroundColor() { + // 马甲包策略:充值页面选中项背景颜色 - 马甲包主题色半透明 + return const Color(0x33FF5722); // withOpacity(0.2) + } + + @override + Color getRechargePageSelectedItemBorderColor() { + // 马甲包策略:充值页面选中项边框颜色 - 马甲包主题色 + return const Color(0xffFF5722); // 马甲包主题色 + } + + @override + Color getRechargePageUnselectedItemBorderColor() { + // 马甲包策略:充值页面未选中项边框颜色 - 深灰色 + return Colors.black26; // 深灰色 + } + + @override + Color getRechargePageUnselectedItemBackgroundColor() { + // 马甲包策略:充值页面未选中项背景颜色 - 深灰色 + return Colors.white; // 深灰色背景 + } + + @override + Color getRechargePageItemTextColor() { + // 马甲包策略:充值页面项目文本颜色 - 浅灰色 + return Colors.black; // #e6e6e6 + } + + @override + Color getRechargePageItemPriceTextColor() { + // 马甲包策略:充值页面价格文本颜色 - 白色 + return Colors.black; + } + + @override + String getRechargePageGoldIconByIndex(int index) { + // 马甲包策略:根据索引获取充值页面金币图标路径 - 与原始应用相同 + switch (index) { + case 0: + return "atu_images/general/at_icon_jb.png"; + case 1: + return "atu_images/general/at_icon_jb2.png"; + case 2: + return "atu_images/general/at_icon_jb3.png"; + default: + return "atu_images/general/at_icon_jb.png"; // 默认图标 + } + } + + @override + Color getRechargePageAppleItemBackgroundColor() { + // 马甲包策略:充值页面Apple产品项背景颜色 - 深灰色 + return const Color(0xff2a2a2a); // 深灰色 + } + + @override + String getRechargePageRecordIcon() { + // 马甲包策略:充值页面记录图标路径 - 与原始应用相同 + return "atu_images/index/at_icon_recharge_recod.png"; + } + + /// === 金币记录页面差异化方法实现 === + + @override + String getGoldRecordPageBackgroundImage() { + // 马甲包策略:金币记录页面背景图像路径 - 与原始应用相同 + return "atu_images/person/at_icon_edit_userinfo_bg.png"; + } + + @override + Color getGoldRecordPageScaffoldBackgroundColor() { + // 马甲包策略:金币记录页面Scaffold背景颜色 - 深灰色 + return Colors.transparent; // #1a1a1a + } + + @override + Color getGoldRecordATPageListBackgroundColor() { + // 马甲包策略:金币记录页面列表背景颜色 - 深灰色 + return const Color(0xff1a1a1a); // #1a1a1a + } + + @override + Color getGoldRecordPageContainerBackgroundColor() { + // 马甲包策略:金币记录页面容器背景颜色 - 深灰色 + return Colors.white; // #1a1a1a + } + + @override + Color getGoldRecordPageBorderColor() { + // 马甲包策略:金币记录页面边框颜色 - 深灰色 + return Colors.black12; // 深灰色 + } + + @override + Color getGoldRecordPagePrimaryTextColor() { + // 马甲包策略:金币记录页面主要文本颜色 - 浅灰色 + return const Color(0xff000000); // #e6e6e6 + } + + @override + Color getGoldRecordPageSecondaryTextColor() { + // 马甲包策略:金币记录页面次要文本颜色 - 灰色 + return const Color(0xff999999); // 灰色 + } + + /// === Store页面差异化方法实现 === + + @override + String getStorePageBackgroundImage() { + // 马甲包策略:Store页面背景图像路径 - 使用相同背景图像(保持一致性) + // 注意:如果需要差异化,可以返回不同的背景图像路径 + return "atu_images/room/at_icon_room_settig_bg.png"; + } + + @override + String getStorePageShoppingBagIcon() { + // 马甲包策略:Store页面购物袋图标路径 - 使用相同图标(保持一致性) + // 注意:如果需要差异化,可以返回不同的图标路径 + return "atu_images/store/at_icon_shop_bag.png"; + } + + @override + EdgeInsetsDirectional getStorePageShoppingBagMargin() { + // 马甲包策略:Store页面购物袋图标外边距 - 保持相同外边距 + return const EdgeInsetsDirectional.only(end: 10); + } + + @override + EdgeInsets getStorePageTabLabelPadding() { + // 马甲包策略:Store页面Tab标签内边距 - 保持相同内边距 + return const EdgeInsets.symmetric(horizontal: 12); + } + + @override + TextStyle getStorePageTabLabelStyle() { + // 马甲包策略:Store页面Tab标签选中文本样式 + // 差异化:使用马甲包主题色作为选中文本颜色 + return const TextStyle( + fontSize: 15, + color: Colors.black87, // 马甲包主题橙色,替代Colors.black87 + fontWeight: FontWeight.w600, + ); + } + + @override + TextStyle getStorePageTabUnselectedLabelStyle() { + // 马甲包策略:Store页面Tab标签未选中文本样式 + // 差异化:调整未选中文本颜色为灰色系 + return const TextStyle( + fontSize: 13, + color: Colors.black38, // 灰色,替代Colors.black38 + fontWeight: FontWeight.w500, + ); + } + + @override + Color getStorePageTabIndicatorColor() { + // 马甲包策略:Store页面Tab指示器颜色 - 使用马甲包主题色 + // 注意:原始应用使用ChatVibeTheme.primaryColor,马甲包使用相同值 + return const Color(0xffFF5722); // ChatVibeTheme.primaryColor + } + + @override + Color getStorePageTabDividerColor() { + // 马甲包策略:Store页面Tab分割线颜色 - 保持透明 + return Colors.transparent; + } + + @override + Color getStorePageBottomDividerColor() { + // 马甲包策略:Store页面底部Divider颜色 - 调整为浅灰色 + return const Color(0xffEEEEEE); // 浅灰色,替代Colors.black12 + } + + @override + double getStorePageBottomDividerThickness() { + // 马甲包策略:Store页面底部Divider厚度 - 保持相同 + return 0.5; + } + + @override + double getStorePageBottomDividerHeight() { + // 马甲包策略:Store页面底部Divider高度 - 保持相同 + return 24; + } + + @override + String getStorePageGoldIcon() { + // 马甲包策略:Store页面金币图标路径 - 使用相同图标 + return "atu_images/general/at_icon_jb.png"; + } + + @override + Color getStorePageGoldTextColor() { + // 马甲包策略:Store页面金币文本颜色 - 使用马甲包主题色 + return const Color(0xffFF9500); // 马甲包主题橙色,替代Color(0xFFFFE134) + } + + @override + Color getStorePageGoldIconColor() { + // 马甲包策略:Store页面金币图标颜色 - 使用马甲包主题色 + return const Color(0xffFF9500); // 马甲包主题橙色,替代Color(0xFFFFE134) + } + + /// === Store子页面(商品项)差异化方法实现 === + + @override + Color getStoreItemBackgroundColor() { + // 马甲包策略:Store商品项背景颜色 - 使用马甲包表面色 + return ChatVibeTheme.surfaceColor; // 马甲包表面色,替代Colors.white + } + + @override + Color getStoreItemUnselectedBorderColor() { + // 马甲包策略:Store商品项未选中边框颜色 - 调整为浅灰色 + return const Color(0xffE0E0E0); // 浅灰色,替代Colors.black12 + } + + @override + Color getStoreItemSelectedBorderColor() { + // 马甲包策略:Store商品项选中边框颜色 - 使用马甲包主题色 + return const Color(0xffFEB219); // 马甲包主题橙色,替代Color(0xffFF9500) + } + + @override + String getStoreItemGoldIcon() { + // 马甲包策略:Store商品项金币图标路径 - 使用相同图标 + return "atu_images/general/at_icon_jb.png"; + } + + @override + Color getStoreItemPriceTextColor() { + // 马甲包策略:Store商品项价格文本颜色 - 使用马甲包主要文本颜色 + return const Color(0xffFF9500); // 马甲包主要文本颜色,替代Colors.black + } + + @override + Color getStoreItemBuyButtonColor() { + // 马甲包策略:Store商品项购买按钮背景颜色 - 使用马甲包主题色 + return const Color(0xffFF5722); // 马甲包主题橙色,替代Color(0xffFF9500) + } + + @override + Color getStoreItemBuyButtonTextColor() { + // 马甲包策略:Store商品项购买按钮文本颜色 - 使用马甲包主色上的文本颜色 + return ChatVibeTheme.textOnPrimary; // 马甲包主色上的文本颜色,替代Colors.white + } + + @override + String getStoreItemActionIcon(String itemType) { + // 马甲包策略:Store商品项操作图标路径 + // 使用相同的图标路径,确保功能一致 + switch (itemType) { + case 'headdress': + return "atu_images/store/at_icon_shop_item_search.png"; // 头饰页面使用搜索图标 + case 'mountains': + return "atu_images/store/at_icon_shop_item_play.png"; // 山脉页面使用播放图标 + case 'theme': + return "atu_images/store/at_icon_store_theme_rev.png"; // 主题页面使用主题图标 + case 'chatbox': + return "atu_images/store/at_icon_shop_item_search.png"; // 聊天气泡页面使用搜索图标 + default: + return "atu_images/store/at_icon_store_theme_rev.png"; // 默认返回theme图标 + } + } + + /// === 动态页面差异化方法实现 === + + @override + String getDynamicPageBackgroundImage() { + // 马甲包策略:动态页面背景图像路径 - 使用相同图像保持功能一致 + return "atu_images/index/at_icon_index_mask.png"; + } + + @override + Color getDynamicPageScaffoldBackgroundColor() { + // 马甲包策略:动态页面Scaffold背景颜色 - 保持透明以保持视觉效果 + return Colors.transparent; + } + + @override + Color getDynamicPageAppBarBackgroundColor() { + // 马甲包策略:动态页面AppBar背景颜色 - 保持透明以保持视觉效果 + return Colors.transparent; + } + + @override + Color getDynamicPageTabLabelColor() { + // 马甲包策略:动态页面Tab标签选中颜色 - 使用马甲包主要文本颜色 + return ChatVibeTheme.textPrimary; // 马甲包主要文本颜色,替代Colors.black87 + } + + @override + Color getDynamicPageTabUnselectedLabelColor() { + // 马甲包策略:动态页面Tab标签未选中颜色 - 使用马甲包次要文本颜色 + return ChatVibeTheme.textSecondary; // 马甲包次要文本颜色,替代Colors.black38 + } + + @override + Color getDynamicPageTabIndicatorColor() { + // 马甲包策略:动态页面Tab指示器颜色 - 使用马甲包主题色 + return const Color(0xffFF5722); // 马甲包主题橙色,替代Colors.transparent + } + + @override + Color getDynamicPageTabDividerColor() { + // 马甲包策略:动态页面Tab分割线颜色 - 保持透明以保持简洁设计 + return Colors.transparent; + } + + @override + String getDynamicPageAddButtonIcon() { + // 马甲包策略:动态页面添加动态按钮图标路径 - 使用相同图标保持功能一致 + return "atu_images/dynamic/at_icon_add_dynamic_btn.png"; + } + + @override + bool shouldDynamicPageTabScrollable() { + // 马甲包策略:动态页面Tab标签是否可滚动 - 保持相同行为 + return true; + } + + @override + TextStyle getDynamicPageTabLabelStyle() { + // 马甲包策略:动态页面Tab标签选中文本样式 - 使用相同样式但可能应用主题颜色 + // 注意:这里无法使用.sp单位,需要在调用处使用ScreenUtil() + return TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: ChatVibeTheme.textPrimary, // 应用马甲包文本颜色 + ); + } + + @override + TextStyle getDynamicPageTabUnselectedLabelStyle() { + // 马甲包策略:动态页面Tab标签未选中文本样式 - 使用相同样式但可能应用主题颜色 + // 注意:这里无法使用.sp单位,需要在调用处使用ScreenUtil() + return TextStyle( + fontWeight: FontWeight.normal, + fontSize: 15, + color: ChatVibeTheme.textSecondary, // 应用马甲包次要文本颜色 + ); + } + + /// === 启动页面差异化方法实现 === + + @override + String getSplashPageBackgroundImage() { + // 马甲包策略:启动页面背景图像路径 - 使用马甲包专属启动背景图像 + // TODO: 根据Figma设计添加马甲包专属启动背景图像 + // 目前先使用相同图像,确保功能正常 + return "atu_images/splash/at_splash.png"; + } + + @override + String getSplashPageIcon() { + // 马甲包策略:启动页面图标路径 - 使用马甲包专属启动图标 + // TODO: 根据Figma设计添加马甲包专属启动图标 + // 目前先使用相同图标,确保功能正常 + return "atu_images/splash/at_icon_splash_icon.png"; + } + + @override + String getSplashPageSkipButtonBackground() { + // 马甲包策略:启动页面跳过按钮背景图像路径 - 使用马甲包专属跳过按钮背景 + // TODO: 根据Figma设计添加马甲包专属跳过按钮背景图像 + // 目前先使用相同图像,确保功能正常 + return "atu_images/splash/at_icon_splash_skip_bg.png"; + } + + @override + Color getSplashPageSkipButtonTextColor() { + // 马甲包策略:启动页面跳过按钮文本颜色 - 使用马甲包主色上的文本颜色 + return ChatVibeTheme.textOnPrimary; // 马甲包主色上的文本颜色,替代Colors.white + } + + @override + String getSplashPageKingGamesNameBackground() { + // 马甲包策略:启动页面游戏名称背景图像路径 - 使用马甲包专属背景图像 + // TODO: 根据Figma设计添加马甲包专属游戏名称背景图像 + // 目前先使用相同图像,确保功能正常 + return "atu_images/index/at_icon_splash_king_games_name_bg.png"; + } + + @override + Color getSplashPageKingGamesTextColor() { + // 马甲包策略:启动页面游戏名称文本颜色 - 使用马甲包主色上的文本颜色 + return ChatVibeTheme.textOnPrimary; // 马甲包主色上的文本颜色,替代Colors.white + } + + @override + String getSplashPageCpNameBackground() { + // 马甲包策略:启动页面CP名称背景图像路径 - 使用马甲包专属背景图像 + // TODO: 根据Figma设计添加马甲包专属CP名称背景图像 + // 目前先使用相同图像,确保功能正常 + return "atu_images/index/at_icon_splash_cp_name_bg.png"; + } + + @override + Color getSplashPageCpTextColor() { + // 马甲包策略:启动页面CP名称文本颜色 - 使用马甲包主色上的文本颜色 + return ChatVibeTheme.textOnPrimary; // 马甲包主色上的文本颜色,替代Colors.white + } + + /// === WebView页面差异化方法实现 === + + @override + Color getWebViewPageAppBarBackgroundColor() { + // 马甲包策略:WebView页面AppBar背景颜色 - 使用马甲包背景颜色 + return ChatVibeTheme.backgroundColor; // 马甲包背景颜色,替代Colors.white + } + + @override + Color getWebViewPageTitleTextColor() { + // 马甲包策略:WebView页面标题文本颜色 - 使用马甲包主要文本颜色 + return ChatVibeTheme.textPrimary; // 马甲包主要文本颜色,替代Color(0xff333333) + } + + @override + Color getWebViewPageBackArrowColor() { + // 马甲包策略:WebView页面返回箭头图标颜色 - 使用马甲包主要文本颜色 + return ChatVibeTheme.textPrimary; // 马甲包主要文本颜色,替代Color(0xff333333) + } + + @override + Color getWebViewPageProgressBarBackgroundColor() { + // 马甲包策略:WebView页面进度条背景颜色 - 使用透明背景 + return Colors.transparent; // 透明背景,替代Colors.white70.withOpacity(0) + } + + @override + Color getWebViewPageProgressBarActiveColor() { + // 马甲包策略:WebView页面进度条活动颜色 - 使用马甲包主题色 + return ChatVibeTheme.primaryColor; // 马甲包主题色,替代Color(0xffFF6000) + } + + @override + Color getGameWebViewCloseIconColor() { + // 马甲包策略:游戏WebView页面关闭图标颜色 - 使用马甲包主要文本颜色 + return ChatVibeTheme.textPrimary; // 马甲包主要文本颜色,替代Colors.white + } + + @override + Color getGameWebViewScaffoldBackgroundColor() { + // 马甲包策略:游戏WebView页面Scaffold背景颜色 - 使用马甲包背景颜色 + return ChatVibeTheme.backgroundColor; // 马甲包背景颜色,替代Colors.transparent + } + + @override + Color getGameWebViewProgressBarBackgroundColor() { + // 马甲包策略:游戏WebView页面进度条背景颜色 - 使用马甲包背景颜色(透明) + return ChatVibeTheme.backgroundColor.withOpacity(0.3); // 马甲包背景颜色,半透明 + } + + @override + Color getGameWebViewLoadingIndicatorColor() { + // 马甲包策略:游戏WebView页面加载指示器颜色 - 使用马甲包主题色 + return ChatVibeTheme.primaryColor; // 马甲包主题色,替代Color(0xffFF6000) + } + + /// === 搜索页面差异化方法实现 === + + @override + Color getSearchPageScaffoldBackgroundColor() { + // 马甲包策略:搜索页面Scaffold背景颜色 - 使用浅灰色背景 + return Colors.white; // 马甲包浅灰色背景颜色,替代Colors.white + } + + @override + Color getSearchPageBackIconColor() { + // 马甲包策略:搜索页面返回图标颜色 - 使用深灰色 + return const Color(0xff666666); // 深灰色,替代Color(0xffBBBBBB) + } + + @override + Color getSearchPageInputBorderColor() { + // 马甲包策略:搜索页面搜索框边框颜色 - 使用马甲包主题色 + return ChatVibeTheme.transparent; // 马甲包主题色,替代Colors.transparent + } + + @override + Color getSearchPageInputTextColor() { + // 马甲包策略:搜索页面搜索框文本颜色 - 使用马甲包主要文本颜色 + return ChatVibeTheme.textPrimary; // 马甲包主要文本颜色,替代Colors.grey + } + + @override + Color getSearchPageButtonTextColor() { + // 马甲包策略:搜索页面搜索按钮文本颜色 - 使用白色 + return Colors.black; // 马甲包使用白色文本,替代Colors.black + } + + @override + List getSearchPageButtonGradient() { + // 马甲包策略:搜索页面搜索按钮渐变颜色 - 使用马甲包主题色渐变 + return [ + ChatVibeTheme.transparent, + ChatVibeTheme.transparent, + ]; // 马甲包主题色渐变,替代[Colors.transparent, Colors.transparent] + } + + @override + Color getSearchPageHistoryTitleTextColor() { + // 马甲包策略:搜索页面历史记录标题文本颜色 - 使用马甲包主要文本颜色 + return ChatVibeTheme.textPrimary; // 马甲包主要文本颜色,替代Colors.black + } + + @override + Color getSearchPageHistoryItemTextColor() { + // 马甲包策略:搜索页面历史项文本颜色 - 使用白色 + return Colors.white38; // 马甲包使用白色文本,替代Colors.white + } + + @override + Color getSearchPageHistoryItemBackgroundColor() { + // 马甲包策略:搜索页面历史项背景颜色 - 使用马甲包主题色 + return ChatVibeTheme.primaryColor; // 马甲包主题色,替代Color(0xffFF9500) + } + + @override + Color getSearchPageTabIndicatorGradientStartColor() { + // 马甲包策略:搜索页面Tab指示器渐变开始颜色 - 使用马甲包主题色变体 + return ChatVibeTheme.primaryColor; // 马甲包主题色,替代Color(0xffFFA500) + } + + @override + Color getSearchPageTabIndicatorGradientEndColor() { + // 马甲包策略:搜索页面Tab指示器渐变结束颜色 - 使用马甲包主题色变体 + return ChatVibeTheme.primaryLight; // 马甲包浅主题色,替代Color(0xffFFD700) + } + + @override + Color getSearchPageTabSelectedLabelColor() { + // 马甲包策略:搜索页面Tab标签选中颜色 - 使用马甲包主要文本颜色 + return ChatVibeTheme.textPrimary; // 马甲包主要文本颜色,替代Colors.black + } + + @override + Color getSearchPageTabDividerColor() { + // 马甲包策略:搜索页面Tab分割线颜色 - 使用马甲包分割线颜色 + return ChatVibeTheme.transparent; // 马甲包分割线颜色,替代Colors.transparent + } + + @override + Color getSearchPageTabUnselectedLabelColor() { + // 马甲包策略:搜索页面Tab标签未选中颜色 - 使用马甲包次要文本颜色 + return ChatVibeTheme.textSecondary; // 马甲包次要文本颜色,替代Colors.black26 + } + + @override + Color getSearchPageDividerContainerBackgroundColor() { + // 马甲包策略:搜索页面分隔容器背景颜色 - 使用马甲包背景颜色 + return Color(0xffFFF8DA); // 马甲包背景颜色,替代Color(0xffFFF8DA) + } + + @override + TextStyle getSearchPageTabSelectedLabelStyle() { + // 马甲包策略:搜索页面Tab标签选中文本样式 - 使用马甲包样式 + // 注意:这里无法使用.sp单位,需要在调用处使用ScreenUtil() + return const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Colors.black, // 将在调用处替换为马甲包主要文本颜色 + ).copyWith(color: ChatVibeTheme.textPrimary); + } + + @override + TextStyle getSearchPageTabUnselectedLabelStyle() { + // 马甲包策略:搜索页面Tab标签未选中文本样式 - 使用马甲包样式 + // 注意:这里无法使用.sp单位,需要在调用处使用ScreenUtil() + return const TextStyle( + fontSize: 17, + fontWeight: FontWeight.w400, + color: Colors.black26, // 将在调用处替换为马甲包次要文本颜色 + ).copyWith(color: ChatVibeTheme.textSecondary); + } + + @override + EdgeInsets getSearchPageResultTabLabelPadding() { + // 马甲包策略:搜索页面搜索结果Tab标签内边距 - 与原始应用相同 + return const EdgeInsets.symmetric(horizontal: 15); + } + + @override + Color getSearchPageResultTabLabelColor() { + // 马甲包策略:搜索页面搜索结果Tab标签选中颜色 - 使用马甲包主要文本颜色 + return ChatVibeTheme.textPrimary; // 马甲包主要文本颜色,替代Colors.black + } + + @override + Color getSearchPageResultTabUnselectedLabelColor() { + // 马甲包策略:搜索页面搜索结果Tab标签未选中颜色 - 使用马甲包次要文本颜色 + return ChatVibeTheme.textSecondary; // 马甲包次要文本颜色,替代Colors.black26 + } + + @override + TextStyle getSearchPageResultTabUnselectedLabelStyle() { + // 马甲包策略:搜索页面搜索结果Tab标签未选中文本样式 - 使用马甲包样式 + return const TextStyle( + fontSize: 17, + fontWeight: FontWeight.w400, + color: Colors.black26, // 将在调用处替换为马甲包次要文本颜色 + ).copyWith(color: ChatVibeTheme.textSecondary); + } + + @override + TextStyle getSearchPageResultTabLabelStyle() { + // 马甲包策略:搜索页面搜索结果Tab标签选中文本样式 - 使用马甲包样式 + return const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Colors.black87, // 将在调用处替换为马甲包主要文本颜色 + ).copyWith(color: ChatVibeTheme.textPrimary); + } + + @override + LinearGradient getSearchPageResultTabIndicatorColor() { + // 马甲包策略:搜索页面搜索结果Tab指示器颜色 - 使用马甲包主题渐变 + return const LinearGradient( + colors: [ChatVibeTheme.primaryColor, Color(0xffFF9A00)], + ); + } + + @override + double getSearchPageResultTabIndicatorWidth() { + // 马甲包策略:搜索页面搜索结果Tab指示器宽度 - 与原始应用相同 + return 15.0; + } + + @override + Color getSearchPageResultBackgroundColor() { + // 马甲包策略:搜索页面搜索结果背景颜色 - 使用浅橙色背景 + return const Color(0xffFFECB3); // 马甲包浅橙色背景,替代Color(0xffFFF8DA) + } + + @override + String getSearchPageClearHistoryIcon() { + // 马甲包策略:搜索页面清除历史记录图标路径 - 使用相同图标 + return "atu_images/general/at_icon_delete.png"; + } + + @override + String getSearchPageEmptyDataIcon() { + // 马甲包策略:搜索页面空数据图标路径 - 使用相同图标 + return "atu_images/room/at_icon_room_defaut_bg.png"; + } + + // === 任务页面差异化方法 === + + @override + String getTaskPageBackgroundImage() { + // 马甲包策略:任务页面背景图像 - 使用相同图片 + return "atu_images/index/at_icon_task_list_bg.png"; + } + + @override + Color getTaskPageBackButtonColor() { + // 马甲包策略:任务页面返回按钮颜色 - 使用马甲包主要文本颜色 + return Color(0xffE3B769); // 马甲包主要文本颜色,替代Colors.black + } + + @override + Color getTaskPageScaffoldBackgroundColor() { + // 马甲包策略:任务页面Scaffold背景颜色 - 使用马甲包背景颜色 + return const Color(0xfff5f5f5); // 马甲包背景颜色 + } + + @override + Color getTaskPageContainerBackgroundColor() { + // 马甲包策略:任务页面容器背景颜色 - 使用马甲包表面颜色 + return ChatVibeTheme.surfaceColor; // 马甲包表面颜色,替代Color(0xffE6E6E6) + } + + @override + Color getTaskPageBorderColor() { + // 马甲包策略:任务页面边框颜色 - 使用马甲包边框颜色 + return ChatVibeTheme.borderColor; // 马甲包边框颜色,替代Color(0xffE6E6E6) + } + + @override + Color getTaskPageSpecialBorderColor() { + // 马甲包策略:任务页面特殊边框颜色 - 使用马甲包主题色 + return ChatVibeTheme.primaryColor; // 马甲包主题色,替代Color(0xffBB92FF) + } + + @override + Color getTaskPagePrimaryTextColor() { + // 马甲包策略:任务页面主要文本颜色 - 使用马甲包主要文本颜色 + return Color(0xffE3B769); // 马甲包主要文本颜色,替代Colors.black + } + + @override + Color getTaskPageGoldTextColor() { + // 马甲包策略:任务页面金币文本颜色 - 使用马甲包主题色 + return Color(0xffF3C892); // 马甲包主题色,替代Color(0xffFFB627) + } + + @override + Color getTaskPageExpTextColor() { + // 马甲包策略:任务页面经验文本颜色 - 使用马甲包成功颜色 + return Color(0xffF3C892); // 马甲包成功颜色,替代Color(0xff52FF90) + } + + @override + Color getTaskPageThemeColor() { + // 马甲包策略:任务页面主题颜色 - 使用马甲包主题色 + return Color(0xffF3C892); // 马甲包主题色,替代Color(0xffFF5722) + } + + @override + Color getTaskPageThemeLightColor() { + // 马甲包策略:任务页面浅主题颜色 - 使用马甲包浅主题色 + return Color(0xff361C06); // 马甲包浅主题色,替代Color(0xffFF9500) + } + + @override + List getTaskPageReceivableButtonGradient() { + // 马甲包策略:任务页面可领取按钮渐变颜色 - 使用马甲包主题色渐变 + return [ + ChatVibeTheme.primaryColor, + const Color(0xffFF9A00), + ]; // 马甲包主题色渐变,替代[Color(0xffBB92FF), Color(0xff8B45FF)] + } + + @override + List getTaskPageCompletedButtonGradient() { + // 马甲包策略:任务页面已完成按钮渐变颜色 - 使用灰色渐变 + return [ + const Color(0xffCCCCCC), + const Color(0xff999999), + ]; // 马甲包灰色渐变,替代[Color(0xffDADADA), Color(0xff585859)] + } + + @override + Color getTaskPageButtonTextColor() { + // 马甲包策略:任务页面按钮文本颜色 - 使用马甲包主色上的文本颜色 + return Color(0xff361C06); // 马甲包主色上的文本颜色,替代Colors.white + } + + @override + Color getTaskPageMaskColor() { + // 马甲包策略:任务页面遮罩颜色 - 半透明黑色 + return Colors.black.withOpacity(0.5); + } + + @override + String getTaskPageHeadBackgroundImage() { + // 马甲包策略:任务页面头部背景图像 - 使用相同图片 + return "atu_images/index/at_icon_task_head_bg.png"; + } + + @override + String getTaskPageGoldIcon() { + // 马甲包策略:任务页面金币图标 - 使用相同图标 + return "atu_images/general/at_icon_jb.png"; + } + + @override + String getTaskPageExpIcon() { + // 马甲包策略:任务页面经验图标 - 使用相同图标 + return "atu_images/index/at_icon_task_exp.png"; + } + + @override + String getTaskPageInvitationRewardBackgroundImage() { + // 马甲包策略:任务页面邀请奖励背景图像 - 使用相同图片 + return "atu_images/index/at_icon_invitation_bg.png"; + } + + @override + Color getTaskPageGiftBagTextColor() { + // 马甲包策略:任务页面礼包文本颜色 - 使用马甲包主色上的文本颜色 + return ChatVibeTheme.textOnPrimary; // 马甲包主色上的文本颜色,替代Colors.white + } + + @override + Color getTaskPageAppBarBackgroundColor() { + // 马甲包策略:任务页面AppBar背景颜色 - 保持透明,与原始应用一致 + return Colors.transparent; + } + + @override + Color getTaskPageTransparentContainerColor() { + // 马甲包策略:任务页面透明容器颜色 - 保持透明,与原始应用一致 + return Colors.transparent; + } + + /// === 设置页面差异化方法实现 === + + @override + String getSettingsPageBackgroundImage() { + // 马甲包策略:设置页面背景图像路径 - 使用相同图片 + return "atu_images/person/at_icon_edit_userinfo_bg.png"; + } + + @override + Color getSettingsPageMainContainerBackgroundColor() { + // 马甲包策略:设置页面主容器背景颜色 - 使用马甲包背景色 + return Colors.white; // 马甲包背景色,与原始应用一致 + } + + @override + Color getSettingsPageMainContainerBorderColor() { + // 马甲包策略:设置页面主容器边框颜色 - 使用马甲包边框色 + return ChatVibeTheme.borderColor; // 马甲包边框色,替代Color(0xffE6E6E6) + } + + @override + Color getSettingsPagePrimaryTextColor() { + // 马甲包策略:设置页面主文本颜色 - 使用马甲包主要文本颜色 + return ChatVibeTheme.textPrimary; // 马甲包主要文本颜色,替代Colors.black + } + + @override + Color getSettingsPageSecondaryTextColor() { + // 马甲包策略:设置页面次要文本颜色 - 使用马甲包次要文本颜色 + return ChatVibeTheme.textSecondary; // 马甲包次要文本颜色,替代Colors.black38 + } + + @override + Color getSettingsPageIconColor() { + // 马甲包策略:设置页面图标颜色 - 使用马甲包次要文本颜色 + return ChatVibeTheme.textSecondary; // 马甲包次要文本颜色,替代Colors.black26 + } + + @override + Color getSettingsPageCommonTextColor() { + // 马甲包策略:设置页面常见文本颜色 - 使用深灰色 + return const Color(0xff666666); + } + + @override + Color getSettingsPageAboutTextColor() { + // 马甲包策略:设置页面关于文本颜色 - 使用深灰色 + return const Color(0xff666666); + } + + @override + Color getSettingsPageContainerBorderColor() { + // 马甲包策略:设置页面容器边框颜色 - 使用浅灰色 + return const Color(0xffCCCCCC); + } + + @override + Color getSettingsPageContainerBackgroundColor() { + // 马甲包策略:设置页面容器背景颜色 - 使用白色(与原始应用相同) + return Colors.white; + } + + @override + Color getSettingsPageButtonBackgroundColor() { + // 马甲包策略:设置页面按钮背景颜色 - 使用马甲包主题色 + return const Color(0xffBBBBBB); // 马甲包主题色 + } + + @override + Color getSettingsPageButtonTextColor() { + // 马甲包策略:设置页面按钮文本颜色 - 使用白色 + return Colors.white; + } + + /// === 语言页面差异化方法实现 === + + @override + String getLanguagePageBackgroundImage() { + // 马甲包策略:语言页面背景图像路径 - 使用相同图片 + return "atu_images/person/at_icon_edit_userinfo_bg.png"; + } + + @override + Color getLanguagePageCheckboxActiveColor() { + // 马甲包策略:语言页面复选框选中颜色 - 使用马甲包主题色 + return ChatVibeTheme.primaryColor; // 马甲包主题色,替代Color(0xffFF5722) + } + + @override + Color getLanguagePageCheckboxBorderColor() { + // 马甲包策略:语言页面复选框边框颜色 - 使用马甲包边框色 + return ChatVibeTheme.borderColor; // 马甲包边框色,替代Colors.grey + } + + @override + Color getLanguagePagePrimaryTextColor() { + // 马甲包策略:语言页面主文本颜色 - 使用马甲包主要文本颜色 + return ChatVibeTheme.textPrimary; // 马甲包主要文本颜色,替代Colors.black + } + + @override + Color getLanguagePageCheckColor() { + // 马甲包策略:语言页面复选框对勾颜色 - 使用透明色 + return Colors.transparent; + } + + @override + double getLanguagePageCheckboxBorderRadius() { + // 马甲包策略:语言页面复选框边框圆角 - 使用相同的圆角值15 + return 15; + } + + @override + double getLanguagePageCheckboxBorderWidth() { + // 马甲包策略:语言页面复选框边框宽度 - 使用相同的边框宽度2 + return 2; + } + + @override + Color getLanguagePageScaffoldBackgroundColor() { + // 马甲包策略:语言页面Scaffold背景颜色 - 使用透明色 + return Colors.transparent; + } + + /// === 账户页面差异化方法实现 === + + @override + String getAccountPageBackgroundImage() { + // 马甲包策略:账户页面背景图像路径 - 使用相同图片 + return "atu_images/person/at_icon_edit_userinfo_bg.png"; + } + + @override + Color getAccountPageMainContainerBackgroundColor() { + // 马甲包策略:账户页面主容器背景颜色 - 使用马甲包背景色 + return Colors.white; // 马甲包背景色,与原始应用一致 + } + + @override + Color getAccountPageMainContainerBorderColor() { + // 马甲包策略:账户页面主容器边框颜色 - 使用马甲包边框色 + return const Color(0xffE0E0E0); // ChatVibeTheme.borderColor + } + + @override + Color getAccountPagePrimaryTextColor() { + // 马甲包策略:账户页面主文本颜色 - 使用马甲包主要文本颜色 + return ChatVibeTheme.textPrimary; // 马甲包主要文本颜色,替代Colors.black + } + + @override + Color getAccountPageSecondaryTextColor() { + // 马甲包策略:账户页面次要文本颜色 - 使用马甲包次要文本颜色 + return ChatVibeTheme.textSecondary; // 马甲包次要文本颜色,替代Colors.black38 + } + + @override + Color getAccountPageIconColor() { + // 马甲包策略:账户页面图标颜色 - 使用马甲包次要文本颜色 + return ChatVibeTheme.textSecondary; // 马甲包次要文本颜色,替代Colors.black26 + } + + @override + Color getAccountPageDividerColor() { + // 马甲包策略:账户页面分隔线颜色 - 使用马甲包边框颜色 + return ChatVibeTheme.borderColor; // 马甲包边框颜色,替代Colors.black12 + } + + @override + Color getAccountPageScaffoldBackgroundColor() { + // 马甲包策略:账户页面Scaffold背景颜色 - 使用透明色 + return Colors.transparent; + } + + @override + Color getGoldRecordPageListBackgroundColor() { + // 马甲包策略:金币记录页面列表背景颜色 - 深灰色 + return Colors.transparent; // #1a1a1a + } +} diff --git a/lib/chatvibe_core/constants/at_app_colors.dart b/lib/chatvibe_core/constants/at_app_colors.dart new file mode 100644 index 0000000..ed506ee --- /dev/null +++ b/lib/chatvibe_core/constants/at_app_colors.dart @@ -0,0 +1,40 @@ +import 'dart:ui'; + +import 'package:flutter/cupertino.dart'; +import 'package:aslan/chatvibe_core/config/app_config.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; + +/// 主题颜色配置(兼容层) + +///主题色 - 从AppConfig获取主色 +@deprecated +Color get themeColor => ChatVibeTheme.primaryColor; + +/// 次要主题色 - 基于主色的浅色版本 +@deprecated +Color get themeColor2 => ChatVibeTheme.primaryLight; + +/// 欢迎页面颜色 +@deprecated +const int welcomeColorValue = 0xFFD0D3B5; +@deprecated +const Color color999 = Color(0xFF999999); + +/// 主题色值(兼容旧代码) +@deprecated +int get THEME_COLOR_VALUE => ChatVibeTheme.primaryColor.value; +@deprecated +const int WELCOME_COLOR_VALUE = 0xFFD0D3B5; + +/// 分隔线颜色 - 使用ChatVibe主题系统的颜色 +@deprecated +Color get dividerColor => ChatVibeTheme.dividerColor; + +@deprecated +LinearGradient getLinearGradient(List colorValues, {bool vertical = false, double opacity = 1}){ + if(vertical){ + return LinearGradient(colors: colorValues.map((e) => Color(e).withOpacity(opacity)).toList(), begin: Alignment.topCenter, end: Alignment.bottomCenter); + } else{ + return LinearGradient(colors: colorValues.map((e) => Color(e).withOpacity(opacity)).toList(), begin: Alignment.centerLeft, end: Alignment.centerRight); + } +} \ No newline at end of file diff --git a/lib/chatvibe_core/constants/at_emoji_datas.dart b/lib/chatvibe_core/constants/at_emoji_datas.dart new file mode 100644 index 0000000..62ea5af --- /dev/null +++ b/lib/chatvibe_core/constants/at_emoji_datas.dart @@ -0,0 +1,34 @@ +class ATEmojiDatas{ + static const List smileys = [ + '😀', '😃', '😄', '😁', '😆', '😅', '😂', '🤣', '😊', '😇', + '🙂', '🙃', '😉', '😌', '😍', '🥰', '😘', '😗', '😙', '😚', + '😋', '😛', '😝', '😜', '🤪', '🤨', '🧐', '🤓', '😎', '🤩', + '🥳', '😏', '😒', '😞', '😔', '😟', '😕', '🙁', '☹️', '😣', + '😖', '😫', '😩', '🥺', '😢', '😭', '😤', '😠', '😡', '🤬', + '🤯', '😳', '🥵', '🥶', '😱', '😨', '😰', '😥', '😓', '🤗', + '🤔', '🤭', '🤫', '🤥', '😶', '😐', '😑', '😬', '🙄', '😯', + '😦', '😧', '😮', '😲', '🥱', '😴', '🤤', '😪', '😵', '🤐', + '🥴', '🤢', '🤮', '🤧', '😷', '🤒', '🤕', '🤑', '🤠', '😈', + '👿', '👹', '👺', '🤡', '💩', '👻', '💀', '☠️', '👽', '👾', + '🤖', '🎃', '😺', '😸', '😹', '😻', '😼', '😽', '🙀', '😿', + '😾','🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼', '🐨', '🐯', + '🦁', '🐮', '🐷', '🐽', '🐸', '🐵', '🙈', '🙉', '🙊', '🐒', + '🐔', '🐧', '🐦', '🐤', '🐣', '🐥', '🦆', '🦅', '🦉', '🦇', + '🐺', '🐗', '🐴', '🦄', '🐝', '🐛', '🦋', '🐌', '🐞', '🐜', + '🦟', '🦗', '🕷️', '🕸️', '🦂', '🐢', '🐍', '🦎', '🦖', '🦕', + '🐙', '🦑', '🦐', '🦞', '🦀', '🐡', '🐠', '🐟', '🐬', '🐳', + '🐋', '🦈', '🐊', '🐅', '🐆', '🦓', '🦍', '🦧', '🐘', + '🦛', '🦏', '🐪', '🐫', '🦒', '🦘', '🐃', '🐂', '🐄', + '🐎', '🐖', '🐏', '🐑', '🦙', '🐐', '🦌', '🐕', '🐩', '🦮', + '🐕‍🦺', '🐈', '🐓', '🦃', '🦚', '🦜', '🦢', + '🦩', '🕊️', '🐇', '🦝', '🦨', '🦡', '🦦', '🦥', '🐁', + '🐀', '🐿️', '🦔', '🐾', '🐉', '🐲', '🥬', '🥒', '🌶️', '🌽', '🥕', '🧄', '🧅', '🥔', + '🍠', '🥐', '🥯', '🍞', '🥖', '🥨', '🧀', '🥚', '🍳', '🧈', + '🥞', '🧇', '🥓', '🥩', '🍗', '🍖', '🦴', '🌭', '🍔', '🍟', + '🍕', '🥪', '🥙', '🧆', '🌮', '🌯', '🥗', '🥘', + '🥫', '🍝', '🍜', '🍲', '🍛', '🍣', '🍱', '🥟', '🦪', '🏍️', '🛺', '🚨', '🚔', '🚍', '🚘', '🚖', '🚡', '🚠', '🚟', + '🚃', '🚋', '🚞', '🚝', '🚄', '🚅', '🚈', '🚂', '🚆', '🚇', + '🚊', '🚉', '✈️', '🛫', '🛬', '🛩️', '💺', '🛰️', '🚀', '🛸', '⚽', '🏀', '🏈', '⚾', '🥎', '🎾', '🏐', '🏉', '🥏', '🎱', + '🪀', '🏓', '🏸', '🏒', '🏑', '🥍', '🏏', + ]; +} \ No newline at end of file diff --git a/lib/chatvibe_core/constants/at_global_config.dart b/lib/chatvibe_core/constants/at_global_config.dart new file mode 100644 index 0000000..31c22ef --- /dev/null +++ b/lib/chatvibe_core/constants/at_global_config.dart @@ -0,0 +1,102 @@ +import 'package:aslan/chatvibe_core/config/app_config.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; + +class ATGlobalConfig { + static String get apiHost => AppConfig.current.apiHost; + static String get imgHost => AppConfig.current.imgHost; + static String get privacyAgreementUrl => AppConfig.current.privacyAgreementUrl; + static String get userAgreementUrl => AppConfig.current.userAgreementUrl; + + // 以下URL从AppConfig获取 + static String get anchorAgentUrl => AppConfig.current.anchorAgentUrl; + static String get hostCenterUrl => AppConfig.current.hostCenterUrl; + static String get bdCenterUrl => AppConfig.current.bdCenterUrl; + static String get bdLeaderUrl => AppConfig.current.bdLeaderUrl; + static String get coinSellerUrl => AppConfig.current.coinSellerUrl; + static String get adminUrl => AppConfig.current.adminUrl; + static String get agencyCenterUrl => AppConfig.current.agencyCenterUrl; + static String get gamesKingUrl => AppConfig.current.gamesKingUrl; + static String get rechargeUrl => AppConfig.current.rechargeUrl; + static String get cpRewardUrl => AppConfig.current.cpRewardUrl; + + // 应用下载链接需要根据Flavor调整 + static String get appDownloadUrlGoogle => AppConfig.current.appDownloadUrlGoogle; + static String get appDownloadUrlApple => AppConfig.current.appDownloadUrlApple; + + + static String get payApplicationId => AppConfig.current.payApplicationId; + + ///语言 + static String lang = "en"; + + ///设备号 + static String imei = ""; + + ///版本 + static String version = "1.0.0"; + static String build = "1"; + static String model = "SM-G9550"; + static String sysVersion = "9"; + static int sdkInt = 28; + + ///高于这个值才播放特效,避免低性能手机卡顿 + static int maxSdkNoAnim = 27; + + ///渠道ChatVibe + static String channel = "Google"; + static String origin = "ATYOU"; + static String originChild = "ATYOU"; + + static String get tencentImAppid => AppConfig.current.tencentImAppid; + static String get agoraRtcAppid => AppConfig.current.agoraRtcAppid; + + static num get gameAppid => AppConfig.current.gameAppid; + static String get gameAppChannel => AppConfig.current.gameAppChannel; + + ///全服广播大群 + static String get bigBroadcastGroup => AppConfig.current.bigBroadcastGroup; + + static String get imAdmin => AppConfig.current.imAdmin; + + ///财富榜单 + static String get wealthRankUrl => AppConfig.current.wealthRankUrl; + + ///魅力榜 + static String get charmRankUrl => AppConfig.current.charmRankUrl; + + ///房间榜单 + static String get roomRankUrl => AppConfig.current.roomRankUrl; + + ///邀请新用户活动链接 + static String get inviteNewUserUrl => AppConfig.current.inviteNewUserUrl; + + ///是否在审核 + static bool? _isReviewOverride; + static bool get isReview => _isReviewOverride ?? AppConfig.current.isReview; + static set isReview(bool value) => _isReviewOverride = value; + + ///礼物特效开关 + static bool _isGiftSpecialEffects = true; + static bool get isGiftSpecialEffects => _isGiftSpecialEffects; + static set isGiftSpecialEffects(bool value) => _isGiftSpecialEffects = value; + + ///入场秀 + static bool _isEntryVehicleAnimation = true; + static bool get isEntryVehicleAnimation => _isEntryVehicleAnimation; + static set isEntryVehicleAnimation(bool value) => _isEntryVehicleAnimation = value; + + ///全局飘屏 + static bool _isFloatingAnimationInGlobal = true; + static bool get isFloatingAnimationInGlobal => _isFloatingAnimationInGlobal; + static set isFloatingAnimationInGlobal(bool value) => _isFloatingAnimationInGlobal = value; + + ///幸运礼物特效开关 + static bool _isLuckGiftSpecialEffects = true; + static bool get isLuckGiftSpecialEffects => _isLuckGiftSpecialEffects; + static set isLuckGiftSpecialEffects(bool value) => _isLuckGiftSpecialEffects = value; + + /// 获取当前业务逻辑策略 + static BusinessLogicStrategy get businessLogicStrategy { + return AppConfig.current.businessLogicStrategy; + } +} diff --git a/lib/chatvibe_core/constants/at_room_msg_type.dart b/lib/chatvibe_core/constants/at_room_msg_type.dart new file mode 100644 index 0000000..0c66efb --- /dev/null +++ b/lib/chatvibe_core/constants/at_room_msg_type.dart @@ -0,0 +1,113 @@ +class ATRoomMsgType { + static const String text = "TEXT"; + static const String image = "IMAGE"; + static const String joinRoom = "JOIN_ROOM"; + static const String systemTips = "SYSTEM_TIPS"; + + ///表情包 + static const String emoticons = "EMOTICONS"; + + + ///新用户 + static const String newUser = "NEW_USER"; + + ///上麦 + static const String shangMai = "SHANG_MAI"; + + ///下麦 + static const String xiaMai = "XIA_MAI"; + + ///踢下麦 + static const String killXiaMai = "KILL_XIA_MAI"; + + ///请出房间 + static const String qcfj = "QCFJ"; + + ///封麦 + static const String fengMai = "FENG_MAI"; + + ///解封 + static const String jieFeng = "JIE_FENG"; + + ///禁麦 + static const String jinMai = "JIN_MAI"; + + ///解禁 + static const String jieJin = "JIE_JIN"; + + ///送礼 + static const String gift = "GIFT"; + + ///幸运礼物飘向麦位 + static const String luckGiftAnimOther = "LUCK_GIFT_ANIM_OTHER"; + + ///送礼 + static const String allGift = "ALL_GIFT"; + + ///收到礼物 + static const String reciverGift = "RECIVER_GIFT"; + + ///抱上麦 + static const String bsm = "BSM"; + + ///欢迎语 + static const String welcome = "WELCOME"; + + ///禁言变动 + static const String jinyanList = "JINYAN_LIST"; + + ///麦位变动 + static const String micChange = "MIC_CHANGE"; + + ///房间身份变动 + static const String roomRoleChange = "ROOM_ROLE_CHANGE"; + + ///火箭进度条更新 + static const String rocketEnergyUpdate = "ROCKET_ENERGY_UPDATE"; + + ///房间红包 + static const String roomRedPacket = "ROOM_RED_PACKET"; + + ///邀请进入房间 + static const String inviteRoom = "INVITE_ROOM"; + + ///用户火箭中奖 + static const String rocketRewardUser = "ROCKET_REWARD_USER"; + + ///管理变动 + static const String managerList = "MANAGER_LIST"; + + ///房间设置更新 + static const String roomSettingUpdate = "ROOM_SETTING_UPDATE"; + + ///掷骰子 + static const String roomDice = "ROOM_DICE"; + + ///石头剪刀布 + static const String roomRPS = "ROOM_RPS"; + + ///幸运数字 + static const String roomLuckNumber = "ROOM_LUCK_NUMBER"; + + ///房间背景更新 + static const String roomBGUpdate = "ROOM_BG_UPDATE"; + + ///幸运礼物 + static const String gameLuckyGift = "GAME_LUCKY_GIFT"; + + ///幸运礼物中奖5倍以及以上 + static const String gameLuckyGift_5 = "GAME_LUCKY_GIFT_5"; + + ///房间多人游戏关闭 + static const String roomGameClose = "ROOM_GAME_CLOSE"; + + ///房间游戏创建 + static const String roomGameCreate = "ROOM_GAME_CREATE"; + + ///暂时不用监听这个 + static const String sendGift = "SEND_GIFT"; + static const String gameBurstCrystalSprint = "GAME_BURST_CRYSTAL_SPRINT"; + static const String gameBurstCrystalBox = "GAME_BURST_CRYSTAL_BOX"; + static const String refreshOnlineUser = "REFRESH_ONLINE_USER"; + +} diff --git a/lib/chatvibe_core/constants/at_screen.dart b/lib/chatvibe_core/constants/at_screen.dart new file mode 100644 index 0000000..ba0618a --- /dev/null +++ b/lib/chatvibe_core/constants/at_screen.dart @@ -0,0 +1,19 @@ +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class ATScreen{ + static double designWidth = 375; + static double designHeight = 667; + static double width = ScreenUtil().setWidth(designWidth); + static double height = ScreenUtil().setHeight(designHeight); +} + +double width(double width) { + return ScreenUtil().setWidth(width); +} + +double height(double height) { + return ScreenUtil().setHeight(height); +} +double sp(double sp) { + return ScreenUtil().setSp(sp); +} \ No newline at end of file diff --git a/lib/chatvibe_core/routes/at_404_page.dart b/lib/chatvibe_core/routes/at_404_page.dart new file mode 100644 index 0000000..8057b75 --- /dev/null +++ b/lib/chatvibe_core/routes/at_404_page.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class ATWidgetNotFound extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('页面不存在'), + ), + body: Center( + child: Text('页面不存在'), + ), + ); + } +} diff --git a/lib/chatvibe_core/routes/at_fluro_navigator.dart b/lib/chatvibe_core/routes/at_fluro_navigator.dart new file mode 100644 index 0000000..7002bdb --- /dev/null +++ b/lib/chatvibe_core/routes/at_fluro_navigator.dart @@ -0,0 +1,148 @@ +import 'dart:io'; + +import 'package:fluro/fluro.dart'; +import 'package:flutter/material.dart'; +import 'package:aslan/chatvibe_data/sources/remote/net/network_client.dart'; +import 'package:aslan/chatvibe_features/chat/at_chat_route.dart'; +import 'package:aslan/chatvibe_features/auth/login_route.dart'; +import 'package:aslan/chatvibe_core/routes/at_lk_application.dart'; + +/// 安卓页面切换时长 +const Duration kAndroidTransitionDuration = Duration(milliseconds: 375); + +/// 进入房间页动画时长 +const Duration kOpenRoomTransitionDuration = Duration(milliseconds: 325); + + +/// 进入房间页过渡动画 +SlideTransition kOpenRoomTransitionBuilder( BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child,) { + return SlideTransition( + position: Tween( + begin: const Offset(0.0, 1.0), + end: const Offset(0.0, 0.0), + ).animate(CurvedAnimation(parent: animation, curve: Curves.ease)), + child: child, + ); +} + +/// fluro的路由跳转工具类 +class ATNavigatorUtils { + static bool inChatPage = false; + //不需要页面返回值的跳转 + static Future push(BuildContext context, String path, + {bool replace = false, + bool clearStack = false, + TransitionType? transition, + Duration? transitionDuration, + RouteTransitionsBuilder? transitionBuilder}) async { + FocusScope.of(context).unfocus(); + inLoginPage = path.startsWith("${LoginRouter.login}"); + inChatPage = path.startsWith("${ATChatRouter.chat}"); + final result = await ATLkApplication.router.navigateTo(context, path, + replace: replace, + clearStack: clearStack, + ///在系统语言为ar语会有问题 + transition: (Platform.isAndroid + ? null + : TransitionType.cupertino), + // transitionDuration: transitionDuration ?? + // (Platform.isAndroid ? kAndroidTransitionDuration : null), + // transitionBuilder: transitionBuilder ?? + // (Platform.isAndroid ? (context, animation, secondaryAnimation, child) { + // return ScaleTransition( + // scale: Tween(begin: 1.0,end: 0.9).animate(CurvedAnimation(parent: secondaryAnimation, curve: Curves.ease)), + // child: SlideTransition( + // position: Tween( + // begin: const Offset(1.0, 0.0), + // end: const Offset(0.0, 0.0), + // ).animate(CurvedAnimation(parent: animation, curve: Curves.ease)), + // child: child, + // ), + // ); + // } : null) + ); + + return result; + } + + //需要页面返回值的跳转 + static pushResult( + BuildContext context, String path, Function(Object) function, + {bool replace = false, + bool clearStack = false, + TransitionType? transition}) { + FocusScope.of(context).unfocus(); + inLoginPage = path == LoginRouter.login; + inChatPage = path.startsWith("${ATChatRouter.chat}"); + ATLkApplication.router + .navigateTo(context, path, + replace: replace, + clearStack: clearStack, + // transition: transition ?? + // (Platform.isAndroid + // ? TransitionType.custom + // : TransitionType.cupertino), + // transitionDuration: + // Platform.isAndroid ? kAndroidTransitionDuration : null, + // transitionBuilder: + // Platform.isAndroid ? (context, animation, secondaryAnimation, child) { + // return ScaleTransition( + // scale: Tween(begin: 1.0,end: 0.9).animate(CurvedAnimation(parent: secondaryAnimation, curve: Curves.ease)), + // child: SlideTransition( + // position: Tween( + // begin: const Offset(1.0, 0.0), + // end: const Offset(0.0, 0.0), + // ).animate(CurvedAnimation(parent: animation, curve: Curves.ease)), + // child: child, + // ), + // ); + // } : null + ) + .then((result) { + // 页面返回result为null + if (result == null) { + return; + } + function(result); + }).catchError((error) { + print('$error'); + }); + } + + /// 直接返回 + static void goBack(BuildContext context) { + if(inChatPage){ + inChatPage = false; + } + FocusScope.of(context).unfocus(); + Navigator.pop(context); + } + + /// 带参数返回 + static void goBackWithParams(BuildContext context, result) { + if(inChatPage){ + inChatPage = false; + } + FocusScope.of(context).unfocus(); + Navigator.pop(context, result); + } + + static void popUntil(BuildContext context, RoutePredicate predicate) { + if(inChatPage){ + inChatPage = false; + } + FocusScope.of(context).unfocus(); + Navigator.popUntil(context, predicate); + } + + static void removeRouteBelowCurrent(BuildContext context) { + final route = ModalRoute.of(context); + if (route == null) return; + try { + Navigator.of(context).removeRouteBelow(route); + } catch (_) {} + } +} diff --git a/lib/chatvibe_core/routes/at_lk_application.dart b/lib/chatvibe_core/routes/at_lk_application.dart new file mode 100644 index 0000000..75b87dc --- /dev/null +++ b/lib/chatvibe_core/routes/at_lk_application.dart @@ -0,0 +1,5 @@ +import 'package:fluro/fluro.dart' as fluro; + +class ATLkApplication { + static fluro.FluroRouter router = fluro.FluroRouter(); +} diff --git a/lib/chatvibe_core/routes/at_router_init.dart b/lib/chatvibe_core/routes/at_router_init.dart new file mode 100644 index 0000000..6da5666 --- /dev/null +++ b/lib/chatvibe_core/routes/at_router_init.dart @@ -0,0 +1,7 @@ + +import 'package:fluro/fluro.dart'; + +abstract class ATIRouterProvider{ + + void initRouter(FluroRouter router); +} \ No newline at end of file diff --git a/lib/chatvibe_core/routes/at_routes.dart b/lib/chatvibe_core/routes/at_routes.dart new file mode 100644 index 0000000..3275155 --- /dev/null +++ b/lib/chatvibe_core/routes/at_routes.dart @@ -0,0 +1,81 @@ +import 'package:fluro/fluro.dart' as fluro; +import 'package:flutter/material.dart'; +import 'package:aslan/chatvibe_core/routes/at_router_init.dart'; +import 'package:aslan/chatvibe_features/country/country_route.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_features/chat/at_chat_route.dart'; +import 'package:aslan/chatvibe_features/family/family_route.dart'; +import 'package:aslan/chatvibe_features/dynamic/dynamic_route.dart'; +import 'package:aslan/chatvibe_features/home/coupon/coupon_route.dart'; +import 'package:aslan/chatvibe_features/store/store_route.dart'; +import 'package:aslan/chatvibe_features/index/index_page.dart'; +import 'package:aslan/chatvibe_features/user/settings/settings_route.dart'; +import 'package:aslan/chatvibe_features/wallet/wallet_route.dart'; +import 'package:aslan/chatvibe_features/auth/login_route.dart'; +import 'package:aslan/chatvibe_features/room/voice_room_route.dart'; +import 'package:aslan/chatvibe_core/routes/at_404_page.dart'; + +class ATRoutes { + static String home = '/'; + + static List _listRouter = []; + + static void configureRoutes(fluro.FluroRouter router) { + /// 指定路由跳转错误返回页 + router.notFoundHandler = fluro.Handler(handlerFunc: (BuildContext? context, Map> params) { + debugPrint('未找到目标页'); + return ATWidgetNotFound(); + }); + + router.define(home, + handler: fluro.Handler(handlerFunc: (BuildContext? context, Map> params) => ATIndexPage())); + + _listRouter.clear(); + + /// 各自路由由各自模块管理,统一在此添加初始化 + + /// 登陆路由组 + _listRouter.add(LoginRouter()); + _listRouter.add(CountryRoute()); + _listRouter.add(SettingsRoute()); + _listRouter.add(VoiceRoomRoute()); + _listRouter.add(MainRoute()); + _listRouter.add(WalletRoute()); + _listRouter.add(StoreRoute()); + _listRouter.add(ATChatRouter()); + _listRouter.add(CouponRoute()); + _listRouter.add(FamilyRoute()); + _listRouter.add(DynamicRoute()); + + /// 初始化路由 + for (var routerProvider in _listRouter) { + routerProvider.initRouter(router); + } + } +} + +// PS:路由使用方法 + +// 1、不需要传参的 替换所有历史记录 +/// ATNavigatorUtils.push(context, LoginRouter.loginPage, replace: true); +/// 2、不需要传参的 不替换历史记录 +/// ATNavigatorUtils.push(context, LoginRouter.loginPage, replace: false); + +// 需要传参的 +// 3、ATNavigatorUtils.push(context,'${ATRoutes.webViewPage}?param1=${Uri.encodeComponent(content1)}¶m2=${Uri.encodeComponent(content2)}', replace: true); +// 4、ATNavigatorUtils.push(context, '${MarketRouter.hotCoin}?id=xxxxxxx'); + +// 有返回值跳转 +/// 5、ATNavigatorUtils.pushResult(context, MarketRouter.hotCoin, (result){ +// setState(() { +// //result是返回的结果 +// TestModel model = result; +// _name = model.name; +// }); +// }); + +// 返回上一级 +// 6、ATNavigatorUtils.goBack(context); + +// 带参数返回上一级 +// 7、ATNavigatorUtils.goBackWithParams(context, result); diff --git a/lib/chatvibe_core/utilities/at_app_utils.dart b/lib/chatvibe_core/utilities/at_app_utils.dart new file mode 100644 index 0000000..2c4354a --- /dev/null +++ b/lib/chatvibe_core/utilities/at_app_utils.dart @@ -0,0 +1,133 @@ +import 'dart:io'; +import 'package:extended_image/extended_image.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_cache_manager/flutter_cache_manager.dart'; +import 'package:path_provider/path_provider.dart'; + +class ATAppUtils { + // 获取缓存总大小(包括应用缓存、图片缓存等) + static Future collectCacheSize() async { + double totalSize = 0; + + try { + // 1. 应用缓存目录 + final appCacheDir = await getTemporaryDirectory(); + totalSize += await _getFolderSize(appCacheDir); + + // 4. Flutter Cache Manager 的图片缓存 + final cacheManager = DefaultCacheManager(); + final cacheSize = await cacheManager.store.getCacheSize(); + totalSize += cacheSize; + } catch (e) { + print('计算缓存大小出错: $e'); + } + + return _formatSize(totalSize); + } + + // 递归计算文件夹大小 + static Future _getFolderSize(Directory folder) async { + try { + if (!await folder.exists()) return 0; + + double size = 0; + final files = folder.listSync(recursive: true); + + for (var file in files) { + if (file is File) { + try { + final stat = await file.stat(); + size += stat.size; + } catch (e) { + // 忽略无法访问的文件 + } + } + } + + return size; + } catch (e) { + print('计算文件夹大小出错: ${folder.path} - $e'); + return 0; + } + } + + // 清理所有缓存 + static Future clearAllCacheRoutine() async { + try { + // 1. 清理临时目录 + final tempDir = await getTemporaryDirectory(); + await _deleteFolder(tempDir); + + // 2. 清理 Flutter Cache Manager 缓存 + final cacheManager = DefaultCacheManager(); + await cacheManager.emptyCache(); + + // 3. 清理 Dio 缓存(如果有) + await _clearDioCache(); + + // 4. 清理图片缓存(如果是用 cached_network_image) + await _clearImageCache(); + + // 重新创建必要的目录 + await tempDir.create(recursive: true); + } catch (e) { + print('清理缓存出错: $e'); + throw Exception('清理缓存失败: $e'); + } + } + + // 删除文件夹 + static Future _deleteFolder(Directory folder) async { + try { + if (await folder.exists()) { + await folder.delete(recursive: true); + } + } catch (e) { + print('删除文件夹失败: ${folder.path} - $e'); + } + } + + // 清理 Dio 缓存 + static Future _clearDioCache() async { + try { + final dioCacheDir = Directory( + '${(await getTemporaryDirectory()).path}/dio_cache', + ); + if (await dioCacheDir.exists()) { + await dioCacheDir.delete(recursive: true); + } + } catch (e) { + print('清理Dio缓存出错: $e'); + } + } + + // 清理图片缓存 + static Future _clearImageCache() async { + try { + // 清理 cached_network_image 缓存 + final imageCache = PaintingBinding.instance?.imageCache; + if (imageCache != null) { + imageCache.clear(); + imageCache.clearLiveImages(); + } + await clearDiskCachedImages(); + } catch (e) { + print('清理图片缓存出错: $e'); + } + } + + // 格式化缓存大小显示 + static String _formatSize(double size) { + if (size <= 0) return "0 B"; + + const units = ["B", "KB", "MB", "GB"]; + int unitIndex = 0; + + while (size >= 1024 && unitIndex < units.length - 1) { + size /= 1024; + unitIndex++; + } + + return "${size.toStringAsFixed(2)} ${units[unitIndex]}"; + } +} diff --git a/lib/chatvibe_core/utilities/at_banner_utils.dart b/lib/chatvibe_core/utilities/at_banner_utils.dart new file mode 100644 index 0000000..e33b46d --- /dev/null +++ b/lib/chatvibe_core/utilities/at_banner_utils.dart @@ -0,0 +1,28 @@ +import 'package:flutter/cupertino.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_core/utilities/at_string_utils.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_index_banner_res.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; + +import '../../chatvibe_data/models/enum/at_banner_open_type.dart'; + +class ATBannerUtils { + static void openBanner(ATIndexBannerRes item, BuildContext context) { + if (item.content == ATBannerOpenType.ENTER_ROOM.name) { + var params = item.params; + if (params != null && params.isNotEmpty) { + ATRoomUtils.goRoom(params, context); + } + } else { + var params = item.params; + if (ATStringUtils.isUrl(params ?? "")) { + ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(params ?? "")}&showTitle=false", + replace: false, + ); + } + } + } +} diff --git a/lib/chatvibe_core/utilities/at_date_utils.dart b/lib/chatvibe_core/utilities/at_date_utils.dart new file mode 100644 index 0000000..00bf888 --- /dev/null +++ b/lib/chatvibe_core/utilities/at_date_utils.dart @@ -0,0 +1,103 @@ +import 'package:flutter/cupertino.dart'; +import 'package:intl/intl.dart'; +import 'package:aslan/app_localizations.dart'; + +class ATMDateUtils { + static const num ONE_HOUR = 3600000; + static const num ONE_DAY = 86400000; + static const num ONE_WEEK = 604800000; + static final String _format1 = "yyyy-MM-dd"; + static final String _format2 = "yyyy/MM/dd/ HH:mm:ss"; + static final String _format3 = "yyyy-MM-dd HH:mm:ss"; + + static String formatDateTime(DateTime date) { + return DateFormat(_format1).format(date); + } + + static String formatDateTime2(DateTime date) { + return DateFormat(_format2).format(date); + } + + static String formatDateTime3(DateTime date) { + return DateFormat(_format3).format(date); + } + + static String formatMessageTime(BuildContext context, DateTime date) { + var now = DateTime.now(); + num delta = now.millisecondsSinceEpoch - date.millisecondsSinceEpoch; + // LogUtils.log( + // "Delta:$delta,now.millisecondsSinceEpoch:${now.millisecondsSinceEpoch},date.millisecondsSinceEpoch:${date.millisecondsSinceEpoch}"); + if (delta < 1 * ONE_DAY) { + if (date.day < now.day) { + return ATAppLocalizations.of(context)!.yesterday( + "${date.hour >= 10 ? date.hour : "0${date.hour}"}:${date.minute >= 10 ? date.minute : "0${date.minute}"}", + ); + } else { + return "${date.hour >= 10 ? date.hour : "0${date.hour}"}:${date.minute >= 10 ? date.minute : "0${date.minute}"}"; + } + } else if (delta < 1 * ONE_WEEK) { + switch (date.weekday) { + case DateTime.monday: + return ATAppLocalizations.of(context)!.monday( + "${date.hour >= 10 ? date.hour : "0${date.hour}"}:${date.minute >= 10 ? date.minute : "0${date.minute}"}", + ); + case DateTime.tuesday: + return ATAppLocalizations.of(context)!.tuesday( + "${date.hour >= 10 ? date.hour : "0${date.hour}"}:${date.minute >= 10 ? date.minute : "0${date.minute}"}", + ); + case DateTime.wednesday: + return ATAppLocalizations.of(context)!.wednesday( + "${date.hour >= 10 ? date.hour : "0${date.hour}"}:${date.minute >= 10 ? date.minute : "0${date.minute}"}", + ); + case DateTime.thursday: + return ATAppLocalizations.of(context)!.thursday( + "${date.hour >= 10 ? date.hour : "0${date.hour}"}:${date.minute >= 10 ? date.minute : "0${date.minute}"}", + ); + case DateTime.friday: + return ATAppLocalizations.of(context)!.friday( + "${date.hour >= 10 ? date.hour : "0${date.hour}"}:${date.minute >= 10 ? date.minute : "0${date.minute}"}", + ); + case DateTime.saturday: + return ATAppLocalizations.of(context)!.saturday( + "${date.hour >= 10 ? date.hour : "0${date.hour}"}:${date.minute >= 10 ? date.minute : "0${date.minute}"}", + ); + case DateTime.sunday: + return ATAppLocalizations.of(context)!.sunday( + "${date.hour >= 10 ? date.hour : "0${date.hour}"}:${date.minute >= 10 ? date.minute : "0${date.minute}"}", + ); + } + } else { + return "${date.year}/${date.month}/${date.day} ${date.hour >= 10 ? date.hour : "0${date.hour}"}:${date.minute >= 10 ? date.minute : "0${date.minute}"}"; + } + return ""; + } + + static int evaluateDaysDifferenceFunction(DateTime from, DateTime to) { + // 归一化日期(忽略时间部分,确保按整天计算) + from = DateTime(from.year, from.month, from.day); + to = DateTime(to.year, to.month, to.day); + + // 使用小时数换算天数并取整,准确处理夏令时导致的 23/25 小时日 + return (to.difference(from).inHours / 24).round(); + } + + static String secondsToMinutesOperation(int seconds) { + Duration duration = Duration(seconds: seconds); + return _formatDuration(duration); + } + + static String millisecondsToMinutesMethod(int milliseconds) { + Duration duration = Duration(milliseconds: milliseconds); + return _formatDuration(duration); + } + + static String _formatDuration(Duration duration) { + String twoDigits(int n) => n.toString().padLeft(2, "0"); + + if (duration.inHours > 0) { + return "${twoDigits(duration.inHours)}:${twoDigits(duration.inMinutes.remainder(60))}:${twoDigits(duration.inSeconds.remainder(60))}"; + } else { + return "${twoDigits(duration.inMinutes.remainder(60))}:${twoDigits(duration.inSeconds.remainder(60))}"; + } + } +} diff --git a/lib/chatvibe_core/utilities/at_deep_link_handler.dart b/lib/chatvibe_core/utilities/at_deep_link_handler.dart new file mode 100644 index 0000000..5a4068a --- /dev/null +++ b/lib/chatvibe_core/utilities/at_deep_link_handler.dart @@ -0,0 +1,61 @@ +import 'dart:async'; +import 'package:app_links/app_links.dart'; // 1. 替换导入 + +class ATDeepLinkHandler { + // 2. 创建 AppLinks 实例(通常作为单例,但也可以每次创建) + final AppLinks _appLinks = AppLinks(); + + StreamSubscription? _linkStreamSubscription; // 注意流类型变为 Uri + final _linkNotifierController = StreamController.broadcast(); + + Stream get linkNotifierStream => _linkNotifierController.stream; + + Function(Uri)? _onLinkReceivedCallback; + + Future initDeepLinks({Function(Uri)? onLinkReceived}) async { + _onLinkReceivedCallback = onLinkReceived; + await _handleInitialLink(); + _startListening_method(); + } + + Future _handleInitialLink() async { + try { + // 3. 使用实例方法获取初始链接,返回 Uri? 而不是 String? + Uri? initialLink = await _appLinks.getInitialLink(); + if (initialLink != null) { + _processIncomingLink_process(initialLink); + } + } catch (err) { + print('获取初始链接失败: $err'); + } + } + + void _startListening_method() { + // 4. 使用 uriLinkStream 监听链接(流类型为 Uri) + _linkStreamSubscription = _appLinks.uriLinkStream.listen( + (Uri? link) { + // 注意:uriLinkStream 可能直接发出 Uri,而不是 String? + // 根据实际版本,可能是 Uri 或 String?,以下处理两者兼容 + if (link != null) { + _processIncomingLink_process(link); + } + }, + onError: (err) { + print('监听链接流出错: $err'); + }, + ); + } + + // 5. 接收 Uri 类型参数(原来接收 String) + void _processIncomingLink_process(Uri uri) { + print('ATDeepLinkHandler 处理链接: $uri'); + // 如果需要存储为字符串,可以使用 uri.toString() + _linkNotifierController.add(uri.toString()); + _onLinkReceivedCallback?.call(uri); + } + + void dispose() { + _linkStreamSubscription?.cancel(); + _linkNotifierController.close(); + } +} \ No newline at end of file diff --git a/lib/chatvibe_core/utilities/at_deviceId_utils.dart b/lib/chatvibe_core/utilities/at_deviceId_utils.dart new file mode 100644 index 0000000..af05d77 --- /dev/null +++ b/lib/chatvibe_core/utilities/at_deviceId_utils.dart @@ -0,0 +1,63 @@ +import 'dart:io'; + +import 'package:device_info_plus/device_info_plus.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:uuid/uuid.dart'; + +class ATDeviceIdUtils { + static void initDeviceinfo() { + PackageInfo.fromPlatform().then((info) { + ATGlobalConfig.version = info.version; + ATGlobalConfig.build = info.buildNumber; + if (Platform.isAndroid) { + ATGlobalConfig.channel = "Google"; + DeviceInfoPlugin().androidInfo.then((dev) { + ATGlobalConfig.model = dev.model; + ATGlobalConfig.sysVersion = dev.version.release; + ATGlobalConfig.sdkInt = dev.version.sdkInt; + }); + } else if (Platform.isIOS) { + ATGlobalConfig.channel = "AppStore"; + DeviceInfoPlugin().iosInfo.then((dev) { + ATGlobalConfig.model = dev.model; + ATGlobalConfig.sysVersion = dev.systemVersion; + }); + } + }); + } + + // 获取持久化的唯一设备ID + static Future collectDeviceId({bool forceRefresh = false}) async { + // 如果强制刷新或配置中不存在,则重新生成 + if (forceRefresh || ATGlobalConfig.imei.isEmpty) { + final newId = await _generateUniqueId(); + DataPersistence.setUniqueId(newId); + ATGlobalConfig.imei = newId; + return newId; + } + + // 检查存储中的ID + String storedId = DataPersistence.getUniqueId(); + if (storedId.isNotEmpty) { + ATGlobalConfig.imei = storedId; + return storedId; + } + + // 生成新ID并存储 + final newId = await _generateUniqueId(); + DataPersistence.setUniqueId(newId); + ATGlobalConfig.imei = newId; + return newId; + } + + // 生成组合唯一ID + static Future _generateUniqueId() async { + final packageInfo = await PackageInfo.fromPlatform(); + // 生成新 UUID 并存储 + final newId = '${const Uuid().v4()}-${packageInfo.packageName}'; + DataPersistence.setUniqueId(newId); + return newId; + } +} diff --git a/lib/chatvibe_core/utilities/at_dialog_queue.dart b/lib/chatvibe_core/utilities/at_dialog_queue.dart new file mode 100644 index 0000000..6c217f1 --- /dev/null +++ b/lib/chatvibe_core/utilities/at_dialog_queue.dart @@ -0,0 +1,37 @@ +class ATDialogQueue { + static final ATDialogQueue _instance = ATDialogQueue._(); + + factory ATDialogQueue() => _instance; + + ATDialogQueue._(); + + final List _queue = []; + bool _showing = false; + + // 添加弹框 + void add(Function showDialogFunc) { + _queue.add(showDialogFunc); + _tryShowNext_function(); + } + + // 弹框关闭 + void dismiss() { + _showing = false; + _tryShowNext_function(); + } + + // 清空 + void clear() { + _queue.clear(); + _showing = false; + } + + // 尝试显示下一个 + void _tryShowNext_function() { + if (_showing || _queue.isEmpty) return; + + _showing = true; + final func = _queue.removeAt(0); + func(); + } +} diff --git a/lib/chatvibe_core/utilities/at_dialog_queue_manager.dart b/lib/chatvibe_core/utilities/at_dialog_queue_manager.dart new file mode 100644 index 0000000..950f7fe --- /dev/null +++ b/lib/chatvibe_core/utilities/at_dialog_queue_manager.dart @@ -0,0 +1,86 @@ +typedef ATDialogQueueManager = ATModalQueue; + +class ATModalQueue { + ///更新弹框-签到-活动-首充 + static final ATModalQueue _singleton = ATModalQueue._internal(); + + factory ATModalQueue() => _singleton; + + ATModalQueue._internal(); + + bool _isShowing = false; + + final ATPriorityQueue _taskQueue = + ATPriorityQueue((a, b) => a.compareTo(b)); + + void addDialog(Function func, {int priority = 0}) { + _taskQueue.add(ATDialogQueueTask(func: func, priority: priority)); + if (!_isShowing) { + _showNext(); + } + } + + void showComplete() { + _isShowing = false; + _showNext(); + } + + void _showNext() { + if (_taskQueue.isEmpty || _isShowing) return; + final task = _taskQueue.removeFirst(); + _isShowing = true; + task.func(); + } + + void clear() { + _isShowing = false; + _taskQueue.clear(); + } +} + +class ATDialogQueueTask implements Comparable { + final Function func; + final int priority; // 优先级 (数值越高越优先) + final DateTime _createTime; // 添加创建时间确保先进先出 + + ATDialogQueueTask({required this.func, this.priority = 0}) + : _createTime = DateTime.now(); + + @override + int compareTo(ATDialogQueueTask other) { + // 先按优先级降序排序(数值大的在前) + if (priority != other.priority) { + return other.priority - priority; // 降序:other - this + } + // 相同优先级按添加时间升序排序(先进先出) + return _createTime.compareTo(other._createTime); + } +} +class ATPriorityQueue { + final List _elements = []; + final Comparator _comparator; + + ATPriorityQueue(this._comparator); + + void add(E element) { + _elements.add(element); + _elements.sort(_comparator); + } + + E removeFirst() { + if (isEmpty) throw StateError("No elements"); + return _elements.removeAt(0); + } + + E? get first => isEmpty ? null : _elements.first; + + bool get isEmpty => _elements.isEmpty; + + bool get isNotEmpty => _elements.isNotEmpty; + + int get length => _elements.length; + + void clear() => _elements.clear(); + + List get unorderedElements => List.from(_elements); +} diff --git a/lib/chatvibe_core/utilities/at_dialog_utils.dart b/lib/chatvibe_core/utilities/at_dialog_utils.dart new file mode 100644 index 0000000..aaf495d --- /dev/null +++ b/lib/chatvibe_core/utilities/at_dialog_utils.dart @@ -0,0 +1,386 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_index_banner_res.dart'; + +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_features/wallet/wallet_route.dart'; +import 'package:aslan/chatvibe_ui/widgets/banner/index_banner_page.dart'; +import 'package:aslan/chatvibe_ui/widgets/countdown_timer.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_dialog_queue_manager.dart'; + +class ATDialogUtils { + static showFirstRechargeDialog(BuildContext context) { + SmartDialog.show( + tag: "showFirstRecharge", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + onDismiss: () { + ATModalQueue().showComplete(); + }, + builder: (_) { + return Center( + child: Stack( + alignment: AlignmentDirectional.bottomCenter, + children: [ + SizedBox( + height: 520.w, + width: ScreenUtil().screenWidth * 0.86, + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/index/at_icon_first_recharge_bg.webp", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + SizedBox(height: 190.w), + GestureDetector( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + textDirection: TextDirection.ltr, + children: [ + SizedBox(width: 40.w), + text( + "${AccountStorage().getCurrentUser()?.userProfile?.firstRechargeAmount ?? 0}", + textColor: Colors.white, + fontSize: 14.sp, + ), + ], + ), + onTap: () { + ATRoomUtils.closeAllDialogs(); + ATNavigatorUtils.push( + context, + WalletRoute.recharge, + replace: false, + ); + }, + ), + SizedBox(height: 31.w), + (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.firstRechargeAmount ?? + 0) < + 0.99 + ? Container(height: 55.w) + : Container( + alignment: AlignmentDirectional.centerStart, + margin: EdgeInsets.only(left: 65.w), + child: Stack( + alignment: Alignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 8.w, + children: [ + Container( + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular( + 10.w, + ), + ), + height: 55.w, + width: 55.w, + ), + Container( + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular( + 10.w, + ), + ), + height: 55.w, + width: 55.w, + ), + Container( + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular( + 10.w, + ), + ), + height: 55.w, + width: 55.w, + ), + ], + ), + Image.asset( + "atu_images/index/at_icon_claimed_text.png", + width: 70.w, + height: 35.w, + ), + ], + ), + ), + SizedBox(height: 23.w), + (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.firstRechargeAmount ?? + 0) < + 4.99 + ? Container(height: 55.w) + : Container( + alignment: AlignmentDirectional.centerStart, + margin: EdgeInsets.only(left: 65.w), + child: Stack( + alignment: Alignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 8.w, + children: [ + Container( + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular( + 10.w, + ), + ), + height: 55.w, + width: 55.w, + ), + Container( + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular( + 10.w, + ), + ), + height: 55.w, + width: 55.w, + ), + Container( + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular( + 10.w, + ), + ), + height: 55.w, + width: 55.w, + ), + ], + ), + Image.asset( + "atu_images/index/at_icon_claimed_text.png", + width: 70.w, + height: 35.w, + ), + ], + ), + ), + SizedBox(height: 23.w), + (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.firstRechargeAmount ?? + 0) < + 9.99 + ? Container(height: 55.w) + : Container( + alignment: AlignmentDirectional.centerStart, + margin: EdgeInsets.only(left: 65.w), + child: Stack( + alignment: Alignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 8.w, + children: [ + Container( + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular( + 10.w, + ), + ), + height: 55.w, + width: 55.w, + ), + Container( + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular( + 10.w, + ), + ), + height: 55.w, + width: 55.w, + ), + Container( + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular( + 10.w, + ), + ), + height: 55.w, + width: 55.w, + ), + ], + ), + Image.asset( + "atu_images/index/at_icon_claimed_text.png", + width: 70.w, + height: 35.w, + ), + ], + ), + ), + ], + ), + ), + ), + PositionedDirectional( + bottom: 30.w, + child: CountdownTimer( + expiryDate: DateTime.fromMillisecondsSinceEpoch( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.firstRechargeEndTime ?? + 0, + ), + color: Colors.white, + fontSize: 15.sp, + ), + ), + ], + ), + ); + }, + ); + } + + static void revealDynamicCommentOptDialog( + BuildContext context, { + Function? reportCallback, + Function? deleteCallback, + }) { + SmartDialog.dismiss(tag: "revealDynamicCommentOptDialog"); + SmartDialog.show( + tag: "revealDynamicCommentOptDialog", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return SafeArea( + top: false, + child: Container( + padding: EdgeInsets.symmetric(vertical: 10.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10.w), + topRight: Radius.circular(10.w), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: 3.w, + children: [ + if (reportCallback != null) + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + child: text( + ATAppLocalizations.of(context)!.report, + textColor: Colors.black, + fontSize: 15.sp, + ), + ), + onTap: () { + reportCallback?.call(); + SmartDialog.dismiss(tag: "revealDynamicCommentOptDialog"); + }, + ), + if (reportCallback != null) + Divider( + color: ChatVibeTheme.dividerColor, + thickness: 0.5, + height: 10.w, + ), + if (deleteCallback != null) + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + child: text( + ATAppLocalizations.of(context)!.delete, + textColor: Colors.black, + fontSize: 15.sp, + ), + ), + onTap: () { + deleteCallback?.call(); + SmartDialog.dismiss(tag: "revealDynamicCommentOptDialog"); + }, + ), + if (deleteCallback != null) + Divider( + color: ChatVibeTheme.dividerColor, + thickness: 0.5, + height: 10.w, + ), + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + child: text( + ATAppLocalizations.of(context)!.cancel, + textColor: Colors.black, + fontSize: 15.sp, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "revealDynamicCommentOptDialog"); + }, + ), + ], + ), + ), + ); + }, + ); + } + + static void revealBannerDialog( + BuildContext context, + List banners, + ) { + if (banners.isEmpty) { + return; + } + ATIndexBannerRes banner; + if (banners.length > 1) { + int index = Random().nextInt(banners.length); + banner = banners[index]; + } else { + banner = banners.first; + } + + SmartDialog.show( + tag: "revealBannerDialog", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + onDismiss: () { + ATModalQueue().showComplete(); + }, + builder: (_) { + return IndexBannerPage(banner); + }, + ); + } +} diff --git a/lib/chatvibe_core/utilities/at_entrance_vap_svga_manager.dart b/lib/chatvibe_core/utilities/at_entrance_vap_svga_manager.dart new file mode 100644 index 0000000..c1d41d5 --- /dev/null +++ b/lib/chatvibe_core/utilities/at_entrance_vap_svga_manager.dart @@ -0,0 +1,246 @@ +import 'package:flutter/animation.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_svga/flutter_svga.dart'; +import 'package:aslan/chatvibe_core/utilities/at_path_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/file_cache_manager.dart'; +import 'package:tancent_vap/widgets/vap_view.dart'; + +import 'package:aslan/chatvibe_core/utilities/at_gift_vap_svga_manager.dart'; + +// class EntranceVapSvgaManager { +// static EntranceVapSvgaManager? _instance; +// +// EntranceVapSvgaManager._internal(); +// +// factory EntranceVapSvgaManager() => +// _instance ??= EntranceVapSvgaManager._internal(); +// +// // 使用 ATPriorityQueue 替代简单列表 +// final ATPriorityQueue _taskQueue = ATPriorityQueue( +// (a, b) => b.priority.compareTo(a.priority), +// ); +// VapController? _roomGiftvapController; +// SVGAAnimationController? _roomGiftsvgaController; +// bool _isPlaying = false; +// bool _isDisposed = false; +// +// // 绑定控制器 +// void bindEntranceVapController(VapController vapController) { +// _isDisposed = false; +// _roomGiftvapController = vapController; +// _roomGiftvapController?.setAnimListener( +// onVideoStart: () {}, +// onVideoComplete: () { +// _handleControllerState(); +// }, +// onFailed: (code, type, msg) { +// _handleControllerState(); +// }, +// ); +// } +// +// void bindEntranceSvgaController(SVGAAnimationController svgaController) { +// _isDisposed = false; +// _roomGiftsvgaController = svgaController; +// _roomGiftsvgaController?.addStatusListener((AnimationStatus status) { +// if (status.isCompleted) { +// _isPlaying = false; +// _roomGiftsvgaController?.reset(); +// _playNext(); +// } +// }); +// } +// +// // 播放任务 +// void play( +// String path, { +// int priority = 0, +// Map? customResources, +// }) { +// if (_isDisposed) return; +// final task = ATVapTask( +// path: path, +// priority: priority, +// customResources: customResources, +// ); +// +// _taskQueue.add(task); +// if (!_isPlaying) { +// _playNext(); +// } +// } +// +// // 播放下一个任务 +// Future _playNext() async { +// if (_isDisposed || _taskQueue.isEmpty || _isPlaying) return; +// +// final task = _taskQueue.removeFirst(); +// _isPlaying = true; +// try { +// final pathType = ATPathUtils.collectPathType(task.path); +// if (pathType == PathType.asset) { +// await _playAsset(task); +// } else if (pathType == PathType.file) { +// await _playFile(task); +// } else if (pathType == PathType.network) { +// await _playNetwork(task); +// } +// } catch (e, s) { +// print('VAP_SVGA播放失败: $e\n$s'); +// _isPlaying = false; +// } finally { +// // 确保状态正确重置 +// if (!_isDisposed) { +// _playNext(); +// } +// } +// } +// +// // 播放资源文件 +// Future _playAsset(ATVapTask task) async { +// if (_isDisposed) return; +// if (ATPathUtils.fetchFileExtensionFunction(task.path).toLowerCase() == ".svga") { +// MovieEntity entity; +// if (ATGiftVapSvgaManager().videoItemCache.containsKey(task.path)) { +// entity = ATGiftVapSvgaManager().videoItemCache[task.path]!; +// } else { +// entity = await SVGAParser.shared.decodeFromAssets(task.path); +// ATGiftVapSvgaManager().videoItemCache[task.path] = entity; +// } +// _roomGiftsvgaController?.videoItem = entity; +// _roomGiftsvgaController?.reset(); +// _roomGiftsvgaController?.forward(); +// } else { +// await _roomGiftvapController?.playAsset(task.path); +// } +// } +// +// // 播放本地文件 +// Future _playFile(ATVapTask task) async { +// if (_isDisposed || _roomGiftvapController == null) return; +// await _roomGiftvapController!.playFile(task.path); +// } +// +// // 播放网络资源 +// Future _playNetwork(ATVapTask task) async { +// if (_isDisposed) return; +// if (ATPathUtils.fetchFileExtensionFunction(task.path).toLowerCase() == ".svga") { +// MovieEntity? entity; +// if (ATGiftVapSvgaManager().videoItemCache.containsKey(task.path)) { +// entity = ATGiftVapSvgaManager().videoItemCache[task.path]!; +// entity.autorelease = false; +// } else { +// try { +// entity = await SVGAParser.shared.decodeFromURL(task.path); +// entity.autorelease = false; +// ATGiftVapSvgaManager().videoItemCache[task.path] = entity; +// } catch (e) { +// _isPlaying = false; +// print('svga解析出错:$e'); +// } +// } +// if (entity != null) { +// _roomGiftsvgaController?.videoItem = entity; +// _roomGiftsvgaController?.reset(); +// _roomGiftsvgaController?.forward(); +// } +// } else { +// final file = await FileCacheManager.getInstance().getFile(url: task.path); +// if (file != null && !_isDisposed) { +// await _playFile( +// ATVapTask( +// path: file.path, +// priority: task.priority, +// customResources: task.customResources, +// ), +// ); +// } +// } +// } +// +// // 处理控制器状态变化 +// void _handleControllerState() { +// if (_roomGiftvapController != null && !_isDisposed) { +// _isPlaying = false; +// // 延迟一小段时间后播放下一个,避免状态冲突 +// Future.delayed(const Duration(milliseconds: 50), _playNext); +// } +// } +// +// // 释放资源 +// void dispose() { +// _isDisposed = true; +// _isPlaying = false; +// _taskQueue.clear(); +// _roomGiftvapController?.stop(); +// _roomGiftvapController?.dispose(); +// _roomGiftvapController = null; +// _roomGiftsvgaController?.stop(); +// _roomGiftsvgaController?.dispose(); +// _roomGiftsvgaController = null; +// } +// +// // 清除所有任务 +// void clearTasks() { +// _taskQueue.clear(); +// } +// +// // 获取当前队列状态 +// VapQueueStatus get queueStatus { +// return VapQueueStatus( +// isPlaying: _isPlaying, +// currentTask: _taskQueue.isNotEmpty ? _taskQueue.first : null, +// queueLength: _taskQueue.length, +// pendingTasks: List.unmodifiable(_taskQueue.unorderedElements), +// ); +// } +// } + +// 任务模型 +class ATVapTask implements Comparable { + final String path; + final int priority; // 优先级 (数值越高越优先) + final Map? customResources; // 自定义资源 + + ATVapTask({required this.path, this.priority = 0, this.customResources}); + + @override + int compareTo(ATVapTask other) { + // 先按优先级排序 + if (priority != other.priority) { + return priority.compareTo(other.priority); + } + + // 相同优先级按添加时间排序(先进先出) + return hashCode.compareTo(other.hashCode); + } +} +// 优先队列实现 +class ATPriorityQueue { + final List _elements = []; + final Comparator _comparator; + + ATPriorityQueue(this._comparator); + + void add(E element) { + _elements.add(element); + _elements.sort(_comparator); + } + + E removeFirst() { + if (isEmpty) throw StateError("No elements"); + return _elements.removeAt(0); + } + + E? get first => isEmpty ? null : _elements.first; + + bool get isEmpty => _elements.isEmpty; + + bool get isNotEmpty => _elements.isNotEmpty; + + int get length => _elements.length; + + void clear() => _elements.clear(); + + List get unorderedElements => List.from(_elements); +} diff --git a/lib/chatvibe_core/utilities/at_family_utils.dart b/lib/chatvibe_core/utilities/at_family_utils.dart new file mode 100644 index 0000000..bf9f3fa --- /dev/null +++ b/lib/chatvibe_core/utilities/at_family_utils.dart @@ -0,0 +1,219 @@ +import 'dart:math'; +import 'dart:ui' as ui; + +import 'package:fluro/fluro.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_ninepatch_image/flutter_ninepatch_image.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:marquee/marquee.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_domain/models/res/family_base_info_res.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; + +class ATFamilyUtils { + static Widget buidFamilyInfo( + ChatVibeFamilyBaseInfoRes? familyBaseInfo, + BuildContext context, + ) { + String familyIntro = (familyBaseInfo?.familyIntro ?? "").replaceAll( + RegExp(r'\s+'), + ' ', + ); + int level = 0; + if ((familyBaseInfo?.familyLevel ?? 0) > 0 && + (familyBaseInfo?.familyLevel ?? 0) < 4) { + level = 1; + } else if ((familyBaseInfo?.familyLevel ?? 0) > 3 && + (familyBaseInfo?.familyLevel ?? 0) < 7) { + level = 2; + } else if ((familyBaseInfo?.familyLevel ?? 0) > 6 && + (familyBaseInfo?.familyLevel ?? 0) < 10) { + level = 3; + } else { + level = familyBaseInfo?.familyLevel ?? 0; + } + return Container( + width: ScreenUtil().screenWidth, + margin: EdgeInsets.symmetric(horizontal: 13.w), + child: NinePatchImage( + alignment: Alignment.center, + imageProvider: AssetImage( + "atu_images/family/at_icon_family_info_item_bg_$level.9.png", + ), + sliceCachedKey: "atu_images/family/at_icon_family_info_item_bg_$level.9.png", + child: Column( + children: [ + SizedBox(height: 5.w), + Row( + children: [ + SizedBox(width: 5.w), + Container( + margin: EdgeInsetsDirectional.only(start: 8.w, top: 5.w), + child: netImage( + height: 75.w, + width: 75.w, + borderRadius: BorderRadius.circular(5.w), + url: familyBaseInfo?.familyAvatar ?? "", + ), + ), + SizedBox(width: 10.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + constraints: BoxConstraints( + maxWidth: 135.w, + maxHeight: 20.w, + ), + child: + (familyBaseInfo?.familyName?.length ?? 0) > 12 + ? Marquee( + text: familyBaseInfo?.familyName ?? "", + style: TextStyle( + fontSize: 13.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + familyBaseInfo?.familyName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + SizedBox(height: 5.w), + Row( + children: [ + text( + "ID:${familyBaseInfo?.familyAccount ?? ""}", + fontWeight: FontWeight.w600, + fontSize: 13.sp, + textColor: Colors.white, + ), + SizedBox(width: 2.w), + Container( + alignment: AlignmentDirectional.center, + height: 25.w, + width: 68.w, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + height: 15.w, + "atu_images/family/at_icon_family_member_tag.png", + ), + SizedBox(width: 3.w), + text( + "${familyBaseInfo?.currentMember ?? 0}/${familyBaseInfo?.maxMember ?? 0}", + fontWeight: FontWeight.w600, + fontSize: 13.sp, + textColor: Colors.white, + ), + ], + ), + ), + ], + ), + ], + ), + ), + Image.asset( + "atu_images/family/at_icon_family_level_${familyBaseInfo?.familyLevel ?? 0}.png", + height: 60.w, + ), + // Image.asset( + // "images/family/at_icon_family_rank_tag.png", + // height: 80.w, + // ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 8.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + padding: EdgeInsets.symmetric(vertical: 5.w, horizontal: 6.w), + // 外部容器用于实现渐变边框 + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + border: Border.all(color: Color(0xFFF5F5F5), width: 0.2.w), + ), + child: Row( + children: [ + Image.asset( + "atu_images/family/at_icon_announcement_tag.png", + height: 22.w, + ), + SizedBox(width: 3.w), + Expanded( + child: SizedBox( + height: 20.w, + child: + (familyIntro?.length ?? 0) > 20 + ? Marquee( + text: familyIntro ?? "", + style: TextStyle( + fontSize: 13.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 40.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + (familyIntro ?? "").isEmpty + ? ATAppLocalizations.of( + context, + )!.welcomeToMyFamily + : familyIntro ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + ), + ], + ), // 背景色, + ), + SizedBox(height: 10.w), + ], + ), + ), + ); + } +} diff --git a/lib/chatvibe_core/utilities/at_file_utils.dart b/lib/chatvibe_core/utilities/at_file_utils.dart new file mode 100644 index 0000000..84db221 --- /dev/null +++ b/lib/chatvibe_core/utilities/at_file_utils.dart @@ -0,0 +1,38 @@ +import 'dart:io'; + +import 'package:flutter/services.dart'; +import 'package:path_provider/path_provider.dart'; + +class ATFileHelper{ + // 将资源文件拷贝到应用文档目录 + static Future copyAssetToLocal(String assetPath) async { + + try { + // 获取应用文档目录 + final Directory appDocDir = await getApplicationDocumentsDirectory(); + final String localFilePath = '${appDocDir.path}/${assetPath.split('/').last}'; + + // 检查文件是否已存在 + if (await File(localFilePath).exists()) { + print('文件已存在,直接使用: $localFilePath'); + return localFilePath; + } + + // 从Assets加载字节数据 + final ByteData data = await rootBundle.load(assetPath); + final List bytes = data.buffer.asUint8List(); + + // 将字节数据写入本地文件 + final File localFile = File(localFilePath); + await localFile.writeAsBytes(bytes); + + print('文件拷贝成功: $localFilePath'); + return localFilePath; + } catch (e) { + print('拷贝文件时发生错误: $e'); + rethrow; // 重新抛出异常以便上层处理 + } finally { + + } + } +} \ No newline at end of file diff --git a/lib/chatvibe_core/utilities/at_gift_vap_svga_manager.dart b/lib/chatvibe_core/utilities/at_gift_vap_svga_manager.dart new file mode 100644 index 0000000..4c208d7 --- /dev/null +++ b/lib/chatvibe_core/utilities/at_gift_vap_svga_manager.dart @@ -0,0 +1,425 @@ +import 'dart:collection'; +import 'dart:io'; + +import 'package:flutter/animation.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_svga/flutter_svga.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/utilities/at_path_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/file_cache_manager.dart'; +import 'package:tancent_vap/utils/constant.dart'; +import 'package:tancent_vap/widgets/vap_view.dart'; + +import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; + +class ATGiftVapSvgaManager { + final LinkedHashMap videoItemCache = + LinkedHashMap(); + static ATGiftVapSvgaManager? _instance; + static const int _maxQueueLength = 40; + static const int _maxSvgaCacheCount = 12; + static const Duration _nextTaskDelay = Duration(milliseconds: 16); + + ATGiftVapSvgaManager._internal(); + + factory ATGiftVapSvgaManager() => _instance ??= ATGiftVapSvgaManager._internal(); + + final PriorityQueue _taskQueue = PriorityQueue( + (a, b) => a.compareTo(b), // 调用 VapTask 的 compareTo 方法 + ); + VapController? _roomGiftvapController; + SVGAAnimationController? _roomGiftsvgaController; + AnimationStatusListener? _svgaStatusListener; + bool _isPlaying = false; + bool _isDisposed = false; + + bool _isPause = false; + + //是否关闭礼物特效声音 + bool _muteMusic = false; + + void setMuteMusic(bool muteMusic) { + _muteMusic = muteMusic; + DataPersistence.setPlayGiftMusic(_muteMusic); + } + + bool getMuteMusic() { + return _muteMusic; + } + + // 绑定控制器 + void bindRoomGiftVapController(VapController vapController) { + _muteMusic = DataPersistence.getPlayGiftMusic(); + _isDisposed = false; + _roomGiftvapController = vapController; + _roomGiftvapController?.setAnimListener( + onVideoStart: () {}, + onVideoComplete: () { + _handleControllerState(); + }, + onFailed: (code, type, msg) { + _handleControllerState(); + }, + ); + if (!_isPlaying && _taskQueue.isNotEmpty) { + Future.microtask(_playNext); + } + } + + void bindRoomGiftSvgaController(SVGAAnimationController svgaController) { + _isDisposed = false; + if (_roomGiftsvgaController != null && _svgaStatusListener != null) { + _roomGiftsvgaController?.removeStatusListener(_svgaStatusListener!); + } + _roomGiftsvgaController = svgaController; + _svgaStatusListener = (AnimationStatus status) { + if (status.isCompleted) { + _roomGiftsvgaController?.reset(); + _isPlaying = false; + _playNext(); + } + }; + _roomGiftsvgaController?.addStatusListener(_svgaStatusListener!); + if (!_isPlaying && _taskQueue.isNotEmpty) { + Future.microtask(_playNext); + } + } + + void unbindRoomGiftVapController([VapController? vapController]) { + if (vapController != null && _roomGiftvapController != vapController) { + return; + } + _roomGiftvapController = null; + } + + void unbindRoomGiftSvgaController([SVGAAnimationController? svgaController]) { + if (svgaController != null && _roomGiftsvgaController != svgaController) { + return; + } + if (_roomGiftsvgaController != null && _svgaStatusListener != null) { + _roomGiftsvgaController?.removeStatusListener(_svgaStatusListener!); + } + _svgaStatusListener = null; + _roomGiftsvgaController = null; + } + + // 播放任务 + void play( + String path, { + int priority = 0, + Map? customResources, + int type = 0, + }) { + if (path.isEmpty) { + return; + } + if (ATGlobalConfig.sdkInt > ATGlobalConfig.maxSdkNoAnim) { + if (_isDisposed) return; + final task = ATVapTask( + path: path, + priority: priority, + customResources: customResources, + type: type, + ); + + if (!_enqueueTask(task)) { + return; + } + if (!_isPlaying) { + _playNext(); + } + } + } + + bool _enqueueTask(ATVapTask task) { + if (_taskQueue.length >= _maxQueueLength) { + final ATVapTask? lastTask = _taskQueue.last; + if (lastTask != null && task.compareTo(lastTask) >= 0) { + return false; + } + _taskQueue.removeLast(); + } + _taskQueue.add(task); + return true; + } + + // 播放下一个任务 + Future _playNext() async { + if (_isPause) { + return; + } + if (_isDisposed || _taskQueue.isEmpty || _isPlaying) return; + + final task = _taskQueue.removeFirst(); + _isPlaying = true; + bool started = false; + try { + final pathType = ATPathUtils.collectPathType(task.path); + if (pathType == PathType.asset) { + started = await _playAsset(task); + } else if (pathType == PathType.file) { + started = await _playFile(task); + } else if (pathType == PathType.network) { + started = await _playNetwork(task); + } + } catch (e, s) { + started = false; + print('VAP_SVGA播放失败: $e\n$s'); + } + if (!started) { + _isPlaying = false; + if (!_isDisposed) { + Future.delayed(_nextTaskDelay, _playNext); + } + } + } + + // 播放资源文件 + Future _playAsset(ATVapTask task) async { + if (_isDisposed) return false; + if (ATPathUtils.fetchFileExtensionFunction(task.path).toLowerCase() == ".svga") { + if (_roomGiftsvgaController == null) { + return false; + } + final MovieEntity entity = await loadSvgaMovie(task.path); + _roomGiftsvgaController?.videoItem = entity; + _roomGiftsvgaController?.reset(); + _roomGiftsvgaController?.forward(); + return true; + } else { + if (_roomGiftvapController == null) { + return false; + } + await _roomGiftvapController?.setMute(_muteMusic); + await _applyCustomResources(task.customResources); + await _roomGiftvapController?.playAsset(task.path); + return true; + } + } + + // 播放本地文件 + Future _playFile(ATVapTask task) async { + if (_isDisposed || _roomGiftvapController == null) return false; + await _roomGiftvapController?.setMute(_muteMusic); + await _applyCustomResources(task.customResources); + await _roomGiftvapController!.playFile(task.path); + return true; + } + + // 播放网络资源 + Future _playNetwork(ATVapTask task) async { + if (_isDisposed) return false; + if (ATPathUtils.fetchFileExtensionFunction(task.path).toLowerCase() == ".svga") { + if (_roomGiftsvgaController == null) { + return false; + } + try { + final MovieEntity entity = await loadSvgaMovie(task.path); + _roomGiftsvgaController?.videoItem = entity; + _roomGiftsvgaController?.reset(); + _roomGiftsvgaController?.forward(); + return true; + } catch (e) { + print('svga解析出错:$e'); + return false; + } + } else { + final file = await FileCacheManager.getInstance().getFile(url: task.path); + if (!_isDisposed) { + return _playFile( + ATVapTask( + path: file.path, + priority: task.priority, + customResources: task.customResources, + ), + ); + } + return false; + } + } + + // 处理控制器状态变化 + void _handleControllerState() { + if (_isDisposed) { + return; + } + _isPlaying = false; + Future.delayed(_nextTaskDelay, _playNext); + } + + //暂停动画播放 + void pauseAnim() { + _isPause = true; + } + + //恢复动画播放 + void resumeAnim() { + _isPause = false; + _playNext(); + } + + // 释放资源 + void dispose({bool clearCache = false}) { + _isDisposed = true; + _isPlaying = false; + _taskQueue.clear(); + _roomGiftvapController?.stop(); + _roomGiftvapController = null; + if (_roomGiftsvgaController != null && _svgaStatusListener != null) { + _roomGiftsvgaController?.removeStatusListener(_svgaStatusListener!); + } + _svgaStatusListener = null; + _roomGiftsvgaController?.stop(); + _roomGiftsvgaController?.reset(); + _roomGiftsvgaController = null; + if (clearCache) { + videoItemCache.clear(); + } + } + + // 清除所有任务 + void clearTasks() { + _isPlaying = false; + _taskQueue.clear(); + _isPause = false; + } + + MovieEntity? _takeCachedMovie(String path) { + final MovieEntity? entity = videoItemCache.remove(path); + if (entity != null) { + videoItemCache[path] = entity; + } + return entity; + } + + void _cacheMovie(String path, MovieEntity entity) { + videoItemCache.remove(path); + videoItemCache[path] = entity; + while (videoItemCache.length > _maxSvgaCacheCount) { + final String oldestKey = videoItemCache.keys.first; + videoItemCache.remove(oldestKey); + } + } + + Future loadSvgaMovie(String path) async { + MovieEntity? entity = _takeCachedMovie(path); + if (entity != null) { + entity.autorelease = false; + return entity; + } + + final PathType pathType = ATPathUtils.collectPathType(path); + if (pathType == PathType.network) { + final file = await FileCacheManager.getInstance().getFile(url: path); + entity = await SVGAParser.shared.decodeFromBuffer( + await file.readAsBytes(), + ); + } else if (pathType == PathType.asset) { + entity = await SVGAParser.shared.decodeFromAssets(path); + } else { + final String filePath = + path.startsWith('file://') ? Uri.parse(path).toFilePath() : path; + entity = await SVGAParser.shared.decodeFromBuffer( + await File(filePath).readAsBytes(), + ); + } + entity.autorelease = false; + _cacheMovie(path, entity); + return entity; + } + + Future _applyCustomResources( + Map? customResources, + ) async { + if (_roomGiftvapController == null || customResources == null) { + return; + } + for (final entry in customResources.entries) { + await _roomGiftvapController?.setVapTagContent(entry.key, entry.value); + } + } +} + +// 任务模型 +// 1. 修改 ATVapTask 类,实现 Comparable + +class ATVapTask implements Comparable { + final String path; + final int type; + final int priority; + final Map? customResources; + final int _sequenceNumber; + + static int _nextSequenceNumber = 0; + + ATVapTask({ + required this.path, + this.priority = 0, + this.customResources, + this.type = 0, + }) : _sequenceNumber = _nextSequenceNumber++; + + @override + int compareTo(ATVapTask other) { + // 先按优先级降序排列 + int priorityComparison = other.priority.compareTo(priority); + if (priorityComparison != 0) { + return priorityComparison; + } + // 相同优先级时,按序列号升序排列(先添加的先执行) + return _sequenceNumber.compareTo(other._sequenceNumber); + } + + @override + String toString() { + return 'VapTask{path: $path, priority: $priority, seq: $_sequenceNumber}'; + } +} + +// 优先队列实现 +class PriorityQueue { + final List _elements = []; + final Comparator _comparator; + + PriorityQueue(this._comparator); + + void add(E element) { + int low = 0; + int high = _elements.length; + while (low < high) { + final int mid = low + ((high - low) >> 1); + if (_comparator(element, _elements[mid]) < 0) { + high = mid; + } else { + low = mid + 1; + } + } + _elements.insert(low, element); + } + + E removeFirst() { + if (isEmpty) throw StateError("No elements"); + return _elements.removeAt(0); + } + + E? get first => isEmpty ? null : _elements.first; + E? get last => isEmpty ? null : _elements.last; + + bool get isEmpty => _elements.isEmpty; + + bool get isNotEmpty => _elements.isNotEmpty; + + int get length => _elements.length; + + void clear() => _elements.clear(); + + E removeLast() { + if (isEmpty) throw StateError("No elements"); + return _elements.removeLast(); + } + + List get unorderedElements => List.from(_elements); + + // 实现 Iterable 接口 + Iterator get iterator => _elements.iterator; +} diff --git a/lib/chatvibe_core/utilities/at_google_auth_utils.dart b/lib/chatvibe_core/utilities/at_google_auth_utils.dart new file mode 100644 index 0000000..18c12a1 --- /dev/null +++ b/lib/chatvibe_core/utilities/at_google_auth_utils.dart @@ -0,0 +1,101 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:google_sign_in/google_sign_in.dart'; + +///谷歌授权登录 +class ATGoogleAuthService { + static final GoogleSignIn _googleSignIn = GoogleSignIn(scopes: ['email', 'profile'],); + + // 获取当前用户 + static User? get currentUser => FirebaseAuth.instance.currentUser; + + // 检查登录状态 + static bool get isSignedIn => currentUser != null; + + // 初始化Firebase + static Future prepareFunction() async { + await Firebase.initializeApp(); + } + + // 谷歌登录 + static Future signInWithGoogleFunction() async { + try { + // 触发谷歌登录 + final GoogleSignInAccount? googleAccount = await _googleSignIn.signIn(); + if (googleAccount == null) return null; + + // 获取认证信息 + final GoogleSignInAuthentication googleAuth = await googleAccount.authentication; + + // 创建Firebase凭证 + final OAuthCredential credential = GoogleAuthProvider.credential( + accessToken: googleAuth.accessToken, + idToken: googleAuth.idToken, + ); + + // 使用凭证登录Firebase + final UserCredential userCredential = + await FirebaseAuth.instance.signInWithCredential(credential); + + return userCredential.user; + } catch (e) { + print('Google sign-in error: $e'); + rethrow; // 抛出异常让调用者处理 + } + } + + // 静默登录(检查现有会话) + static Future signInSilently() async { + try { + // 尝试静默登录 + final GoogleSignInAccount? googleAccount = + await _googleSignIn.signInSilently(); + + if (googleAccount == null) return null; + + // 获取认证信息 + final GoogleSignInAuthentication googleAuth = + await googleAccount.authentication; + + // 创建Firebase凭证 + final OAuthCredential credential = GoogleAuthProvider.credential( + accessToken: googleAuth.accessToken, + idToken: googleAuth.idToken, + ); + + // 使用凭证登录Firebase + final UserCredential userCredential = + await FirebaseAuth.instance.signInWithCredential(credential); + + return userCredential.user; + } catch (e) { + print('Silent sign-in error: $e'); + return null; + } + } + + // 登出 + static Future signOutMethod() async { + try { + await _googleSignIn.signOut(); + await FirebaseAuth.instance.signOut(); + } catch (e) { + print('Sign out error: $e'); + rethrow; + } + } + + // 获取用户信息 + static Map? getUserInfo() { + final user = currentUser; + if (user == null) return null; + + return { + 'uid': user.uid, + 'name': user.displayName, + 'email': user.email, + 'photoUrl': user.photoURL, + 'isEmailVerified': user.emailVerified, + }; + } +} \ No newline at end of file diff --git a/lib/chatvibe_core/utilities/at_heartbeat_utils.dart b/lib/chatvibe_core/utilities/at_heartbeat_utils.dart new file mode 100644 index 0000000..324d9f9 --- /dev/null +++ b/lib/chatvibe_core/utilities/at_heartbeat_utils.dart @@ -0,0 +1,67 @@ +import 'dart:async'; + +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; + +import '../../chatvibe_data/models/enum/at_heartbeat_status.dart'; + + +class ATHeartbeatUtils { + static Timer? _heartbeatTimer; + static Timer? _AnchorheartbeatTimer; + static String _currentStatus = ATHeartbeatStatus.ONLINE.name; + static String? _roomId; + static bool _upMick = false; + + ///定时发送心跳 + static void scheduleHeartbeat( + String status, + bool upMick, { + String? roomId, + }) async { + _currentStatus = status; + _upMick = upMick; + _roomId = roomId; + AccountRepository().heartbeat( + _currentStatus, + _upMick, + roomId: _roomId, + ).whenComplete((){ + try{ + _heartbeatTimer ??= Timer.periodic(Duration(seconds: 60), (timer) { + AccountRepository().heartbeat(_currentStatus, _upMick, roomId: _roomId); + }); + }catch(e){ + } + }); + + } + + static void cancelTimer() { + _heartbeatTimer?.cancel(); + _heartbeatTimer = null; + _roomId = null; + cancelAnchorTimer(); + } + + ///上麦主播发送心跳 + static void scheduleAnchorHeartbeat(String roomId) async { + cancelAnchorTimer(); + scheduleHeartbeat(_currentStatus, true, roomId: roomId); + AccountRepository().anchorHeartbeat(roomId).whenComplete(() { + _AnchorheartbeatTimer ??= Timer.periodic(Duration(seconds: 60), (timer) { + try{ + AccountRepository().anchorHeartbeat(roomId); + }catch(e){ + + } + + }); + }); + } + + static void cancelAnchorTimer() { + _AnchorheartbeatTimer?.cancel(); + _AnchorheartbeatTimer = null; + _upMick = false; + } +} diff --git a/lib/chatvibe_core/utilities/at_keybord_util.dart b/lib/chatvibe_core/utilities/at_keybord_util.dart new file mode 100644 index 0000000..4bc7760 --- /dev/null +++ b/lib/chatvibe_core/utilities/at_keybord_util.dart @@ -0,0 +1,64 @@ +import 'package:flutter/cupertino.dart'; + +class ATKeybordUtil { + static final List _listeners = []; + static bool _isKeyboardVisible = false; + static double _keyboardHeight = 0.0; + + /// 添加全局键盘监听 + static void onChange(Function(bool visible) listener) { + _listeners.add(listener); + // 立即通知当前状态 + listener(_isKeyboardVisible); + } + + /// 移除监听器 + static void removeListener(Function(bool visible) listener) { + _listeners.remove(listener); + } + + /// 初始化键盘监听(必须在MaterialApp外层调用) + static void initOperation() { + WidgetsBinding.instance.addObserver(_KeyboardObserver()); + } + + /// 获取当前键盘是否可见 + static bool get isVisible => _isKeyboardVisible; + + /// 获取当前键盘高度 + static double get height => _keyboardHeight; + + /// 隐藏键盘 + static void conceal(BuildContext context) { + FocusScope.of(context).unfocus(); + } + + /// 更新键盘状态(内部使用) + static void _updateState(bool visible, double height) { + if (visible != _isKeyboardVisible || height != _keyboardHeight) { + _isKeyboardVisible = visible; + _keyboardHeight = height; + + // 通知所有监听器 + for (final listener in _listeners) { + listener(visible); + } + } + } +} + +// 键盘状态观察者 +class _KeyboardObserver extends WidgetsBindingObserver { + @override + void didChangeMetrics() { + final view = WidgetsBinding.instance.platformDispatcher.views.first; + + if (view.viewInsets.bottom > 0) { + // 键盘弹出 + ATKeybordUtil._updateState(true, view.viewInsets.bottom); + } else { + // 键盘收起 + ATKeybordUtil._updateState(false, 0.0); + } + } +} \ No newline at end of file diff --git a/lib/chatvibe_core/utilities/at_lk_dialog_util.dart b/lib/chatvibe_core/utilities/at_lk_dialog_util.dart new file mode 100644 index 0000000..5535bab --- /dev/null +++ b/lib/chatvibe_core/utilities/at_lk_dialog_util.dart @@ -0,0 +1,654 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/at_lk_tap_widget.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; + +void showEnterRoomConfirm( + String roomId, + BuildContext context, { + bool needOpenRedenvelope = false, + bool fromFloting = false, + String redPackId = "", + bool openGameDialog = false, + bool openGiftDialogToLuckyGift = false, + bool openGiftDialogToGift = false, + bool needShowEnterRoomConfirm = true, +}) { + if (roomId.isNotEmpty) { + if(needShowEnterRoomConfirm){ + SmartDialog.dismiss(tag: "showConfirmDialog"); + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: + fromFloting + ? ATAppLocalizations.of(context)!.enterThisVoiceChatRoom + : ATAppLocalizations.of(context)!.tips, + msg: + fromFloting + ? ATAppLocalizations.of( + context, + )!.swipeLeftOnTheFloatingScreenAreaToQuicklyCloseIt + : ATAppLocalizations.of(context)!.enterRoomConfirmTips, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + Provider.of(context, listen: false).joinRoom( + context, + roomId, + needOpenRedenvelope: needOpenRedenvelope, + redPackId: redPackId, + openGameDialog: openGameDialog, + openGiftDialogToLuckyGift: openGiftDialogToLuckyGift, + openGiftDialogToGift: openGiftDialogToGift, + ); + }, + ); + }, + ); + }else{ + Provider.of(context, listen: false).joinRoom( + context, + roomId, + needOpenRedenvelope: needOpenRedenvelope, + redPackId: redPackId, + openGameDialog: openGameDialog, + openGiftDialogToLuckyGift: openGiftDialogToLuckyGift, + openGiftDialogToGift: openGiftDialogToGift, + ); + } + } +} + +///中间对话框 半透明 +Future showCenterDialog( + BuildContext context, + Widget child, { + Color? barrierColor, +}) { + return showGeneralDialog( + context: context, + barrierLabel: '', + barrierDismissible: true, + transitionDuration: Duration(milliseconds: 350), + barrierColor: barrierColor ?? Colors.black.withOpacity(0.2), + pageBuilder: ( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return Center(child: child); + }, + ); +} + +///中间对话框 半透明 +showCenterAnimationDialog(BuildContext context, Widget child) { + showGeneralDialog( + context: context, + barrierLabel: '', + barrierDismissible: true, + transitionDuration: Duration(milliseconds: 800), + barrierColor: Colors.black.withOpacity(0.2), + pageBuilder: ( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + print( + 'showCenterAnimationDialog:${animation.value},${secondaryAnimation.value}', + ); + CurvedAnimation curvedAnimation = CurvedAnimation( + curve: Curves.bounceOut, + reverseCurve: Curves.easeInExpo, + parent: animation, + ); + return Center( + child: ScaleTransition( + scale: Tween(begin: 0.0, end: 1.0).animate(curvedAnimation), + child: SlideTransition( + position: Tween( + begin: Offset(0.0, 0.0), + end: Offset(0.0, 3.0), + ).animate(secondaryAnimation), + child: FadeTransition( + opacity: Tween(begin: 0.0, end: 1.0).animate(curvedAnimation), + child: child, + ), + ), + ), + ); + }, + ); +} + +///中间对话框 半透明(重下面弹出) +showBottomInCenterDialog(BuildContext context, Widget child) { + showGeneralDialog( + context: context, + barrierLabel: '', + barrierDismissible: true, + transitionDuration: Duration(milliseconds: 350), + barrierColor: Colors.black.withOpacity(0.6), + pageBuilder: ( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return SlideTransition( + position: Tween( + begin: Offset(0.0, 2.0), + end: Offset(0.0, 0.0), + ).chain(CurveTween(curve: Curves.ease)).animate(animation), + child: Center(child: child), + ); + }, + ); +} + +///中间对话框 半透明 点外部不消失 +showCenterDialog2(BuildContext context, Widget child, {Color? mask}) { + showGeneralDialog( + context: context, + barrierLabel: '', + barrierDismissible: false, + transitionDuration: Duration(milliseconds: 350), + barrierColor: mask ?? Colors.black.withOpacity(0.6), + pageBuilder: ( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return Center(child: child); + }, + ); +} + +///底部对话框 透明 +showBottomDialog( + BuildContext context, + Widget child, { + Color? barrierColor, + Duration? transitionDuration, +}) { + showGeneralDialog( + context: context, + barrierLabel: '', + barrierDismissible: true, + barrierColor: barrierColor ?? Colors.transparent, + transitionDuration: transitionDuration ?? Duration(milliseconds: 350), + pageBuilder: ( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return Column( + children: [Expanded(child: SizedBox(width: width(1))), child], + ); + }, + ); +} + +///底部对话框 透明(从下面弹出) +showBottomInBottomDialog( + BuildContext context, + Widget child, { + Color? barrierColor, + bool barrierDismissible = true, +}) { + showGeneralDialog( + context: context, + barrierLabel: '', + barrierDismissible: barrierDismissible, + transitionDuration: Duration(milliseconds: 325), + barrierColor: barrierColor ?? Colors.black.withOpacity(0.3), + pageBuilder: ( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return Column( + children: [ + Expanded(child: SizedBox(width: width(1))), + SlideTransition( + position: Tween( + begin: Offset(0.0, 2.0), + end: Offset(0.0, 0.0), + ).chain(CurveTween(curve: Curves.ease)).animate(animation), + child: child, + ), + ], + ); + }, + ); +} + +///底部对话框 半透明 +showBottomDialog2(BuildContext context, Widget child) { + showGeneralDialog( + context: context, + barrierLabel: '', + barrierDismissible: true, + transitionDuration: Duration(milliseconds: 350), + barrierColor: Colors.black.withOpacity(0.2), + pageBuilder: ( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return Column( + children: [Expanded(child: SizedBox(width: width(1))), child], + ); + }, + ); +} + +///显示菜单 +showMenuPop(BuildContext context, Widget child) { + showGeneralDialog( + context: context, + barrierLabel: '', + barrierDismissible: true, + transitionDuration: Duration(milliseconds: 350), + pageBuilder: ( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return Row( + children: [ + Spacer(), + Column( + children: [SizedBox(height: height(55)), child, Spacer()], + ), + SizedBox(width: width(15)), + ], + ); + }, + ); +} + +class ATForceDialog extends StatelessWidget { + final String msg; + final Function onClick; + + const ATForceDialog({Key? key, required this.msg, required this.onClick}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + width: width(269), + decoration: ShapeDecoration( + color: Color(0xffffffff), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(width(10))), + ), + ), + child: _buildDialog_routine(context), + ); + } + + Widget _buildDialog_routine(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: EdgeInsets.symmetric( + horizontal: width(30), + vertical: height(15), + ), + alignment: Alignment.center, + child: Text( + msg, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: sp(16), + color: Color(0xff333333), + fontWeight: FontWeight.w500, + decoration: TextDecoration.none, + ), + ), + ), + Divider(height: height(1), color: ChatVibeTheme.dividerColor), + Container( + height: height(46), + alignment: Alignment.center, + child: Row( + children: [ + Expanded( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + child: Text( + "确定", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: sp(16), + color: Color(0xfFFFF4FBB), + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + ), + ), + onTap: () { + Navigator.of(context).pop(); + if (onClick != null) { + onClick(); + } + }, + ), + ), + ], + ), + ), + ], + ); + } +} + +///提示对话框 +class ATTsDialog extends StatelessWidget { + final String msg; + final Function onClick; + + const ATTsDialog({Key? key, required this.msg, required this.onClick}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + width: width(269), + decoration: ShapeDecoration( + color: Color(0xffffffff), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(width(10))), + ), + ), + child: _buildDialog_routine(context), + ); + } + + Widget _buildDialog_routine(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: EdgeInsets.symmetric( + horizontal: width(30), + vertical: height(25), + ), + alignment: Alignment.center, + child: Text( + msg, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: sp(16), + color: Color(0xff333333), + fontWeight: FontWeight.w500, + decoration: TextDecoration.none, + ), + ), + ), + Divider(height: height(1), color: ChatVibeTheme.dividerColor), + Container( + height: height(46), + alignment: Alignment.center, + child: Row( + children: [ + Expanded( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + child: Text( + "取消", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: sp(16), + color: Color(0xFFAAAAAA), + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + ), + ), + onTap: () { + Navigator.of(context).pop(); + }, + ), + ), + VerticalDivider(width: width(1), color: ChatVibeTheme.dividerColor), + Expanded( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + child: Text( + "确定", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: sp(16), + color: ChatVibeTheme.primaryColor, + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + ), + ), + onTap: () { + Navigator.of(context).pop(); + if (onClick != null) { + onClick(); + } + }, + ), + ), + ], + ), + ), + ], + ); + } +} + +///提示对话框 +class ATTsDialog2 extends StatelessWidget { + final String msg; + final String btmText; + final Function onClick; + + const ATTsDialog2({ + Key? key, + required this.msg, + required this.onClick, + required this.btmText, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + width: width(269), + decoration: ShapeDecoration( + color: Color(0xffffffff), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(width(10))), + ), + ), + child: _buildDialog_routine(context), + ); + } + + Widget _buildDialog_routine(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: EdgeInsets.symmetric( + horizontal: width(30), + vertical: height(15), + ), + alignment: Alignment.center, + child: Text( + msg, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: sp(16), + color: Color(0xff333333), + fontWeight: FontWeight.w500, + decoration: TextDecoration.none, + ), + ), + ), + Divider(color: Color(0xffF3F3F3), endIndent: 15.w, indent: 15.w), + Container( + height: height(46), + alignment: Alignment.center, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + SizedBox(width: 15.w), + Expanded( + child: ATLkTapWidget( + borderRadius: BorderRadius.circular(52), + child: Container( + width: 100.w, + height: 35.w, + alignment: Alignment.center, + decoration: BoxDecoration( + color: Color(0xffF3F3F3), + borderRadius: BorderRadius.circular(52), + ), + child: Text( + "在考虑下", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: sp(16), + color: Color(0xFF333333), + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + ), + ), + ), + onTap: () { + Navigator.of(context).pop(); + }, + ), + ), + SizedBox(width: 35.w), + Expanded( + child: ATLkTapWidget( + borderRadius: BorderRadius.circular(52), + child: Container( + width: 100.w, + height: 35.w, + alignment: Alignment.center, + decoration: BoxDecoration( + color: Color(0xffBA0CF9), + borderRadius: BorderRadius.circular(52), + ), + child: Text( + "$btmText", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: sp(16), + color: Colors.white, + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + ), + ), + ), + onTap: () { + Navigator.of(context).pop(); + if (onClick != null) { + onClick(); + } + }, + ), + ), + SizedBox(width: 15.w), + ], + ), + ), + ], + ); + } +} + +// 提示框 +class ATPromptDialog extends StatelessWidget { + final String msg; + final Function onClick; + + const ATPromptDialog({Key? key, required this.msg, required this.onClick}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + width: width(269), + decoration: ShapeDecoration( + color: Color(0xffffffff), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(width(10))), + ), + ), + child: _buildDialog_routine(context), + ); + } + + Widget _buildDialog_routine(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: EdgeInsets.symmetric( + horizontal: width(30), + vertical: height(15), + ), + alignment: Alignment.center, + child: Text( + msg, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: sp(16), + color: Color(0xff333333), + fontWeight: FontWeight.w500, + decoration: TextDecoration.none, + ), + ), + ), + Divider(height: height(1), color: ChatVibeTheme.dividerColor), + Container( + height: height(46), + alignment: Alignment.center, + child: Row( + children: [ + Expanded( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + child: Text( + "我知道了", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: sp(16), + color: Color(0xffFFE400), + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + ), + ), + onTap: () { + Navigator.of(context).pop(); + if (onClick != null) { + onClick(); + } + }, + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/chatvibe_core/utilities/at_loading_manager.dart b/lib/chatvibe_core/utilities/at_loading_manager.dart new file mode 100644 index 0000000..088ca44 --- /dev/null +++ b/lib/chatvibe_core/utilities/at_loading_manager.dart @@ -0,0 +1,44 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; + +typedef ATLoadingManager = ATProgressIndicator; + +class ATProgressIndicator { + // 显示全局 Loading + static void exhibitOperation({String? message, BuildContext? context, bool cancelable = false}) { + SmartDialog.dismiss(status: SmartStatus.loading); + SmartDialog.showLoading( + alignment: Alignment.center, + maskColor: Colors.transparent, + animationType: SmartAnimationType.fade, + clickMaskDismiss: cancelable, // 使用新的参数 + backType: cancelable ? SmartBackType.normal : SmartBackType.block, + builder: (_) { + return Center( + child: Container( + decoration: BoxDecoration( + color: Colors.black26, + borderRadius: BorderRadius.circular(8), + ), + padding: EdgeInsets.all(2.w), + width: 62.w, + height: 62.w, + child: Image.asset("atu_images/general/at_icon_loading.webp",), + ), + ); + }, + ); + } + + // static bool _backInterceptor(bool stopDefaultButtonEvent, RouteInfo info) { + // // 拦截返回键 + // return true; // 返回 true 表示已处理该事件 + // } + + // 隐藏全局 Loading + static void veilRoutine() { + SmartDialog.dismiss(status: SmartStatus.loading); + } +} diff --git a/lib/chatvibe_core/utilities/at_message_notifier.dart b/lib/chatvibe_core/utilities/at_message_notifier.dart new file mode 100644 index 0000000..de80c20 --- /dev/null +++ b/lib/chatvibe_core/utilities/at_message_notifier.dart @@ -0,0 +1,20 @@ +import 'package:audioplayers/audioplayers.dart'; +class ATMessageNotifier { + + static bool canPlay = true; + static final AudioPlayer _audioPlayer = AudioPlayer(); + static final _audioPlayerCache = AudioCache(prefix: ""); + // 播放提示音 + static Future playNotificationSoundProcess() async { + if(!canPlay){ + return; + } + // _audioPlayer.setVolume(0.2); + try { + _audioPlayer.play(AssetSource("atu_images/msg/im_noti_music.MP3")); + } catch (e) { + print('播放提示音失败: $e'); + } + } + +} \ No newline at end of file diff --git a/lib/chatvibe_core/utilities/at_message_utils.dart b/lib/chatvibe_core/utilities/at_message_utils.dart new file mode 100644 index 0000000..1fe2b72 --- /dev/null +++ b/lib/chatvibe_core/utilities/at_message_utils.dart @@ -0,0 +1,213 @@ +import 'dart:io'; +import 'dart:math'; + +import 'package:flutter_image_compress/flutter_image_compress.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:tencent_cloud_chat_sdk/enum/friend_type_enum.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_online_url.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'; +import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart'; +import 'package:video_thumbnail/video_thumbnail.dart'; + +import 'package:aslan/chatvibe_data/sources/local/file_cache_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_user_red_packet_send_res.dart'; + +class ATMessageUtils { + //私聊红包Future缓存 + static Map> redPacketFutureCache = {}; + + static Future buildImageElem(File file) async { + try { + // 2. 获取文件信息 + final originalSize = file.lengthSync(); + final fileExtension = _getFileExtension(file.path).toLowerCase(); + print('压缩前文件: ${file.path}'); + print('文件类型: $fileExtension'); + print('压缩前大小: ${_formatFileSize(originalSize)}'); + + // 3. 小文件不压缩(小于 100KB) + if (originalSize < 100 * 1024) { + print('文件较小,跳过压缩'); + return file; + } + + // 4. GIF 文件特殊处理 + if (fileExtension == 'gif') { + print('GIF 文件,使用特殊处理'); + return await _compressGifFile_function(file); + } + + // 5. 创建临时目录和文件 + final directory = await getTemporaryDirectory(); + final timestamp = DateTime.now().millisecondsSinceEpoch; + final String fileName = + 'compressed_${timestamp}_${_getFileHash_routine(file)}.jpg'; + final File newFile = File("${directory.path}/$fileName"); + + // 6. 动态调整压缩质量(根据原文件大小) + final int quality = _calculateQuality(originalSize); + print('使用压缩质量: $quality%'); + + // 7. 执行压缩 + final XFile? compressedFile = + await FlutterImageCompress.compressAndGetFile( + file.absolute.path, + newFile.path, + quality: quality, + minWidth: 480, + // 设置最小宽度,保持图片可用性 + minHeight: 480, + // 设置最小高度 + rotate: 0, + format: CompressFormat.jpeg, + inSampleSize: _calculateSampleSize(originalSize), // 采样率,减少内存使用 + ); + + if (compressedFile == null) { + print('压缩失败,返回原文件'); + return file; + } + + // 8. 检查压缩效果 + final compressedSize = await compressedFile.length(); + final compressionRatio = + (originalSize - compressedSize) / originalSize * 100; + + print('压缩后大小: ${_formatFileSize(compressedSize)}'); + print('压缩率: ${compressionRatio.toStringAsFixed(1)}%'); + + // 9. 如果压缩后文件反而更大,返回原文件 + if (compressedSize >= originalSize) { + print('压缩后文件更大,返回原文件'); + await newFile.delete(); // 删除无用的压缩文件 + return file; + } + + // 10. 缓存管理 + File localFile = File(compressedFile.path); + final String cacheKey = _getFileHash_routine(localFile); + await FileCacheManager.getInstance().putFileFromFile(cacheKey, localFile); + + print('压缩成功,发送路径: ${compressedFile.path}'); + return localFile; + } catch (e) { + print('图片压缩异常: $e'); + return file; // 异常时返回原文件,保证功能可用 + } + } + + // 获取文件扩展名 + static String _getFileExtension(String path) { + return path.split('.').last; + } + + // 格式化文件大小显示 + static String _formatFileSize(int bytes) { + if (bytes <= 0) return "0 B"; + const suffixes = ["B", "KB", "MB", "GB"]; + final i = (log(bytes) / log(1024)).floor(); + return '${(bytes / pow(1024, i)).toStringAsFixed(1)} ${suffixes[i]}'; + } + + // 计算压缩质量(根据文件大小动态调整) + static int _calculateQuality(int fileSize) { + if (fileSize > 5 * 1024 * 1024) { + // > 5MB + return 50; + } else if (fileSize > 2 * 1024 * 1024) { + // 2-5MB + return 60; + } else if (fileSize > 1 * 1024 * 1024) { + // 1-2MB + return 70; + } else if (fileSize > 500 * 1024) { + // 500KB-1MB + return 80; + } else { + return 85; // < 500KB 使用较高质量 + } + } + + // 计算采样率 + static int _calculateSampleSize(int fileSize) { + if (fileSize > 5 * 1024 * 1024) return 4; + if (fileSize > 2 * 1024 * 1024) return 3; + if (fileSize > 1 * 1024 * 1024) return 2; + return 1; + } + + // 生成文件哈希(用于缓存键) + static String _getFileHash_routine(File file) { + final stats = file.statSync(); + return '${file.path}_${stats.modified.millisecondsSinceEpoch}'.hashCode + .toString(); + } + + // GIF 文件压缩处理 + static Future _compressGifFile_function(File file) async { + // 对于 GIF,我们可以考虑: + // 1. 使用其他库处理(如 image 包) + // 2. 调整尺寸但保持动图特性 + // 3. 或者直接返回原文件(保持动图效果) + + // 这里简单返回原文件,避免破坏 GIF 动画 + print('GIF 文件保持原样发送'); + return file; + } + + static Future produceFileThumbnailProcess( + String videoPath, + int maxHeight, + ) async { + try { + final thumbnailPath = await VideoThumbnail.thumbnailFile( + video: videoPath, + thumbnailPath: (await getTemporaryDirectory()).path, + // 保存到临时目录 + maxHeight: maxHeight, + // 指定高度,宽度按比例缩放 + imageFormat: ImageFormat.PNG, + quality: 75, + ); + return thumbnailPath; // 返回文件路径,可用于 Image.file(File(thumbnailPath!)) + } catch (e) { + print("生成缩略图文件时出错: $e"); + return null; + } + } + + ///下载多媒体消息 + static Future downloadMessageProcess({ + required String msgID, + required int messageType, + required int imageType, // 图片类型,仅messageType为图片消息是有效 + required bool isSnapshot, // 是否是视频封面,仅messageType为视频消息是有效 + }) async { + var result = await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .downloadMessage( + msgID: msgID, + messageType: messageType, + imageType: imageType, + isSnapshot: isSnapshot, + ); + return result; + } + + Future> getMessageOnlineUrl({ + required String msgID, + }) async { + var result = await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .getMessageOnlineUrl(msgID: msgID); + return result; + } + + /// 清空与该联系人的所有单聊消息记录 + static Future clearAllMessagesProcess(String userId) async { + V2TimCallback clearResult = await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .clearC2CHistoryMessage(userID: userId); // 指定要清空消息的联系人ID[citation:2] + } +} diff --git a/lib/chatvibe_core/utilities/at_path_utils.dart b/lib/chatvibe_core/utilities/at_path_utils.dart new file mode 100644 index 0000000..bfa1e81 --- /dev/null +++ b/lib/chatvibe_core/utilities/at_path_utils.dart @@ -0,0 +1,165 @@ +import 'package:path/path.dart' as path; + +class ATPathUtils { + /// 判断路径类型 + static PathType collectPathType(String uri) { + if (uri.isEmpty) return PathType.unknown; + + // 1. 检查网络地址 + if (_isNetworkUrl_method(uri)) return PathType.network; + + // 2. 检查 Asset 路径 + if (_isAssetPath(uri)) return PathType.asset; + + // 3. 检查文件路径 + if (_isFilePath(uri)) return PathType.file; + + return PathType.unknown; + } + + // 检查是否是网络地址 + static bool _isNetworkUrl_method(String uri) { + final uriPattern = RegExp( + r'^(https?|ftp):\/\/' // 协议 + r'([\w\-]+(\.[\w\-]+)+)' // 域名 + r'(:\d+)?' // 端口 + r'(\/[^\s]*)?$', // 路径 + caseSensitive: false, + ); + + return uriPattern.hasMatch(uri) || uri.startsWith('http'); + } + + // 检查是否是 Asset 路径 + static bool _isAssetPath(String uri) { + // 排除包含特殊字符的情况 + if (uri.contains('://') || uri.contains('\\') || path.isAbsolute(uri)) { + return false; + } + + // 常见的 asset 路径特征 + final isDirectAsset = + uri.startsWith('assets/') || + uri.startsWith('atu_images/') || + uri.startsWith('videos/'); + + // 检查文件扩展名 + final ext = path.extension(uri).toLowerCase(); + final isSupportedAsset = [ + '.png', + '.jpg', + '.jpeg', + '.gif', + '.mp4', + '.vap', + ].contains(ext); + + return isDirectAsset && isSupportedAsset; + } + + // 检查是否是文件路径 + static bool _isFilePath(String uri) { + // 检查文件 URI 格式 + if (uri.startsWith('file://')) return true; + + // 检查绝对路径 + if (path.isAbsolute(uri)) return true; + + // 检查 Windows 路径 + if (uri.contains(':\\') || uri.contains(':/')) return true; + + // 检查常见文件扩展名 + final ext = path.extension(uri).toLowerCase(); + return [ + '.mp4', + '.mov', + '.avi', + '.png', + '.jpg', + '.txt', + '.pdf', + ].contains(ext); + } + + /// 获取路径的文件名(无扩展名) + static String acquireFileNameWithoutExtension(String filePath) { + return path.basenameWithoutExtension(filePath); + } + + /// 获取路径的文件扩展名 + static String fetchFileExtensionFunction(String filePath) { + return path.extension(filePath).toLowerCase(); + } + + /// 从网络地址中提取文件名 + static String retrieveFileNameFromUrl(String url) { + try { + final uri = Uri.parse(url); + return path.basename(uri.path); + } catch (e) { + return 'unknown_file'; + } + } + + static String receiveFileType(String filePath) { + String extension = path.extension(filePath).toLowerCase(); + + // 图片格式 + final imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']; + // 视频格式 + final videoExtensions = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv']; + + if (imageExtensions.contains(extension)) { + return 'image'; + } else if (videoExtensions.contains(extension)) { + return 'video_pic'; + } else { + return 'unknown'; + } + } + + static bool fileTypeIsPicAndMp(String filePath) { + String extension = path.extension(filePath).toLowerCase(); + + // 图片格式 + final imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']; + // 视频格式 + final videoExtensions = ['.mp4']; + if (imageExtensions.contains(extension) || + videoExtensions.contains(extension)) { + return true; + } + return false; + } + + + static bool fileTypeIsPic(String filePath) { + String extension = path.extension(filePath).toLowerCase(); + + // 图片格式 + // final imageExtensions = ['.jpg', '.jpeg', '.png', '.bmp', '.webp']; + final imageExtensions = ['.jpg', '.jpeg', '.png']; + if (imageExtensions.contains(extension)) { + return true; + } + return false; + } + + static bool fileTypeIsPicOperation(String filePath) { + String extension = path.extension(filePath).toLowerCase(); + + // 图片格式 + final imageExtensions = ['.jpg', '.jpeg', '.png', '.bmp', '.webp']; + if (imageExtensions.contains(extension)) { + return true; + } + return false; + } +} + +enum PathType { + asset, // 资源文件 (assets/) + file, // 本地文件路径 + network, // 网络地址 + unknown, // 未知类型 +} diff --git a/lib/chatvibe_core/utilities/at_permission_utils.dart b/lib/chatvibe_core/utilities/at_permission_utils.dart new file mode 100644 index 0000000..6d3e11c --- /dev/null +++ b/lib/chatvibe_core/utilities/at_permission_utils.dart @@ -0,0 +1,23 @@ +import 'package:permission_handler/permission_handler.dart'; + +class ATPermissionUtils{ + static Future checkMicrophonePermission() async { + // 检查当前权限状态 + var status = await Permission.microphone.status; + if (status.isDenied) { + // 请求权限(弹出系统弹窗) + status = await Permission.microphone.request(); + } + return status.isGranted; + } + + static Future inspectPhotosPermission() async { + // 检查当前权限状态 + var status = await Permission.photos.status; + if (status.isDenied) { + // 请求权限(弹出系统弹窗) + status = await Permission.photos.request(); + } + return status.isGranted; + } +} \ No newline at end of file diff --git a/lib/chatvibe_core/utilities/at_pick_utils.dart b/lib/chatvibe_core/utilities/at_pick_utils.dart new file mode 100644 index 0000000..e0812d2 --- /dev/null +++ b/lib/chatvibe_core/utilities/at_pick_utils.dart @@ -0,0 +1,244 @@ +import 'dart:io'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/utilities/at_path_utils.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/general_repository_imp.dart'; +import 'package:aslan/chatvibe_features/user/crop/crop_image_page.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; + +class ATPickUtils { + static final ImagePicker _picker = ImagePicker(); + + static void pickImageProcess( + BuildContext context, + OnUpLoadCallBack onUpLoadCallBack, { + double? aspectRatio, + bool? backOriginalFile = true, + bool? neeCrop = true, + bool canUploadGif = false, + }) async { + try { + final XFile? pickedFile = await _picker.pickImage( + source: ImageSource.gallery, + imageQuality: canUploadGif ? null : 90, + maxWidth: canUploadGif ? null : 1920, + maxHeight: canUploadGif ? null : 1080, + ); + + if (pickedFile == null) return; + if (!canUploadGif) { + if (!ATPathUtils.fileTypeIsPic(pickedFile.path)) { + ATTts.show("Please select images in .jpg, .jpeg, .png format."); + return; + } + // 检查是否为 GIF 图像 + if (await _isGifImage_function(pickedFile)) { + ATTts.show("GIF atu_images are not supported"); + return; + } + } else { + if (!await _isGifImage_function(pickedFile)) { + ATTts.show("Please select the GIF image"); + return; + } + } + final File imageFile = File(pickedFile.path); + if (imageFile.lengthSync() > 2000000) { + ATTts.show(ATAppLocalizations.of(context)!.theImageSizeCannotExceed); + onUpLoadCallBack?.call(false, ""); + return; + } + if (neeCrop ?? false) { + cropImage( + imageFile, + context, + aspectRatio, + backOriginalFile, + onUpLoadCallBack, + ); + } else { + ATLoadingManager.exhibitOperation(context: context); + try { + String fileUrl = await GeneralRepositoryImp().upload(imageFile); + ATLoadingManager.veilRoutine(); + onUpLoadCallBack?.call(true, fileUrl); + } catch (e) { + ATTts.show("upload fail $e"); + onUpLoadCallBack?.call(false, ""); + ATLoadingManager.veilRoutine(); + } + } + } catch (e) { + onUpLoadCallBack?.call(false, ""); + print("Image selection error: $e"); + ATTts.show("Failed to select image"); + } + } + + /// 检查图像是否为 GIF 格式 + static Future _isGifImage_function(XFile file) async { + try { + // 方法1: 通过文件扩展名检查 + if (file.path.toLowerCase().endsWith('.gif')) { + return true; + } + + // 方法2: 通过读取文件头检查 (更可靠) + final bytes = await file.readAsBytes(); + if (bytes.length >= 6) { + // GIF 文件头特征: "GIF87a" 或 "GIF89a" + final header = String.fromCharCodes(bytes.sublist(0, 6)); + return header == 'GIF87a' || header == 'GIF89a'; + } + + return false; + } catch (e) { + print("Error checking GIF: $e"); + return false; + } + } + + static Future _isWebpImage(XFile file) async { + try { + // 方法1: 通过文件扩展名检查 + if (file.path.toLowerCase().endsWith('.webp')) { + return true; + } + // 方法2: 通过读取文件头检查 + final bytes = await file.readAsBytes(); + + if (bytes.length < 12) { + return false; + } + + // 检查基本的 WebP 文件头 + final riffHeader = String.fromCharCodes(bytes.sublist(0, 4)); + final webpHeader = String.fromCharCodes(bytes.sublist(8, 12)); + + if (riffHeader == 'RIFF' && webpHeader == 'WEBP') { + return true; + } + + // 检查扩展的 WebP 格式(如果有更多字节可用) + if (bytes.length >= 16) { + // 检查 VP8 (有损) WebP + if (bytes[12] == 0x56 && + bytes[13] == 0x50 && + bytes[14] == 0x38 && + bytes[15] == 0x20) { + return true; + } + + // 检查 VP8L (无损) WebP + if (bytes[12] == 0x56 && + bytes[13] == 0x50 && + bytes[14] == 0x38 && + bytes[15] == 0x4C) { + return true; + } + + // 检查 VP8X (扩展) WebP + if (bytes[12] == 0x56 && + bytes[13] == 0x50 && + bytes[14] == 0x38 && + bytes[15] == 0x58) { + return true; + } + } + + return false; + } catch (e) { + print("Error checking WebP: $e"); + return false; + } + } + + /// 拍照并过滤 GIF + static void takePhoto( + BuildContext context, + OnUpLoadCallBack onUpLoadCallBack, { + double? aspectRatio, + bool? backOriginalFile = true, + }) async { + try { + final XFile? pickedFile = await _picker.pickImage( + source: ImageSource.camera, + imageQuality: 90, + maxWidth: 1920, + maxHeight: 1080, + ); + + if (pickedFile == null) return; + + // 相机拍摄的照片不会是 GIF,但为了安全也可以检查 + if (await _isGifImage_function(pickedFile)) { + ATTts.show("Unsupported image format"); + return; + } + + final File imageFile = File(pickedFile.path); + cropImage( + imageFile, + context, + aspectRatio, + backOriginalFile, + onUpLoadCallBack, + ); + } catch (e) { + print("Camera error: $e"); + ATTts.show("Camera failed"); + } + } + + /// 选择视频 (不涉及 GIF 过滤) + static Future pickVideo(BuildContext context) async { + try { + final XFile? videoFile = await _picker.pickVideo( + source: ImageSource.gallery, + ); + + if (videoFile != null) { + return File(videoFile.path); + } + return null; + } catch (e) { + print("Video selection error: $e"); + ATTts.show("Failed to select video_pic"); + return null; + } + } + + // 原有的 cropImage 方法保持不变 + static void cropImage( + File originalImage, + BuildContext context, + double? aspectRatio, + bool? backOriginalFile, + OnUpLoadCallBack onUpLoadCallBack, { + bool needUpload = true, + }) async { + if (originalImage == null) { + return; + } + await Navigator.push( + context, + MaterialPageRoute( + builder: + (context) => CropImagePage( + originalImage, + aspectRatio: aspectRatio, + backOriginalFile: backOriginalFile, + needUpload: needUpload, + onUpLoadCallBack: (success, url) { + if (url.isNotEmpty) { + onUpLoadCallBack(success, url); + } + }, + ), + ), + ); + } +} diff --git a/lib/chatvibe_core/utilities/at_reg_exp_utils.dart b/lib/chatvibe_core/utilities/at_reg_exp_utils.dart new file mode 100644 index 0000000..744d3bd --- /dev/null +++ b/lib/chatvibe_core/utilities/at_reg_exp_utils.dart @@ -0,0 +1,24 @@ +class ATRegExpUtils { + static String regExImg = "]*>([\\s\\S]*?)"; + + static RegExp regExNicname = RegExp( + r'[\u0300-\u036f\u1AB0-\u1AFF\u1DC0-\u1DFF]', + ); + static var reg = RegExp(regExImg); + + ///检索At用户名 + static String collectAtNameRoutine(String input) { + if (reg.hasMatch(input)) { + return reg.firstMatch(input)!.group(1) ?? ""; + } + return ""; + } + + static String obtainContent(String input) { + String content = input.replaceAll( + reg.firstMatch(input)![0] ?? "", + "@${collectAtNameRoutine(input)}", + ); + return content; + } +} diff --git a/lib/chatvibe_core/utilities/at_room_utils.dart b/lib/chatvibe_core/utilities/at_room_utils.dart new file mode 100644 index 0000000..d661818 --- /dev/null +++ b/lib/chatvibe_core/utilities/at_room_utils.dart @@ -0,0 +1,530 @@ +import 'dart:math'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_features/wallet/wallet_route.dart'; +import 'package:aslan/chatvibe_features/room/voice_room_route.dart'; +import 'package:aslan/chatvibe_features/webview/game_bs_webview_page.dart'; +import 'package:aslan/chatvibe_features/webview/game_lx_webview_page.dart'; +import 'package:aslan/chatvibe_features/webview/game_ry_webview_page.dart'; +import 'package:aslan/chatvibe_features/webview/game_ym_webview_page.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/redpack/room_redenvelope_rec_record_page.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/rocket/room_rocket_playing_anim_page.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_float_ichart.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_gift_vap_svga_manager.dart'; +import 'package:aslan/chatvibe_core/utilities/at_lk_dialog_util.dart'; + +import '../../chatvibe_data/models/enum/at_game_mode_type.dart'; +import '../../chatvibe_data/models/enum/at_game_origin_type.dart'; +import '../../chatvibe_data/models/enum/at_vip_type.dart'; +import '../../chatvibe_domain/models/res/at_get_list_game_config_res.dart'; + +typedef ATRoomUtils = ATChatRoomHelper; + +class ATChatRoomHelper { + static Map roomUsersMap = {}; + + static void goRoom( String roomId, + BuildContext context, { + bool needOpenRedenvelope = false, + bool fromFloting = false, + String redPackId = "", + bool openGameDialog = false, + bool openGiftDialogToLuckyGift = false, + bool openGiftDialogToGift = false, + bool needShowEnterRoomConfirm = true, + }) { + if (Provider + .of(context, listen: false) + .currenRoom != null) { + if (ATFloatIchart().isShow()) { + ///房间最小化了 + if (Provider + .of( + context, + listen: false, + ) + .currenRoom + ?.roomProfile + ?.roomProfile + ?.id == + roomId) { + ATRoomUtils.openCurrentRoom( + context, + needOpenRedenvelope: needOpenRedenvelope, + redPackId: redPackId, + openGameDialog: openGameDialog, + openGiftDialogToLuckyGift: openGiftDialogToLuckyGift, + openGiftDialogToGift: openGiftDialogToGift, + ); + } else { + showEnterRoomConfirm( + roomId, + context, + needOpenRedenvelope: needOpenRedenvelope, + fromFloting: fromFloting, + redPackId: redPackId, + openGameDialog: openGameDialog, + openGiftDialogToLuckyGift: openGiftDialogToLuckyGift, + openGiftDialogToGift: openGiftDialogToGift, + needShowEnterRoomConfirm: needShowEnterRoomConfirm, + ); + } + } else { + if (Provider + .of( + context, + listen: false, + ) + .currenRoom + ?.roomProfile + ?.roomProfile + ?.id == + roomId) { + ///正在当前房间,并且房间页面处于打开状态 + if (needOpenRedenvelope) { + Provider.of( + context, + listen: false, + ).openRedEnvelope(redPackId ?? ""); + } + } else { + showEnterRoomConfirm( + roomId, + context, + needOpenRedenvelope: needOpenRedenvelope, + fromFloting: fromFloting, + redPackId: redPackId, + openGameDialog: openGameDialog, + openGiftDialogToLuckyGift: openGiftDialogToLuckyGift, + openGiftDialogToGift: openGiftDialogToGift, + needShowEnterRoomConfirm: needShowEnterRoomConfirm, + ); + } + } + } else { + showEnterRoomConfirm( + roomId, + context, + needOpenRedenvelope: needOpenRedenvelope, + fromFloting: fromFloting, + redPackId: redPackId, + openGameDialog: openGameDialog, + openGiftDialogToLuckyGift: openGiftDialogToLuckyGift, + openGiftDialogToGift: openGiftDialogToGift, + needShowEnterRoomConfirm: needShowEnterRoomConfirm, + ); + } + } + + static void openCurrentRoom(BuildContext context, { + bool needOpenRedenvelope = false, + String? redPackId, + bool openGameDialog = false, + bool openGiftDialogToLuckyGift = false, + bool openGiftDialogToGift = false, + }) { + ATFloatIchart().remove(); + Provider.of(context, listen: false) + .loadRoomInfo( + Provider + .of( + context, + listen: false, + ) + .currenRoom + ?.roomProfile + ?.roomProfile + ?.id ?? + "", + ); + Provider.of(context, listen: false) + .getMyUserInfo(); + Provider.of(context, listen: false) + .getMicList(); + Provider + .of(context, listen: false) + .closeFullGame = true; + ATNavigatorUtils.push( + context, + '${VoiceRoomRoute.voiceRoom}?id=${Provider + .of(context, listen: false) + .currenRoom + ?.roomProfile + ?.roomProfile + ?.id}', + ); + if (needOpenRedenvelope) { + Provider.of( + context, + listen: false, + ).openRedEnvelope(redPackId ?? ""); + } + if (openGameDialog) { + Provider.of(context, listen: false).openGameDialog(); + } + + if (openGiftDialogToLuckyGift) { + Provider.of( + context, + listen: false, + ).openGiftDialogToLuckyGift(); + } + if (openGiftDialogToGift) { + Provider.of(context, listen: false).openGiftDialogToGift(); + } + } + + static void playRoomRocketAnim(String cover, + String rewardType, + int rocketLevel,) { + SmartDialog.show( + tag: "showRoomRocketAnimDialog", + alignment: Alignment.bottomCenter, + debounce: true, + animationType: SmartAnimationType.fade, + maskColor: Colors.transparent, + clickMaskDismiss: false, + backType: SmartBackType.block, + onDismiss: () { + ATGiftVapSvgaManager().resumeAnim(); + }, + builder: (_) { + return RoomRocketPlayingAnimPage(cover, rewardType, rocketLevel); + }, + ); + } + + static double collectCurrenProgressProcess(int currentEnergy, + int maxEnergy, + double maxWidth,) { + var progress = (currentEnergy / maxEnergy); + var curren = progress * maxWidth; + if (curren > maxWidth) { + return maxWidth; + } else { + return curren; + } + } + + static void getRoomRedPacket(String packetId) { + ChatRoomRepository().roomRedPacketGrab(packetId).then((result) { + SmartDialog.dismiss(tag: "showRoomRedenvelopeList"); + SmartDialog.dismiss(tag: "showRoomRedenvelopeRec"); + SmartDialog.show( + tag: "showRoomRedenvelopeRec", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return RoomRedenvelopeRecRecordPage(packetId); + }, + ); + }); + } + + static void goRechargeOperation(BuildContext context) { + SmartDialog.dismiss(tag: "showGoToRecharge"); + SmartDialog.show( + tag: "showGoToRecharge", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return Container( + width: ScreenUtil().screenWidth * 0.75, + height: 120.w, + padding: EdgeInsets.symmetric(horizontal: 10.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(12.w)), + ), + child: Column( + children: [ + SizedBox(height: 8.w), + Row( + children: [ + Spacer(), + GestureDetector( + child: Icon( + Icons.close, + color: Colors.black, + size: 20.w, + ), + onTap: () { + SmartDialog.dismiss(tag: "showGoToRecharge"); + }, + ), + SizedBox(width: 10.w), + ], + ), + Row( + children: [ + Expanded( + child: text( + ATAppLocalizations.of( + context, + )!.insufhcientGoldsGoToRecharge, + textColor: Colors.black, + textAlign: TextAlign.center, + fontSize: 13.sp, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + SizedBox(height: 10.w), + GestureDetector( + child: Container( + alignment: Alignment.center, + height: 38.w, + width: 125.w, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xFFFEB219), + Color(0xFFFF9326), + ], + ), + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + child: text( + ATAppLocalizations.of(context)!.goToRecharge, + textColor: Colors.white, + fontSize: 13.sp, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + closeAllDialogs(); + ATNavigatorUtils.push( + context, + WalletRoute.recharge, + replace: false, + ); + }, + ), + ], + ), + ); + }, + ); + } + + static int collectRandomInt(int min, int max) { + final random = Random(); + return min + random.nextInt(max - min + 1); + } + + ///房间游客是否可以发消息 + static bool touristCanMsg(BuildContext context) { + ///游客可以发送文字 + if (AccountStorage() + .getVIP() + ?.name == ATVIPType.KING.name) { + ///vip5无视所有规则 + } else { + if (Provider + .of( + context, + listen: false, + ) + .currenRoom + ?.roomProfile + ?.roomSetting + ?.touristMsg ?? + false) {} else { + if (Provider.of(context, listen: false) + .isTourists()) { + ATTts.show(ATAppLocalizations.of(context)!.touristsCannotSendMessages); + return false; + } + } + } + return true; + } + + static void roomAtGlobalConfig(String roomId) { + ATGlobalConfig.isGiftSpecialEffects = DataPersistence.getBool( + "${AccountStorage() + .getCurrentUser() + ?.userProfile + ?.account}-GiftSpecialEffects", + defaultValue: true, + ); + ATGlobalConfig.isEntryVehicleAnimation = DataPersistence.getBool( + "${AccountStorage() + .getCurrentUser() + ?.userProfile + ?.account}-EntryVehicleAnimation", + defaultValue: true, + ); + if (!ATGlobalConfig.isEntryVehicleAnimation) { + ///如果是开启的状态,还要判断下vip是否过期,过期了需要重置 + var vip = AccountStorage() + .getCurrentUser() + ?.userProfile + ?.getVIP(); + if (vip?.name == ATVIPType.MARQUIS.name ||vip?.name == ATVIPType.DUKE.name || vip?.name == ATVIPType.KING.name|| vip?.name == ATVIPType.EMPEROR.name) { + + } else { + ATGlobalConfig.isEntryVehicleAnimation = true; + DataPersistence.setBool( + "${AccountStorage() + .getCurrentUser() + ?.userProfile + ?.account}-EntryVehicleAnimation", + ATGlobalConfig.isEntryVehicleAnimation, + ); + } + } + ATGlobalConfig.isFloatingAnimationInGlobal = DataPersistence.getBool( + "${AccountStorage() + .getCurrentUser() + ?.userProfile + ?.account}-FloatingAnimationInGlobal", + defaultValue: true, + ); + ATGlobalConfig.isLuckGiftSpecialEffects = DataPersistence.getBool( + "${AccountStorage() + .getCurrentUser() + ?.userProfile + ?.account}-LuckGiftSpecialEffects", + defaultValue: true, + ); + DataPersistence.setLastTimeRoomId(roomId); + } + + static void closeAllDialogs() async { + // 循环检查并关闭弹窗,直到不存在任何弹窗 + while (SmartDialog.checkExist( + dialogTypes: {SmartAllDialogType.custom, SmartAllDialogType.attach}, + )) { + SmartDialog.dismiss(); + await Future.delayed(const Duration(milliseconds: 50)); + } + } + + static String acquireIconInRoomFamilyTagBgRoutine(int familyLevel) { + int level = 0; + if (familyLevel > 0 && familyLevel < 4) { + level = 1; + } else if (familyLevel > 3 && familyLevel < 7) { + level = 2; + } else if (familyLevel > 6 && familyLevel < 10) { + level = 3; + } else { + level = familyLevel; + } + return "atu_images/family/at_icon_inroom_familytag_bg_$level.png"; + } + + static void openRoomGame(BuildContext context, ATGetListGameConfigRes item) { + Navigator.of(context).pop(); + if (item.gameOrigin == ATGameOriginType.LINGXIAN.name) { + showBottomInBottomDialog( + context, + GameLxWebviewPage( + url: item.gameCode ?? "", + height: item.height ?? 420.w, + width: item.width ?? ScreenUtil().screenWidth, + gameId: item.id ?? "", + ), + barrierDismissible: false, + ); + } else if (item.gameOrigin == ATGameOriginType.BAISHUN.name) { + if (item.gameMode == ATGameModeType.CHAT_ROOM.name) { + if (Provider + .of( + context, + listen: false, + ) + .playingLudoGame + ?.gameInfo + ?.id != + item.id) { + if (Provider + .of( + context, + listen: false, + ) + .playingLudoGame != + null) { + ///先退出上一个游戏才能开始下一个游戏 + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.haveGamePlayingTips, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + Provider.of( + context, + listen: false, + ).gameLudoDisband(); + }, + ); + }, + ); + return; + } + } + + if (Provider.of(context, listen: false) + .isFz()) { + Provider.of(context, listen: false) + .gameLudoCreate(item); + } + } else { + showBottomInBottomDialog( + context, + GameBsWebviewPage( + url: item.gameCode ?? "", + height: item.height ?? 420.w, + width: item.width ?? ScreenUtil().screenWidth, + gameId: item.id ?? "", + ), + barrierDismissible: false, + ); + } + } else if (item.gameOrigin == ATGameOriginType.HOTGAME.name) { + showBottomInBottomDialog( + context, + GameRyWebviewPage( + url: item.gameCode ?? "", + height: item.height ?? 420.w, + width: item.width ?? ScreenUtil().screenWidth, + gameId: item.id ?? "", + ), + barrierDismissible: false, + ); + } else if (item.gameOrigin == ATGameOriginType.YOMI.name) { + showBottomInBottomDialog( + context, + GameYmWebviewPage( + url: item.gameCode ?? "", + height: item.height ?? 420.w, + width: item.width ?? ScreenUtil().screenWidth, + gameId: item.id ?? "", + ), + barrierDismissible: false, + ); + } + } +} diff --git a/lib/chatvibe_core/utilities/at_share_utils.dart b/lib/chatvibe_core/utilities/at_share_utils.dart new file mode 100644 index 0000000..aa169ca --- /dev/null +++ b/lib/chatvibe_core/utilities/at_share_utils.dart @@ -0,0 +1,41 @@ +import 'package:flutter/services.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:social_sharing_plus/social_sharing_plus.dart'; + +class ATSharingHelper { + // 分享文本到 Facebook + static Future shareTextToFacebook(String text) async { + await SocialSharingPlus.shareToSocialMedia( + SocialPlatform.facebook, // 指定平台 + text, // 要分享的文本 + isOpenBrowser: false, // 如果 App 未安装,不跳转浏览器 + onAppNotInstalled: () { + // 自定义未安装时的提示 + ATTts.show("Facebook is not installed."); + }, + ); + } + + // 分享文本到 WhatsApp + static Future shareTextToWhatsApp(String text) async { + await SocialSharingPlus.shareToSocialMedia( + SocialPlatform.whatsapp, + text, + isOpenBrowser: false, + onAppNotInstalled: () { + ATTts.show("WhatsApp is not installed."); + }, + ); + } + + /// 直接打开Snapchat并分享文本 + static Future shareToSnapchatDirectlyMethod(String text) async { + const platform = MethodChannel('com.chat.auu.snapchat_share/intent'); + try { + // 调用Android原生Intent分享 + await platform.invokeMethod('shareToSnapchat', {'text': text}); + } on PlatformException catch (e) { + ATTts.show("$e"); + } + } +} diff --git a/lib/chatvibe_core/utilities/at_string_utils.dart b/lib/chatvibe_core/utilities/at_string_utils.dart new file mode 100644 index 0000000..e2a8e95 --- /dev/null +++ b/lib/chatvibe_core/utilities/at_string_utils.dart @@ -0,0 +1,80 @@ +typedef ATStringUtils = ATTextHelper; + +class ATTextHelper { + static bool isUrl(String str) { + if (str.startsWith("http://") || str.startsWith("https://")) { + return true; + } + return false; + } + + ///判断字符串是否为合法的Int + static bool determineifValidInt(String str) { + try { + int.parse(str); + return true; + } catch (e) { + return false; + } + } + + ///判断字符串是否为合法的Double + static bool validateifValidDouble(String str) { + try { + double.parse(str); + return true; + } catch (e) { + return false; + } + } + + ///将文本转成数字 + static int toIntFunction(String? str, {int defalut = -1}) { + if (str == null || str.isEmpty) { + return defalut; + } + var value = 0; + try { + value = int.parse(str); + } catch (e) {} + return value; + } + + static String formatNumber(num number) { + if (number >= 1000000) { + return '${(number / 1000000).toStringAsFixed(1)}M'; + } else if (number >= 1000) { + return '${(number / 1000).toStringAsFixed(1)}K'; + } + return number.toString(); + } + + ///检测文本是否有阿语 + static bool determineifArabicChar(String char) { + if (char.isEmpty) return false; + final code = char.runes.first; + + // 阿拉伯语主要 Unicode 范围: + // 1. 阿拉伯语基本字符 (Arabic): U+0600 - U+06FF + // 2. 阿拉伯语补充 (Arabic Supplement): U+0750 - U+077F + // 3. 阿拉伯语扩展-A (Arabic Extended-A): U+08A0 - U+08FF + // 4. 阿拉伯语扩展-B (Arabic Extended-B): U+0870 - U+089F + // 5. 阿拉伯语表示形式-A (Arabic Presentation Forms-A): U+FB50 - U+FDFF + // 6. 阿拉伯语表示形式-B (Arabic Presentation Forms-B): U+FE70 - U+FEFF + + return ( + // 阿拉伯语基本字符 + (code >= 0x0600 && code <= 0x06FF) || + // 阿拉伯语补充 + (code >= 0x0750 && code <= 0x077F) || + // 阿拉伯语扩展-A + (code >= 0x08A0 && code <= 0x08FF) || + // 阿拉伯语扩展-B + (code >= 0x0870 && code <= 0x089F) || + // 阿拉伯语表示形式-A + (code >= 0xFB50 && code <= 0xFDFF) || + // 阿拉伯语表示形式-B + (code >= 0xFE70 && code <= 0xFEFF) + ); + } +} diff --git a/lib/chatvibe_core/utilities/at_system_message_utils.dart b/lib/chatvibe_core/utilities/at_system_message_utils.dart new file mode 100644 index 0000000..7a7b069 --- /dev/null +++ b/lib/chatvibe_core/utilities/at_system_message_utils.dart @@ -0,0 +1,1378 @@ +import 'dart:convert'; + +import 'package:extended_text/extended_text.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; + +import '../../chatvibe_domain/models/res/at_system_invit_message_res.dart'; + +class ATSystemMessageUtils { + static Widget buildAgentSendInviteHostMessage( + data, + Function(BuildContext ct, String content) showMsgItemMenu, + Function(String id) rejectOpt, + Function(String id) acceptOpt, + BuildContext context, + ) { + String expand = data["expand"] ?? ""; + if (data["content"] != null) { + var bean = ATSystemInvitMessageRes.fromJson(jsonDecode(data["content"])); + return Builder( + builder: (ct) { + return GestureDetector( + onLongPress: () { + showMsgItemMenu(ct, ""); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [BoxShadow(blurRadius: 2.w, color: Colors.black26)], + borderRadius: BorderRadius.only( + topLeft: Radius.circular(0), + topRight: Radius.circular(8.w), + bottomLeft: Radius.circular(8.w), + bottomRight: Radius.circular(8), + ), + ), + padding: EdgeInsets.all(8.0.w), + child: Column( + children: [ + Row( + children: [ + head(url: bean.userAvatar ?? "", width: 65.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + netImage( + url: + Provider.of( + context, + listen: false, + ) + .getCountryByName( + bean.countryName ?? "", + ) + ?.nationalFlag ?? + "", + borderRadius: BorderRadius.all( + Radius.circular(3.w), + ), + width: 19.w, + height: 14.w, + ), + SizedBox(width: 3.w), + Container( + constraints: BoxConstraints(maxWidth: 160.w), + child: text( + bean.userNickname ?? "", + fontWeight: FontWeight.bold, + textColor: Colors.black, + fontSize: 16.sp, + ), + ), + ], + ), + GestureDetector( + child: Container( + child: Row( + textDirection: TextDirection.ltr, + children: [ + text( + "ID:${bean.actualAccount ?? 0}", + fontSize: 12.sp, + textColor: Colors.black, + fontWeight: FontWeight.bold, + ), + SizedBox(width: 5.w), + Image.asset( + "atu_images/room/at_icon_user_card_copy_id.png", + width: 12.w, + height: 12.w, + ), + SizedBox(width: 8.w), + ], + ), + ), + onTap: () { + Clipboard.setData( + ClipboardData(text: bean.actualAccount ?? ""), + ); + ATTts.show( + ATAppLocalizations.of( + context, + )!.copiedToClipboard, + ); + }, + ), + ], + ), + ], + ), + SizedBox(height: 3.w), + Row( + children: [ + SizedBox(width: 10.w), + GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + height: 25.w, + width: 65.w, + decoration: BoxDecoration( + color: Color(0xffFF6464), + borderRadius: BorderRadius.all( + Radius.circular(15.0), + ), + ), + child: text( + ATAppLocalizations.of(context)!.reject, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () { + rejectOpt(expand); + }, + ), + Spacer(), + GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + height: 25.w, + width: 65.w, + decoration: BoxDecoration( + color: Color(0xffFF9500), + borderRadius: BorderRadius.all( + Radius.circular(15.0), + ), + ), + child: text( + ATAppLocalizations.of(context)!.accept, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () { + acceptOpt(expand); + }, + ), + SizedBox(width: 10.w), + ], + ), + Divider(color: Colors.black12, thickness: 0.5, height: 28.w), + ExtendedText( + ATAppLocalizations.of(context)!.inviteYouToBecomeHost, + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle(fontSize: 14.sp, color: Colors.black54), + ), + SizedBox(height: 5.w), + ], + ), + ), + ); + }, + ); + } + return Container(); + } + + static Widget buildBdSendInviteAgentMessage( + data, + Function(BuildContext ct, String content) showMsgItemMenu, + Function(String id) rejectOpt, + Function(String id) acceptOpt, + BuildContext context, + ) { + String expand = data["expand"] ?? ""; + if (data["content"] != null) { + var bean = ATSystemInvitMessageRes.fromJson(jsonDecode(data["content"])); + return Builder( + builder: (ct) { + return GestureDetector( + onLongPress: () { + showMsgItemMenu(ct, ""); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [BoxShadow(blurRadius: 2.w, color: Colors.black26)], + borderRadius: BorderRadius.only( + topLeft: Radius.circular(0), + topRight: Radius.circular(8.w), + bottomLeft: Radius.circular(8.w), + bottomRight: Radius.circular(8), + ), + ), + padding: EdgeInsets.all(8.0.w), + child: Column( + children: [ + Row( + children: [ + head(url: bean.userAvatar ?? "", width: 65.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + netImage( + url: + Provider.of( + context, + listen: false, + ) + .getCountryByName( + bean.countryName ?? "", + ) + ?.nationalFlag ?? + "", + borderRadius: BorderRadius.all( + Radius.circular(3.w), + ), + width: 19.w, + height: 14.w, + ), + SizedBox(width: 3.w), + Container( + constraints: BoxConstraints(maxWidth: 160.w), + child: text( + bean.userNickname ?? "", + fontWeight: FontWeight.bold, + textColor: Colors.black, + fontSize: 16.sp, + ), + ), + ], + ), + GestureDetector( + child: Container( + child: Row( + textDirection: TextDirection.ltr, + children: [ + text( + "ID:${bean.actualAccount ?? 0}", + fontSize: 12.sp, + textColor: Colors.black, + fontWeight: FontWeight.bold, + ), + SizedBox(width: 5.w), + Image.asset( + "atu_images/room/at_icon_user_card_copy_id.png", + width: 12.w, + height: 12.w, + ), + SizedBox(width: 8.w), + ], + ), + ), + onTap: () { + Clipboard.setData( + ClipboardData(text: bean.actualAccount ?? ""), + ); + ATTts.show( + ATAppLocalizations.of( + context, + )!.copiedToClipboard, + ); + }, + ), + ], + ), + ], + ), + SizedBox(height: 3.w), + Row( + children: [ + SizedBox(width: 10.w), + GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + height: 25.w, + width: 65.w, + decoration: BoxDecoration( + color: Color(0xffFF6464), + borderRadius: BorderRadius.all( + Radius.circular(15.0), + ), + ), + child: text( + ATAppLocalizations.of(context)!.reject, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () { + rejectOpt(expand); + }, + ), + Spacer(), + GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + height: 25.w, + width: 65.w, + decoration: BoxDecoration( + color: Color(0xffFF9500), + borderRadius: BorderRadius.all( + Radius.circular(15.0), + ), + ), + child: text( + ATAppLocalizations.of(context)!.accept, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () { + acceptOpt(expand); + }, + ), + SizedBox(width: 10.w), + ], + ), + Divider(color: Colors.black12, thickness: 0.5, height: 28.w), + ExtendedText( + ATAppLocalizations.of(context)!.inviteYouToBecomeAgent, + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle(fontSize: 14.sp, color: Colors.black54), + ), + SizedBox(height: 5.w), + ], + ), + ), + ); + }, + ); + } + return Container(); + } + + static Widget buildBdLeaderInviteBdMessage( + data, + Function(BuildContext ct, String content) showMsgItemMenu, + Function(String id) rejectOpt, + Function(String id) acceptOpt, + BuildContext context, + ) { + String expand = data["expand"] ?? ""; + if (data["content"] != null) { + var bean = ATSystemInvitMessageRes.fromJson(jsonDecode(data["content"])); + return Builder( + builder: (ct) { + return GestureDetector( + onLongPress: () { + showMsgItemMenu(ct, ""); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [BoxShadow(blurRadius: 2.w, color: Colors.black26)], + borderRadius: BorderRadius.only( + topLeft: Radius.circular(0), + topRight: Radius.circular(8.w), + bottomLeft: Radius.circular(8.w), + bottomRight: Radius.circular(8), + ), + ), + padding: EdgeInsets.all(8.0.w), + child: Column( + children: [ + Row( + children: [ + head(url: bean.userAvatar ?? "", width: 65.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + netImage( + url: + Provider.of( + context, + listen: false, + ) + .getCountryByName( + bean.countryName ?? "", + ) + ?.nationalFlag ?? + "", + borderRadius: BorderRadius.all( + Radius.circular(3.w), + ), + width: 19.w, + height: 14.w, + ), + SizedBox(width: 3.w), + Container( + constraints: BoxConstraints(maxWidth: 160.w), + child: text( + bean.userNickname ?? "", + fontWeight: FontWeight.bold, + textColor: Colors.black, + fontSize: 16.sp, + ), + ), + ], + ), + GestureDetector( + child: Container( + child: Row( + textDirection: TextDirection.ltr, + children: [ + text( + "ID:${bean.actualAccount ?? 0}", + fontSize: 12.sp, + textColor: Colors.black, + fontWeight: FontWeight.bold, + ), + SizedBox(width: 5.w), + Image.asset( + "atu_images/room/at_icon_user_card_copy_id.png", + width: 12.w, + height: 12.w, + ), + SizedBox(width: 8.w), + ], + ), + ), + onTap: () { + Clipboard.setData( + ClipboardData(text: bean.actualAccount ?? ""), + ); + ATTts.show( + ATAppLocalizations.of( + context, + )!.copiedToClipboard, + ); + }, + ), + ], + ), + ], + ), + SizedBox(height: 3.w), + Row( + children: [ + SizedBox(width: 10.w), + GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + height: 25.w, + width: 65.w, + decoration: BoxDecoration( + color: Color(0xffFF6464), + borderRadius: BorderRadius.all( + Radius.circular(15.0), + ), + ), + child: text( + ATAppLocalizations.of(context)!.reject, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () { + rejectOpt(expand); + }, + ), + Spacer(), + GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + height: 25.w, + width: 65.w, + decoration: BoxDecoration( + color: Color(0xffFF9500), + borderRadius: BorderRadius.all( + Radius.circular(15.0), + ), + ), + child: text( + ATAppLocalizations.of(context)!.accept, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () { + acceptOpt(expand); + }, + ), + SizedBox(width: 10.w), + ], + ), + Divider(color: Colors.black12, thickness: 0.5, height: 28.w), + ExtendedText( + ATAppLocalizations.of(context)!.inviteYouToBecomeBD, + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle(fontSize: 14.sp, color: Colors.black54), + ), + SizedBox(height: 5.w), + ], + ), + ), + ); + }, + ); + } + return Container(); + } + + static Widget buildBdLeaderInviteBdLeaderMessage( + data, + Function(BuildContext ct, String content) showMsgItemMenu, + Function(String id) rejectOpt, + Function(String id) acceptOpt, + BuildContext context, + ) { + String expand = data["expand"] ?? ""; + if (data["content"] != null) { + var bean = ATSystemInvitMessageRes.fromJson(jsonDecode(data["content"])); + return Builder( + builder: (ct) { + return GestureDetector( + onLongPress: () { + showMsgItemMenu(ct, ""); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [BoxShadow(blurRadius: 2.w, color: Colors.black26)], + borderRadius: BorderRadius.only( + topLeft: Radius.circular(0), + topRight: Radius.circular(8.w), + bottomLeft: Radius.circular(8.w), + bottomRight: Radius.circular(8), + ), + ), + padding: EdgeInsets.all(8.0.w), + child: Column( + children: [ + Row( + children: [ + head(url: bean.userAvatar ?? "", width: 65.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + netImage( + url: + Provider.of( + context, + listen: false, + ) + .getCountryByName( + bean.countryName ?? "", + ) + ?.nationalFlag ?? + "", + borderRadius: BorderRadius.all( + Radius.circular(3.w), + ), + width: 19.w, + height: 14.w, + ), + SizedBox(width: 3.w), + Container( + constraints: BoxConstraints(maxWidth: 160.w), + child: text( + bean.userNickname ?? "", + fontWeight: FontWeight.bold, + textColor: Colors.black, + fontSize: 16.sp, + ), + ), + ], + ), + GestureDetector( + child: Container( + child: Row( + textDirection: TextDirection.ltr, + children: [ + text( + "ID:${bean.actualAccount ?? 0}", + fontSize: 12.sp, + textColor: Colors.black, + fontWeight: FontWeight.bold, + ), + SizedBox(width: 5.w), + Image.asset( + "atu_images/room/at_icon_user_card_copy_id.png", + width: 12.w, + height: 12.w, + ), + SizedBox(width: 8.w), + ], + ), + ), + onTap: () { + Clipboard.setData( + ClipboardData(text: bean.actualAccount ?? ""), + ); + ATTts.show( + ATAppLocalizations.of( + context, + )!.copiedToClipboard, + ); + }, + ), + ], + ), + ], + ), + SizedBox(height: 3.w), + Row( + children: [ + SizedBox(width: 10.w), + GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + height: 25.w, + width: 65.w, + decoration: BoxDecoration( + color: Color(0xffFF6464), + borderRadius: BorderRadius.all( + Radius.circular(15.0), + ), + ), + child: text( + ATAppLocalizations.of(context)!.reject, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () { + rejectOpt(expand); + }, + ), + Spacer(), + GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + height: 25.w, + width: 65.w, + decoration: BoxDecoration( + color: Color(0xffFF9500), + borderRadius: BorderRadius.all( + Radius.circular(15.0), + ), + ), + child: text( + ATAppLocalizations.of(context)!.accept, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () { + acceptOpt(expand); + }, + ), + SizedBox(width: 10.w), + ], + ), + Divider(color: Colors.black12, thickness: 0.5, height: 28.w), + ExtendedText( + ATAppLocalizations.of( + context, + )!.bDLeaderInviteYouToBecomeBDLeader, + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle(fontSize: 14.sp, color: Colors.black54), + ), + SizedBox(height: 5.w), + ], + ), + ), + ); + }, + ); + } + return Container(); + } + + static Widget buildAdminInviteRechargeAgentMessage( + data, + Function(BuildContext ct, String content) showMsgItemMenu, + Function(String id) rejectOpt, + Function(String id) acceptOpt, + BuildContext context, + ) { + String expand = data["expand"] ?? ""; + if (data["content"] != null) { + var bean = ATSystemInvitMessageRes.fromJson(jsonDecode(data["content"])); + return Builder( + builder: (ct) { + return GestureDetector( + onLongPress: () { + showMsgItemMenu(ct, ""); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [BoxShadow(blurRadius: 2.w, color: Colors.black26)], + borderRadius: BorderRadius.only( + topLeft: Radius.circular(0), + topRight: Radius.circular(8.w), + bottomLeft: Radius.circular(8.w), + bottomRight: Radius.circular(8), + ), + ), + padding: EdgeInsets.all(8.0.w), + child: Column( + children: [ + Row( + children: [ + head(url: bean.userAvatar ?? "", width: 65.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + netImage( + url: + Provider.of( + context, + listen: false, + ) + .getCountryByName( + bean.countryName ?? "", + ) + ?.nationalFlag ?? + "", + borderRadius: BorderRadius.all( + Radius.circular(3.w), + ), + width: 19.w, + height: 14.w, + ), + SizedBox(width: 3.w), + Container( + constraints: BoxConstraints(maxWidth: 160.w), + child: text( + bean.userNickname ?? "", + fontWeight: FontWeight.bold, + textColor: Colors.black, + fontSize: 16.sp, + ), + ), + ], + ), + GestureDetector( + child: Container( + child: Row( + textDirection: TextDirection.ltr, + children: [ + text( + "ID:${bean.actualAccount ?? 0}", + fontSize: 12.sp, + textColor: Colors.black, + fontWeight: FontWeight.bold, + ), + SizedBox(width: 5.w), + Image.asset( + "atu_images/room/at_icon_user_card_copy_id.png", + width: 12.w, + height: 12.w, + ), + SizedBox(width: 8.w), + ], + ), + ), + onTap: () { + Clipboard.setData( + ClipboardData(text: bean.actualAccount ?? ""), + ); + ATTts.show( + ATAppLocalizations.of( + context, + )!.copiedToClipboard, + ); + }, + ), + ], + ), + ], + ), + SizedBox(height: 3.w), + Row( + children: [ + SizedBox(width: 10.w), + GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + height: 25.w, + width: 65.w, + decoration: BoxDecoration( + color: Color(0xffFF6464), + borderRadius: BorderRadius.all( + Radius.circular(15.0), + ), + ), + child: text( + ATAppLocalizations.of(context)!.reject, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () { + rejectOpt(expand); + }, + ), + Spacer(), + GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + height: 25.w, + width: 65.w, + decoration: BoxDecoration( + color: Color(0xffFF9500), + borderRadius: BorderRadius.all( + Radius.circular(15.0), + ), + ), + child: text( + ATAppLocalizations.of(context)!.accept, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () { + acceptOpt(expand); + }, + ), + SizedBox(width: 10.w), + ], + ), + Divider(color: Colors.black12, thickness: 0.5, height: 28.w), + ExtendedText( + ATAppLocalizations.of(context)!.adminInviteRechargeAgent, + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle(fontSize: 14.sp, color: Colors.black54), + ), + SizedBox(height: 5.w), + ], + ), + ), + ); + }, + ); + } + return Container(); + } + + static Widget buildCPBuildMessage( + data, + Function(BuildContext ct, String content) showMsgItemMenu, + Function(String id) rejectOpt, + Function(String id) acceptOpt, + BuildContext context, + ) { + String expand = data["expand"] ?? ""; + if (data["content"] != null) { + var bean = ATSystemInvitMessageRes.fromJson(jsonDecode(data["content"])); + return Builder( + builder: (ct) { + return GestureDetector( + onLongPress: () { + showMsgItemMenu(ct, ""); + }, + child: Container( + height: 490.w, + width: ScreenUtil().screenWidth * 0.8, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_dialog_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + SizedBox(height: 58.w), + Container( + child: text( + bean.applyType == "APPLY" + ? ATAppLocalizations.of(context)!.cpRequest + : ATAppLocalizations.of(context)!.reconcileInvitation, + fontSize: 21.sp, + textColor: Color(0xffECB45C), + fontWeight: FontWeight.bold, + ), + ), + Transform.translate( + offset: Offset(0, -23), + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + PositionedDirectional( + top: 24.w, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + head(url: bean.userAvatar ?? "", width: 80.w), + SizedBox(width: 12.w), + head( + url: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userAvatar ?? + "", + width: 80.w, + ), + ], + ), + ), + Image.asset( + "atu_images/person/at_icon_send_cp_requst_dialog_head.png", + height: 100.w, + fit: BoxFit.fill, + ), + ], + ), + ), + Transform.translate( + offset: Offset(0, -23), + child: Container( + height: 25.w, + width: ScreenUtil().screenWidth * 0.6, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_username_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Row( + children: [ + SizedBox(width: 25.w,), + Expanded( + child: Container( + alignment: Alignment.center, + height: 15.w, + child: + (bean.userNickname?.length ?? 0) > 8 + ? Marquee( + text: bean.userNickname ?? "", + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration( + seconds: 1, + ), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + bean.userNickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + ), + SizedBox(width: 20.w), + Expanded( + child: Container( + alignment: Alignment.center, + height: 15.w, + child: + (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname + ?.length ?? + 0) > + 8 + ? Marquee( + text: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname ?? + "", + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration( + seconds: 1, + ), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname ?? + "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + ), + SizedBox(width: 25.w,) + ], + ), + ), + ), + Container( + padding: EdgeInsets.only(bottom: 12.w), + margin: EdgeInsets.symmetric(horizontal: 18.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_dialog_content.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + Container( + margin: EdgeInsets.symmetric( + horizontal: 35.w, + ).copyWith(top: 40.w), + child: text( + bean.applyType == "APPLY" + ? ATAppLocalizations.of( + context, + )!.areYouSureYouWantToSpend5( + bean.userNickname ?? "", + ) + : ATAppLocalizations.of( + context, + )!.areYouSureYouWantToSpend6( + bean.userNickname ?? "", + ), + fontWeight: FontWeight.w500, + textColor: Color(0xffECB45C), + maxLines: 7, + fontSize: 14.sp, + ), + ), + ], + ), + ), + SizedBox(height: 10.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + child: Container( + padding: EdgeInsets.only(top: 7.w), + alignment: AlignmentDirectional.center, + width: 120.w, + height: 45.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_cancel_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: text( + ATAppLocalizations.of(context)!.reject, + fontSize: 17.sp, + fontWeight: FontWeight.bold, + textColor: Color(0xffECB45C), + ), + ), + onTap: () { + rejectOpt(expand); + }, + ), + SizedBox(width: 25.w), + GestureDetector( + child: Container( + padding: EdgeInsets.only(top: 7.w), + alignment: AlignmentDirectional.center, + width: 120.w, + height: 45.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_ok_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: text( + ATAppLocalizations.of(context)!.accept, + fontSize: 17.sp, + fontWeight: FontWeight.bold, + textColor: Color(0xffECB45C), + ), + ), + onTap: () { + acceptOpt(expand); + }, + ), + ], + ), + ], + ), + ), + ); + }, + ); + } + return Container(); + } + + static Widget buildCPLoveLetterMessage( + data, + Function(BuildContext ct, String content) showMsgItemMenu, + BuildContext context, + ) { + if (data["content"] != null) { + var bean = ATSystemInvitMessageRes.fromJson(jsonDecode(data["content"])); + return Builder( + builder: (ct) { + return GestureDetector( + onLongPress: () { + showMsgItemMenu(ct, ""); + }, + child: Container( + height: 400.w, + width: ScreenUtil().screenWidth * 0.8, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/msg/at_icon_cp_lover_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + SizedBox(height: 35.w), + Image.asset( + ATGlobalConfig.lang == "ar" + ? "atu_images/msg/at_icon_cp_lover_text_ar.png" + : "atu_images/msg/at_icon_cp_lover_text_en.png", + width: 110.w, + fit: BoxFit.fill, + ), + SizedBox(height: 45.w), + Row( + children: [ + SizedBox(width: 55.w), + netImage( + url: bean.userAvatar ?? "", + width: 45.w, + height: 45.w, + shape: BoxShape.circle, + ), + Column( + children: [ + Row( + children: [ + Container( + alignment: Alignment.center, + width: 100.w, + height: 15.w, + child: + (bean.userNickname?.length ?? 0) > 8 + ? Marquee( + text: bean.userNickname ?? "", + style: TextStyle( + fontSize: 12.sp, + color: Color(0xffDB5872), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration( + seconds: 1, + ), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + bean.userNickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12.sp, + color: Color(0xffDB5872), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + GestureDetector( + child: Container( + child: Row( + textDirection: TextDirection.ltr, + children: [ + text( + "ID:${bean.actualAccount ?? 0}", + fontSize: 12.sp, + textColor: Colors.black, + fontWeight: FontWeight.bold, + ), + SizedBox(width: 5.w), + Image.asset( + "atu_images/room/at_icon_user_card_copy_id.png", + width: 12.w, + height: 12.w, + ), + SizedBox(width: 8.w), + ], + ), + ), + onTap: () { + Clipboard.setData( + ClipboardData(text: bean.actualAccount ?? ""), + ); + ATTts.show( + ATAppLocalizations.of( + context, + )!.copiedToClipboard, + ); + }, + ), + ], + ), + ], + ), + SizedBox(height: 5.w), + Container( + height: 160.w, + margin: EdgeInsets.symmetric(horizontal: 53.w), + child: SingleChildScrollView( + child: ExtendedText( + bean.content ?? "", + style: TextStyle( + fontSize: 12.sp, + color: Color(0xffDB5872), + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ), + ), + ); + }, + ); + } + return Container(); + } + + static Widget buildUserCoinsReceivedMessage( + data, + Function(BuildContext ct, String content) showMsgItemMenu, + BuildContext context, + String title, + ) { + if (data["content"] != null) { + return Builder( + builder: (ct) { + return GestureDetector( + onLongPress: () { + showMsgItemMenu(ct, ""); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [BoxShadow(blurRadius: 2.w, color: Colors.black26)], + borderRadius: BorderRadius.only( + topLeft: Radius.circular(0), + topRight: Radius.circular(8.w), + bottomLeft: Radius.circular(8.w), + bottomRight: Radius.circular(8), + ), + ), + padding: EdgeInsets.all(8.0.w), + child: Column( + children: [ + text( + title, + fontSize: 16.sp, + textColor: Colors.black, + fontWeight: FontWeight.bold, + ), + ExtendedText( + data["content"], + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle(fontSize: 14.sp, color: Colors.black54), + ), + ], + ), + ), + ); + }, + ); + } + return Container(); + } +} diff --git a/lib/chatvibe_core/utilities/at_text_utils.dart b/lib/chatvibe_core/utilities/at_text_utils.dart new file mode 100644 index 0000000..86dbef2 --- /dev/null +++ b/lib/chatvibe_core/utilities/at_text_utils.dart @@ -0,0 +1,94 @@ +import 'dart:ui'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +richText({required String txt, required String key, Color highlight = Colors.red, Color defaultColor = Colors.black}) { + assert(txt != null); + assert(key != null); + if (txt.contains(key)) { + // 采用正则表达式环视保留分割的文本 + // 目前只考虑命中一次的情况简单分割 + // List sps = txt.trim().split('$key'); //new RegExp('[?=$key]') + + // 使用最新的算法来计算 + List sps = splitStr(txt, key); + if (sps.length > 0) { + List textSpanList = []; + for (String sp in sps) { + print(sp); + textSpanList.add(TextSpan(text: sp, style: TextStyle(color: sp == key ? highlight : defaultColor))); + } + return Text.rich(TextSpan(children: textSpanList)); + } + } + return Text(txt, style: TextStyle(color: defaultColor),); +} + +List splitStr(String txt, String key) { + int keyLength = key.length; + int index = txt.indexOf(key); + // 因为要replace,所以使用备份的字符串 + String bakTxt = txt; + if (index > -1) { + List indexes = []; + indexes.add(index); + int count = 0; + while (index > -1) { + bakTxt = bakTxt.replaceFirst('$key', ''); + int localIndex = bakTxt.indexOf(key); + if (localIndex > -1) { + count += 1; + indexes.add(localIndex + keyLength * count); + index = localIndex + keyLength * count; + } else { + index = localIndex; + } + } + print('所有的索引:${indexes.toString()}'); + + List results = []; + + // 对上一步算出的所有索引进行迭代,导出分割后的数组 + int indexArrayIndex = 0; + while (indexArrayIndex < indexes.length) { + // 从左往后迭代。 + if (indexArrayIndex == 0) { + if (indexes[indexArrayIndex] == 0) { + results.add(txt.substring(0, keyLength)); + } else { + results.add(txt.substring(0, indexes[indexArrayIndex])); + results.add(txt.substring(indexes[indexArrayIndex], indexes[indexArrayIndex] + keyLength)); + } + } else { + results.add(txt.substring(indexes[indexArrayIndex], indexes[indexArrayIndex] + keyLength)); + } + + int endIndex = indexes[indexArrayIndex] + keyLength; + + // 如果还有索引,这一次只能算到下一个索引前 + if (indexArrayIndex < indexes.length - 1) { + if (endIndex < indexes[indexArrayIndex + 1]) { + results.add(txt.substring(endIndex, indexes[indexArrayIndex + 1])); + } else { + // 刚好是下一个自己,等下一次循环再说 + } + } else { + //只有一个索引的情况 + if (endIndex < txt.length) { + results.add(txt.substring(endIndex, txt.length)); + } else { + //结束了 + } + } + + indexArrayIndex += 1; + } + + print('迭代的结果:$results'); + return results; + } + + return []; +} + diff --git a/lib/chatvibe_core/utilities/at_user_utils.dart b/lib/chatvibe_core/utilities/at_user_utils.dart new file mode 100644 index 0000000..99d025b --- /dev/null +++ b/lib/chatvibe_core/utilities/at_user_utils.dart @@ -0,0 +1,173 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/dynamic_repository_imp.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_core/utilities/at_message_utils.dart'; +import '../../chatvibe_data/models/enum/at_vip_type.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +class ATAccountHelper { + static void optBlacklist( + String userId, + bool isBlacklist, + BuildContext context, + Function(bool succ) callback, + ) { + SmartDialog.dismiss(tag: "showUserInfoOptMenu"); + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context!)!.tips, + msg: + isBlacklist + ? ATAppLocalizations.of(context)!.areYouSureToCancelBlacklist + : ATAppLocalizations.of( + context, + )!.areYouSureYouWantToBlockThisUser, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + ATLoadingManager.exhibitOperation(); + if (isBlacklist) { + AccountRepository() + .deleteUserBlacklist(userId) + .then((result) { + ATTts.show( + ATAppLocalizations.of( + context, + )!.successfullyRemovedFromTheBlacklist, + ); + ATLoadingManager.veilRoutine(); + isBlacklist = !isBlacklist; + callback.call(isBlacklist); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } else { + AccountRepository() + .addUserBlacklist(userId) + .then((result) { + ATTts.show( + ATAppLocalizations.of( + context, + )!.successfullyAddedToTheBlacklist, + ); + ATMessageUtils.clearAllMessagesProcess(userId); + ATLoadingManager.veilRoutine(); + isBlacklist = !isBlacklist; + callback.call(isBlacklist); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } + }, + ); + }, + ); + } + + static void optDynamicBlacklist( + String userId, + bool isBlacklist, + BuildContext context, + Function(bool succ) callback, + ) { + SmartDialog.dismiss(tag: "showUserInfoOptMenu"); + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context!)!.tips, + msg: + isBlacklist + ? ATAppLocalizations.of( + context, + )!.areYouSureToCancelDynamicBlacklist + : ATAppLocalizations.of( + context, + )!.areYouSureYouWantToDynamicBlockThisUser, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + ATLoadingManager.exhibitOperation(); + if (!isBlacklist) { + DynamicRepositoryImp() + .dynamicAdminBlacklistAdd(userId) + .then((result) { + ATTts.show( + ATAppLocalizations.of( + context, + )!.successfullyAddedToTheDynamicBlacklist, + ); + ATLoadingManager.veilRoutine(); + isBlacklist = !isBlacklist; + callback.call(isBlacklist); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } else { + DynamicRepositoryImp() + .dynamicAdminBlacklistDelete(userId) + .then((result) { + ATTts.show( + ATAppLocalizations.of( + context, + )!.successfullyRemovedFromTheDynamicBlacklist, + ); + ATMessageUtils.clearAllMessagesProcess(userId); + ATLoadingManager.veilRoutine(); + isBlacklist = !isBlacklist; + callback.call(isBlacklist); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } + }, + ); + }, + ); + } + + static String getVipLevel(String vipName) { + if (vipName == ATVIPType.VISCOUNT.name) { + return "VIP1"; + } else if (vipName == ATVIPType.EARL.name) { + return "VIP2"; + } else if (vipName == ATVIPType.MARQUIS.name) { + return "VIP3"; + } else if (vipName == ATVIPType.DUKE.name) { + return "VIP4"; + } else if (vipName == ATVIPType.KING.name) { + return "VIP5"; + } + return ""; + } + + static String getVipLevel2(String vipName) { + if (vipName == ATVIPType.VISCOUNT.name) { + return "1"; + } else if (vipName == ATVIPType.EARL.name) { + return "2"; + } else if (vipName == ATVIPType.MARQUIS.name) { + return "3"; + } else if (vipName == ATVIPType.DUKE.name) { + return "4"; + } else if (vipName == ATVIPType.KING.name) { + return "5"; + } + return ""; + } +} diff --git a/lib/chatvibe_core/utilities/at_version_utils.dart b/lib/chatvibe_core/utilities/at_version_utils.dart new file mode 100644 index 0000000..0e13f68 --- /dev/null +++ b/lib/chatvibe_core/utilities/at_version_utils.dart @@ -0,0 +1,14 @@ +import 'package:aslan/chatvibe_data/sources/repositories/config_repository_imp.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; + +class ATVersionUtils { + static Future checkReview() async { + try { + var versionInfo = await ConfigRepositoryImp().versionManageLatestReview(); + if (versionInfo.review != null) { + ATGlobalConfig.isReview = + "${versionInfo.review?.version}" == ATGlobalConfig.version; + } + } catch (e) {} + } +} diff --git a/lib/chatvibe_data/models/at_music_mode.dart b/lib/chatvibe_data/models/at_music_mode.dart new file mode 100644 index 0000000..875d45d --- /dev/null +++ b/lib/chatvibe_data/models/at_music_mode.dart @@ -0,0 +1,37 @@ +class ATMusicMode { + ATMusicMode({String? title, String? artist,int? duration,String? localPath}) { + _title = title; + _artist = artist; + _duration = duration; + _localPath = localPath; + } + + String? _title; + String? _artist; + int? _duration; + String? _localPath; + + String? get title => _title; + + String? get artist => _artist; + + int? get duration => _duration; + + String? get localPath => _localPath; + + Map toJson() { + final map = {}; + map['title'] = _title; + map['artist'] = _artist; + map['duration'] = _duration; + map['localPath'] = _localPath; + return map; + } + + ATMusicMode.fromJson(dynamic json) { + _title = json['title']; + _artist = json['artist']; + _duration = json['duration']; + _localPath = json['localPath']; + } +} diff --git a/lib/chatvibe_data/models/country_mode.dart b/lib/chatvibe_data/models/country_mode.dart new file mode 100644 index 0000000..8ec1a8c --- /dev/null +++ b/lib/chatvibe_data/models/country_mode.dart @@ -0,0 +1,7 @@ +import 'package:aslan/chatvibe_domain/models/res/country_res.dart'; + +class CountryMode{ + String prefix = ""; + ListprefixCountrys = []; + CountryMode(this.prefix,this.prefixCountrys); +} \ No newline at end of file diff --git a/lib/chatvibe_data/models/enum/at_activity_reward_props_type.dart b/lib/chatvibe_data/models/enum/at_activity_reward_props_type.dart new file mode 100644 index 0000000..07c8247 --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_activity_reward_props_type.dart @@ -0,0 +1,5 @@ +enum ATActivityRewardATPropsType{ + PROPS, + GOLD, + GIFT, +} \ No newline at end of file diff --git a/lib/chatvibe_data/models/enum/at_auth_type.dart b/lib/chatvibe_data/models/enum/at_auth_type.dart new file mode 100644 index 0000000..c3f49ef --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_auth_type.dart @@ -0,0 +1,7 @@ +enum ATAuthType{ + MOBILE, + FACEBOOK, + GOOGLE, + HUAWEI, + APPLE +} \ No newline at end of file diff --git a/lib/chatvibe_data/models/enum/at_badge_type.dart b/lib/chatvibe_data/models/enum/at_badge_type.dart new file mode 100644 index 0000000..c4bb66a --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_badge_type.dart @@ -0,0 +1,8 @@ +enum ATBadgeType{ + ACHIEVEMENT, + ACTIVITY, + ADMINISTRATOR, + ROOM_BADGE, + FAMILY, + ALL +} \ No newline at end of file diff --git a/lib/chatvibe_data/models/enum/at_banner_open_type.dart b/lib/chatvibe_data/models/enum/at_banner_open_type.dart new file mode 100644 index 0000000..08e7652 --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_banner_open_type.dart @@ -0,0 +1,3 @@ +enum ATBannerOpenType{ + ENTER_ROOM, +} \ No newline at end of file diff --git a/lib/chatvibe_data/models/enum/at_banner_type.dart b/lib/chatvibe_data/models/enum/at_banner_type.dart new file mode 100644 index 0000000..93dd890 --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_banner_type.dart @@ -0,0 +1,6 @@ +enum ATBannerType{ + EXPLORE_PAGE, + HOME_ALERT, + GAME, + ROOM +} \ No newline at end of file diff --git a/lib/chatvibe_data/models/enum/at_cp_status_type.dart b/lib/chatvibe_data/models/enum/at_cp_status_type.dart new file mode 100644 index 0000000..b9b4dc0 --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_cp_status_type.dart @@ -0,0 +1,4 @@ +enum ATCpStatusType{ + NORMAL, + DISMISSING, +} \ No newline at end of file diff --git a/lib/chatvibe_data/models/enum/at_currency_type.dart b/lib/chatvibe_data/models/enum/at_currency_type.dart new file mode 100644 index 0000000..20454d1 --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_currency_type.dart @@ -0,0 +1,5 @@ +enum ATCurrencyType{ + GOLD, + DIAMOND, + FREE, +} \ No newline at end of file diff --git a/lib/chatvibe_data/models/enum/at_date_type.dart b/lib/chatvibe_data/models/enum/at_date_type.dart new file mode 100644 index 0000000..d1bd75f --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_date_type.dart @@ -0,0 +1,5 @@ +enum ATDateType{ + DAY, + WEEK, + MONTH, +} \ No newline at end of file diff --git a/lib/chatvibe_data/models/enum/at_erro_code.dart b/lib/chatvibe_data/models/enum/at_erro_code.dart new file mode 100644 index 0000000..e266adf --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_erro_code.dart @@ -0,0 +1,20 @@ +enum ATErroCode { + userNotRegistered(4000), + passwordNotRules(4061), + authUnauthorized(401), + passwordNotTrue(9005), + redPacketFinished(3262), + userJoinedFamily(2518), + orderExistsCreated(5900), + unknown(-1); + + final int code; + const ATErroCode(this.code); + + static ATErroCode fromCode(int code) { + return ATErroCode.values.firstWhere( + (e) => e.code == code, + orElse: () => ATErroCode.unknown, // 避免 StateError + ); + } +} \ No newline at end of file diff --git a/lib/chatvibe_data/models/enum/at_family_roles_type.dart b/lib/chatvibe_data/models/enum/at_family_roles_type.dart new file mode 100644 index 0000000..7b0788f --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_family_roles_type.dart @@ -0,0 +1 @@ +enum ATFamilyRolesType {ADMIN, MANAGE, MEMBER } diff --git a/lib/chatvibe_data/models/enum/at_game_mode_type.dart b/lib/chatvibe_data/models/enum/at_game_mode_type.dart new file mode 100644 index 0000000..5413ada --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_game_mode_type.dart @@ -0,0 +1,3 @@ +enum ATGameModeType{ + CHAT_ROOM +} \ No newline at end of file diff --git a/lib/chatvibe_data/models/enum/at_game_origin_type.dart b/lib/chatvibe_data/models/enum/at_game_origin_type.dart new file mode 100644 index 0000000..1186ee8 --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_game_origin_type.dart @@ -0,0 +1,6 @@ +enum ATGameOriginType{ + LINGXIAN, + BAISHUN, + HOTGAME, + YOMI, +} \ No newline at end of file diff --git a/lib/chatvibe_data/models/enum/at_gift_type.dart b/lib/chatvibe_data/models/enum/at_gift_type.dart new file mode 100644 index 0000000..6e067d0 --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_gift_type.dart @@ -0,0 +1,8 @@ +enum ATGiftType{ + ANIMATION, + MUSIC, + GLOBAL_GIFT, + LUCKY_GIFT, + MAGIC, + CP, +} \ No newline at end of file diff --git a/lib/chatvibe_data/models/enum/at_heartbeat_status.dart b/lib/chatvibe_data/models/enum/at_heartbeat_status.dart new file mode 100644 index 0000000..2575b0d --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_heartbeat_status.dart @@ -0,0 +1,4 @@ +enum ATHeartbeatStatus{ + VOICE_LIVE, + ONLINE +} \ No newline at end of file diff --git a/lib/chatvibe_data/models/enum/at_level_type.dart b/lib/chatvibe_data/models/enum/at_level_type.dart new file mode 100644 index 0000000..863670c --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_level_type.dart @@ -0,0 +1,4 @@ +enum ATLevelType{ + WEALTH, + CHARM, +} \ No newline at end of file diff --git a/lib/chatvibe_data/models/enum/at_props_type.dart b/lib/chatvibe_data/models/enum/at_props_type.dart new file mode 100644 index 0000000..6c4d39f --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_props_type.dart @@ -0,0 +1,13 @@ +enum ATPropsType { + AVATAR_FRAME, + FLOAT_PICTURE, + DATA_CARD, + CHAT_BUBBLE, + RIDE, + LAYOUT, + THEME, + BADGE, + NOBLE_VIP, + FRAGMENTS, + RED_PACKET, +} diff --git a/lib/chatvibe_data/models/enum/at_room_info_event_type.dart b/lib/chatvibe_data/models/enum/at_room_info_event_type.dart new file mode 100644 index 0000000..dd3cc90 --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_room_info_event_type.dart @@ -0,0 +1,5 @@ +enum ATRoomInfoEventType{ + WAITING_CONFIRMED, + ID_CHANGE, + AVAILABLE +} \ No newline at end of file diff --git a/lib/chatvibe_data/models/enum/at_room_roles_type.dart b/lib/chatvibe_data/models/enum/at_room_roles_type.dart new file mode 100644 index 0000000..316dea2 --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_room_roles_type.dart @@ -0,0 +1 @@ +enum ATRoomRolesType { HOMEOWNER, ADMIN, MEMBER, TOURIST } diff --git a/lib/chatvibe_data/models/enum/at_room_special_mike_type.dart b/lib/chatvibe_data/models/enum/at_room_special_mike_type.dart new file mode 100644 index 0000000..f602c14 --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_room_special_mike_type.dart @@ -0,0 +1 @@ +enum ATRoomSpecialMikeType { TYPE_VIP3, TYPE_VIP4, TYPE_VIP5, TYPE_VIP6 } diff --git a/lib/chatvibe_data/models/enum/at_sysytem_message_type.dart b/lib/chatvibe_data/models/enum/at_sysytem_message_type.dart new file mode 100644 index 0000000..2d842b1 --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_sysytem_message_type.dart @@ -0,0 +1,18 @@ +enum ATSysytemMessageType{ + GIVE_AWAY_PROPS, + AGENT_SEND_INVITE_HOST, + BD_SEND_INVITE_AGENT, + BD_LEADER_INVITE_BD, + USER_FOLLOW, + RECEIVE_PROP_COUPON, + BD_LEADER_INVITE_BD_LEADER, + VIOLATION_WARNING, + VIOLATION_ADJUST, + ADMIN_INVITE_RECHARGE_AGENT, + CP_BUILD, + USER_SPECIAL_AUDIT_RESULTS, + FAMILY_APPLY_APPROVAL, + CP_LOVE_LETTER, + USER_COINS_RECEIVED, + COIN_SELLER_COINS_RECEIVED, +} \ No newline at end of file diff --git a/lib/chatvibe_data/models/enum/at_task_code_type.dart b/lib/chatvibe_data/models/enum/at_task_code_type.dart new file mode 100644 index 0000000..4778013 --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_task_code_type.dart @@ -0,0 +1,19 @@ +enum ATTaskCodeType { + PERSONAL_ACTIVE_IN_ROOM, + PERSONAL_MIC_IN_ROOM, + PERSONAL_SEND_GIFT, + PERSONAL_LUCKY_GIFT_GOLD, + PERSONAL_MAGIC_GIFT_GOLD, + PERSONAL_GAME_CONSUME, + ROOM_NEW_MEMBER, + ROOM_OWNER_SEND_RED_PACKET, + ROOM_OWNER_SEND_GIFT_USER, + ROOM_OWNER_SEND_GIFT_GOLD, + ROOM_USER_SEND_GIFT_GOLD, + ROOM_MIC_USER_120MIN, + ROOM_MIC_USER_60MIN, + ROOM_MIC_USER_30MIN, + ROOM_ONLINE_USER_COUNT, + ROOM_OWNER_INVITE_MIC, + ROOM_OWNER_MIC_TIME, +} diff --git a/lib/chatvibe_data/models/enum/at_vip_contact_mode_type.dart b/lib/chatvibe_data/models/enum/at_vip_contact_mode_type.dart new file mode 100644 index 0000000..1418205 --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_vip_contact_mode_type.dart @@ -0,0 +1,7 @@ +enum ATVipContactModeType{ + EVERYONE, + VIP2_ABOVE, + VIP3_ABOVE, + VIP4_ABOVE, + VIP5_ABOVE, +} \ No newline at end of file diff --git a/lib/chatvibe_data/models/enum/at_vip_type.dart b/lib/chatvibe_data/models/enum/at_vip_type.dart new file mode 100644 index 0000000..f0c1ef7 --- /dev/null +++ b/lib/chatvibe_data/models/enum/at_vip_type.dart @@ -0,0 +1,8 @@ +enum ATVIPType{ + VISCOUNT, + EARL, + MARQUIS, + DUKE, + KING, + EMPEROR +} \ No newline at end of file diff --git a/lib/chatvibe_data/models/message/at_floating_message.dart b/lib/chatvibe_data/models/message/at_floating_message.dart new file mode 100644 index 0000000..7e1fa9e --- /dev/null +++ b/lib/chatvibe_data/models/message/at_floating_message.dart @@ -0,0 +1,69 @@ +class ATFloatingMessage { + String? userId; // 可选:用户ID + String? roomId; // 可选:房间ID + String? toUserId; // 可选:用户ID + String? userAvatarUrl; // 用户头像 + String? toUserAvatarUrl; // 用户头像 + String? userName; // 用户昵称 + String? toUserName; // 用户昵称 + String? giftUrl; // 礼物图标 + int? type; + int? rocketLevel; + num? coins; + num? number; + num? multiple; + int priority = 10; //排序权重 + + ATFloatingMessage({ + this.type = 0, + this.rocketLevel = 0, + this.userId = '', + this.roomId = '', + this.toUserId = '', + this.userAvatarUrl = '', + this.toUserAvatarUrl = '', + this.userName = '', + this.toUserName = '', + this.giftUrl = '', + this.number = 0, + this.coins = 0, + this.priority = 10, + this.multiple = 10, + }); + + ATFloatingMessage.fromJson(dynamic json) { + userId = json['userId']; + rocketLevel = json['rocketLevel']; + toUserId = json['toUserId']; + roomId = json['roomId']; + type = json['type']; + userAvatarUrl = json['userAvatarUrl']; + toUserAvatarUrl = json['toUserAvatarUrl']; + userName = json['userName']; + toUserName = json['toUserName']; + giftUrl = json['giftUrl']; + coins = json['coins']; + number = json['number']; + priority = json['priority']; + multiple = json['multiple']; + } + + Map toJson() { + final map = {}; + map['userId'] = userId; + map['rocketLevel'] = rocketLevel; + map['roomId'] = roomId; + map['type'] = type; + map['toUserId'] = toUserId; + map['userAvatarUrl'] = userAvatarUrl; + map['toUserAvatarUrl'] = toUserAvatarUrl; + map['userName'] = userName; + map['toUserName'] = toUserName; + map['giftUrl'] = giftUrl; + map['coins'] = coins; + map['number'] = number; + map['priority'] = priority; + map['multiple'] = multiple; + return map; + } +} diff --git a/lib/chatvibe_data/models/message/big_broadcast_group_message.dart b/lib/chatvibe_data/models/message/big_broadcast_group_message.dart new file mode 100644 index 0000000..0c658e7 --- /dev/null +++ b/lib/chatvibe_data/models/message/big_broadcast_group_message.dart @@ -0,0 +1,22 @@ +import 'package:aslan/chatvibe_data/models/message/at_floating_message.dart'; + +class BigBroadcastGroupMessage { + String type = ""; + ATFloatingMessage? data; + + BigBroadcastGroupMessage(this.type, this.data); + + BigBroadcastGroupMessage.fromJson(dynamic json) { + type = json['type']; + data = json['data'] != null ? ATFloatingMessage.fromJson(json['data']) : null; + } + + Map toJson() { + final map = {}; + map['type'] = type; + if (data != null) { + map['data'] = data?.toJson(); + } + return map; + } +} diff --git a/lib/chatvibe_data/sources/local/at_foreground_service_helper.dart b/lib/chatvibe_data/sources/local/at_foreground_service_helper.dart new file mode 100644 index 0000000..c50a26a --- /dev/null +++ b/lib/chatvibe_data/sources/local/at_foreground_service_helper.dart @@ -0,0 +1,190 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_foreground_task/flutter_foreground_task.dart'; + +/// 前台服务工具类 - 专门用于管理flutter_foreground_task插件的开启和关闭 +class ATForegroundServiceHelper { + static final ATForegroundServiceHelper _instance = + ATForegroundServiceHelper._internal(); + + factory ATForegroundServiceHelper() => _instance; + + ATForegroundServiceHelper._internal(); + + Future _serviceOperation = Future.value(); + bool _isStartInFlight = false; + bool _isStopInFlight = false; + + /// 初始化前台服务插件 + Future initialize() async { + try { + FlutterForegroundTask.initCommunicationPort(); + FlutterForegroundTask.addTaskDataCallback(_onReceiveTaskData); + FlutterForegroundTask.init( + androidNotificationOptions: AndroidNotificationOptions( + channelId: 'foreground_service', + channelName: 'Foreground Service Notification', + channelDescription: + 'This notification appears when the foreground service is running.', + onlyAlertOnce: true, + ), + iosNotificationOptions: const IOSNotificationOptions( + showNotification: false, + playSound: false, + ), + foregroundTaskOptions: ForegroundTaskOptions( + eventAction: ForegroundTaskEventAction.nothing(), + allowWakeLock: true, + ), + ); + _fgLog('前台服务插件初始化成功'); + } catch (e) { + _fgLog('前台服务插件初始化失败: $e'); + rethrow; + } + } + + Future _requestPermissions() async { + final NotificationPermission notificationPermission = + await FlutterForegroundTask.checkNotificationPermission(); + if (notificationPermission != NotificationPermission.granted) { + await FlutterForegroundTask.requestNotificationPermission(); + } + } + + Future startService() { + return _enqueueOperation(() async { + if (_isStartInFlight) { + return const ServiceRequestSuccess(); + } + _isStartInFlight = true; + try { + await _requestPermissions(); + if (await FlutterForegroundTask.isRunningService) { + _fgLog('前台服务已在运行,跳过重复启动'); + return const ServiceRequestSuccess(); + } + return FlutterForegroundTask.startService( + serviceId: 256, + notificationTitle: '', + notificationText: 'Tap to return to the app', + notificationIcon: null, + notificationButtons: [ + const NotificationButton(id: 'btn_hello', text: 'Back App'), + ], + notificationInitialRoute: '/second', + callback: startCallback, + ); + } finally { + _isStartInFlight = false; + } + }); + } + + Future stopService() { + return _enqueueOperation(() async { + if (_isStopInFlight) { + return const ServiceRequestSuccess(); + } + _isStopInFlight = true; + try { + if (!await FlutterForegroundTask.isRunningService) { + _fgLog('前台服务未运行,跳过停止'); + return const ServiceRequestSuccess(); + } + return FlutterForegroundTask.stopService(); + } finally { + _isStopInFlight = false; + } + }); + } + + Future _enqueueOperation( + Future Function() action, + ) { + final completer = Completer(); + _serviceOperation = _serviceOperation.then((_) async { + try { + completer.complete(await action()); + } catch (e) { + completer.complete(ServiceRequestFailure(error: e)); + } + }); + return completer.future; + } +} + +void _fgLog(String message) { + if (!kReleaseMode) { + debugPrint(message); + } +} + +@pragma('vm:entry-point') +void startCallback() { + FlutterForegroundTask.setTaskHandler(MyTaskHandler()); +} + +void _onReceiveTaskData(Object data) { + _fgLog('onReceiveTaskData: $data'); +} + +class MyTaskHandler extends TaskHandler { + static const String incrementCountCommand = 'incrementCount'; + + void _doSomeThing() { + // Send data to main isolate. + // FlutterForegroundTask.sendDataToMain(_count); + } + + // Called when the task is started. + @override + Future onStart(DateTime timestamp, TaskStarter starter) async { + _fgLog('onStart(starter: ${starter.name})'); + FlutterForegroundTask.updateService( + notificationTitle: 'Aslan', + notificationText: 'Rooming', + ); + _doSomeThing(); + } + + // Called based on the eventAction set in ForegroundTaskOptions. + @override + void onRepeatEvent(DateTime timestamp) { + _doSomeThing(); + } + + // Called when the task is destroyed. + @override + Future onDestroy(DateTime timestamp, bool isTimeout) async { + _fgLog('onDestroy(isTimeout: $isTimeout)'); + } + + // Called when data is sent using `FlutterForegroundTask.sendDataToTask`. + @override + void onReceiveData(Object data) { + _fgLog('onReceiveData: $data'); + if (data == incrementCountCommand) { + // _incrementCount(); + } + } + + // Called when the notification button is pressed. + @override + void onNotificationButtonPressed(String id) { + _fgLog('onNotificationButtonPressed: $id'); + } + + // Called when the notification itself is pressed. + @override + void onNotificationPressed() { + _fgLog('onNotificationPressed'); + } + + // Called when the notification itself is dismissed. + @override + void onNotificationDismissed() { + _fgLog('onNotificationDismissed'); + } +} diff --git a/lib/chatvibe_data/sources/local/data_persistence.dart b/lib/chatvibe_data/sources/local/data_persistence.dart new file mode 100644 index 0000000..093d0fa --- /dev/null +++ b/lib/chatvibe_data/sources/local/data_persistence.dart @@ -0,0 +1,248 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class DataPersistence { + static SharedPreferences? _prefs; + static bool _isInitialized = false; + static Completer? _initializationCompleter; + + // 初始化 SharedPreferences + static Future initialize() async { + if (_isInitialized) return; + + if (_initializationCompleter != null) { + return _initializationCompleter!.future; + } + + _initializationCompleter = Completer(); + + try { + _prefs = await SharedPreferences.getInstance(); + _isInitialized = true; + _initializationCompleter!.complete(); + debugPrint('SharedPreferences initialized successfully'); + } catch (e) { + _initializationCompleter!.completeError(e); + _initializationCompleter = null; + debugPrint('SharedPreferences initialization failed: $e'); + rethrow; + } + } + + // 检查是否已初始化 + static void _checkInitialized() { + if (!_isInitialized || _prefs == null) { + throw Exception( + "SharedPreferences not initialized! Call DataPersistence.initialize() first.", + ); + } + } + + // 存储字符串 + static Future setString(String key, String value) async { + _checkInitialized(); + return await _prefs!.setString(key, value); + } + + // 读取字符串 + static String getString(String key, {String defaultValue = ''}) { + if (!_isInitialized) return defaultValue; + return _prefs!.getString(key) ?? defaultValue; + } + + // 存储整数 + static Future setInt(String key, int value) async { + _checkInitialized(); + return await _prefs!.setInt(key, value); + } + + // 读取整数 + static int getInt(String key, {int defaultValue = 0}) { + if (!_isInitialized) return defaultValue; + return _prefs!.getInt(key) ?? defaultValue; + } + + // 存储布尔值 + static Future setBool(String key, bool value) async { + _checkInitialized(); + return await _prefs!.setBool(key, value); + } + + // 读取布尔值 + static bool getBool(String key, {bool defaultValue = false}) { + if (!_isInitialized) return defaultValue; + return _prefs!.getBool(key) ?? defaultValue; + } + + // 存储双精度浮点数 + static Future setDouble(String key, double value) async { + _checkInitialized(); + return await _prefs!.setDouble(key, value); + } + + // 读取双精度浮点数 + static double getDouble(String key, {double defaultValue = 0.0}) { + if (!_isInitialized) return defaultValue; + return _prefs!.getDouble(key) ?? defaultValue; + } + + // 删除指定键 + static Future remove(String key) async { + if (!_isInitialized) return false; + return await _prefs!.remove(key); + } + + // 批量删除(通过键前缀) + static Future removeByPrefix(String prefix) async { + if (!_isInitialized) return; + + final keys = _prefs!.getKeys().where((key) => key.startsWith(prefix)); + for (final key in keys) { + await _prefs!.remove(key); + } + } + + // 清空所有数据 + static Future clearAll() async { + if (!_isInitialized) return false; + return await _prefs!.clear(); + } + + // 检查键是否存在 + static bool containsKey(String key) { + if (!_isInitialized) return false; + return _prefs!.containsKey(key); + } + + // 获取所有键 + static Set getKeys() { + if (!_isInitialized) return {}; + return _prefs!.getKeys(); + } + + // 业务方法 - 与之前保持相同的API + static String getToken() { + return getString("token"); + } + + static Future setToken(String token) async { + return await setString("token", token); + } + + ///获取当前用户 + static String getCurrentUser() { + return getString("currentUser"); + } + + ///保存当前用户 + static Future setCurrentUser(String userJson) async { + return await setString("currentUser", userJson); + } + + ///保存搜索历史记录 + static Future setSearchHistroy(String userId, String userJson) async { + return await setString("${userId}-currentUser", userJson); + } + + ///获取搜索历史记录 + static String getSearchHistroy(String userId) { + return getString("${userId}-currentUser"); + } + + ///保存当前语言 + static Future setLang(String value) async { + return await setString("lang", value); + } + + ///获取当前语言 + static String getLang() { + return getString("lang"); + } + + static Future setUniqueId(String value) async { + return await setString("device_unique_id", value); + } + + static String getUniqueId() { + return getString("device_unique_id"); + } + + static Future setLastTimeRoomId(String id) async { + return await setString("last_time_room_id", id); + } + + static String getLastTimeRoomId() { + return getString("last_time_room_id"); + } + + static Future setPlayGiftMusic(bool muteMusic) async { + return await setBool("play_gift_music", muteMusic); + } + + static bool getPlayGiftMusic() { + return getBool("play_gift_music"); + } + + ///获取用户已经添加的房间音乐 + static String getUserRoomMusic() { + final currentUser = AccountStorage().getCurrentUser()?.userProfile?.account; + if (currentUser == null) return ""; + return getString("room_music$currentUser"); + } + + static Future setUserRoomMusic(String musicJson) async { + final currentUser = AccountStorage().getCurrentUser()?.userProfile?.account; + if (currentUser == null) return false; + return await setString("room_music$currentUser", musicJson); + } + + // 等待初始化完成 + static Future get initializationDone { + if (_isInitialized) return Future.value(); + return _initializationCompleter?.future ?? Future.value(); + } + + // 重置初始化状态(用于测试) + static void reset() { + _isInitialized = false; + _prefs = null; + _initializationCompleter = null; + } + + ///发送动态的草稿图片 + static List getDraftDynamicPhoto() { + String st = getString( + "draft_dynamic_photo${AccountStorage().getCurrentUser()?.userProfile?.account}", + ); + List photos = []; + if (st.isNotEmpty) { + photos = (jsonDecode(st) as List).map((e) => e as String).toList(); + } + return photos; + } + + static void setDraftDynamicPhoto(List images) { + setString( + "draft_dynamic_photo${AccountStorage().getCurrentUser()?.userProfile?.account}", + jsonEncode(images), + ); + } + + ///发送动态的草稿文本 + static String getDraftDynamicText() { + return getString( + "draft_dynamic_text${AccountStorage().getCurrentUser()?.userProfile?.account}", + ); + } + + static void setDraftDynamicText(String text) { + setString( + "draft_dynamic_text${AccountStorage().getCurrentUser()?.userProfile?.account}", + text, + ); + } +} diff --git a/lib/chatvibe_data/sources/local/file_cache_manager.dart b/lib/chatvibe_data/sources/local/file_cache_manager.dart new file mode 100644 index 0000000..1ba5d86 --- /dev/null +++ b/lib/chatvibe_data/sources/local/file_cache_manager.dart @@ -0,0 +1,137 @@ +import 'dart:io'; +import 'package:flutter_cache_manager/flutter_cache_manager.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as p; + +typedef FileCacheManager = MediaCache; +class MediaCache { + late CacheManager _cacheManager; + static FileCacheManager? _instance; + static String temporaryPath = ""; + static const musicPath = "/music"; + static String soundPath = ""; + final String cacheKey; + final Duration stalePeriod; + final int maxNrOfCacheObjects; + + static String get videoCachePath => "$temporaryPath"; + static String get imageCachePath => "$temporaryPath"; + + MediaCache._internal({ + required this.cacheKey, + required this.stalePeriod, + required this.maxNrOfCacheObjects, + }) { + _cacheManager = CacheManager( + Config( + cacheKey, + stalePeriod: stalePeriod, + maxNrOfCacheObjects: maxNrOfCacheObjects, + ), + ); + } + + factory MediaCache.getInstance({ + String cacheKey = 'customCache', + Duration stalePeriod = const Duration(days: 7), + int maxNrOfCacheObjects = 100, + }) { + _instance ??= FileCacheManager._internal( + cacheKey: cacheKey, + stalePeriod: stalePeriod, + maxNrOfCacheObjects: maxNrOfCacheObjects, + ); + return _instance!; + } + + Future getFilePath() async { + try { + Directory directory = await getTemporaryDirectory(); + temporaryPath = p.join(directory.path, cacheKey); + + // 使用递归创建目录,更简洁 + await Directory(temporaryPath).create(recursive: true); + await Directory("$temporaryPath/sound").create(recursive: true); + await Directory("$temporaryPath/$musicPath").create(recursive: true); + + soundPath = "$temporaryPath/sound"; + print('语音文件夹路径: $soundPath'); + return temporaryPath; + } catch (e) { + print('目录创建失败: $e'); + rethrow; // 或者返回一个备用路径 + } + } + + /// 下载或获取缓存文件 + /// [url] 文件网络地址 + /// [ignoreCache] 是否忽略缓存强制重新下载 + Future getFile({ + required String url, + bool ignoreCache = false, + }) async { + if (ignoreCache) { + // 强制重新下载 + final fileInfo = await _cacheManager.downloadFile(url); + return fileInfo.file; + } else { + // 优先从缓存获取 + final cacheFile = await _cacheManager.getSingleFile(url); + return cacheFile; + } + } + + /// 获取缓存文件路径(如果不存在则返回 null) + Future getCachedFilePath(String url) async { + final fileInfo = await _cacheManager.getFileFromCache(url); + return fileInfo?.file.path; + } + + /// 清除指定文件的缓存 + Future removeCachedFile(String url) async { + await _cacheManager.removeFile(url); + } + + /// 清空所有缓存 + Future clearAllCache() async { + await _cacheManager.emptyCache(); + } + + /// 获取缓存大小(字节) + Future getCacheSize() async { + final directory = await getTemporaryDirectory(); + final cacheDir = Directory('${directory.path}/libCachedData'); + + if (!await cacheDir.exists()) return 0; + + int totalSize = 0; + await for (var file in cacheDir.list(recursive: true)) { + if (file is File) { + totalSize += await file.length(); + } + } + return totalSize; + } + + Future putFileFromFile(String cacheKey, File localFile) async { + final cacheManager = DefaultCacheManager(); + + try { + // 1. 将本地文件添加到缓存 + await cacheManager.putFile( + cacheKey, // 缓存键(唯一标识) + localFile.readAsBytesSync(), // 文件字节数据 + fileExtension: localFile.path.split('.').last, // 文件扩展名 + ); + + // 2. 验证文件已缓存 + final cachedFile = await cacheManager.getFileFromCache(cacheKey); + if (cachedFile != null) { + print('文件已成功添加到缓存: ${cachedFile.file.path}'); + } + } catch (e) { + print('添加文件到缓存失败: $e'); + } + } + +} \ No newline at end of file diff --git a/lib/chatvibe_data/sources/local/floating_screen_manager.dart b/lib/chatvibe_data/sources/local/floating_screen_manager.dart new file mode 100644 index 0000000..aba2285 --- /dev/null +++ b/lib/chatvibe_data/sources/local/floating_screen_manager.dart @@ -0,0 +1,219 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/main.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_core/utilities/at_entrance_vap_svga_manager.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/floating/floating_game_screen_widget.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/floating/floating_gift_screen_widget.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/floating/floating_luck_gift_screen_widget.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/floating/floating_room_redenvelope_screen_widget.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/floating/floating_room_rocket_screen_widget.dart'; +import 'package:aslan/chatvibe_data/models/message/at_floating_message.dart'; + +import '../../../chatvibe_ui/widgets/room/floating/floating_cp_gift_screen_widget.dart'; + +typedef FloatingScreenManager = OverlayManager; + +class OverlayManager { + final ATPriorityQueue _messageQueue = ATPriorityQueue( + (a, b) => b.priority.compareTo(a.priority), + ); + bool _isPlaying = false; + OverlayEntry? _currentOverlayEntry; + + bool _isProcessing = false; + bool _isDisposed = false; + + static final OverlayManager _instance = + OverlayManager._internal(); + + factory OverlayManager() => _instance; + + OverlayManager._internal(); + + void addMessage(ATFloatingMessage message) { + if (_isDisposed) return; + if (ATGlobalConfig.isFloatingAnimationInGlobal) { + _messageQueue.add(message); + _safeScheduleNext(); + } else { + _messageQueue.clear(); + _isPlaying = false; + _isProcessing = false; + _isDisposed = false; + } + } + + void _safeScheduleNext() { + if (_isProcessing || _isPlaying || _messageQueue.isEmpty) return; + _isProcessing = true; + + try { + _scheduleNext(); + } finally { + _isProcessing = false; + } + } + + void _scheduleNext() { + if (_isPlaying || _messageQueue.isEmpty || _isDisposed) return; + + try { + final context = navigatorKey.currentState?.context; + if (context == null || !context.mounted) return; + + // 安全地获取第一个消息 + if (_messageQueue.isEmpty) return; + final messageToProcess = _messageQueue.first; + + if (messageToProcess?.type == 1) { + final rtcProvider = Provider.of(context, listen: false); + if (rtcProvider.currenRoom == null) { + // 从队列中移除第一个元素 + _messageQueue.removeFirst(); + _safeScheduleNext(); + return; + } + } + + // 安全地移除并播放消息 + final messageToPlay = _messageQueue.removeFirst(); + _playMessage(messageToPlay); + } catch (e) { + debugPrint('播放悬浮消息出错: $e'); + _isPlaying = false; + _safeScheduleNext(); + } + } + + void _playMessage(ATFloatingMessage message) { + _isPlaying = true; + final context = navigatorKey.currentState?.context; + if (context == null || !context.mounted) { + _isPlaying = false; + _safeScheduleNext(); + return; + } + + _currentOverlayEntry = OverlayEntry( + builder: + (_) => Align( + alignment: AlignmentDirectional.topStart, + child: Transform.translate( + offset: Offset(0, 70.w), + child: _buildScreenWidget(message), + ), + ), + ); + + Overlay.of(context)?.insert(_currentOverlayEntry!); + } + + Widget _buildScreenWidget(ATFloatingMessage message) { + bool completed = false; + + void onComplete() { + if (completed) return; + completed = true; + + try { + _currentOverlayEntry?.remove(); + _currentOverlayEntry = null; + _isPlaying = false; + _safeScheduleNext(); + } catch (e) { + debugPrint('清理悬浮消息出错: $e'); + _isPlaying = false; + _safeScheduleNext(); + } + } + + switch (message.type) { + case 0: + return FloatingLuckGiftScreenWidget( + message: message, + onAnimationCompleted: onComplete, + ); + case 1: + //房间礼物 + return FloatingGiftScreenWidget( + message: message, + onAnimationCompleted: onComplete, + ); + case 2: + //游戏中奖 + return FloatingGameScreenWidget( + message: message, + onAnimationCompleted: onComplete, + ); + case 3: + //火箭 + return FloatingRoomRocketScreenWidget( + message: message, + onAnimationCompleted: onComplete, + ); + case 4: + //红包 + return FloatingRoomRedenvelopeScreenWidget( + message: message, + onAnimationCompleted: onComplete, + ); + case 5: + //CP礼物 + return FloatingCPGiftScreenWidget( + message: message, + onAnimationCompleted: onComplete, + ); + default: + onComplete(); + return Container(); + } + } + + void activate() { + _isDisposed = false; + } + + void dispose() { + _isDisposed = true; + _currentOverlayEntry?.remove(); + _currentOverlayEntry = null; + _messageQueue.clear(); + _isPlaying = false; + _isProcessing = false; + } + + void removeRoom() { + // 正确地从 PriorityQueue 中移除特定类型的消息 + _removeMessagesByType(1); + } + + // 辅助方法:移除特定类型的消息 + void _removeMessagesByType(int type) { + // 由于 PriorityQueue 没有直接的方法来移除特定元素, + // 我们需要重建队列,排除指定类型的消息 + final newQueue = ATPriorityQueue( + (a, b) => b.priority.compareTo(a.priority), + ); + + // 遍历原始队列,只添加非指定类型的消息 + while (_messageQueue.isNotEmpty) { + final message = _messageQueue.removeFirst(); + if (message.type != type) { + newQueue.add(message); + } + } + + // 将新队列赋值给原队列 + while (newQueue.isNotEmpty) { + _messageQueue.add(newQueue.removeFirst()); + } + } + + // 辅助方法:获取队列信息 + int get queueLength => _messageQueue.length; + + bool get isPlaying => _isPlaying; +} diff --git a/lib/chatvibe_data/sources/local/user_manager.dart b/lib/chatvibe_data/sources/local/user_manager.dart new file mode 100644 index 0000000..86e5dee --- /dev/null +++ b/lib/chatvibe_data/sources/local/user_manager.dart @@ -0,0 +1,190 @@ +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/utilities/at_message_utils.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; +import 'package:aslan/main.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_heartbeat_utils.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_features/auth/login_route.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/floating_screen_manager.dart'; + +import '../../models/enum/at_props_type.dart'; + +typedef UserManager = AccountStorage; + +class AccountStorage { + static AccountStorage? _instance; + + AccountStorage._internal(); + + factory AccountStorage() { + return _instance ??= AccountStorage._internal(); + } + + String token = ""; + ChatVibeLoginRes? _currentUser; + + ///获取当前用户 + ChatVibeLoginRes? getCurrentUser() { + if (_currentUser != null) { + return _currentUser; + } + var userJson = DataPersistence.getCurrentUser(); + if (userJson.isNotEmpty) { + _currentUser = ChatVibeLoginRes.fromJson(jsonDecode(userJson)); + } + return _currentUser; + } + + ///同步优化数据 + void setCurrentUser(ChatVibeLoginRes user) { + _currentUser = user; + setToken(_currentUser!.token ?? ""); + String userJson = jsonEncode(_currentUser?.toJson()); + if (userJson.isNotEmpty) { + DataPersistence.setCurrentUser(userJson); + } + } + + ///获取自己佩戴的头饰信息 + PropsResources? getHeaddress() { + PropsResources? pr; + _currentUser?.userProfile?.useProps?.forEach((value) { + if (value.propsResources?.type == ATPropsType.AVATAR_FRAME.name) { + ///判断有没有过期 + if (int.parse(value.expireTime ?? "0") > + DateTime.now().millisecondsSinceEpoch) { + pr = value.propsResources; + } + } + }); + return pr; + } + + ///获取自己佩戴的气泡框 + PropsResources? getChatbox() { + PropsResources? pr; + _currentUser?.userProfile?.useProps?.forEach((value) { + if (value.propsResources?.type == ATPropsType.CHAT_BUBBLE.name) { + ///判断有没有过期 + if (int.parse(value.expireTime ?? "0") > + DateTime.now().millisecondsSinceEpoch) { + pr = value.propsResources; + } + } + }); + return pr; + } + + ///获取自己佩戴的坐骑信息 + PropsResources? getMountains() { + PropsResources? pr; + _currentUser?.userProfile?.useProps?.forEach((value) { + if (value.propsResources?.type == ATPropsType.RIDE.name) { + ///判断有没有过期 + if (int.parse(value.expireTime ?? "0") > + DateTime.now().millisecondsSinceEpoch) { + pr = value.propsResources; + } + } + }); + return pr; + } + + PropsResources? cpr; + + ///获取自己VIP信息 + PropsResources? getVIP() { + PropsResources? pr; + _currentUser?.userProfile?.useProps?.forEach((value) { + if (value.propsResources?.type == ATPropsType.NOBLE_VIP.name) { + ///判断有没有过期 + if (int.parse(value.expireTime ?? "0") > + DateTime.now().millisecondsSinceEpoch) { + pr = value.propsResources; + } + } + }); + //vip变化了 + if (cpr?.name != pr?.name) { + Provider.of( + navigatorKey.currentState!.context, + listen: false, + ).needUpDataUserInfo = + true; + } + cpr = pr; + return pr; + } + + ///获取VIp过期时间 + String getVIPExpireTime() { + String expireTime = "0"; + _currentUser?.userProfile?.useProps?.forEach((value) { + if (value.propsResources?.type == ATPropsType.NOBLE_VIP.name) { + expireTime = value.expireTime ?? "0"; + } + }); + return expireTime; + } + + ///获取自己资料卡信息 + PropsResources? getDataCard() { + PropsResources? pr; + _currentUser?.userProfile?.useProps?.forEach((value) { + if (value.propsResources?.type == ATPropsType.DATA_CARD.name) { + ///判断有没有过期 + if (int.parse(value.expireTime ?? "0") > + DateTime.now().millisecondsSinceEpoch) { + pr = value.propsResources; + } + } + }); + return pr; + } + + String getToken() { + if (token.isNotEmpty) { + return token; + } + token = DataPersistence.getToken(); + return token; + } + + void setToken(String tk) { + token = tk; + DataPersistence.setToken(token); + } + + void _cleanUser() { + token = ""; + _currentUser = null; + DataPersistence.setToken(""); + DataPersistence.setCurrentUser(""); + } + + ///退出登录 + void logout(BuildContext context) { + _cleanUser(); + ATHeartbeatUtils.cancelTimer(); + Provider.of(context, listen: false).extRoom(true); + Provider.of(context, listen: false).logout(); + ATNavigatorUtils.push(context, LoginRouter.login, clearStack: true); + ATRoomUtils.closeAllDialogs(); + ATMessageUtils.redPacketFutureCache.clear(); + ATGlobalConfig.isEntryVehicleAnimation = true; + ATGlobalConfig.isGiftSpecialEffects = true; + ATGlobalConfig.isFloatingAnimationInGlobal = true; + ATGlobalConfig.isLuckGiftSpecialEffects = true; + OverlayManager().dispose(); + } +} diff --git a/lib/chatvibe_data/sources/remote/net/api.dart b/lib/chatvibe_data/sources/remote/net/api.dart new file mode 100644 index 0000000..7838754 --- /dev/null +++ b/lib/chatvibe_data/sources/remote/net/api.dart @@ -0,0 +1,299 @@ +// api.dart +import 'dart:convert'; +import 'package:dio/dio.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_features/family/family_route.dart'; +import 'package:aslan/chatvibe_features/auth/login_route.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/pwd/input_room_pwd_page.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/main.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/redpack/room_redenvelope_rec_record_page.dart'; +import 'package:aslan/chatvibe_data/sources/remote/net/network_client.dart'; + +import '../../../models/enum/at_erro_code.dart'; + +export 'package:dio/dio.dart'; + +_parseAndDecode(String response) => jsonDecode(response); + +parseJson(String text) => compute(_parseAndDecode, text); + +class BaseNetworkClient { + final Dio dio; + + BaseNetworkClient() : dio = Dio() { + _configureDio(); + init(); + } + + void _configureDio() { + dio.transformer = BackgroundTransformer()..jsonDecodeCallback = parseJson; + dio.options = BaseOptions( + connectTimeout: const Duration(seconds: 12), + receiveTimeout: const Duration(seconds: 15), + contentType: 'application/json; charset=UTF-8', + ); + } + + void init() {} + + // 通用 GET 请求 + Future get( + String path, { + Map? queryParams, + required T Function(dynamic) fromJson, + CancelToken? cancelToken, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + }) async { + return _request( + path, + method: 'GET', + queryParams: queryParams, + fromJson: fromJson, + cancelToken: cancelToken, + onSendProgress: onSendProgress, + onReceiveProgress: onReceiveProgress, + ); + } + + // 通用 put 请求 + Future put( + String path, { + dynamic data, + Map? queryParams, + required T Function(dynamic) fromJson, + CancelToken? cancelToken, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + }) async { + return _request( + path, + method: 'PUT', + data: data, + queryParams: queryParams, + fromJson: fromJson, + cancelToken: cancelToken, + onSendProgress: onSendProgress, + onReceiveProgress: onReceiveProgress, + ); + } + + // 通用 POST 请求 + Future post( + String path, { + dynamic data, + Map? queryParams, + required T Function(dynamic) fromJson, + CancelToken? cancelToken, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + }) async { + return _request( + path, + method: 'POST', + data: data, + queryParams: queryParams, + fromJson: fromJson, + cancelToken: cancelToken, + onSendProgress: onSendProgress, + onReceiveProgress: onReceiveProgress, + ); + } + + // 通用 POST 请求 + Future delete( + String path, { + dynamic data, + Map? queryParams, + required T Function(dynamic) fromJson, + CancelToken? cancelToken, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + }) async { + return _request( + path, + method: 'DELETE', + data: data, + queryParams: queryParams, + fromJson: fromJson, + cancelToken: cancelToken, + onSendProgress: onSendProgress, + onReceiveProgress: onReceiveProgress, + ); + } + + // 核心请求方法 + Future _request( + String path, { + required String method, + dynamic data, + Map? queryParams, + CancelToken? cancelToken, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + required T Function(dynamic) fromJson, + }) async { + try { + final response = await dio.request( + path, + data: data, + queryParameters: queryParams, + options: Options(method: method), + cancelToken: cancelToken, + onSendProgress: onSendProgress, + onReceiveProgress: onReceiveProgress, + ); + + // 处理基础响应 + final baseResponse = ResponseData.fromJson( + response.data as Map, + fromJsonT: fromJson, + ); + // 业务逻辑成功判断 + if (baseResponse.success) { + if (baseResponse.body != null) { + return baseResponse.body!; + } else { + if (T == bool) return false as T; + if (T == int) return 0 as T; + if (T == String) return "" as T; + ATLoadingManager.veilRoutine(); + throw Exception('Response data is null'); + } + } else { + // 业务逻辑错误 + ATLoadingManager.veilRoutine(); + throw DioException( + requestOptions: response.requestOptions, + response: response, + error: '业务错误: ${baseResponse.errorMsg}', + ); + } + } on DioException catch (e) { + // 网络错误处理 + throw _handleDioError(e); + } catch (e) { + ATLoadingManager.veilRoutine(); + throw Exception('未知错误: $e'); + } + } + + // 错误处理 + DioException _handleDioError(DioException e) { + ATLoadingManager.veilRoutine(); + switch (e.type) { + case DioExceptionType.connectionTimeout: + return DioException(requestOptions: e.requestOptions, error: '连接超时'); + case DioExceptionType.sendTimeout: + return DioException(requestOptions: e.requestOptions, error: '发送超时'); + case DioExceptionType.receiveTimeout: + return DioException(requestOptions: e.requestOptions, error: '接收超时'); + case DioExceptionType.badResponse: + var errorCode = e.response?.data["errorCode"]; + var errorMsg = e.response?.data["errorMsg"]; + if (errorCode == ATErroCode.userNotRegistered.code) { + //用户还没有注册 + ATTts.show("Please register an account first."); + BuildContext? context = navigatorKey.currentContext; + if (context != null) { + ATNavigatorUtils.push( + context, + LoginRouter.editProfile, + replace: false, + ); + } + } else if (errorCode == ATErroCode.authUnauthorized.code) { + //token过期 + BuildContext? context = navigatorKey.currentContext; + if (context != null) { + if (!inLoginPage) { + AccountStorage().logout(context); + inLoginPage = true; + } + } + } else if (errorCode == ATErroCode.passwordNotTrue.code) { + //需要输入房间密码 + String roomId = e.requestOptions.data["roomId"]; + bool needOpenRedenvelope = + e.requestOptions.data["needOpenRedenvelope"]; + String redPackId = e.requestOptions.data["redPackId"]; + SmartDialog.dismiss(tag: "showInputRoomPwd"); + SmartDialog.show( + tag: "showInputRoomPwd", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return InputRoomPwdPage( + roomId, + needOpenRedenvelope: needOpenRedenvelope, + redPackId: redPackId, + ); + }, + ); + } else if (errorCode == ATErroCode.redPacketFinished.code) { + //红包领完了直接跳到红包详情页面 + String packetId = e.requestOptions.data["packetId"]; + SmartDialog.dismiss(tag: "showRoomRedenvelopeList"); + SmartDialog.show( + tag: "showRoomRedenvelopeRec", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return RoomRedenvelopeRecRecordPage(packetId); + }, + ); + } else if (errorCode == ATErroCode.userJoinedFamily.code) { + BuildContext? context = navigatorKey.currentContext; + Provider.of( + context!, + listen: false, + ).getMyUserInfo(needRefreshFamily: true).then((res) { + navigatorKey.currentState?.popUntil( + ModalRoute.withName(FamilyRoute.reqRecordFamily), + ); + }); + } else { + if (errorMsg.toString().endsWith("balance not made")) { + BuildContext? context = navigatorKey.currentContext; + if (context != null) { + ATRoomUtils.goRechargeOperation(context); + } + } else { + ATTts.show(errorMsg); + } + } + return DioException( + requestOptions: e.requestOptions, + response: e.response, + error: '服务器错误: ${e.response?.data["errorCode"]}', + ); + case DioExceptionType.cancel: + return DioException(requestOptions: e.requestOptions, error: 'Cancel'); + default: + return DioException( + requestOptions: e.requestOptions, + error: 'Net fail', + ); + } + } +} + +class NotSuccessException implements Exception { + final String message; + + NotSuccessException(this.message); + + factory NotSuccessException.fromRespData(ResponseData respData) { + return NotSuccessException(respData.errorMsg ?? "操作失败"); + } +} diff --git a/lib/chatvibe_data/sources/remote/net/at_logger.dart b/lib/chatvibe_data/sources/remote/net/at_logger.dart new file mode 100644 index 0000000..567ceaa --- /dev/null +++ b/lib/chatvibe_data/sources/remote/net/at_logger.dart @@ -0,0 +1,267 @@ +/* +* @message: 日志拦截器 +* @Author: Jack +* @Email: Jack@163.com +* @Date: 2020-06-18 19:47:32 +*/ +import 'package:dio/dio.dart'; +import 'dart:math' as math; +import 'package:flutter/foundation.dart'; +// 必须引入此库处理 Emoji 截断问题 +import 'package:characters/characters.dart'; + +/// description: Dio 日志拦截器 +class ATDioLogger extends Interceptor { + final bool request; + final bool requestHeader; + final bool requestBody; + final bool responseBody; + final bool responseHeader; + final bool error; + static const int initialTab = 1; + static const String tabStep = ' '; + final bool compact; + final int maxWidth; + + /// Log printer; defaults debugPrint to console. + void Function(Object object) logPrint; + final bool enableLog; + + ATDioLogger({ + this.enableLog = kDebugMode, + this.request = true, + this.requestHeader = true, + this.requestBody = true, + this.responseHeader = false, + this.responseBody = true, + this.error = true, + this.maxWidth = 90, + this.compact = true, + this.logPrint = print, // 修复:在 iOS 上 debugPrint 比 print 更稳健 + }); + + @override + void onRequest(RequestOptions options, RequestInterceptorHandler handler) { + if (!enableLog) return handler.next(options); + + if (request) _printRequestHeader(options); + if (requestHeader) { + _printMapAsTable(options.queryParameters, header: 'Query Parameters'); + final requestHeaders = {}; + requestHeaders.addAll(options.headers); + requestHeaders['contentType'] = options.contentType?.toString(); + requestHeaders['responseType'] = options.responseType?.toString(); + requestHeaders['followRedirects'] = options.followRedirects; + requestHeaders['connectTimeout'] = options.connectTimeout; + requestHeaders['receiveTimeout'] = options.receiveTimeout; + _printMapAsTable(requestHeaders, header: 'Headers'); + _printMapAsTable(options.extra, header: 'Extras'); + } + if (requestBody && options.method != 'GET') { + final data = options.data; + if (data != null) { + if (data is Map) { + _printMapAsTable(data, header: 'Body'); + } else if (data is FormData) { + final formDataMap = Map() + ..addEntries(data.fields) + ..addEntries(data.files); + _printMapAsTable(formDataMap, header: 'Form data | ${data.boundary}'); + } else { + _printBlock(data.toString()); + } + } + } + handler.next(options); + } + + @override + void onError(DioException err, ErrorInterceptorHandler handler) { + if (!enableLog) return handler.next(err); + + if (error) { + if (err.type == DioExceptionType.badResponse) { + final uri = err.response?.requestOptions.uri; + _printBoxed( + header: 'DioError ║ Status: ${err.response?.statusCode} ${err.response?.statusMessage}', + text: uri.toString()); + if (err.response?.data != null) { + logPrint('╔ ${err.type.toString()}'); + _printResponse(err.response!); + } + _printLine('╚'); + } else { + final uri = err.requestOptions.uri; + _printBoxed(header: 'DioError ║ ${err.type} ║ ${uri.toString()}', text: err.message); + } + } + handler.next(err); + } + + @override + void onResponse(Response response, ResponseInterceptorHandler handler) { + if (!enableLog) return handler.next(response); + + if (responseHeader) { + final responseHeaders = {}; + response.headers.forEach((k, list) => responseHeaders[k] = list.toString()); + _printMapAsTable(responseHeaders, header: 'Headers'); + } + + if (responseBody) { + _printResponseHeader(response); + logPrint('╔ Body'); + logPrint('║'); + _printResponse(response); + logPrint('║'); + _printLine('╚'); + } + handler.next(response); + } + + void _printBoxed({String? header, String? text}) { + logPrint(''); + logPrint('╔╣ $header'); + logPrint('║ $text'); + _printLine('╚'); + } + + void _printResponse(Response response) { + if (response.data != null) { + if (response.data is Map) { + _printPrettyMap(response.data); + } else if (response.data is List) { + logPrint('║${_indent()}['); + _printList(response.data); + logPrint('║${_indent()}]'); + } else { + _printBlock(response.data.toString()); + } + } + } + + void _printResponseHeader(Response response) { + final uri = response.requestOptions.uri; + final method = response.requestOptions.method; + String header = 'Response ║ $method ║ Status: ${response.statusCode} ${response.statusMessage}'; + logPrint('╔╣ $header ${'═' * 20}'); + logPrint('║ ${uri.toString()}'); + logPrint('║ '); + } + + void _printRequestHeader(RequestOptions options) { + final uri = options.uri; + final method = options.method; + _printBoxed(header: 'Request ║ $method ', text: uri.toString()); + } + + void _printLine([String pre = '', String suf = '╝']) => logPrint('$pre${'═' * maxWidth}'); + + void _printKV(String key, Object v) { + final pre = '╟ $key: '; + final msg = v.toString(); + + if (pre.length + msg.length > maxWidth) { + logPrint(pre); + _printBlock(msg); + } else { + logPrint('$pre$msg'); + } + } + + // 修复:使用 characters 处理截断,防止 Emoji 损坏 + void _printBlock(String msg) { + final charData = msg.characters; + int lines = (charData.length / maxWidth).ceil(); + for (int i = 0; i < lines; ++i) { + final start = i * maxWidth; + final end = math.min(start + maxWidth, charData.length); + logPrint('║ ' + charData.getRange(start, end).toString()); + } + } + + String _indent([int tabCount = initialTab]) => tabStep * tabCount; + + void _printPrettyMap(Map data, {int tabs = initialTab, bool isListItem = false, bool isLast = false}) { + final bool isRoot = tabs == initialTab; + final initialIndent = _indent(tabs); + tabs++; + + if (isRoot || isListItem) logPrint('║$initialIndent{'); + + data.keys.toList().asMap().forEach((index, key) { + final isLast = index == data.length - 1; + var value = data[key]; + if (value is String) value = '\"$value\"'; + + if (value is Map) { + if (compact && _canFlattenMap(value)) { + logPrint('║${_indent(tabs)} $key: $value${!isLast ? ',' : ''}'); + } else { + logPrint('║${_indent(tabs)} $key: {'); + _printPrettyMap(value, tabs: tabs); + } + } else if (value is List) { + if (compact && _canFlattenList(value)) { + logPrint('║${_indent(tabs)} $key: ${value.toString()}'); + } else { + logPrint('║${_indent(tabs)} $key: ['); + _printList(value, tabs: tabs); + logPrint('║${_indent(tabs)} ]${isLast ? '' : ','}'); + } + } else { + final msg = value.toString().replaceAll('\n', ''); + final indent = _indent(tabs); + final charMsg = msg.characters; + final linWidth = maxWidth - indent.length; + + // 修复:字符串分段时处理 Emoji + if (charMsg.length + indent.length > maxWidth) { + int lines = (charMsg.length / linWidth).ceil(); + for (int i = 0; i < lines; ++i) { + final start = i * linWidth; + final end = math.min(start + linWidth, charMsg.length); + logPrint('║${_indent(tabs)} ${charMsg.getRange(start, end).toString()}'); + } + } else { + logPrint('║${_indent(tabs)} $key: $msg${!isLast ? ',' : ''}'); + } + } + }); + + logPrint('║$initialIndent}${isListItem && !isLast ? ',' : ''}'); + } + + void _printList(List list, {int tabs = initialTab}) { + list.asMap().forEach((i, e) { + final isLast = i == list.length - 1; + if (e is Map) { + if (compact && _canFlattenMap(e)) { + logPrint('║${_indent(tabs)} $e${!isLast ? ',' : ''}'); + } else { + _printPrettyMap(e, tabs: tabs + 1, isListItem: true, isLast: isLast); + } + } else { + logPrint('║${_indent(tabs + 2)} $e${isLast ? '' : ','}'); + } + }); + } + + bool _canFlattenMap(Map map) { + return map.values.where((val) => val is Map || val is List).isEmpty && + map.toString().length < maxWidth; + } + + bool _canFlattenList(List list) { + return (list.length < 10 && list.toString().length < maxWidth); + } + + void _printMapAsTable(Map? map, {String? header}) { + if (map == null || map.isEmpty) return; + logPrint('╔ $header '); + map.forEach((key, value) { + _printKV(key.toString(), (value ?? 'null').toString()); + }); + _printLine('╚'); + } +} \ No newline at end of file diff --git a/lib/chatvibe_data/sources/remote/net/network_client.dart b/lib/chatvibe_data/sources/remote/net/network_client.dart new file mode 100644 index 0000000..9c10cb7 --- /dev/null +++ b/lib/chatvibe_data/sources/remote/net/network_client.dart @@ -0,0 +1,238 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/utilities/at_deviceId_utils.dart'; +import 'package:aslan/chatvibe_data/sources/remote/net/api.dart'; +import 'package:aslan/chatvibe_data/sources/remote/net/at_logger.dart'; + +NetworkClient get http => _httpInstance; +bool inLoginPage = false; + +late final NetworkClient _httpInstance = NetworkClient(); + +class NetworkClient extends BaseNetworkClient { + @override + void init() { + // 设置基础URL + dio.options.baseUrl = ATGlobalConfig.apiHost; + + // 添加拦截器 + dio.interceptors + ..add(ApiInterceptor()) + ..add(ATDioLogger()) + ..add(TimeOutInterceptor()); + + setupCacheInterceptor(dio); // 传入 dio 实例 + } + + Future setupCacheInterceptor(Dio dio) async { + // 缓存配置保持不变... + } +} + +class ApiInterceptor extends Interceptor { + @override + void onRequest( + RequestOptions options, + RequestInterceptorHandler handler, + ) async { + // 确保使用最新的 baseUrl + options.baseUrl = ATGlobalConfig.apiHost; + String token = AccountStorage().getToken(); + var imei = await ATDeviceIdUtils.collectDeviceId(); + Map headMap = {}; + headMap["req-lang"] = ATGlobalConfig.lang; + headMap["X-Forwarded-Proto"] = "http"; + headMap["User-Agent"] = "Dart/3.7.2(dart:io)"; + headMap["req-imei"] = imei; + + headMap["Host"] = options.uri.host; + headMap["req-app-intel"] = + "version=${ATGlobalConfig.version};build=${ATGlobalConfig.build};model=${ATGlobalConfig.model};sysVersion=${ATGlobalConfig.sysVersion};channel=${ATGlobalConfig.channel}"; + headMap["req-client"] = Platform.isAndroid ? 'Android' : 'iOS'; + headMap["req-sys-origin"] = + "origin=${ATGlobalConfig.origin};originChild=${ATGlobalConfig.originChild}"; + if (token.isNotEmpty) { + headMap["Authorization"] = "Bearer $token"; + } + + ///加密 + headMap["Req-Atyou"] = true; + options.headers.addAll(headMap); + + handler.next(options); + } + + @override + void onResponse(Response response, ResponseInterceptorHandler handler) async { + if (response.statusCode == 200) { + final ResponseData respData = ResponseData.fromJson(response.data); + if (respData.success) { + handler.next(response); + } else { + // if (respData.code == 210||respData.code == 212) { // 登录失效 + // if (!inLoginPage) { + // Fluttertoast.showToast(msg: respData.msg ?? "需要重新登录"); + // SpUtils.sp?.clear(); + // navigatorKey.currentState?.pushAndRemoveUntil( + // MaterialPageRoute(builder: (_) { + // Provider.of(_, listen: false).destoryRtm(); + // Provider.of(_, listen: false).logout(); + // Provider.of(_, listen: false).engine?.leaveChannel(); + // return const LoginPage(); + // }), + // (route) => false // 清除所有路由 + // ); + // + // inLoginPage = true; + // } + // } else { + // Fluttertoast.showToast(msg: respData.msg ?? "操作失败"); + // } + + // 使用 DioException 传递错误 + handler.reject( + DioException( + requestOptions: response.requestOptions, + error: NotSuccessException.fromRespData(respData), + response: response, + message: respData.errorMsg, + ), + ); + } + } else { + Fluttertoast.showToast(msg: 'Network error, please check the status.'); + handler.reject( + DioException( + requestOptions: response.requestOptions, + error: 'HTTP ${response.statusCode} 错误', + response: response, + ), + ); + } + } +} + +/// 响应数据结构 +class ResponseData { + final bool status; + final int? errorCode; + final String? errorCodeName; + final String? errorMsg; + final T? body; + final num? time; + + bool get success => status; + + ResponseData({ + required this.status, + this.errorCode, + this.errorCodeName, + this.errorMsg, + this.body, + this.time, + }); + + // 特殊处理基础类型 + static T? _parseData(dynamic data, T Function(dynamic) fromJsonT) { + if (data == null) return null; + + // 布尔值特殊处理 + if (T == bool) { + if (data is bool) return data as T; + if (data is int) return (data != 0) as T; + if (data is String) return (data.toLowerCase() == 'true') as T; + } + + // 其他基础类型 + if (T == int || T == double || T == String || T == bool) { + return data as T; + } + + // 复杂类型使用转换函数 + return fromJsonT(data); + } + + factory ResponseData.fromJson( + Map json, { + T Function(dynamic)? fromJsonT, + }) { + if (fromJsonT == null) { + return ResponseData( + status: json['status'], + errorCode: json['errorCode'], + errorCodeName: json['errorCodeName'], + errorMsg: json['errorMsg'], + body: json['body'], + time: json['time'], + ); + } else { + return ResponseData( + status: json['status'], + errorCode: json['errorCode'], + errorCodeName: json['errorCodeName'], + errorMsg: json['errorMsg'], + body: json['body'] != null ? _parseData(json['body'], fromJsonT) : null, + time: json['time'], + ); + } + } +} + +/// 超时拦截器 +class TimeOutInterceptor extends Interceptor { + static const int globalTimeoutSeconds = 15; + final Map _timers = {}; + + @override + void onError(DioException err, ErrorInterceptorHandler handler) async { + + if (err.type == DioExceptionType.connectionTimeout || + err.type == DioExceptionType.receiveTimeout || + err.type == DioExceptionType.sendTimeout) { + print('超时错误: URL => ${err.requestOptions.baseUrl}'); + print('当前使用的Host: ${ATGlobalConfig.apiHost}'); + } + + // 请求出错,取消计时器 + final String requestKey = err.requestOptions.uri.toString(); + _timers[requestKey]?.cancel(); + _timers.remove(requestKey); + + handler.next(err); + } + + @override + void onRequest(RequestOptions options, RequestInterceptorHandler handler) { + // 为每个请求创建超时计时器 + final cancelToken = options.cancelToken ?? CancelToken(); + final String requestKey = options.uri.toString(); + + // 设置15秒后自动取消 + _timers[requestKey] = Timer( + const Duration(seconds: globalTimeoutSeconds), + () { + ATLoadingManager.veilRoutine(); + if (!cancelToken.isCancelled) { + cancelToken.cancel('请求超时(${globalTimeoutSeconds}秒)'); + // 从映射中移除计时器 + _timers.remove(requestKey); + } + } + ); + handler.next(options); + } + + @override + void onResponse(Response response, ResponseInterceptorHandler handler) { + // 请求成功完成,取消计时器 + final String requestKey = response.requestOptions.uri.toString(); + _timers[requestKey]?.cancel(); + _timers.remove(requestKey); + handler.next(response); + } +} diff --git a/lib/chatvibe_data/sources/repositories/config_repository_imp.dart b/lib/chatvibe_data/sources/repositories/config_repository_imp.dart new file mode 100644 index 0000000..e98e2fd --- /dev/null +++ b/lib/chatvibe_data/sources/repositories/config_repository_imp.dart @@ -0,0 +1,305 @@ +import 'package:aslan/chatvibe_domain/models/res/at_game_ranking_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_google_pay_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_product_config_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_version_manage_latest_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/country_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_get_list_game_config_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_index_banner_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_level_config_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_start_page_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_top_four_with_reward_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/version_manage_lates_review_res.dart'; +import 'package:aslan/chatvibe_domain/repositories/config_repository.dart'; +import 'package:aslan/chatvibe_data/sources/remote/net/network_client.dart'; + +class ConfigRepositoryImp implements ChatVibeConfigRepository { + static ConfigRepositoryImp? _instance; + + ConfigRepositoryImp._internal(); + + factory ConfigRepositoryImp() { + return _instance ??= ConfigRepositoryImp._internal(); + } + + ///sys/config/country + @override + Future loadCountry() async { + final result = await http.get( + "9c370c6fef50fc728152e7a3fd5b47d2841a1d9a5a2b0bb769039fdd47f01585", + fromJson: (json) => CountryRes.fromJson(json), + ); + return result; + } + + ///sys/config/banner + @override + Future> getBanner({List?types}) async { + Map params = {}; + if (types != null) { + params["types"] = types; + } + final result = await http.get( + "0d8319c7a696c73d58f5e0ae304dc663f574ade4154ed90ccaee524f6ba14490", + queryParams: params, + fromJson: + (json) => + (json as List).map((e) => ATIndexBannerRes.fromJson(e)).toList(), + ); + return result; + } + + ///sys/config/enum/config + @override + Future getConfig() async { + final result = await http.get( + "e6fcae3c4a41806e15b4fc015126361267972b2854bbea42d0f0089c5f1c7724", + fromJson: (json) => CountryRes.fromJson(json), + ); + return result; + } + + ///sys/config/customer-service + @override + Future getCustomerService() async { + final result = await http.get( + "ba316258c14cc3ebddb6d28ec314bc5704e593861ca693058e9e98ab3114cf05", + fromJson: (json) => CountryRes.fromJson(json), + ); + return result; + } + + ///sys/config/match/game-config + @override + Future getGameConfig() async { + final result = await http.get( + "3fad8211eb3926e695ffdc18a6134548472dfaa70220e4891575f52df4d0ad58", + fromJson: (json) => CountryRes.fromJson(json), + ); + return result; + } + + ///sys/config/list-game-config + @override + Future> getListGameConfig({ + String? roomId, + String? category, + String? countryCode, + }) async { + Map params = {}; + if (roomId != null) { + params["roomId"] = roomId; + } + if (category != null) { + params["category"] = category; + } + if (countryCode != null) { + params["countryCode"] = countryCode; + } + final result = await http.get>( + "f5150bc703a9c6ef72363f0e90709cb4d8988f5ee8f8029b84399fb30334d728", + queryParams: params, + fromJson: + (json) => + (json as List) + .map((e) => ATGetListGameConfigRes.fromJson(e)) + .toList(), + ); + return result; + } + + ///ranking/top-four-with-reward + @override + Future> topFourWithReward() async { + final result = await http.post>( + "162c2587d0236c6122a197f872daf865c29cc45b9c80570e9f4432051b2040e4", + data: {}, + fromJson: + (json) => + (json as List) + .map((e) => ATTopFourWithRewardRes.fromJson(e)) + .toList(), + ); + return result; + } + + ///sys/config/sys/notice-message + @override + Future getNoticeMessage() async { + final result = await http.get( + "b45ab3738ec718525eb02dcc8b1c79165ae6295dd8ec0e521307c70a005119b2", + fromJson: (json) => CountryRes.fromJson(json), + ); + return result; + } + + ///sys/config/room/shortcut-game + @override + Future getShortcutGame() async { + final result = await http.get( + "690f390b1c91b6cb0b4fdbeb98215a9f6250d3c75d1a379f07f015a1728e009d", + fromJson: (json) => CountryRes.fromJson(json), + ); + return result; + } + + ///sys/config/banner/start-page + @override + Future> getStartPage() async { + final result = await http.get( + "0d8319c7a696c73d58f5e0ae304dc66352cfd4d4480504cd5f589338f4f90157", + fromJson: + (json) => + (json as List).map((e) => ATStartPageRes.fromJson(e)).toList(), + ); + return result; + } + + ///sys/config/room/getSudCode + @override + Future getSudCode() async { + final result = await http.get( + "690f390b1c91b6cb0b4fdbeb98215a9f2fb1663213697d032f7fe4dd9aa9e8f4", + fromJson: (json) => CountryRes.fromJson(json), + ); + return result; + } + + ///sys/config/country/top-six + @override + Future loadTopSix() async { + final result = await http.get( + "9c370c6fef50fc728152e7a3fd5b47d263ca03b0e63d90aeb3c57f8d946ecd78", + fromJson: (json) => CountryRes.fromJson(json), + ); + return result; + } + + ///order/product-config + @override + Future> productConfig() async { + final result = await http.get>( + "1585c72b88d0d249c7078aae852a0806cc3d8e483b8d861962a977a00523180a", + fromJson: + (json) => + (json as List).map((e) => ATProductConfigRes.fromJson(e)).toList(), + ); + return result; + } + + ///order/purchase-pay/google + @override + Future googlePay( + String product, + String signature, + String purchaseData, { + String? friendId, + }) async { + Map params = {}; + params["product"] = product; + params["signature"] = signature; + params["purchaseData"] = purchaseData; + if (friendId != null) { + params["friendId"] = friendId; + } + final result = await http.post( + "3a5ab504e90faccde8258f4ea1dd6bcdad6eec89ad8cc807b184537e0fdb6904", + data: params, + fromJson: (json) => ATGooglePayRes.fromJson(json), + ); + return result; + } + + ///order/purchase-pay/apple + @override + Future applePay( + String product, + String receipt, + String transaction, { + String? friendId, + }) async { + Map params = {}; + params["product"] = product; + params["receipt"] = receipt; + params["transaction"] = transaction; + if (friendId != null) { + params["friendId"] = friendId; + } + final result = await http.post( + "3a5ab504e90faccde8258f4ea1dd6bcd7e041553c5af750a6eff64c18e7de276", + data: params, + fromJson: (json) => ATGooglePayRes.fromJson(json), + ); + return result; + } + + ///sys/static-config/level + @override + Future configLevel() async { + final result = await http.get( + "9d964981940fe0403d53d6f44e7cf7a48c2534f1e31f6b5fa9b1fae672b836d1", + fromJson: (json) => ATLevelConfigRes.fromJson(json), + ); + return result; + } + + ///sys/config/game/ranking + @override + Future gameRanking(int current,String periodType, {int? size = 20}) async { + Map parm = {}; + parm["current"] = current; + parm["periodType"] = periodType; + parm["size"] = size; + final result = await http.get( + "262d45e2d05764dea846032c89f556fb8164885fbff10478cf55aa3721a0ba34", + queryParams: parm, + fromJson: (json) => ATGameRankingRes.fromJson(json), + ); + return result; + } + + ///sys/version/manage/release/latest + @override + Future versionManageLatest() async{ + final result = await http.get( + "ee9584f714ded864780e47dab2cf4a2e84ac21c90fcd0966a13d2ce9e8845eb8e580afbe66f9f0fef79429cd5c1e0687", + fromJson: (json) => ATVersionManageLatestRes.fromJson(json), + ); + return result; + } + + ///sys/version/manage/latest/review + @override + Future versionManageLatestReview() async{ + final result = await http.get( + "ee9584f714ded864780e47dab2cf4a2e11ce42bdd061186d4efe3305b73f10fe574aff257ce7e668d08f4caccd1c6232", + fromJson: (json) => VersionManageLatesReviewRes.fromJson(json), + ); + return result; + } + + ///sys/config/customer-service + @override + Future customerService() async{ + final result = await http.get( + "ba316258c14cc3ebddb6d28ec314bc5704e593861ca693058e9e98ab3114cf05", + fromJson: (json) => ChatVibeUserProfile.fromJson(json), + ); + return result; + } + + ///ranking/king-games-daily-top-three + @override + Future> kingGamesDailyTopThree() async{ + final result = await http.post>( + "c0c68485b2c8a2c7eb1973ee17866bfb6015dceaae385abe0e1a1598d3af93f64eb4a9d0d7e9231cab7de49e86a5b8bc", + data: {}, + fromJson: + (json) => + (json as List) + .map((e) => ATTopFourWithRewardRes.fromJson(e)) + .toList(), + ); + return result; + } +} diff --git a/lib/chatvibe_data/sources/repositories/dynamic_repository_imp.dart b/lib/chatvibe_data/sources/repositories/dynamic_repository_imp.dart new file mode 100644 index 0000000..8ab2573 --- /dev/null +++ b/lib/chatvibe_data/sources/repositories/dynamic_repository_imp.dart @@ -0,0 +1,342 @@ +import 'package:aslan/chatvibe_domain/models/req/at_dynamic_create_pic_cmd.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_comment_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/dynamic_list_res.dart'; + +import 'package:aslan/chatvibe_domain/models/res/create_dynamic_res.dart'; +import 'package:aslan/chatvibe_domain/repositories/dynamic_repository.dart'; +import 'package:aslan/chatvibe_data/sources/remote/net/network_client.dart'; + +class DynamicRepositoryImp extends ChatVibeDynamicRepository { + ///dynamic/create + @override + Future create({ + String? content, + List? pics, + }) async { + var parm = {}; + if (content != null && content.isNotEmpty) { + parm["content"] = content; + } + if (pics != null && pics.isNotEmpty) { + parm["pictures"] = pics.map((e) => e.toJson()).toList(); + } + final result = await http.post( + "c6b7e853f0287ca904e62b817c0c2804", + data: parm, + fromJson: (json) => ChatVibeCreateDynamicRes.fromJson(json), + ); + return result; + } + + ///dynamic/list/latest + @override + Future dynamicListLatest({int? current, int? size}) async { + Map param = {}; + param["pageReq.cursor"] = current ?? 1; + param["pageReq.limit"] = size ?? 20; + final result = await http.get( + "14eb279db3a1d374a3ec238caf5d50ce49104613187b63818de310dc5ff8f023", + queryParams: param, + fromJson: (json) => ChatVibeDynamicListRes.fromJson(json), + ); + return result; + } + + ///dynamic/like + @override + Future dynamicLike(String dynamicId, bool like) async { + var parm = {}; + parm["dynamicId"] = dynamicId; + parm["like"] = like; + final result = await http.post( + "0b8ed002815cedf85677bb20ee541df2", + data: parm, + fromJson: (json) => json as num, + ); + return result; + } + + ///dynamic/details + @override + Future dynamicDetails(String id) async { + Map param = {}; + param["id"] = id; + final result = await http.get( + "dc9c33ccfc556b23a6a1f43ae6ac892b", + queryParams: param, + fromJson: (json) => Records.fromJson(json), + ); + return result; + } + + ///dynamic/list/comment + @override + Future dynamicListComment( + String dynamicId, + int pageNumber, + int limit, + ) async { + Map param = {}; + param["dynamicId"] = dynamicId; + param["pageReq.cursor"] = pageNumber; + param["pageReq.limit"] = limit; + final result = await http.get( + "e5dafae15452c9c43b77f2a55759e89458bd80de366d385801a87e72ff905b76", + queryParams: param, + fromJson: (json) => ATCommentListRes.fromJson(json), + ); + return result; + } + + ///dynamic/comment + @override + Future dynamicComment( + String content, + String id, { + String? toUserId, + num? rootCommentId, + String? commentId, + }) async { + var parm = {}; + parm["content"] = content; + parm["id"] = id; + if (toUserId != null) { + parm["toUserId"] = toUserId; + } + if (rootCommentId != null) { + parm["rootCommentId"] = rootCommentId; + } + if (commentId != null) { + parm["commentId"] = commentId; + } + final result = await http.post( + "5ed46d5f6b06994009e19a00a356de4f", + data: parm, + fromJson: (json) => CommentListRecords.fromJson(json), + ); + return result; + } + + ///dynamic/delete + @override + Future dynamicDelete(String id) async { + var parm = {}; + parm["id"] = id; + final result = await http.post( + "fc911f202ed209368d814e10e2081c14", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///dynamic/like/comment + @override + Future likeComment( + String dynamicId, + String commentId, + num rootCommentId, + String toUserId, + bool like, + ) async { + var parm = {}; + parm["dynamicId"] = dynamicId; + parm["commentId"] = commentId; + parm["rootCommentId"] = rootCommentId; + parm["toUserId"] = toUserId; + parm["like"] = like; + final result = await http.post( + "4b6a119b0018efd2a529f9d80cb8a2ad58bd80de366d385801a87e72ff905b76", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///dynamic/delete/comment + @override + Future deleteComment(String id) async { + var parm = {}; + parm["id"] = id; + final result = await http.post( + "4608156017946910082713caaf4bf5026be7e32dc9da9233e9d482f05998a9e1", + data: parm, + fromJson: (json) => json as num, + ); + return result; + } + + ///dynamic/list/comment/children + @override + Future dynamicListCommentChildren( + num rootCommentId, + int pageNumber, + ) async { + Map param = {}; + param["rootCommentId"] = rootCommentId; + param["pageReq.cursor"] = pageNumber; + param["pageReq.limit"] = 2; + final result = await http.get( + "e5dafae15452c9c43b77f2a55759e894d07519ae1921777af75b7dbafb78ffc5", + queryParams: param, + fromJson: (json) => ATCommentListRes.fromJson(json), + ); + return result; + } + + ///dynamic/list/follow + @override + Future dynamicListFollow({int? current, int? size}) async { + Map param = {}; + param["pageReq.cursor"] = current ?? 1; + param["pageReq.limit"] = size ?? 20; + final result = await http.get( + "2ba8ee7857e57a94051636c5c67033204f07c10e6e082e600b3e6319a1b07d23", + queryParams: param, + fromJson: (json) => ChatVibeDynamicListRes.fromJson(json), + ); + return result; + } + + ///dynamic/list/like + @override + Future dynamicListLike( + String dynamicId, { + int? current, + int? size, + }) async { + Map param = {}; + param["dynamicId"] = dynamicId; + param["pageReq.cursor"] = current ?? 1; + param["pageReq.limit"] = size ?? 20; + final result = await http.get( + "0b236bd50c535f920f1b2303463d19061e1cf9b945681bdfb3d4cf7a5ecfc943", + queryParams: param, + fromJson: (json) => ChatVibeDynamicListRes.fromJson(json), + ); + return result; + } + + ///dynamic/list/gift + @override + Future dynamicListGift( + String dynamicId, { + int? current, + int? size, + }) async { + Map param = {}; + param["dynamicId"] = dynamicId; + param["pageReq.cursor"] = current ?? 1; + param["pageReq.limit"] = size ?? 20; + final result = await http.get( + "b8946ddfa428036aa88c3cef961b43ace580afbe66f9f0fef79429cd5c1e0687", + queryParams: param, + fromJson: (json) => ChatVibeDynamicListRes.fromJson(json), + ); + return result; + } + + ///dynamic/report + @override + Future dynamicReport( + String dynamicContentId, + num reportType, { + String? reportedContent, + String? imageUrls, + }) async { + Map params = {}; + params["dynamicContentId"] = dynamicContentId; + params["reportType"] = reportType; + if (reportedContent != null && reportedContent.isNotEmpty) { + params["reportedContent"] = reportedContent; + } + if (imageUrls != null && imageUrls.isNotEmpty) { + params["imageUrls"] = imageUrls; + } + final result = await http.post( + "fd360b42cbc91122440791159b0807f9", + data: params, + fromJson: (json) => json as bool, + ); + return result; + } + + ///dynamic/check/send/permission + @override + Future dynamicCheckSendPermission({String? userId}) async { + Map params = {}; + if (userId != null) { + params["userId"] = userId; + } + final result = await http.get( + "0a44ee2a9465cc170fbadd6ace714fa7fd878668129b45fdacad49686c802808", + queryParams: params, + fromJson: (json) => json as bool, + ); + return result; + } + + ///dynamic/list/message + @override + Future dynamicListMessage( + List dynamicMessageEnums, { + int? current, + int? size, + }) async { + Map param = {}; + param["dynamicMessageEnums"] = dynamicMessageEnums; + param["pageReq.cursor"] = current ?? 1; + param["pageReq.limit"] = size ?? 20; + final result = await http.get( + "4d0832531899c4cde40a7ced28e9ecfeeddbc14bc7c7d75649a3605379e58462", + queryParams: param, + fromJson: (json) => ChatVibeDynamicListRes.fromJson(json), + ); + return result; + } + + ///dynamic/my/page + @override + Future dynamicMyPage( + String userId, { + int? current, + int? size, + }) async { + Map param = {}; + param["userId"] = userId; + param["pageReq.cursor"] = current ?? 1; + param["pageReq.limit"] = size ?? 20; + final result = await http.get( + "b4a300b9a4e5cfbb0720f3485b792430", + queryParams: param, + fromJson: (json) => ChatVibeDynamicListRes.fromJson(json), + ); + return result; + } + + ///dynamic/admin/blacklist/add + @override + Future dynamicAdminBlacklistAdd(String userId) async { + Map params = {}; + params["userId"] = userId; + final result = await http.post( + "892254a6d84947223f3a3f578a24cc5a7c17ba27bd5186affb0766578fc1e9fc", + data: params, + fromJson: (json) => json as bool, + ); + return result; + } + + ///dynamic/admin/blacklist/delete + @override + Future dynamicAdminBlacklistDelete(String userId) async { + Map params = {}; + params["userId"] = userId; + final result = await http.post( + "892254a6d84947223f3a3f578a24cc5af3d77f6072c23628d3c2244fed516d92", + data: params, + fromJson: (json) => json as bool, + ); + return result; + } +} diff --git a/lib/chatvibe_data/sources/repositories/family_repository_impl.dart b/lib/chatvibe_data/sources/repositories/family_repository_impl.dart new file mode 100644 index 0000000..cdf9b98 --- /dev/null +++ b/lib/chatvibe_data/sources/repositories/family_repository_impl.dart @@ -0,0 +1,344 @@ +import 'package:aslan/chatvibe_domain/models/res/at_famaily_leader_board_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/family_base_info_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_family_home_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_family_level_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_family_list_member_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_family_list_message_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_family_my_apply_list_res.dart'; + +import 'package:aslan/chatvibe_domain/models/res/family_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_family_news_list_res.dart'; +import 'package:aslan/chatvibe_domain/repositories/family_repository.dart'; +import 'package:aslan/chatvibe_data/sources/remote/net/network_client.dart'; + +class GroupRepository implements ChatVibeFamilyRepository { + ///family/create + @override + Future createFamily( + String familyAvatar, + String familyName, + String familyIntro, + ) async { + final result = await http.post( + "a3b4aa6c1e1426a824987fb6ebc57c53", + data: { + "familyAvatar": familyAvatar, + "familyName": familyName, + "familyIntro": familyIntro, + }, + fromJson: (json) => json as num, + ); + return result; + } + + ///family/list + @override + Future familyList({ + String? familyAccount, + int? current, + int? size, + }) async { + Map param = {}; + if (familyAccount != null) { + param["familyAccount"] = familyAccount; + } + param["current"] = current ?? 1; + param["size"] = size ?? 20; + final result = await http.get( + "312418dfa639684f14e8a2a6bb199dc8", + queryParams: param, + fromJson: (json) => ChatVibeFamilyListRes.fromJson(json), + ); + return result; + } + + ///family/base-info + @override + Future familyBaseInfo({String? familyId}) async { + Map param = {}; + if (familyId != null) { + param["familyId"] = familyId; + } + final result = await http.get( + "4b2c214569313ebaaa7d082cd38ec708574aff257ce7e668d08f4caccd1c6232", + queryParams: param, + fromJson: (json) => ChatVibeFamilyBaseInfoRes.fromJson(json), + ); + return result; + } + + ///family/my-apply-list + @override + Future> familyMyApplyList({num? lastId}) async { + Map param = {}; + if (lastId != null) { + param["lastId"] = lastId; + } + final result = await http.get>( + "bff32928ab5b6e050e88f431cdbe437fbc4a9e8c4704c0f73c085e7ab50e7fdb", + queryParams: param, + fromJson: + (json) => + (json as List) + .map((e) => ATFamilyMyApplyListRes.fromJson(e)) + .toList(), + ); + return result; + } + + ///family/list/message + @override + Future> familyListMessage({String? lastId}) async { + Map param = {}; + if (lastId != null) { + param["lastId"] = lastId; + } + final result = await http.get>( + "bf45ba69fc02587bd93e32b6cc84f7f0118a90740b43e354a7fdc9ac837e0671", + queryParams: param, + fromJson: + (json) => + (json as List) + .map((e) => ATFamilyListMessageRes.fromJson(e)) + .toList(), + ); + return result; + } + + ///family/list/member + @override + Future familyListMember({ + String? familyId, + String? account, + int? pageNo, + int? pageSize, + }) async { + Map param = {}; + if (familyId != null) { + param["familyId"] = familyId; + } + if (account != null) { + param["account"] = account; + } + param["pageNo"] = pageNo ?? 1; + param["pageSize"] = pageSize ?? 20; + final result = await http.get( + "e5d32a29d0b976ed2a2e7e464c879466a5bdd1a37e4505ec37dd5d285a1471c6", + queryParams: param, + fromJson: (json) => ATFamilyListMemberRes.fromJson(json), + ); + return result; + } + + ///family/member-top10 + @override + Future familyMemberTop10(String familyId) async { + final result = await http.get( + "5243e73197073ef44447981e3595bd5a7a5a85377a417be33abe2b448701736d", + queryParams: {"familyId": familyId}, + fromJson: (json) => ATFamailyLeaderBoardRes.fromJson(json), + ); + return result; + } + + ///family/member-top100 + @override + Future familyMemberTop100(String familyId) async { + final result = await http.get( + "5243e73197073ef44447981e3595bd5a6a775d7a79f0f62ae48a8c55b6118f3b", + fromJson: (json) => ATFamailyLeaderBoardRes.fromJson(json), + ); + return result; + } + + ///family/edit + @override + Future familyEdit( + String familyAvatar, + String familyName, + String familyIntro, + String familyNotice, + ) async { + final result = await http.post( + "8434d8ad73e5ddb94776a997328f13f7", + data: { + "familyAvatar": familyAvatar, + "familyName": familyName, + "familyIntro": familyIntro, + "familyNotice": familyNotice, + }, + fromJson: (json) => json as bool, + ); + return result; + } + + ///family/box/contribute + @override + Future familyBoxContribute(String familyId) async { + final result = await http.post( + "80f6748a73d20c6059962041b183137cbaa92bfd0fc7d4157fe8dba90ce74c07", + data: {"familyId": familyId}, + fromJson: (json) => json as bool, + ); + return result; + } + + ///family/grant-user-role + @override + Future familyGrantUserRole( + String authorizedMemberId, + String roleKey, + ) async { + final result = await http.post( + "54c970de0627f59b0d5b4d43f00db1e954b48c84cf173de5ec959d1ee27d5fc7", + data: {"authorizedMemberId": authorizedMemberId, "roleKey": roleKey}, + fromJson: (json) => json as bool, + ); + return result; + } + + ///family/remove-user + @override + Future familyRemoveUser(String removeMemberId) async { + final result = await http.get( + "2e6b109eb9a7ca11920d3fba48c27c2ca5bdd1a37e4505ec37dd5d285a1471c6", + queryParams: {"removeMemberId": removeMemberId}, + fromJson: (json) => json as bool, + ); + return result; + } + + ///family/list/level-config + @override + Future> familyLevelConfig() async { + final result = await http.get>( + "c39b6cbb6105927a47a782ca7ad5230f96f84d621e91f8ffef39ccaf32efed47", + fromJson: + (json) => + (json as List).map((e) => ATFamilyLevelRes.fromJson(e)).toList(), + ); + return result; + } + + ///family/home-list + @override + Future familyHomeList({int? pageNo, int? pageSize}) async { + Map param = {}; + param["current"] = pageNo ?? 1; + param["size"] = pageSize ?? 20; + final result = await http.get( + "871a28a4335d028a0d3c96962e82e487574aff257ce7e668d08f4caccd1c6232", + queryParams: param, + fromJson: (json) => ATFamilyHomeListRes.fromJson(json), + ); + return result; + } + + ///family/apply-join + @override + Future familyApplyJoin(String familyId) async { + final result = await http.post( + "125d2fea9bb127e72ca4287828ffa1039cfc000a312a4dfb807b23aa6cb840ee", + data: {"familyId": familyId}, + fromJson: (json) => json as bool, + ); + return result; + } + + ///family/reapply + @override + Future familyReapply(String msgId) async { + final result = await http.post( + "f91b30ba4fff219e91b57a28bdbafc37", + data: {"msgId": msgId}, + fromJson: (json) => json as bool, + ); + return result; + } + + ///family/message/handle + @override + Future familyMessageHandle(String familyMessageId, String event) async { + final result = await http.post( + "dd9c14df25a77671aa16c5efbe604d7c6f894acdcdbac7ccb7e88e9d69ee3eff", + data: {"familyMessageId": familyMessageId, "event": event}, + fromJson: (json) => json as bool, + ); + return result; + } + + ///family/cancel-apply + @override + Future familyCancelApply({num? msgId}) async { + final result = await http.post( + "b1961ce4e32c5fb2109fae673c208b9b50a963ba1f7d0b07773c88f61750e549", + data: {"msgId": msgId}, + fromJson: (json) => json as bool, + ); + return result; + } + + ///family/news/list + @override + Future familyNewsList( + String familyId, { + int? pageNo, + int? pageSize, + }) async { + Map param = {}; + param["familyId"] = familyId; + param["cursor"] = pageNo ?? 1; + param["limit"] = pageSize ?? 20; + final result = await http.get( + "fa4f8f2ceb43f797ac580b65d4b51df4574aff257ce7e668d08f4caccd1c6232", + fromJson: (json) => ATFamilyNewsListRes.fromJson(json), + queryParams: param, + ); + return result; + } + + ///family/user-exit + @override + Future familyUserExit() async { + final result = await http.get( + "495649c1cd53936a027692346b958b6c574aff257ce7e668d08f4caccd1c6232", + fromJson: (json) => json as bool, + ); + return result; + } + + ///family/box/claim + @override + Future familyBoxClaim(num level) async { + final result = await http.post( + "226878307fec2b3cc9f1412db6b1d180574aff257ce7e668d08f4caccd1c6232", + data: {"level": level}, + fromJson: (json) => json as bool, + ); + return result; + } + + ///family/disband + @override + Future familyDisband() async { + final result = await http.post( + "b65ae5960473c3837f21d4ac5efce1b0", + data: {}, + fromJson: (json) => json as bool, + ); + return result; + } + + ///family/user-base-info + @override + Future familyUserBaseInfo(String userId) async { + Map param = {}; + param["userId"] = userId; + final result = await http.get( + "2f55d791d9cbb44fbd6a84cd5bdbef8b99730cbad037822e4b51642ea774d04c", + queryParams: param, + fromJson: (json) => ChatVibeFamilyBaseInfoRes.fromJson(json), + ); + return result; + } +} diff --git a/lib/chatvibe_data/sources/repositories/general_repository_imp.dart b/lib/chatvibe_data/sources/repositories/general_repository_imp.dart new file mode 100644 index 0000000..c46bdd3 --- /dev/null +++ b/lib/chatvibe_data/sources/repositories/general_repository_imp.dart @@ -0,0 +1,30 @@ +import 'dart:io'; +import 'package:aslan/chatvibe_domain/repositories/general_repository.dart'; +import 'package:aslan/chatvibe_data/sources/remote/net/api.dart'; +import 'package:aslan/chatvibe_data/sources/remote/net/network_client.dart'; + +class GeneralRepositoryImp implements ChatVibeGeneralRepository { + static GeneralRepositoryImp? _instance; + + GeneralRepositoryImp._internal(); + + factory GeneralRepositoryImp() { + return _instance ??= GeneralRepositoryImp._internal(); + } + + ///external/oss/upload + @override + Future upload(File image) async { + String filePath = image.path; + var name = + filePath.substring(filePath.lastIndexOf("/") + 1, filePath.length); + FormData formData = FormData.fromMap( + {"file": await MultipartFile.fromFile(filePath, filename: name)}); + final result = await http.post( + "91458261d1ab337ad7b1da3610dd496cf7f58dbb0fecacfbdfe516c4892b1694", + data: formData, + fromJson: (json) => json as String, + ); + return result; + } +} diff --git a/lib/chatvibe_data/sources/repositories/gift_repository_imp.dart b/lib/chatvibe_data/sources/repositories/gift_repository_imp.dart new file mode 100644 index 0000000..a16a8fb --- /dev/null +++ b/lib/chatvibe_data/sources/repositories/gift_repository_imp.dart @@ -0,0 +1,28 @@ +import 'dart:io'; + +import 'package:aslan/chatvibe_domain/models/res/gift_res.dart'; +import 'package:aslan/chatvibe_domain/repositories/gift_repository.dart'; + +import 'package:aslan/chatvibe_data/sources/remote/net/network_client.dart'; + +class GiftRepositoryImp implements ChatVibeGiftRepository { + static GiftRepositoryImp? _instance; + + GiftRepositoryImp._internal(); + + factory GiftRepositoryImp() { + return _instance ??= GiftRepositoryImp._internal(); + } + + ///gift/wall + @override + Future> giftWall(String userId) async { + final result = await http.get>( + "6cda6ef50ed40e4656f0484136188abe", + queryParams: {"userId": userId}, + fromJson: + (json) => (json as List).map((e) => ChatVibeGiftRes.fromJson(e)).toList(), + ); + return result; + } +} diff --git a/lib/chatvibe_data/sources/repositories/room_repository_imp.dart b/lib/chatvibe_data/sources/repositories/room_repository_imp.dart new file mode 100644 index 0000000..81ad442 --- /dev/null +++ b/lib/chatvibe_data/sources/repositories/room_repository_imp.dart @@ -0,0 +1,944 @@ +import 'package:flutter/cupertino.dart'; +import 'package:aslan/chatvibe_core/constants/at_room_msg_type.dart'; +import 'package:aslan/chatvibe_domain/models/req/at_give_away_gift_room_acceptscmd.dart'; +import 'package:aslan/chatvibe_domain/models/res/game_ludo_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_get_list_game_config_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/gift_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_is_follow_room_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_mic_go_up_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/mic_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_contribute_level_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_join_black_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_red_packet_detail_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_red_packet_grab_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_red_packet_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_reward_info_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_rocket_config_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_rocket_status_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_task_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_top_four_with_reward_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/user_count_guard_res.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_msg_item.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_domain/models/res/at_banner_leaderboard_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/gift_backpack_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/gift_by_group_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/my_room_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_emoji_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_gift_rank_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_member_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_task_claimable_count_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_user_card_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_violation_handle_res.dart'; +import 'package:aslan/chatvibe_domain/repositories/room_repository.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_data/sources/remote/net/network_client.dart'; + +import '../../../chatvibe_domain/models/res/country_res.dart'; + +class ChatRoomRepository implements ChatVibeRoomRepository { + static ChatRoomRepository? _instance; + + ChatRoomRepository._internal(); + + factory ChatRoomRepository() { + return _instance ??= ChatRoomRepository._internal(); + } + + ///room/live-voice/discovery + @override + Future> discovery({bool? allRegion}) async { + Map queryParams = {}; + if (allRegion != null) { + queryParams["allRegion"] = allRegion; + } + final result = await http.get( + "363603111e51beac2d183014dd29b81ced652239ec13178acd7001096cab7429", + queryParams: queryParams, + fromJson: + (json) => (json as List).map((e) => ChatVibeRoomRes.fromJson(e)).toList(), + ); + return result; + } + + ///live/mic/list + @override + Future> micList(String roomId) async { + Map queryParams = {}; + queryParams["roomId"] = roomId; + final result = await http.get( + "b1d6a1284f5ddeaa1cbb1430f2991e8a", + queryParams: queryParams, + fromJson: + (json) => (json as List).map((e) => MicRes.fromJson(e)).toList(), + ); + return result; + } + + ///room/profile/specific + @override + Future specific(String roomId) async { + Map queryParams = {}; + queryParams["roomId"] = roomId; + final result = await http.get( + "08c7cb961a83d24c9e0d4b11d7af3f0a8f2fe5ccf18a5ca6a0caf80ecefaeb02", + queryParams: queryParams, + fromJson: (json) => MyRoomRes.fromJson(json), + ); + return result; + } + + ///room/relation/status + @override + Future isFollowRoom(String roomId) async { + Map queryParams = {}; + queryParams["roomId"] = roomId; + final result = await http.get( + "1c9e38f372be8ae5afde3a37710c24815dee9b62b097054c3e40e333e7747681", + queryParams: queryParams, + fromJson: (json) => ATIsFollowRoomRes.fromJson(json), + ); + return result; + } + + ///room/contribution/level + @override + Future contributeLevel(String roomId) async { + Map queryParams = {}; + queryParams["roomId"] = roomId; + final result = await http.get( + "730cfe8ab28a5bb6b75adb2d29320bd85285ddc5a507f997b1c6a23980d22a70", + queryParams: queryParams, + fromJson: (json) => ATRoomContributeLevelRes.fromJson(json), + ); + return result; + } + + ///activity/room-contribution-activity + @override + Future roomContributionActivity(String roomId) async { + Map queryParams = {}; + queryParams["roomId"] = roomId; + final result = await http.get( + "fe8a114454bb0a64c51b5a2840b89b9caad8e1b76b3e1c570f21d856505cee7d1621a5cc87df186c3e223af28d3b4455", + queryParams: queryParams, + fromJson: (json) => ATRoomContributeLevelRes.fromJson(json), + ); + return result; + } + + ///live/mic/go-up + @override + Future micGoUp( + String roomId, + num mickIndex, { + String? eventType, + String? inviterId, + }) async { + Map params = {}; + params["roomId"] = roomId; + params["mickIndex"] = mickIndex; + if (eventType != null) { + params["eventType"] = eventType; + } + if (inviterId != null) { + params["inviterId"] = inviterId; + } + final result = await http.post( + "6fb96d680637b8e931b962f6e05260b0", + data: params, + fromJson: (json) => ATMicGoUpRes.fromJson(json), + ); + return result; + } + + ///live/mic/go-down + @override + Future micGoDown(String roomId, num mickIndex) async { + final result = await http.post( + "11335ecd55d99c9100f5cd070e5b73f1574aff257ce7e668d08f4caccd1c6232", + data: {"roomId": roomId, "mickIndex": mickIndex}, + fromJson: (json) => null, + ); + return result; + } + + ///live/mic/lock + @override + Future micLock(String roomId, num mickIndex, bool lock) async { + final result = await http.post( + "70ffb9681e0103463b1faf883935e55d", + data: {"roomId": roomId, "mickIndex": mickIndex, "lock": lock}, + fromJson: (json) => null, + ); + return result; + } + + ///live/mic/mute + @override + Future micMute(String roomId, num mickIndex, bool mute) async { + final result = await http.post( + "97927b83eb6b9fccda567f3bf12cbf49", + data: {"roomId": roomId, "mickIndex": mickIndex, "mute": mute}, + fromJson: (json) => json as bool, + ); + return result; + } + + ///live/user/list + @override + Future> roomOnlineUsers(String roomId) async { + final result = await http.get( + "a01ade8fbb604c0904557a9483fcc4d9", + queryParams: {"roomId": roomId}, + fromJson: + (json) => (json as List).map((e) => ChatVibeUserProfile.fromJson(e)).toList(), + ); + return result; + } + + ///user/user-profile/card + @override + Future roomUserCard(String roomId, String userId) async { + final result = await http.get( + "2fa1d4f56bb726558904ce2a50b83f967732a24a30dccae0c57cf03f27319ea4", + queryParams: {"roomId": roomId, "userId": userId}, + fromJson: (json) => RoomUserCardRes.fromJson(json), + ); + return result; + } + + ///gift/list + @override + Future> giftList() async { + final result = await http.get>( + "3a613b7450f8fd9082b988bd21df454d", + fromJson: + (json) => (json as List).map((e) => ChatVibeGiftRes.fromJson(e)).toList(), + ); + return result; + } + + ///gift/group-tab + @override + Future> giftListByGroup() async { + final result = await http.get>( + "bfbb6d98524f1e8aecf695e32ed24c2f", + fromJson: + (json) => + (json as List).map((e) => GiftByGroupRes.fromJson(e)).toList(), + ); + return result; + } + + ///gift/batch + @override + Future giveGift( + List acceptUserIds, + String giftId, + num quantity, + bool checkCombo, { + String? roomId, + ATGiveAwayGiftRoomAcceptsCmd? accepts, + String? dynamicContentId, + }) async { + Map params = {}; + if (roomId != null) { + params["roomId"] = roomId; + } + params["giftId"] = giftId; + params["acceptUserIds"] = acceptUserIds; + params["quantity"] = quantity; + params["checkCombo"] = checkCombo; + if (accepts != null) { + params["accepts"] = accepts.toJson(); + } + if (dynamicContentId != null) { + params["dynamicContentId"] = dynamicContentId; + } + + final result = await http.post( + "daa4f379ff3e429e55c318b6af7291e8", + data: params, + fromJson: (json) => json as double, + ); + return result; + } + + ///live/mic/kill + @override + Future micKill(String roomId, num mickIndex) async { + final result = await http.post( + "e50ed2f3406bc9421f3271f8be367d9b", + data: {"roomId": roomId, "mickIndex": mickIndex}, + fromJson: (json) => json as bool, + ); + return result; + } + + ///sys/reported + @override + Future reported( + String reportUserId, + String reportedUserId, + num reportType, { + String? reportedContent, + num? relatedId, + String? imageUrls, + String? videoUrls, + }) async { + Map params = {}; + params["reportUserId"] = reportUserId; + params["reportedUserId"] = reportedUserId; + params["reportType"] = reportType; + if (reportedContent != null && reportedContent.isNotEmpty) { + params["reportedContent"] = reportedContent; + } + if (relatedId != null) { + params["relatedId"] = relatedId; + } + if (imageUrls != null && imageUrls.isNotEmpty) { + params["imageUrls"] = imageUrls; + } + if (videoUrls != null && videoUrls.isNotEmpty) { + params["videoUrls"] = videoUrls; + } + final result = await http.post( + "552d752693c91a4a3dd9723634dff21f", + data: params, + fromJson: (json) => json as bool, + ); + return result; + } + + ///room/live-voice/search + @override + Future> searchRoom(String roomAccount) async { + final result = await http.get>( + "363603111e51beac2d183014dd29b81cce9dda0f9cd8c267f6e4fd3f05bda090", + queryParams: {"roomAccount": roomAccount}, + fromJson: + (json) => (json as List).map((e) => ChatVibeRoomRes.fromJson(e)).toList(), + ); + return result; + } + + ///room/blacklist/join + @override + Future joinBlacklist( + String roomId, + String userId, + num outTime, + ) async { + final result = await http.post( + "fe07c4b4a0ab1a38a5674c77444296f1a31f65259a63574dbbfbfe543f1b99a6", + data: {"roomId": roomId, "userId": userId, "outTime": outTime}, + fromJson: (json) => ATRoomJoinBlackListRes.fromJson(json), + ); + return result; + } + + ///room/blacklist/remove + @override + Future removeBlacklist(String roomId, String userId) async { + final result = await http.post( + "68900c787a89c1092b21ee1a610ef323380d4f62784dacdb4e3d4e1f60770a39", + data: {"roomId": roomId, "userId": userId}, + fromJson: (json) => null, + ); + return result; + } + + ///user/count/guard + @override + Future> userCountGuard(String userId) async { + final result = await http.get>( + "cef5832baa42d4adb30f4b10bef0887e574aff257ce7e668d08f4caccd1c6232", + queryParams: {"userId": userId}, + fromJson: + (json) => + (json as List).map((e) => UserCountGuardRes.fromJson(e)).toList(), + ); + return result; + } + + ///material/emoji/all + @override + Future> emojiAll() async { + final result = await http.get>( + "1d2546ea50639e65f04d029dd0a9655eda4aeb3a4bd182b663d60d87cb3f2938", + fromJson: + (json) => + (json as List).map((e) => ATRoomEmojiRes.fromJson(e)).toList(), + ); + return result; + } + + ///room/member/change-role + @override + Future changeRoomRole( + String roomId, + String userType, + String changeUserId, + String operatorUserId, + String reason, + ) async { + final result = await http.put( + "39b2c4ffda0682c126435fc37480a0961be75ad669bd0bf28d4e875a45934dfe", + data: { + "roomId": roomId, + "userType": userType, + "changeUserId": changeUserId, + "operatorUserId": operatorUserId, + "reason": reason, + }, + fromJson: (json) => json as bool, + ); + return result; + } + + ///room/setting/locked + @override + Future roomLocked(String roomId, String password) async { + final result = await http.put( + "e9531da4dd5c83bd5386366519ac6d0b727c997eb7a1adb8323f850b6c541f3b", + data: {"roomId": roomId, "password": password}, + fromJson: (json) => json as bool, + ); + return result; + } + + ///room/setting/unlocked + @override + Future roomUnlocked(String roomId) async { + final result = await http.post( + "d8e78b8fb4cf9f0d41716d9978235334d9382f6a419ce06c2dab0b362c318a5d", + data: {"roomId": roomId}, + fromJson: (json) => json as bool, + ); + return result; + } + + ///room/setting + @override + Future updateRoomSetting( + String roomId, + String roomAcount, + BuildContext context, { + bool? touristMike, + String? takeMicRole, + bool? touristMsg, + String? joinGolds, + num? mikeSize, + bool? allowMusic, + bool? adminLockSeat, + bool? showHeartbeat, + bool? openKtvMode, + String? roomSpecialMikeType, + }) async { + Map params = {}; + params["roomId"] = roomId; + if (touristMike != null) { + params["touristMike"] = touristMike; + } + if (takeMicRole != null) { + params["takeMicRole"] = takeMicRole; + } + if (touristMsg != null) { + params["touristMsg"] = touristMsg; + } + if (joinGolds != null) { + params["joinGolds"] = joinGolds; + } + if (mikeSize != null) { + params["mikeSize"] = mikeSize; + } + if (allowMusic != null) { + params["allowMusic"] = allowMusic; + } + if (adminLockSeat != null) { + params["adminLockSeat"] = adminLockSeat; + } + if (showHeartbeat != null) { + params["showHeartbeat"] = showHeartbeat; + } + if (openKtvMode != null) { + params["openKtvMode"] = openKtvMode; + } + if (roomSpecialMikeType != null) { + params["roomSpecialMikeType"] = roomSpecialMikeType; + } + final result = await http.put( + "cd447427897a27ec5a710ae5861907c0", + data: params, + fromJson: (json) => json as bool, + ); + + Provider.of(context, listen: false).sendMsg( + Msg( + groupId: roomAcount, + msg: roomId, + type: ATRoomMsgType.roomSettingUpdate, + ), + addLocal: false, + ); + return result; + } + + ///room/member + @override + Future> roomMember( + String roomId, { + String? lastId, + }) async { + Map parm = {}; + parm["roomId"] = roomId; + if (lastId != null) { + parm["lastId"] = lastId; + } + final result = await http.get>( + "76393fe91160b664a81fdc0545a586ef", + queryParams: parm, + fromJson: + (json) => + (json as List).map((e) => ChatVibeRoomMemberRes.fromJson(e)).toList(), + ); + return result; + } + + ///room/contribution/rank + @override + Future> roomContributionRank( + String roomId, + String dataType, { + num size = 10, + }) async { + Map parm = {}; + parm["roomId"] = roomId; + parm["dataType"] = dataType; + parm["size"] = size; + final result = await http.get>( + "730cfe8ab28a5bb6b75adb2d29320bd80ed2ff7efd67897d5b80fd620a1b0676", + queryParams: parm, + fromJson: + (json) => + (json as List).map((e) => RoomGiftRankRes.fromJson(e)).toList(), + ); + return result; + } + + ///room/setting/kick-off-microphone + @override + Future kickOffMicrophone(String roomId, String kickUserId) async { + final result = await http.post( + "a4465894e21be08d833ef701566ffe1b6a835338046b35fb94271445c996f6e3574aff257ce7e668d08f4caccd1c6232", + data: {"roomId": roomId, "kickUserId": kickUserId}, + fromJson: (json) => json as bool, + ); + return result; + } + + ///gift/give/lucky-gift + @override + Future giveLuckyGift( + List acceptUserIds, + String giftId, + num quantity, + bool checkCombo, { + String? roomId, + ATGiveAwayGiftRoomAcceptsCmd? accepts, + }) async { + Map params = {}; + if (roomId != null) { + params["roomId"] = roomId; + } + params["giftId"] = giftId; + params["acceptUserIds"] = acceptUserIds; + params["quantity"] = quantity; + params["checkCombo"] = checkCombo; + if (accepts != null) { + params["accepts"] = accepts.toJson(); + } + + final result = await http.post( + "6ddf7ebecfa97adf23c6303877c7763ce591ff710a7abc4fe1e335d16efee21b", + data: params, + fromJson: (json) => json as double, + ); + return result; + } + + ///activity/leaderboard/appLeaderboard + @override + Future appLeaderboard() async { + final result = await http.get( + "04f600f838ff20e7f794d3b555103541adda7fb851a14e1c733d58bc5cd3a23b343860371cb9a34e6cdb9005c91c78a9", + fromJson: (json) => ATBannerLeaderboardRes.fromJson(json), + ); + return result; + } + + ///gift/backpack + @override + Future> giftBackpack() async { + final result = await http.get>( + "48ab0afa0e4305070eaa82edc3fc3996", + fromJson: + (json) => + (json as List).map((e) => ChatVibeGiftBackpackRes.fromJson(e)).toList(), + ); + return result; + } + + ///api/rocket/status + @override + Future rocketStatus(String roomId) async { + Map params = {}; + params["roomId"] = roomId; + final result = await http.get( + "1b650f6fc863c8c86ec8d921340a65ccba2fafbaede21817c8d7e89774331852", + queryParams: params, + fromJson: (json) => ATRoomRocketStatusRes.fromJson(json), + ); + return result; + } + + ///api/rocket/config/enabled + @override + Future> rocketConfigEnabled() async { + final result = await http.get>( + "09620f7f773a75af633e034b80393e0ee8adb46dafdd3600f74d3c85e2c07e4f", + fromJson: + (json) => + (json as List) + .map((e) => ATRoomRocketConfigRes.fromJson(e)) + .toList(), + ); + return result; + } + + ///api/rocket/claim + @override + Future rocketClaim(String roomId) async { + final result = await http.post( + "4c083b13f2be2a26641018619bdc0347574aff257ce7e668d08f4caccd1c6232", + data: {"roomId": roomId}, + fromJson: (json) => json as bool, + ); + return result; + } + + ///room-violation/handle + @override + Future roomViolationHandle( + String roomId, + int violationType, + int operationType, + String description, { + List? imageUrls, + }) async { + Map parm = {}; + parm["roomId"] = roomId; + parm["violationType"] = violationType; + parm["operationType"] = operationType; + parm["description"] = description; + if (imageUrls != null && imageUrls.isNotEmpty) { + parm["screenshotUrls"] = imageUrls; + } + final result = await http.post( + "fc47c4e6e3ba228391cd35bf99f301206f894acdcdbac7ccb7e88e9d69ee3eff", + data: parm, + fromJson: (json) => ATViolationHandleRes.fromJson(json), + ); + return result; + } + + ///room/red-packet/list + @override + Future> roomRedPacketList( + String roomId, + int current, { + int size = 20, + }) async { + Map parm = {}; + parm["roomId"] = roomId; + parm["current"] = current; + parm["size"] = size; + final result = await http.get>( + "318de84592dca60c287ba2f54d60756abc4a9e8c4704c0f73c085e7ab50e7fdb", + queryParams: parm, + fromJson: + (json) => + (json as List) + .map((e) => ATRoomRedPacketListRes.fromJson(e)) + .toList(), + ); + return result; + } + + ///room/red-packet/send + @override + Future roomSendRedPacket( + String roomId, + String totalAmount, + String totalCount, + String expireMinutes, + ) async { + Map parm = {}; + parm["roomId"] = roomId; + parm["totalAmount"] = totalAmount; + parm["totalCount"] = totalCount; + parm["expireMinutes"] = expireMinutes; + final result = await http.post( + "318de84592dca60c287ba2f54d60756a678b876f095286917f0b96d146dcc00a", + data: parm, + fromJson: (json) => ATRoomRedPacketListRes.fromJson(json), + ); + return result; + } + + ///room/red-packet/grab + @override + Future roomRedPacketGrab(String packetId) async { + Map parm = {}; + parm["packetId"] = packetId; + final result = await http.post( + "318de84592dca60c287ba2f54d60756a0b2a61d0ce4f3f4d50875dcc95b640af", + data: parm, + fromJson: (json) => ATRoomRedPacketGrabRes.fromJson(json), + ); + return result; + } + + ///room/red-packet/detail + @override + Future roomRedPacketDetail(String packetId) async { + Map parm = {}; + parm["packetId"] = packetId; + final result = await http.get( + "318de84592dca60c287ba2f54d60756a7f342deecb1b83d2d8429c59146f10f0", + queryParams: parm, + fromJson: (json) => ATRoomRedPacketDetailRes.fromJson(json), + ); + return result; + } + + ///activity/room-contribution-activity/room-reward/info + @override + Future roomRewardInfo(String roomId) async { + Map parm = {}; + parm["roomId"] = roomId; + final result = await http.get( + "fe8a114454bb0a64c51b5a2840b89b9caad8e1b76b3e1c570f21d856505cee7ddf616bd4bdc133af05422ff3f7becc575ad9f024e25ab8275b5792a94159a781", + queryParams: parm, + fromJson: (json) => ATRoomRewardInfoRes.fromJson(json), + ); + return result; + } + + ///activity/room-contribution-activity/room-reward/receive + @override + Future roomRewardReceive(String roomId) async { + Map parm = {}; + parm["roomId"] = roomId; + final result = await http.post( + "1224764475bbd027233e79b81fbdbbf8b25565bc1234221b5e7f9651e0d97576fbcc314a4dd141539dc44cd84f2f13e4c35b35e57872c47078faccd94224944e", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///room/red-packet/create-platform + @override + Future sendPlatformRedPack( + String roomId, + String totalAmount, + String totalCount, + String expireMinutes, + ) async { + Map parm = {}; + parm["roomId"] = roomId; + parm["totalAmount"] = totalAmount; + parm["totalCount"] = totalCount; + parm["expireMinutes"] = expireMinutes; + final result = await http.post( + "318de84592dca60c287ba2f54d60756a3d2cb7989afc93dcdbec1a68c53cac37", + data: parm, + fromJson: (json) => ATRoomRedPacketListRes.fromJson(json), + ); + return result; + } + + ///gift/activity/list + @override + Future> giftActivityList() async { + final result = await http.get>( + "60296415428765e04263c733bfc8428c397bb180e31856d94bf25d85eb342787", + fromJson: + (json) => (json as List).map((e) => ChatVibeGiftRes.fromJson(e)).toList(), + ); + return result; + } + + ///room-task/list + @override + Future roomTaskList(int taskCategory) async { + Map parm = {}; + parm["taskCategory"] = taskCategory; + final result = await http.get( + "30743db31a4b5236451c71864f914359", + queryParams: parm, + fromJson: (json) => ATRoomTaskListRes.fromJson(json), + ); + return result; + } + + ///room-task/claim + @override + Future roomTaskClaim(String taskCode) async { + Map parm = {}; + parm["taskCode"] = taskCode; + final result = await http.post( + "439c60bae741f83884ada20b837a00f9", + data: parm, + fromJson: (json) => Tiers.fromJson(json), + ); + return result; + } + + ///room-task/claimable-count + @override + Future roomTaskClaimableCount() async { + final result = await http.get( + "dba51232871855314973867d38c76191ae96d33f4d1b3265974cd070f03c3830", + fromJson: (json) => ATRoomTaskClaimableCountRes.fromJson(json), + ); + return result; + } + + ///game/ludo/start + @override + Future gameLudoStart(String id, String homeownerUserId) async { + Map parm = {}; + parm["id"] = id; + parm["homeownerUserId"] = homeownerUserId; + final result = await http.post( + "23bfabbd6da65525be5095b9dbe3e514", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///game/ludo + @override + Future gameLudo(String roomId) async { + Map parm = {}; + parm["roomId"] = roomId; + final result = await http.get( + "f7523a0b46fbf2b54dd91d4edb2e31c1", + queryParams: parm, + fromJson: (json) => GameLudoRes.fromJson(json), + ); + return result; + } + + ///game/ludo/create + @override + Future gameLudoCreate( + String roomId, + num amount, + String localSeat, + String homeownerUserId, + String gameId, + ) async { + Map parm = {}; + parm["roomId"] = roomId; + parm["amount"] = amount; + parm["localSeat"] = localSeat; + parm["homeownerUserId"] = homeownerUserId; + parm["gameId"] = gameId; + final result = await http.post( + "253eb7ce6a6dd0fa8077b2d3356458b0574aff257ce7e668d08f4caccd1c6232", + data: parm, + fromJson: (json) => GameLudoRes.fromJson(json), + ); + return result; + } + + ///game/ludo/disband + @override + Future gameLudoDisband(String id) async{ + Map parm = {}; + parm["id"] = id; + final result = await http.post( + "40b1b64b652f4a83c3cfc6b089d0c2c8f50d962af79761b92d094018013c8ba8", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///game/ludo/history/record + @override + Future ludoHistoryRecord(String gameListConfigId,String scene) async{ + Map parm = {}; + parm["gameListConfigId"] = gameListConfigId; + parm["scene"] = scene; + final result = await http.post( + "fc9cec3f7b35b3ac92f6703f79ee5e42e174b8b637ea1282dad30600f131ed0f", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///game/ludo/history/recent + @override + Future> gameLudoHistoryRecent(String scene) async{ + Map parm = {}; + parm["scene"] = scene; + final result = await http.get>( + "fc9cec3f7b35b3ac92f6703f79ee5e42e6df410dc077907bd2559d92dbfc72a8", + queryParams: parm, + fromJson: + (json) => + (json as List) + .map((e) => ATGetListGameConfigRes.fromJson(e)) + .toList(), + ); + return result; + } + + ///room/live-voice/explore + @override + Future> roomLiveVoiceExplore({String? countryCode}) async { + Map parm = {}; + if (countryCode != null) { + parm["countryCode"] = countryCode; + } + final result = await http.get>( + "363603111e51beac2d183014dd29b81c87d23fbd4f48d5a82aad402cbdd31a67", + queryParams: parm, + fromJson: + (json) => (json as List).map((e) => ChatVibeRoomRes.fromJson(e)).toList(), + ); + return result; + } + + ///room/live-voice/country + @override + Future> roomLiveVoiceCountry() async { + final result = await http.get>( + "363603111e51beac2d183014dd29b81c103495588d84f0487a718430892c5a82", + fromJson: + (json) => (json as List).map((e) => Country.fromJson(e)).toList(), + ); + return result; + } + +} diff --git a/lib/chatvibe_data/sources/repositories/store_repository_imp.dart b/lib/chatvibe_data/sources/repositories/store_repository_imp.dart new file mode 100644 index 0000000..73d06f2 --- /dev/null +++ b/lib/chatvibe_data/sources/repositories/store_repository_imp.dart @@ -0,0 +1,148 @@ +import 'package:aslan/chatvibe_domain/models/res/at_room_theme_list_res.dart'; + +import 'package:aslan/chatvibe_domain/models/res/bags_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/store_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/vip_list_res.dart'; +import 'package:aslan/chatvibe_domain/repositories/store_repository.dart'; +import 'package:aslan/chatvibe_data/sources/remote/net/network_client.dart'; + +class StoreRepositoryImp implements ChatVibeStoreRepository { + ///material/props-store/list + @override + Future> storeList( + String currencyType, + String propsType, + ) async { + Map parm = {}; + parm["currencyType"] = currencyType; + parm["propsType"] = propsType; + final result = await http.get>( + "cfa25ffa98eedd1fe4e0c0466e0aadba85e4421b482ce9dd58a9f96d51f5a577", + queryParams: parm, + fromJson: + (json) => + (json as List).map((e) => StoreListRes.fromJson(e)).toList(), + ); + return result; + } + + ///material/props-store/purchasing + @override + Future storePurchasing( + String propsId, + String type, + String currencyType, + String days, { + String? acceptUserId, + }) async { + Map parm = {}; + parm["propsId"] = propsId; + parm["type"] = type; + parm["currencyType"] = currencyType; + parm["days"] = days; + if (acceptUserId != null) { + parm["acceptUserId"] = acceptUserId; + } + final result = await http.post( + "cfa25ffa98eedd1fe4e0c0466e0aadba34acc7c71f29c8b833f6bb11b09789c9", + data: parm, + fromJson: (json) => json as double, + ); + return result; + } + + ///material/props-store/backpack + @override + Future> storeBackpack(String userId, String type) async{ + Map parm = {}; + parm["userId"] = userId; + parm["type"] = type; + final result = await http.get>( + "cfa25ffa98eedd1fe4e0c0466e0aadba7ae183d89168d8fafbd3be51ed693f3b", + queryParams: parm, + fromJson: + (json) => + (json as List).map((e) => BagsListRes.fromJson(e)).toList(), + ); + return result; + } + + ///material/props-store/switch-use + @override + Future switchPropsUse(String type, String propsId, bool unload) async{ + Map parm = {}; + parm["type"] = type; + parm["propsId"] = propsId; + parm["unload"] = unload; + final result = await http.post( + "cfa25ffa98eedd1fe4e0c0466e0aadba840b6522b1ce781d9917b065f1fa7724", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///room/theme/customize/latest + @override + Future themeCustomizeLatest() async{ + final result = await http.get( + "dffae19fd5bf021ce728384106772e22854a7fa69c4cb0a234128de26618a9f2", + fromJson: (json) => ATRoomThemeListRes.fromJson(json), + ); + return result; + } + + ///room/theme/backpack + @override + Future> roomThemeBackpack() async{ + final result = await http.get>( + "6056121c48cba2c743ed6d2c235403a22826cd0a280582ee3ae7bfc4b1ce7391", + fromJson: + (json) => + (json as List).map((e) => ATRoomThemeListRes.fromJson(e)).toList(), + ); + return result; + } + + ///room/theme/switch-use + @override + Future themeSwitchUse(String themeId, bool unload) async{ + Map parm = {}; + parm["themeId"] = themeId; + parm["unload"] = unload; + final result = await http.get( + "1af2a817cf106ccd65b9499d0b6987dfaa528b33bc4166e3f8db18f674507134", + queryParams: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///room/theme/customize + @override + Future roomThemeCustomize(String themeUrl) async{ + Map parm = {}; + parm["themeUrl"] = themeUrl; + final result = await http.post( + "dffae19fd5bf021ce728384106772e2296e9eab98ece5441112164ce0ab9521f", + data: parm, + fromJson: (json) => json as double, + ); + return result; + } + + ///material/props-store/noble-vip + @override + Future> nobleVipList(String currencyType) async{ + Map parm = {}; + parm["currencyType"] = currencyType; + final result = await http.get>( + "cfa25ffa98eedd1fe4e0c0466e0aadbab09a3a572dab88747601a666ef32514b", + queryParams: parm, + fromJson: + (json) => + (json as List).map((e) => VipListRes.fromJson(e)).toList(), + ); + return result; + } +} diff --git a/lib/chatvibe_data/sources/repositories/user_repository_impl.dart b/lib/chatvibe_data/sources/repositories/user_repository_impl.dart new file mode 100644 index 0000000..4689035 --- /dev/null +++ b/lib/chatvibe_data/sources/repositories/user_repository_impl.dart @@ -0,0 +1,1317 @@ +import 'package:aslan/chatvibe_domain/models/req/at_user_profile_cmd.dart'; +import 'package:aslan/chatvibe_domain/models/res/badge_backpack_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/get_cp_relationship_pair_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_gold_record_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_material_badge_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_prop_coupon_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_prop_coupon_record_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_public_message_page_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_res.dart' hide WearBadge; +import 'package:aslan/chatvibe_domain/models/res/at_rtc_token_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/task_checkIn_latest_vip_record_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_task_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_user_counter_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_user_identity_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_user_level_exp_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_user_red_packet_grab_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_user_red_packet_send_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_user_vip_ability_res.dart'; +import 'package:aslan/chatvibe_domain/models/req/at_mobile_auth_cmd.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_edit_room_info_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/follow_room_res.dart' + hide WearBadge; +import 'package:aslan/chatvibe_domain/models/res/follow_user_res.dart' + hide WearBadge; +import 'package:aslan/chatvibe_domain/models/res/join_room_res.dart' + hide WearBadge; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/message_friend_user_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/my_room_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_black_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_sign_in_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_violation_handle_res.dart'; +import 'package:aslan/chatvibe_domain/repositories/user_repository.dart'; +import 'package:aslan/chatvibe_data/sources/remote/net/network_client.dart'; + +import '../../../chatvibe_core/constants/at_global_config.dart'; +import '../../../chatvibe_domain/models/res/at_user_freight_balance_page_res.dart'; +import '../../../chatvibe_domain/models/res/at_web_pay_commodity_res.dart'; +import '../../../chatvibe_domain/models/res/at_web_pay_recharge_res.dart'; +import '../../../chatvibe_domain/models/res/at_web_pay_user_profile_res.dart'; + +class AccountRepository implements ChatVibeUserRepository { + static AccountRepository? _instance; + + AccountRepository._internal(); + + factory AccountRepository() { + return _instance ??= AccountRepository._internal(); + } + + @override + Future getUser(String id) async { + final result = await http.get( + "", + fromJson: (json) => ChatVibeLoginRes.fromJson(json), + ); + return result; + } + + ///auth/account/create + @override + Future regist( + String type, + String openId, + ATUserProfileCmd userProfileCmd, { + ATMobileAuthCmd? mobileAuthCmd, + String invitePeople = "", + }) async { + var param = {}; + param["type"] = type; + param["openId"] = openId; + param["profile"] = userProfileCmd.toJson(); + if (mobileAuthCmd != null) { + param["mobile"] = mobileAuthCmd.toJson(); + } + if (invitePeople.isNotEmpty) { + param["invitePeople"] = invitePeople; + } + final result = await http.post( + "aa35848b37e5ba74d93e5a1f719c579cb289b5940c15040473b6f57550de35df", + data: param, + fromJson: (json) => ChatVibeLoginRes.fromJson(json), + ); + return result; + } + + ///auth/account/login + @override + Future loginForAccount(String account, String pwd) async { + final result = await http.post( + "e64f27b9ba6b37881120f4584a5444a5c684d8491b703d0af953e5cc6a5f4cec", + data: {"account": account, "pwd": pwd}, + fromJson: (json) => ChatVibeLoginRes.fromJson(json), + ); + return result; + } + + ///auth/account/login/channel + @override + Future loginForChannel( + String authType, + String openId, + ) async { + final result = await http.post( + "e64f27b9ba6b37881120f4584a5444a5531a33eb137749a5decad584f58aaa44", + data: {"authType": authType, "openId": openId}, + fromJson: (json) => ChatVibeLoginRes.fromJson(json), + ); + return result; + } + + ///room/relation/follow + @override + Future> followRoomList({String? lastId}) async { + Map param = {}; + if (lastId != null) { + param["lastId"] = lastId; + } + final result = await http.get( + "59a548c91daed73d9becdf4eba7daf628ba84731fa88da5433b944ca2e6d6098", + queryParams: param, + fromJson: + (json) => + (json as List).map((e) => FollowRoomRes.fromJson(e)).toList(), + ); + return result; + } + + ///room/relation/joined + @override + Future> joined() async { + final result = await http.get( + "06ff6a670e789a391a7af40ce5d6b15c326060e4bcd797edd258ca1d8bb139be", + fromJson: + (json) => + (json as List).map((e) => FollowRoomRes.fromJson(e)).toList(), + ); + return result; + } + + ///room/relation/trace + @override + Future> trace({String? lastId}) async { + Map param = {}; + if (lastId != null) { + param["lastId"] = lastId; + } + final result = await http.get( + "95523e566901192267301b47f35adc8d5de33f03aaf96911b1a0c8131b938f28", + queryParams: param, + fromJson: + (json) => + (json as List).map((e) => FollowRoomRes.fromJson(e)).toList(), + ); + return result; + } + + ///room/profile + @override + Future myProfile() async { + final result = await http.get( + "1e41384e55cbd6c2374608b129e2ed27", + fromJson: (json) => MyRoomRes.fromJson(json), + ); + return result; + } + + ///room/live-voice/create + @override + Future createRoom() async { + final result = await http.post( + "363603111e51beac2d183014dd29b81cf01ca103e12cc332b59b5a8e39c778ec", + fromJson: (json) => ChatVibeRoomRes.fromJson(json), + ); + return result; + } + + ///room/live-voice/entry + @override + Future entryRoom( + String roomId, { + String? pwd, + String redPackId = "", + bool needOpenRedenvelope = false, + }) async { + var param = {}; + param["roomId"] = roomId; + if (pwd != null) { + param["pwd"] = pwd; + } + //自定义字段,用来在输入密码框api拦截时获取 + param["needOpenRedenvelope"] = needOpenRedenvelope; + param["redPackId"] = redPackId; + final result = await http.post( + "363603111e51beac2d183014dd29b81c219577507bcc9c6abfaf327548fed0e0", + data: param, + fromJson: (json) => JoinRoomRes.fromJson(json), + ); + return result; + } + + ///live/user/heartbeat + @override + Future heartbeat(String status, bool upMick, {String? roomId}) async { + var parm = {}; + parm["status"] = status; + parm["upMick"] = upMick; + parm["task"] = true; + if (roomId != null) { + parm["sessionId"] = roomId; + } + final result = await http.post( + "58f9485b5b9f401c6a14bc1e6534122c7a085bb5f6a613094188ca56fe8bc03d", + data: parm, + fromJson: (json) => {}, + ); + return result; + } + + ///external/im-trtc/tmp/agora/sig + @override + Future getRtcToken(String channel, String userId) async { + final result = await http.get( + "2572c69d25a0946b0cb35b24bdfcd61c9bcef4afdaced7dfc47a641bd9584f96", + queryParams: {"channelName": channel, "userId": userId}, + fromJson: (json) => ATRtcTokenRes.fromJson(json), + ); + return result; + } + + ///live/room/quit + @override + Future quitRoom(String roomId) async { + final result = await http.get( + "3e451c31ab651a577a3b7796b32041e7", + queryParams: {"roomId": roomId}, + fromJson: (json) => json as bool, + ); + return result; + } + + ///room/profile + @override + Future editRoomInfo( + String roomId, + String roomCover, + String roomName, + String roomDesc, + String event, + ) async { + final result = await http.put( + "1e41384e55cbd6c2374608b129e2ed27", + data: { + "id": roomId, + "roomCover": roomCover, + "roomName": roomName, + "roomDesc": roomDesc, + "event": event, + }, + fromJson: (json) => ATEditRoomInfoRes.fromJson(json), + ); + return result; + } + + ///user/user-profile + @override + Future loadUserInfo(String userId) async { + final result = await http.get( + "2fa1d4f56bb726558904ce2a50b83f961e1cf9b945681bdfb3d4cf7a5ecfc943", + queryParams: {"userId": userId}, + fromJson: (json) => ChatVibeUserProfile.fromJson(json), + ); + return result; + } + + ///room/relation/follow/action + @override + Future followRoom(String roomId) async { + final result = await http.post( + "59a548c91daed73d9becdf4eba7daf6226c857599fe196e293c826f60146fa9f", + data: {"roomId": roomId}, + fromJson: (json) => json as bool, + ); + return result; + } + + ///user/support-relation/follow + @override + Future followUser(String userId) async { + final result = await http.post( + "5ee2ca437d883bcd9a4030c44b20b059555c9f831613c06da141d92ffef9d8b7", + data: {"userId": userId}, + fromJson: (json) => json as bool, + ); + return result; + } + + ///team/heartbeat + @override + Future anchorHeartbeat(String roomId) async { + final result = await http.get( + "d28c7d4eeb945fb765c8206c8f79d77f", + queryParams: {"roomId": roomId}, + fromJson: (json) => {}, + ); + return result; + } + + ///wallet/gold/balance + @override + Future balance() async { + final result = await http.get( + "6e864e3aa03c0c398fa2d8f909d21aef2f0b7f5a6a5c523c28407ff12d872b31", + fromJson: (json) => json as double, + ); + return result; + } + + ///user/user-profile/search + @override + Future searchUser(String account) async { + final result = await http.get( + "2fa1d4f56bb726558904ce2a50b83f9604b0c834cdef3e5a6c3f81b198ab9a4b", + queryParams: {"account": account}, + fromJson: (json) => ChatVibeUserProfile.fromJson(json), + ); + return result; + } + + ///user/account/account-pwd-reset + @override + Future pwdReset(String pwd) async { + final result = await http.post( + "745ce65f1d68f106702ad3c636aca0e0ac57f82127b3ac414551924946593db7", + data: {"pwd": pwd}, + fromJson: (json) => null, + ); + return result; + } + + ///user/user-auth-type/account-is-bind + @override + Future accountIsBind() async { + final result = await http.get( + "0bc43bb5ac93dd30919c4a7c3bbd60335894d3a279a24600c6a7e5dc9536f0e10e634626c0d4977d4df50adfb36c576e", + fromJson: (json) => json as bool, + ); + return result; + } + + ///user/user-auth-type/account-bind + @override + Future bind(String pwd) async { + final result = await http.post( + "0bc43bb5ac93dd30919c4a7c3bbd6033963c952c9eca882afb1a172894887db3574aff257ce7e668d08f4caccd1c6232", + data: {"pwd": pwd}, + fromJson: (json) => json as bool, + ); + return result; + } + + ///user/user-auth-type/account-update + @override + Future updatePwd(String pwd, String oldPwd) async { + final result = await http.post( + "0bc43bb5ac93dd30919c4a7c3bbd60336a92ac11dbc1fb082f877af0c807bf9f6ad9a4bc80736146ebc083f857e42b4f", + data: {"pwd": pwd, "oldPwd": oldPwd}, + fromJson: (json) => json as bool, + ); + return result; + } + + ///room/blacklist + @override + Future> roomBlacklist( + String roomId, + String lastId, + ) async { + Map parm = {}; + parm["roomId"] = roomId; + if (lastId != "0") { + parm["lastId"] = lastId; + } + final result = await http.get>( + "9ce0dac3a29735467208f19ed4edc330", + queryParams: parm, + fromJson: + (json) => + (json as List).map((e) => RoomBlackListRes.fromJson(e)).toList(), + ); + return result; + } + + ///user/user-profile/update + @override + Future updateUserInfo({ + String? userAvatar, + String? userNickname, + num? userSex, + num? age, + num? bornYear, + num? bornMonth, + num? bornDay, + String? countryId, + String? hobby, + String? autograph, + List? backgroundPhotos, + List? personalPhotos, + }) async { + Map parm = {}; + if (userAvatar != null) { + parm["userAvatar"] = userAvatar; + } + if (userNickname != null) { + parm["userNickname"] = userNickname; + } + if (userSex != null) { + parm["userSex"] = userSex; + } + + if (age != null) { + parm["age"] = age; + } + + if (bornYear != null) { + parm["bornYear"] = bornYear; + } + + if (bornMonth != null) { + parm["bornMonth"] = bornMonth; + } + if (bornDay != null) { + parm["bornDay"] = bornDay; + } + if (countryId != null) { + parm["countryId"] = countryId; + } + if (hobby != null) { + parm["hobby"] = hobby; + } + if (autograph != null) { + parm["autograph"] = autograph; + } + if (backgroundPhotos != null) { + parm["backgroundPhotos"] = backgroundPhotos; + } + if (personalPhotos != null) { + parm["personalPhotos"] = personalPhotos; + } + + final result = await http.post( + "2fa1d4f56bb726558904ce2a50b83f96365e62b6aa808acde7b56d2d5945fbe4", + data: parm, + fromJson: (json) => ChatVibeUserProfile.fromJson(json), + ); + return result; + } + + ///user/support-relation/follow/my-list + @override + Future> followMyList({ + String? account, + String? lastId, + }) async { + Map parm = {}; + if (account != null) { + parm["account"] = account; + } + if (lastId != null) { + parm["lastId"] = lastId; + } + final result = await http.get>( + "5ee2ca437d883bcd9a4030c44b20b059984d86f34396dfa8045aa8afe71cb567bc4a9e8c4704c0f73c085e7ab50e7fdb", + queryParams: parm, + fromJson: + (json) => + (json as List).map((e) => FollowUserRes.fromJson(e)).toList(), + ); + return result; + } + + ///user/support-relation/fans/my-list + @override + Future> fansMyList({ + String? account, + String? lastId, + }) async { + Map parm = {}; + if (account != null) { + parm["account"] = account; + } + if (lastId != null) { + parm["lastId"] = lastId; + } + final result = await http.get>( + "5ee2ca437d883bcd9a4030c44b20b05995242771a17d74da900d0d3c02debf52397bb180e31856d94bf25d85eb342787", + queryParams: parm, + fromJson: + (json) => + (json as List).map((e) => FollowUserRes.fromJson(e)).toList(), + ); + return result; + } + + ///wallet/gold/this-month/asset-record + @override + Future> goldRecord({num? type, String? lastId}) async { + Map parm = {}; + if (type != null) { + parm["type"] = type; + } + if (lastId != null) { + parm["lastId"] = lastId; + } + final result = await http.get>( + "aabf121bb4c193ccceac70586aa16f3b06ad35ecd192416cde0bc922045e1dda68a9458578aa4b21fc86df1010fa2ca2", + queryParams: parm, + fromJson: + (json) => + (json as List).map((e) => ATGoldRecordRes.fromJson(e)).toList(), + ); + return result; + } + + ///user/support-relation/counter + @override + Future> userCounter(String userId) async { + Map parm = {}; + parm["userId"] = userId; + final result = await http.get>( + "5ee2ca437d883bcd9a4030c44b20b05980e9d6b90837a95599c7f32e68756428", + queryParams: parm, + fromJson: + (json) => + (json as List).map((e) => ATUserCounterRes.fromJson(e)).toList(), + ); + return result; + } + + ///app/h5/identity + @override + Future userIdentity({String? userId}) async { + Map parm = {}; + if (userId != null) { + parm["userId"] = userId; + } + final result = await http.get( + "d29121f09fa26c45b5322df6de0fc25b", + queryParams: parm, + fromJson: (json) => ATUserIdentityRes.fromJson(json), + ); + return result; + } + + ///material/badge + @override + Future> materialBadge({String? type}) async { + Map parm = {}; + if (type != null) { + parm["type"] = type; + } + final result = await http.get>( + "ed982b19758aef72740cbe49f534ed1b", + queryParams: parm, + fromJson: + (json) => + (json as List) + .map((e) => ATMaterialBadgeRes.fromJson(e)) + .toList(), + ); + return result; + } + + ///material/badge/backpack + @override + Future> badgeBackpack(String userId) async { + Map parm = {}; + parm["userId"] = userId; + final result = await http.get>( + "89f04aa005dc3eb8bbafbc76b8e69ee2d2678f09caf0c37d30998132e7539cf1", + queryParams: parm, + fromJson: + (json) => + (json as List).map((e) => BadgeBackpackRes.fromJson(e)).toList(), + ); + return result; + } + + ///user/user-level/consumption/exp + @override + Future userLevelConsumptionExp( + String userId, + String type, + ) async { + Map parm = {}; + parm["userId"] = userId; + parm["type"] = type; + final result = await http.get( + "9863061ae38e7d452fdd7f95840a952f2751db44c8c4787063c9ccf1dcd80366", + queryParams: parm, + fromJson: (json) => ATUserLevelExpRes.fromJson(json), + ); + return result; + } + + ///task/check/in/today + @override + Future checkInToday() async { + final result = await http.get( + "b5908b3dc914a82da088fadf8be59213143ec06ec0fc08a0d7d915f0556e3821", + fromJson: (json) => json as bool, + ); + return result; + } + + ///task/check/in/list/award + @override + Future sginListAward() async { + final result = await http.get( + "acdc80e923fd539da1401826ee3e99169b3b4b9cccf8933804239f0e070c54c7", + fromJson: (json) => ATSignInRes.fromJson(json), + ); + return result; + } + + ///task/check/in/receive + @override + Future checkInReceive(String id, String resourceGroupId) async { + Map parm = {}; + parm["id"] = id; + parm["resourceGroupId"] = resourceGroupId; + final result = await http.post( + "cd4f02cf4ef018533e882d3cbb23a6aeaeb1d976c8b96774303e69619d73e630", + data: parm, + fromJson: (json) => json as int, + ); + return result; + } + + ///team/create + @override + Future teamCreate(String ownUserId) async { + Map parm = {}; + parm["ownUserId"] = ownUserId; + final result = await http.post( + "9d3a59e43ee9cf3729f57abc0c596005", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///public-message/page + @override + Future publicMessagePage( + String type, + num pageNo, + num pageSize, + ) async { + Map parm = {}; + parm["type"] = type; + parm["pageNo"] = pageNo; + parm["pageSize"] = pageSize; + final result = await http.get( + "38801b8e4843cf3f040b20e8e7a32554118a90740b43e354a7fdc9ac837e0671", + queryParams: parm, + fromJson: (json) => ATPublicMessagePageRes.fromJson(json), + ); + return result; + } + + ///user/friend-relation/list + @override + Future> friendList({String? lastId}) async { + Map parm = {}; + if (lastId != null) { + parm["lastId"] = lastId; + } + final result = await http.get>( + "4aff0909d268eb1d0a7f1ded14d28357d8c0811eb69bd36aaaf7bf3d8bf0e486", + queryParams: parm, + fromJson: + (json) => + (json as List) + .map((e) => MessageFriendUserRes.fromJson(e)) + .toList(), + ); + return result; + } + + ///user/friend-relation/search + @override + Future> friendSearch(String account) async { + Map parm = {}; + parm["account"] = account; + final result = await http.get>( + "4aff0909d268eb1d0a7f1ded14d28357879fc2cee9e8f2051bbaccb8bf07e372", + queryParams: parm, + fromJson: + (json) => + (json as List) + .map((e) => MessageFriendUserRes.fromJson(e)) + .toList(), + ); + return result; + } + + ///team/invite-message-process + @override + Future inviteHost(String id, String status) async { + Map parm = {}; + parm["id"] = id; + parm["status"] = status; + final result = await http.post( + "e5989162aa832581f74b9a383248e4b8317f6af14ecd57b0c1e01a5e47e7414d", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///team/bd/invite-message-process + @override + Future inviteAgent(String id, String status) async { + Map parm = {}; + parm["id"] = id; + parm["status"] = status; + final result = await http.post( + "5e44a26c2a5805a21846bf04cb67a75108fa66d1b5877cd04fb8d1761aa95547", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///team/bd/invite-bd-message-process + @override + Future inviteBD(String id, String status) async { + Map parm = {}; + parm["id"] = id; + parm["status"] = status; + final result = await http.post( + "bbdc8997a823680923efb54a9d31072eabf7f2c09434925bdc6cd2a847f93dcfba2fafbaede21817c8d7e89774331852", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///team/bd/invite-bdleader-message-process + @override + Future inviteBDLeader(String id, String status) async { + Map parm = {}; + parm["id"] = id; + parm["status"] = status; + final result = await http.post( + "bbdc8997a823680923efb54a9d31072eb14922cefad1be12ebcebd874a66585c7f7f07aa8fdbecc97b3d68d3389dd470", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///team/bd/invite-recharge-agent-message-process + @override + Future inviteRechargeAgent(String id, String status) async { + Map parm = {}; + parm["id"] = id; + parm["status"] = status; + final result = await http.post( + "771f90aeda269f6e1eb1421f4335c8bf02f5ada0ff7a70fca49e8b367fd079ffd991f48d7a7af790a9593ff2caf1f6fc", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///user/support-relation/follow/check + @override + Future followCheck(String userId) async { + Map parm = {}; + parm["userId"] = userId; + final result = await http.get( + "5ee2ca437d883bcd9a4030c44b20b059cda65c203d085b6a0dc3eb6c86372454b6de76cbe76f15fa0a33e1759219198c", + queryParams: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///user/support-relation/follow-new + @override + Future followNew(String userId) async { + Map parm = {}; + parm["userId"] = userId; + final result = await http.post( + "5ee2ca437d883bcd9a4030c44b20b059787556c9e7c91af11f63f466fc47a7a6574aff257ce7e668d08f4caccd1c6232", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///app/v2/task/list + @override + Future> tasks() async { + final result = await http.get>( + "6e87048da643aa0388a9e46ea391daff574aff257ce7e668d08f4caccd1c6232", + fromJson: + (json) => + (json as List).map((e) => ATTaskListRes.fromJson(e)).toList(), + ); + return result; + } + + ///app/v2/task/reward + @override + Future taskReward(num taskId) async { + Map parm = {}; + parm["taskId"] = taskId; + final result = await http.get( + "0047abf8502867ed03180e3a6b505d411832ea9bebe4ec426a7036c865082475", + queryParams: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///user/support-relation/visitor/list + @override + Future> visitorList(num pageNumber) async { + Map parm = {}; + parm["pageNumber"] = pageNumber; + final result = await http.get>( + "5ee2ca437d883bcd9a4030c44b20b05903ab400c9bf27c42312a7e75b67b25a0397bb180e31856d94bf25d85eb342787", + queryParams: parm, + fromJson: + (json) => + (json as List).map((e) => FollowUserRes.fromJson(e)).toList(), + ); + return result; + } + + ///prop/coupon/list + @override + Future propCouponList( + num size, + num current, { + int? couponType, + }) async { + Map parm = {}; + parm["size"] = size; + parm["current"] = current; + if (couponType != null) { + parm["couponType"] = couponType; + } + final result = await http.post( + "c05bb66ab1d14464aa75f1842e222ce0574aff257ce7e668d08f4caccd1c6232", + data: parm, + fromJson: (json) => ATPropCouponListRes.fromJson(json), + ); + return result; + } + + ///prop/coupon/record/list + @override + Future couponRecordList( + num size, + num current, { + int? couponType, + int? status, + }) async { + Map parm = {}; + parm["size"] = size; + parm["current"] = current; + if (couponType != null) { + parm["couponType"] = couponType; + } + if (status != null) { + parm["status"] = status; + } + final result = await http.post( + "0348972fba6d4c510c1860e435c3f68297e6790742d0344ce6b20c597ee86fd1", + data: parm, + fromJson: (json) => ATPropCouponRecordListRes.fromJson(json), + ); + return result; + } + + ///prop/coupon/use + @override + Future couponUse(String couponNo) async { + Map parm = {}; + parm["couponNo"] = couponNo; + final result = await http.post( + "6163f60d541d3361be0d3f8ef816fc1d", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///prop/coupon/create + @override + Future couponSend(String couponNo, String receiverId) async { + Map parm = {}; + parm["couponNo"] = couponNo; + parm["receiverId"] = receiverId; + final result = await http.post( + "431750258fbc9a54a3298bf431007fd56ad9a4bc80736146ebc083f857e42b4f", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///user/badge/own/list + @override + Future> badgeOwnList(String type) async { + Map parm = {}; + parm["type"] = type; + final result = await http.get>( + "2d234c6012ceaca8dbd3923bb17b2adaf13e23fb57376accca26ee78f1baa860", + queryParams: parm, + fromJson: + (json) => (json as List).map((e) => WearBadge.fromJson(e)).toList(), + ); + return result; + } + + ///user/badge/toggle + @override + Future badgeToggle(String badgeId) async { + Map parm = {}; + parm["badgeId"] = badgeId; + final result = await http.post( + "84169ec3c928dde3a22cb4b7b6ae95c31e1cf9b945681bdfb3d4cf7a5ecfc943", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///user-violation/handle + @override + Future userViolationHandle( + String userId, + int violationType, + int operationType, + String description, { + List? imageUrls, + }) async { + Map parm = {}; + parm["userId"] = userId; + parm["violationType"] = violationType; + parm["operationType"] = operationType; + parm["description"] = description; + if (imageUrls != null && imageUrls.isNotEmpty) { + parm["screenshotUrls"] = imageUrls; + } + final result = await http.post( + "cc84151e5fba2824710e0645046729a06f894acdcdbac7ccb7e88e9d69ee3eff", + data: parm, + fromJson: (json) => ATViolationHandleRes.fromJson(json), + ); + return result; + } + + ///user/cp-relationship/pair + @override + Future> getCpRelationshipPairList() async { + final result = await http.get>( + "b06a43ca828553e84580a84da4a3af617c8a63c6e1cf2b0e17d5c678791544a9", + fromJson: + (json) => + (json as List) + .map((e) => GetCpRelationshipPairListRes.fromJson(e)) + .toList(), + ); + return result; + } + + ///user/cp-relationship/send-apply + @override + Future cpRelationshipSendApply(String acceptApplyUserId) async { + Map parm = {}; + parm["acceptApplyUserId"] = acceptApplyUserId; + final result = await http.post( + "b06a43ca828553e84580a84da4a3af611bdb98051e16d6f8bbde943836c2f871", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///user/cp-relationship/process-apply + @override + Future cpRelationshipProcessApply(String applyId, bool agree) async { + Map parm = {}; + parm["applyId"] = applyId; + parm["agree"] = agree; + final result = await http.post( + "b06a43ca828553e84580a84da4a3af61cb64264b44425ec78a0115bfc976a827fd57dd4d0233d969f8991e901a14e144", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///user/cp-relationship/dismiss-apply + @override + Future cpRelationshipDismissApply(String cpUserId) async { + Map parm = {}; + parm["cpUserId"] = cpUserId; + final result = await http.get( + "b06a43ca828553e84580a84da4a3af6147b0e44e9311c7615dee77b6c4c425a1fd57dd4d0233d969f8991e901a14e144", + queryParams: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///user/friend-relation/check + @override + Future friendRelationCheck(String userId) async { + Map parm = {}; + parm["userId"] = userId; + final result = await http.get( + "4aff0909d268eb1d0a7f1ded14d28357691f1c364e338d7fc3c50cf21be0f908", + queryParams: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///user/red-packet/create + @override + Future userRedPacketSend( + String receiverUserId, + String totalAmount, + ) async { + Map parm = {}; + parm["receiverUserId"] = receiverUserId; + parm["totalAmount"] = totalAmount; + final result = await http.post( + "69585024cc7ee090d1e29ff7ca160a45f01ca103e12cc332b59b5a8e39c778ec", + data: parm, + fromJson: (json) => ATUserRedPacketSendRes.fromJson(json), + ); + return result; + } + + ///user/red-packet/detail + @override + Future userRedPacketDetail(String packetId) async { + Map parm = {}; + parm["packetId"] = packetId; + final result = await http.get( + "69585024cc7ee090d1e29ff7ca160a457f342deecb1b83d2d8429c59146f10f0", + queryParams: parm, + fromJson: (json) => ATUserRedPacketSendRes.fromJson(json), + ); + return result; + } + + ///user/red-packet/grab + @override + Future userRedPacketGrab(String packetId) async { + Map parm = {}; + parm["packetId"] = packetId; + final result = await http.post( + "69585024cc7ee090d1e29ff7ca160a450b2a61d0ce4f3f4d50875dcc95b640af", + data: parm, + fromJson: (json) => ATUserRedPacketGrabRes.fromJson(json), + ); + return result; + } + + ///user/blacklist/list + @override + Future> userBlacklistList({ + String? account, + String? lastId, + }) async { + Map parm = {}; + if (account != null) { + parm["account"] = account; + } + if (lastId != null) { + parm["lastId"] = lastId; + } + final result = await http.get>( + "62bd6154ca9eb9207f3e50294ed18035f13e23fb57376accca26ee78f1baa860", + queryParams: parm, + fromJson: + (json) => + (json as List) + .map((e) => MessageFriendUserRes.fromJson(e)) + .toList(), + ); + return result; + } + + ///user/blacklist/check + @override + Future blacklistCheck(String shieldUserId) async { + Map parm = {}; + parm["shieldUserId"] = shieldUserId; + final result = await http.post( + "66644b702102d48f4284444b05b1200deda65d2aec525e95d29625cf93fa9e45", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///user/blacklist + @override + Future addUserBlacklist(String shieldUserId) async { + Map parm = {}; + parm["shieldUserId"] = shieldUserId; + final result = await http.post( + "3f6e2326c10aaed6e637ac0d78a20a3b", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///user/blacklist/cancel + @override + Future deleteUserBlacklist(String shieldUserId) async { + Map parm = {}; + parm["shieldUserId"] = shieldUserId; + final result = await http.post( + "66644b702102d48f4284444b05b1200d13afc33ba8e2ca51c7245d9201b4569d", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///auth/logout + @override + Future logoutAccount() async { + final result = await http.delete( + "814a34263274ff501f4ca9ae76c4aae3", + fromJson: (json) => json as bool, + ); + return result; + } + + ///task/check/in/latest-vip-record + @override + Future> + taskCheckInLatestVipRecord() async { + final result = await http.get>( + "2d7da2d9a85ef68c3fc9126e695fc1dc7fc00c3535db41de8a52ee83d463bccb", + fromJson: + (json) => + (json as List) + .map((e) => TaskCheckInLatestVipRecordRes.fromJson(e)) + .toList(), + ); + return result; + } + + ///user/vip/ability + @override + Future userVipAbility() async { + final result = await http.get( + "9bad890ecd3a153658cbe38367fcc46f574aff257ce7e668d08f4caccd1c6232", + fromJson: (json) => ATUserVipAbilityRes.fromJson(json), + ); + return result; + } + + ///user/vip/ability/update + @override + Future userVipAbilityUpdate({ + String? contactMode, + bool? kickPrevention, + bool? mysteriousInvisibility, + bool? antiBlock, + }) async { + Map parm = {}; + if (contactMode != null) { + parm["contactMode"] = contactMode; + } + if (kickPrevention != null) { + parm["kickPrevention"] = kickPrevention; + } + if (mysteriousInvisibility != null) { + parm["mysteriousInvisibility"] = mysteriousInvisibility; + } + if (antiBlock != null) { + parm["antiBlock"] = antiBlock; + } + final result = await http.post( + "9bad890ecd3a153658cbe38367fcc46f3db9e152c7701ff2e5e1d4261f763895", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///team/invite-host + @override + Future teamInviteHost(String inviteUserId) async { + Map parm = {}; + parm["inviteUserId"] = inviteUserId; + final result = await http.post( + "1c68b3f6f824e5a12b898b4596103386574aff257ce7e668d08f4caccd1c6232", + data: parm, + fromJson: (json) => json as bool, + ); + return result; + } + + ///order/web/pay/user-profile + @override + Future webPayUserProfile({ + String? account, + String? userId, + String? type, + }) async { + Map parm = {}; + if (account != null) { + parm["account"] = account; + } + if (userId != null) { + parm["userId"] = userId; + } + if (type != null) { + parm["type"] = type; + } + final result = await http.get( + "9b57e4c7d5bac0bad1634295942ce0f8a560e5294d4ab4e768e6ff7e10c1aa89", + queryParams: parm, + fromJson: (json) => ATWebPayUserProfileRes.fromJson(json), + ); + return result; + } + + ///order/web/pay/commodity + @override + Future webPayCommodity({ + String? payCountryId, + String? type, + String? regionId, + }) async { + Map parm = {}; + parm["applicationId"] = ATGlobalConfig.payApplicationId; + if (payCountryId != null) { + parm["payCountryId"] = payCountryId; + } + if (type != null) { + parm["type"] = type; + } + if (regionId != null) { + parm["regionId"] = regionId; + } + final result = await http.get( + "79adbdf8ec3ab29b8acd91682e788cf60c47cb587009984b5926ac40a93d78c7", + queryParams: parm, + fromJson: (json) => ATWebPayCommodityRes.fromJson(json), + ); + return result; + } + + ///order/web/pay/recharge + @override + Future webPayRecharge( + String goodsId, + String payCountryId, + String userId, + String channelCode, { + bool? newVersion, + String? requestIp, + }) async { + Map parm = {}; + parm["goodsId"] = goodsId; + parm["applicationId"] = ATGlobalConfig.payApplicationId; + parm["payCountryId"] = payCountryId; + parm["userId"] = userId; + parm["channelCode"] = channelCode; + if (newVersion != null) { + parm["newVersion"] = newVersion; + } + if (requestIp != null) { + parm["requestIp"] = requestIp; + } + final result = await http.post( + "49479c0663bd16904addb32cf78115f7a414e4024b83d20e1a5896bd60802359", + data: parm, + fromJson: (json) => ATWebPayRechargeRes.fromJson(json), + ); + return result; + } + + ///user/freight-balance/page + @override + Future userFreightBalancePage({ + int? cursor = 1, + int? limit = 20, + }) async { + Map parm = {}; + parm["cursor"] = cursor; + parm["limit"] = limit; + final result = await http.get( + "4d4adc412dff790e38ec1183ed715697a7ecd8d35759445e0da11a496aabe0f1", + queryParams: parm, + fromJson: (json) => AtUserFreightBalancePageRes.fromJson(json), + ); + return result; + } +} diff --git a/lib/chatvibe_domain/models/req/at_dynamic_create_pic_cmd.dart b/lib/chatvibe_domain/models/req/at_dynamic_create_pic_cmd.dart new file mode 100644 index 0000000..74858c9 --- /dev/null +++ b/lib/chatvibe_domain/models/req/at_dynamic_create_pic_cmd.dart @@ -0,0 +1,26 @@ +class ATDynamicCreatePicCmd { + ATDynamicCreatePicCmd({ + String? resourceUrl, + int? sort, + }) { + _resourceUrl = resourceUrl; + _sort = sort; + } + String? _resourceUrl; + int? _sort; + String? get resourceUrl => _resourceUrl; + + int? get sort => _sort; + + Map toJson() { + final map = {}; + map['resourceUrl'] = _resourceUrl; + map['sort'] = _sort; + return map; + } + + ATDynamicCreatePicCmd.fromJson(dynamic json) { + _resourceUrl = json['resourceUrl']; + _sort = json['sort']; + } +} diff --git a/lib/chatvibe_domain/models/req/at_give_away_gift_room_acceptscmd.dart b/lib/chatvibe_domain/models/req/at_give_away_gift_room_acceptscmd.dart new file mode 100644 index 0000000..5cdf211 --- /dev/null +++ b/lib/chatvibe_domain/models/req/at_give_away_gift_room_acceptscmd.dart @@ -0,0 +1,49 @@ +/// acceptUserId : 0 +/// reqTraceId : "" +/// seatIndex : 0 + +class ATGiveAwayGiftRoomAcceptsCmd { + ATGiveAwayGiftRoomAcceptsCmd({ + String? acceptUserId, + String? reqTraceId, + num? seatIndex, + }) { + _acceptUserId = acceptUserId; + _reqTraceId = reqTraceId; + _seatIndex = seatIndex; + } + + ATGiveAwayGiftRoomAcceptsCmd.fromJson(dynamic json) { + _acceptUserId = json['acceptUserId']; + _reqTraceId = json['reqTraceId']; + _seatIndex = json['seatIndex']; + } + + String? _acceptUserId; + String? _reqTraceId; + num? _seatIndex; + + ATGiveAwayGiftRoomAcceptsCmd copyWith({ + String? acceptUserId, + String? reqTraceId, + num? seatIndex, + }) => ATGiveAwayGiftRoomAcceptsCmd( + acceptUserId: acceptUserId ?? _acceptUserId, + reqTraceId: reqTraceId ?? _reqTraceId, + seatIndex: seatIndex ?? _seatIndex, + ); + + String? get acceptUserId => _acceptUserId; + + String? get reqTraceId => _reqTraceId; + + num? get seatIndex => _seatIndex; + + Map toJson() { + final map = {}; + map['acceptUserId'] = _acceptUserId; + map['reqTraceId'] = _reqTraceId; + map['seatIndex'] = _seatIndex; + return map; + } +} diff --git a/lib/chatvibe_domain/models/req/at_mobile_auth_cmd.dart b/lib/chatvibe_domain/models/req/at_mobile_auth_cmd.dart new file mode 100644 index 0000000..6e401ab --- /dev/null +++ b/lib/chatvibe_domain/models/req/at_mobile_auth_cmd.dart @@ -0,0 +1,51 @@ +/// code : "522" +/// phoneNumber : "13570191401" +/// phonePrefix : 5477 +/// pwd : "z88888888" + +class ATMobileAuthCmd { + ATMobileAuthCmd({ + String? code, + String? phoneNumber, + num? phonePrefix, + String? pwd,}){ + _code = code; + _phoneNumber = phoneNumber; + _phonePrefix = phonePrefix; + _pwd = pwd; + } + + ATMobileAuthCmd.fromJson(dynamic json) { + _code = json['code']; + _phoneNumber = json['phoneNumber']; + _phonePrefix = json['phonePrefix']; + _pwd = json['pwd']; + } + String? _code; + String? _phoneNumber; + num? _phonePrefix; + String? _pwd; + ATMobileAuthCmd copyWith({ String? code, + String? phoneNumber, + num? phonePrefix, + String? pwd, + }) => ATMobileAuthCmd( code: code ?? _code, + phoneNumber: phoneNumber ?? _phoneNumber, + phonePrefix: phonePrefix ?? _phonePrefix, + pwd: pwd ?? _pwd, + ); + String? get code => _code; + String? get phoneNumber => _phoneNumber; + num? get phonePrefix => _phonePrefix; + String? get pwd => _pwd; + + Map toJson() { + final map = {}; + map['code'] = _code; + map['phoneNumber'] = _phoneNumber; + map['phonePrefix'] = _phonePrefix; + map['pwd'] = _pwd; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/req/at_user_profile_cmd.dart b/lib/chatvibe_domain/models/req/at_user_profile_cmd.dart new file mode 100644 index 0000000..ff9b37e --- /dev/null +++ b/lib/chatvibe_domain/models/req/at_user_profile_cmd.dart @@ -0,0 +1,126 @@ +/// id : 0 +/// userNickname : "string" +/// userAvatar : "string" +/// userSex : 0 +/// bornYear : 0 +/// bornMonth : 0 +/// bornDay : 0 +/// age : 0 +/// countryCode : "string" +/// countryId : 0 +/// countryName : "string" + +class ATUserProfileCmd { + ATUserProfileCmd({ + num? id, + String? userNickname, + String? userAvatar, + num? userSex=0, + num? bornYear, + num? bornMonth, + num? bornDay, + num? age, + String? countryCode, + String? countryId, + String? countryName,}){ + _id = id; + _userNickname = userNickname; + _userAvatar = userAvatar; + _userSex = userSex; + _bornYear = bornYear; + _bornMonth = bornMonth; + _bornDay = bornDay; + _age = age; + _countryCode = countryCode; + _countryId = countryId; + _countryName = countryName; + } + + ATUserProfileCmd.fromJson(dynamic json) { + _id = json['id']; + _userNickname = json['userNickname']; + _userAvatar = json['userAvatar']; + _userSex = json['userSex']; + _bornYear = json['bornYear']; + _bornMonth = json['bornMonth']; + _bornDay = json['bornDay']; + _age = json['age']; + _countryCode = json['countryCode']; + _countryId = json['countryId']; + _countryName = json['countryName']; + } + num? _id; + String? _userNickname; + String? _userAvatar; + num? _userSex; + num? _bornYear; + num? _bornMonth; + num? _bornDay; + num? _age; + String? _countryCode; + String? _countryId; + String? _countryName; + ATUserProfileCmd copyWith({ num? id, + String? userNickname, + String? userAvatar, + num? userSex, + num? bornYear, + num? bornMonth, + num? bornDay, + num? age, + String? countryCode, + String? countryId, + String? countryName, + }) => ATUserProfileCmd( id: id ?? _id, + userNickname: userNickname ?? _userNickname, + userAvatar: userAvatar ?? _userAvatar, + userSex: userSex ?? _userSex, + bornYear: bornYear ?? _bornYear, + bornMonth: bornMonth ?? _bornMonth, + bornDay: bornDay ?? _bornDay, + age: age ?? _age, + countryCode: countryCode ?? _countryCode, + countryId: countryId ?? _countryId, + countryName: countryName ?? _countryName, + ); + num? get id => _id; + String? get userNickname => _userNickname; + String? get userAvatar => _userAvatar; + num? get userSex => _userSex; + num? get bornYear => _bornYear; + num? get bornMonth => _bornMonth; + num? get bornDay => _bornDay; + num? get age => _age; + String? get countryCode => _countryCode; + String? get countryId => _countryId; + String? get countryName => _countryName; + + set setUserAvatar(String value) => _userAvatar = value; + set setUserNickname(String value) => _userNickname = value; + set setUserSex(num value) => _userSex = value; + set setBornYear(num value) => _bornYear = value; + set setBornMonth(num value) => _bornMonth = value; + set setBornDay(num value) => _bornDay = value; + set setAge(num value) => _age = value; + set setCountryCode(String value) => _countryCode = value; + set setCountryId(String value) => _countryId = value; + set setCountryName(String value) => _countryName = value; + + + Map toJson() { + final map = {}; + map['id'] = _id; + map['userNickname'] = _userNickname; + map['userAvatar'] = _userAvatar; + map['userSex'] = _userSex; + map['bornYear'] = _bornYear; + map['bornMonth'] = _bornMonth; + map['bornDay'] = _bornDay; + map['age'] = _age; + map['countryCode'] = _countryCode; + map['countryId'] = _countryId; + map['countryName'] = _countryName; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_banner_leaderboard_res.dart b/lib/chatvibe_domain/models/res/at_banner_leaderboard_res.dart new file mode 100644 index 0000000..ab318ff --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_banner_leaderboard_res.dart @@ -0,0 +1,281 @@ +/// giftsReceivedLeaderboard : {"daily":[{"avatar":"","id":0,"nickname":"","quantity":0,"quantityFormat":""}],"monthly":[{"avatar":"","id":0,"nickname":"","quantity":0,"quantityFormat":""}],"weekly":[{"avatar":"","id":0,"nickname":"","quantity":0,"quantityFormat":""}]} +/// giftsSendLeaderboard : {"daily":[{"avatar":"","id":0,"nickname":"","quantity":0,"quantityFormat":""}],"monthly":[{"avatar":"","id":0,"nickname":"","quantity":0,"quantityFormat":""}],"weekly":[{"avatar":"","id":0,"nickname":"","quantity":0,"quantityFormat":""}]} +/// roomGiftsLeaderboard : {"daily":[{"avatar":"","id":0,"nickname":"","quantity":0,"quantityFormat":""}],"monthly":[{"avatar":"","id":0,"nickname":"","quantity":0,"quantityFormat":""}],"weekly":[{"avatar":"","id":0,"nickname":"","quantity":0,"quantityFormat":""}]} + +class ATBannerLeaderboardRes { + ATBannerLeaderboardRes({ + GiftsReceivedLeaderboard? giftsReceivedLeaderboard, + GiftsSendLeaderboard? giftsSendLeaderboard, + RoomGiftsLeaderboard? roomGiftsLeaderboard,}){ + _giftsReceivedLeaderboard = giftsReceivedLeaderboard; + _giftsSendLeaderboard = giftsSendLeaderboard; + _roomGiftsLeaderboard = roomGiftsLeaderboard; +} + + ATBannerLeaderboardRes.fromJson(dynamic json) { + _giftsReceivedLeaderboard = json['giftsReceivedLeaderboard'] != null ? GiftsReceivedLeaderboard.fromJson(json['giftsReceivedLeaderboard']) : null; + _giftsSendLeaderboard = json['giftsSendLeaderboard'] != null ? GiftsSendLeaderboard.fromJson(json['giftsSendLeaderboard']) : null; + _roomGiftsLeaderboard = json['roomGiftsLeaderboard'] != null ? RoomGiftsLeaderboard.fromJson(json['roomGiftsLeaderboard']) : null; + } + GiftsReceivedLeaderboard? _giftsReceivedLeaderboard; + GiftsSendLeaderboard? _giftsSendLeaderboard; + RoomGiftsLeaderboard? _roomGiftsLeaderboard; + + GiftsReceivedLeaderboard? get giftsReceivedLeaderboard => _giftsReceivedLeaderboard; + GiftsSendLeaderboard? get giftsSendLeaderboard => _giftsSendLeaderboard; + RoomGiftsLeaderboard? get roomGiftsLeaderboard => _roomGiftsLeaderboard; + + Map toJson() { + final map = {}; + if (_giftsReceivedLeaderboard != null) { + map['giftsReceivedLeaderboard'] = _giftsReceivedLeaderboard?.toJson(); + } + if (_giftsSendLeaderboard != null) { + map['giftsSendLeaderboard'] = _giftsSendLeaderboard?.toJson(); + } + if (_roomGiftsLeaderboard != null) { + map['roomGiftsLeaderboard'] = _roomGiftsLeaderboard?.toJson(); + } + return map; + } + +} + +/// daily : [{"avatar":"","id":0,"nickname":"","quantity":0,"quantityFormat":""}] +/// monthly : [{"avatar":"","id":0,"nickname":"","quantity":0,"quantityFormat":""}] +/// weekly : [{"avatar":"","id":0,"nickname":"","quantity":0,"quantityFormat":""}] + +class RoomGiftsLeaderboard { + RoomGiftsLeaderboard({ + List? daily, + List? monthly, + List? weekly,}){ + _daily = daily; + _monthly = monthly; + _weekly = weekly; +} + + RoomGiftsLeaderboard.fromJson(dynamic json) { + if (json['daily'] != null) { + _daily = []; + json['daily'].forEach((v) { + _daily?.add(LeaderboardUser.fromJson(v)); + }); + } + if (json['monthly'] != null) { + _monthly = []; + json['monthly'].forEach((v) { + _monthly?.add(LeaderboardUser.fromJson(v)); + }); + } + if (json['weekly'] != null) { + _weekly = []; + json['weekly'].forEach((v) { + _weekly?.add(LeaderboardUser.fromJson(v)); + }); + } + } + List? _daily; + List? _monthly; + List? _weekly; + + List? get daily => _daily; + List? get monthly => _monthly; + List? get weekly => _weekly; + + Map toJson() { + final map = {}; + if (_daily != null) { + map['daily'] = _daily?.map((v) => v.toJson()).toList(); + } + if (_monthly != null) { + map['monthly'] = _monthly?.map((v) => v.toJson()).toList(); + } + if (_weekly != null) { + map['weekly'] = _weekly?.map((v) => v.toJson()).toList(); + } + return map; + } + +} + +/// avatar : "" +/// id : 0 +/// nickname : "" +/// quantity : 0 +/// quantityFormat : "" + +class LeaderboardUser { + LeaderboardUser({ + String? avatar, + String? id, + String? nickname, + String? quantity, + String? quantityFormat,}){ + _avatar = avatar; + _id = id; + _nickname = nickname; + _quantity = quantity; + _quantityFormat = quantityFormat; +} + + LeaderboardUser.fromJson(dynamic json) { + _avatar = json['avatar']; + _id = json['id']; + _nickname = json['nickname']; + _quantity = json['quantity']; + _quantityFormat = json['quantityFormat']; + } + String? _avatar; + String? _id; + String? _nickname; + String? _quantity; + String? _quantityFormat; + + String? get avatar => _avatar; + String? get id => _id; + String? get nickname => _nickname; + String? get quantity => _quantity; + String? get quantityFormat => _quantityFormat; + + Map toJson() { + final map = {}; + map['avatar'] = _avatar; + map['id'] = _id; + map['nickname'] = _nickname; + map['quantity'] = _quantity; + map['quantityFormat'] = _quantityFormat; + return map; + } + +} + +class GiftsSendLeaderboard { + GiftsSendLeaderboard({ + List? daily, + List? monthly, + List? weekly,}){ + _daily = daily; + _monthly = monthly; + _weekly = weekly; +} + + GiftsSendLeaderboard.fromJson(dynamic json) { + if (json['daily'] != null) { + _daily = []; + json['daily'].forEach((v) { + _daily?.add(LeaderboardUser.fromJson(v)); + }); + } + if (json['monthly'] != null) { + _monthly = []; + json['monthly'].forEach((v) { + _monthly?.add(LeaderboardUser.fromJson(v)); + }); + } + if (json['weekly'] != null) { + _weekly = []; + json['weekly'].forEach((v) { + _weekly?.add(LeaderboardUser.fromJson(v)); + }); + } + } + List? _daily; + List? _monthly; + List? _weekly; + + List? get daily => _daily; + List? get monthly => _monthly; + List? get weekly => _weekly; + + Map toJson() { + final map = {}; + if (_daily != null) { + map['daily'] = _daily?.map((v) => v.toJson()).toList(); + } + if (_monthly != null) { + map['monthly'] = _monthly?.map((v) => v.toJson()).toList(); + } + if (_weekly != null) { + map['weekly'] = _weekly?.map((v) => v.toJson()).toList(); + } + return map; + } + +} + +/// avatar : "" +/// id : 0 +/// nickname : "" +/// quantity : 0 +/// quantityFormat : "" + + +/// avatar : "" +/// id : 0 +/// nickname : "" +/// quantity : 0 +/// quantityFormat : "" + + +class GiftsReceivedLeaderboard { + GiftsReceivedLeaderboard({ + List? daily, + List? monthly, + List? weekly,}){ + _daily = daily; + _monthly = monthly; + _weekly = weekly; +} + + GiftsReceivedLeaderboard.fromJson(dynamic json) { + if (json['daily'] != null) { + _daily = []; + json['daily'].forEach((v) { + _daily?.add(LeaderboardUser.fromJson(v)); + }); + } + if (json['monthly'] != null) { + _monthly = []; + json['monthly'].forEach((v) { + _monthly?.add(LeaderboardUser.fromJson(v)); + }); + } + if (json['weekly'] != null) { + _weekly = []; + json['weekly'].forEach((v) { + _weekly?.add(LeaderboardUser.fromJson(v)); + }); + } + } + List? _daily; + List? _monthly; + List? _weekly; + + List? get daily => _daily; + List? get monthly => _monthly; + List? get weekly => _weekly; + + Map toJson() { + final map = {}; + if (_daily != null) { + map['daily'] = _daily?.map((v) => v.toJson()).toList(); + } + if (_monthly != null) { + map['monthly'] = _monthly?.map((v) => v.toJson()).toList(); + } + if (_weekly != null) { + map['weekly'] = _weekly?.map((v) => v.toJson()).toList(); + } + return map; + } + +} + +/// avatar : "" +/// id : 0 +/// nickname : "" +/// quantity : 0 +/// quantityFormat : "" + + diff --git a/lib/chatvibe_domain/models/res/at_broad_cast_luck_gift_push.dart b/lib/chatvibe_domain/models/res/at_broad_cast_luck_gift_push.dart new file mode 100644 index 0000000..85de7d1 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_broad_cast_luck_gift_push.dart @@ -0,0 +1,190 @@ +/// data : {"acceptNickname":"Sukabuliete","acceptUserId":"1957345312961527809","account":"8826602","avatarFrameCover":"https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/svga_cover/manager-33adbddc-b68c-407d-8cbb-3c7b9878b953.png","avatarFrameSvg":"https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/svgasource/manager-97edd7e3-0335-4521-8eb0-5a84b009d2a9.svga","balance":1611358.0,"giftCandy":1000.0,"giftCover":"https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/gifts/manager-effb4f1c-872d-4abf-8797-9987bf206006.png","giftQuantity":1,"globalNews":true,"multiple":1,"multipleType":"WIN","nickname":"Sukabuliete","regionCode":"AF","roomAccount":"8826602","roomId":"1957345511792508929","sendUserId":"1957345312961527809","sysOrigin":"atu","userAvatar":"https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/avatar/0e031d7e-d269-44fe-98eb-543060d37f07.jpg"} +/// type : "GAME_LUCKY_GIFT" + +class ATBroadCastLuckGiftPush { + ATBroadCastLuckGiftPush({ + Data? data, + String? type,}){ + _data = data; + _type = type; +} + + ATBroadCastLuckGiftPush.fromJson(dynamic json) { + _data = json['data'] != null ? Data.fromJson(json['data']) : null; + _type = json['type']; + } + Data? _data; + String? _type; + Data? get data => _data; + String? get type => _type; + + Map toJson() { + final map = {}; + if (_data != null) { + map['data'] = _data?.toJson(); + } + map['type'] = _type; + return map; + } + +} + +/// acceptNickname : "Sukabuliete" +/// acceptUserId : "1957345312961527809" +/// account : "8826602" +/// avatarFrameCover : "https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/svga_cover/manager-33adbddc-b68c-407d-8cbb-3c7b9878b953.png" +/// avatarFrameSvg : "https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/svgasource/manager-97edd7e3-0335-4521-8eb0-5a84b009d2a9.svga" +/// balance : 1611358.0 +/// giftCandy : 1000.0 +/// giftCover : "https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/gifts/manager-effb4f1c-872d-4abf-8797-9987bf206006.png" +/// giftQuantity : 1 +/// globalNews : true +/// multiple : 倍率 +/// multipleType : "WIN" +/// nickname : "Sukabuliete" +/// regionCode : "AF" +/// roomAccount : "8826602" +/// roomId : "1957345511792508929" +/// sendUserId : "1957345312961527809" +/// sysOrigin : "atu" +/// userAvatar : "https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/avatar/0e031d7e-d269-44fe-98eb-543060d37f07.jpg" + +class Data { + Data({ + String? acceptNickname, + String? acceptUserId, + String? account, + String? avatarFrameCover, + String? avatarFrameSvg, + num? balance, + num? awardAmount, + num? giftCandy, + String? giftCover, + num? giftQuantity, + bool? globalNews, + num? multiple, + String? multipleType, + String? nickname, + String? regionCode, + String? roomAccount, + String? roomId, + String? giftId, + String? sendUserId, + String? sysOrigin, + String? userAvatar,}){ + _acceptNickname = acceptNickname; + _acceptUserId = acceptUserId; + _account = account; + _avatarFrameCover = avatarFrameCover; + _avatarFrameSvg = avatarFrameSvg; + _balance = balance; + _awardAmount = awardAmount; + _giftCandy = giftCandy; + _giftCover = giftCover; + _giftQuantity = giftQuantity; + _globalNews = globalNews; + _multiple = multiple; + _multipleType = multipleType; + _nickname = nickname; + _regionCode = regionCode; + _roomAccount = roomAccount; + _roomId = roomId; + _sendUserId = sendUserId; + _giftId = giftId; + _sysOrigin = sysOrigin; + _userAvatar = userAvatar; +} + + Data.fromJson(dynamic json) { + _acceptNickname = json['acceptNickname']; + _acceptUserId = json['acceptUserId']; + _account = json['account']; + _avatarFrameCover = json['avatarFrameCover']; + _avatarFrameSvg = json['avatarFrameSvg']; + _balance = json['balance']; + _awardAmount = json['awardAmount']; + _giftCandy = json['giftCandy']; + _giftCover = json['giftCover']; + _giftQuantity = json['giftQuantity']; + _globalNews = json['globalNews']; + _multiple = json['multiple']; + _multipleType = json['multipleType']; + _nickname = json['nickname']; + _regionCode = json['regionCode']; + _roomAccount = json['roomAccount']; + _roomId = json['roomId']; + _sendUserId = json['sendUserId']; + _sysOrigin = json['sysOrigin']; + _userAvatar = json['userAvatar']; + _giftId = json['giftId']; + } + String? _acceptNickname; + String? _acceptUserId; + String? _account; + String? _avatarFrameCover; + String? _avatarFrameSvg; + num? _balance; + num? _awardAmount; + num? _giftCandy; + String? _giftCover; + num? _giftQuantity; + bool? _globalNews; + num? _multiple; + String? _multipleType; + String? _nickname; + String? _regionCode; + String? _roomAccount; + String? _roomId; + String? _sendUserId; + String? _giftId; + String? _sysOrigin; + String? _userAvatar; + String? get acceptNickname => _acceptNickname; + String? get acceptUserId => _acceptUserId; + String? get account => _account; + String? get avatarFrameCover => _avatarFrameCover; + String? get avatarFrameSvg => _avatarFrameSvg; + num? get balance => _balance; + num? get awardAmount => _awardAmount; + num? get giftCandy => _giftCandy; + String? get giftCover => _giftCover; + String? get giftId => _giftId; + num? get giftQuantity => _giftQuantity; + bool? get globalNews => _globalNews; + num? get multiple => _multiple; + String? get multipleType => _multipleType; + String? get nickname => _nickname; + String? get regionCode => _regionCode; + String? get roomAccount => _roomAccount; + String? get roomId => _roomId; + String? get sendUserId => _sendUserId; + String? get sysOrigin => _sysOrigin; + String? get userAvatar => _userAvatar; + + Map toJson() { + final map = {}; + map['acceptNickname'] = _acceptNickname; + map['acceptUserId'] = _acceptUserId; + map['account'] = _account; + map['avatarFrameCover'] = _avatarFrameCover; + map['avatarFrameSvg'] = _avatarFrameSvg; + map['balance'] = _balance; + map['awardAmount'] = _awardAmount; + map['giftCandy'] = _giftCandy; + map['giftCover'] = _giftCover; + map['giftQuantity'] = _giftQuantity; + map['globalNews'] = _globalNews; + map['multiple'] = _multiple; + map['multipleType'] = _multipleType; + map['nickname'] = _nickname; + map['regionCode'] = _regionCode; + map['roomAccount'] = _roomAccount; + map['roomId'] = _roomId; + map['sendUserId'] = _sendUserId; + map['sysOrigin'] = _sysOrigin; + map['userAvatar'] = _userAvatar; + map['giftId'] = _giftId; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_comment_list_res.dart b/lib/chatvibe_domain/models/res/at_comment_list_res.dart new file mode 100644 index 0000000..32d7a90 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_comment_list_res.dart @@ -0,0 +1,218 @@ +/// records : [{"id":"","dynamicId":"","userAvatar":"","userNickname":"","userSex":0,"userId":"","toUserId":"","toUserNickname":"","content":"","likeStrQuantity":"","createTime":0,"like":false,"rootCommentId":0}] +/// total : 0 +/// size : 0 +/// current : 0 + +class ATCommentListRes { + ATCommentListRes({ + List? records, + num? total, + num? size, + num? current, + }) { + _records = records; + _total = total; + _size = size; + _current = current; + } + + ATCommentListRes.fromJson(dynamic json) { + if (json['records'] != null) { + _records = []; + json['records'].forEach((v) { + _records?.add(CommentListRecords.fromJson(v)); + }); + } + _total = json['total']; + _size = json['size']; + _current = json['current']; + } + + List? _records; + num? _total; + num? _size; + num? _current; + + List? get records => _records; + + num? get total => _total; + + num? get size => _size; + + num? get current => _current; + + Map toJson() { + final map = {}; + if (_records != null) { + map['records'] = _records?.map((v) => v.toJson()).toList(); + } + map['total'] = _total; + map['size'] = _size; + map['current'] = _current; + return map; + } +} + +/// id : "" +/// dynamicId : "" +/// userAvatar : "" +/// userNickname : "" +/// userSex : 0 +/// userId : "" +/// toUserId : "" +/// toUserNickname : "" +/// content : "" +/// likeStrQuantity : "" +/// createTime : 0 +/// like : false +/// rootCommentId : 0 + +class CommentListRecords { + CommentListRecords({ + String? id, + String? dynamicId, + String? userAvatar, + String? userNickname, + num? userSex, + String? userId, + String? toUserId, + String? toUserNickname, + String? content, + String? likeStrQuantity, + String? commentStrQuantity, + int? createTime, + bool? like, + num? rootCommentId, + num? replyCommentId, + num? childCommentCount, + }) { + _id = id; + _dynamicId = dynamicId; + _userAvatar = userAvatar; + _userNickname = userNickname; + _userSex = userSex; + _userId = userId; + _toUserId = toUserId; + _toUserNickname = toUserNickname; + _content = content; + _likeStrQuantity = likeStrQuantity; + _commentStrQuantity = commentStrQuantity; + _createTime = createTime; + _like = like; + _rootCommentId = rootCommentId; + _replyCommentId = replyCommentId; + _childCommentCount = childCommentCount; + } + + CommentListRecords.fromJson(dynamic json) { + _id = json['id']; + _dynamicId = json['dynamicId']; + _userAvatar = json['userAvatar']; + _userNickname = json['userNickname']; + _userSex = json['userSex']; + _userId = json['userId']; + _toUserId = json['toUserId']; + _toUserNickname = json['toUserNickname']; + _content = json['content']; + _likeStrQuantity = json['likeStrQuantity']; + _commentStrQuantity = json['commentStrQuantity']; + _createTime = json['createTime']; + _like = json['like']; + _rootCommentId = json['rootCommentId']; + _replyCommentId = json['replyCommentId']; + _childCommentCount = json['childCommentCount']; + } + + String? _id; + String? _dynamicId; + String? _userAvatar; + String? _userNickname; + num? _userSex; + String? _userId; + String? _toUserId; + String? _toUserNickname; + String? _content; + String? _likeStrQuantity; + String? _commentStrQuantity; + int? _createTime; + bool? _like; + num? _rootCommentId; + num? _replyCommentId; + num? _childCommentCount; + + String? get id => _id; + + String? get dynamicId => _dynamicId; + + String? get userAvatar => _userAvatar; + + String? get userNickname => _userNickname; + + num? get userSex => _userSex; + + String? get userId => _userId; + + String? get toUserId => _toUserId; + + String? get toUserNickname => _toUserNickname; + + String? get content => _content; + + String? get likeStrQuantity => _likeStrQuantity; + + String? get commentStrQuantity => _commentStrQuantity; + + int? get createTime => _createTime; + + bool? get like => _like; + + num? get rootCommentId => _rootCommentId; + + num? get replyCommentId => _replyCommentId; + + num? get childCommentCount => _childCommentCount; + + setChildCommentCount(num count) { + _childCommentCount = count; + } + + setLike(bool like) { + _like = like; + } + + setLikeStrQuantity(String likeStrQuantity) { + _likeStrQuantity = likeStrQuantity; + } + + setUserNickname(String name) { + _userNickname = name; + } + + setUserAvatar(String avatar) { + _userAvatar = avatar; + } + + setUserSex(num sex) { + _userSex = sex; + } + + Map toJson() { + final map = {}; + map['id'] = _id; + map['dynamicId'] = _dynamicId; + map['userAvatar'] = _userAvatar; + map['userNickname'] = _userNickname; + map['userSex'] = _userSex; + map['userId'] = _userId; + map['toUserId'] = _toUserId; + map['toUserNickname'] = _toUserNickname; + map['content'] = _content; + map['likeStrQuantity'] = _likeStrQuantity; + map['commentStrQuantity'] = _commentStrQuantity; + map['createTime'] = _createTime; + map['like'] = _like; + map['rootCommentId'] = _rootCommentId; + map['childCommentCount'] = _childCommentCount; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/at_edit_room_info_res.dart b/lib/chatvibe_domain/models/res/at_edit_room_info_res.dart new file mode 100644 index 0000000..aa30bed --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_edit_room_info_res.dart @@ -0,0 +1,159 @@ +/// id : 0 +/// roomAccount : "" +/// userId : 0 +/// roomCover : "" +/// roomName : "" +/// roomDesc : "" +/// event : "" +/// sysOrigin : "" +/// countryCode : "" +/// countryName : "" +/// nationalFlag : "" +/// langCode : "" +/// del : false +/// createTime : 0 +/// updateTime : 0 +/// activeTime : 0 + +class ATEditRoomInfoRes { + ATEditRoomInfoRes({ + String? id, + String? roomAccount, + String? userId, + String? roomCover, + String? roomName, + String? roomDesc, + String? event, + String? sysOrigin, + String? countryCode, + String? countryName, + String? nationalFlag, + String? langCode, + bool? del, + num? createTime, + num? updateTime, + num? activeTime,}){ + _id = id; + _roomAccount = roomAccount; + _userId = userId; + _roomCover = roomCover; + _roomName = roomName; + _roomDesc = roomDesc; + _event = event; + _sysOrigin = sysOrigin; + _countryCode = countryCode; + _countryName = countryName; + _nationalFlag = nationalFlag; + _langCode = langCode; + _del = del; + _createTime = createTime; + _updateTime = updateTime; + _activeTime = activeTime; +} + + ATEditRoomInfoRes.fromJson(dynamic json) { + _id = json['id']; + _roomAccount = json['roomAccount']; + _userId = json['userId']; + _roomCover = json['roomCover']; + _roomName = json['roomName']; + _roomDesc = json['roomDesc']; + _event = json['event']; + _sysOrigin = json['sysOrigin']; + _countryCode = json['countryCode']; + _countryName = json['countryName']; + _nationalFlag = json['nationalFlag']; + _langCode = json['langCode']; + _del = json['del']; + _createTime = json['createTime']; + _updateTime = json['updateTime']; + _activeTime = json['activeTime']; + } + String? _id; + String? _roomAccount; + String? _userId; + String? _roomCover; + String? _roomName; + String? _roomDesc; + String? _event; + String? _sysOrigin; + String? _countryCode; + String? _countryName; + String? _nationalFlag; + String? _langCode; + bool? _del; + num? _createTime; + num? _updateTime; + num? _activeTime; +ATEditRoomInfoRes copyWith({ String? id, + String? roomAccount, + String? userId, + String? roomCover, + String? roomName, + String? roomDesc, + String? event, + String? sysOrigin, + String? countryCode, + String? countryName, + String? nationalFlag, + String? langCode, + bool? del, + num? createTime, + num? updateTime, + num? activeTime, +}) => ATEditRoomInfoRes( id: id ?? _id, + roomAccount: roomAccount ?? _roomAccount, + userId: userId ?? _userId, + roomCover: roomCover ?? _roomCover, + roomName: roomName ?? _roomName, + roomDesc: roomDesc ?? _roomDesc, + event: event ?? _event, + sysOrigin: sysOrigin ?? _sysOrigin, + countryCode: countryCode ?? _countryCode, + countryName: countryName ?? _countryName, + nationalFlag: nationalFlag ?? _nationalFlag, + langCode: langCode ?? _langCode, + del: del ?? _del, + createTime: createTime ?? _createTime, + updateTime: updateTime ?? _updateTime, + activeTime: activeTime ?? _activeTime, +); + String? get id => _id; + String? get roomAccount => _roomAccount; + String? get userId => _userId; + String? get roomCover => _roomCover; + String? get roomName => _roomName; + String? get roomDesc => _roomDesc; + String? get event => _event; + String? get sysOrigin => _sysOrigin; + String? get countryCode => _countryCode; + String? get countryName => _countryName; + String? get nationalFlag => _nationalFlag; + String? get langCode => _langCode; + bool? get del => _del; + num? get createTime => _createTime; + num? get updateTime => _updateTime; + num? get activeTime => _activeTime; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['roomAccount'] = _roomAccount; + map['userId'] = _userId; + map['roomCover'] = _roomCover; + map['roomName'] = _roomName; + map['roomDesc'] = _roomDesc; + map['event'] = _event; + map['sysOrigin'] = _sysOrigin; + map['countryCode'] = _countryCode; + map['countryName'] = _countryName; + map['nationalFlag'] = _nationalFlag; + map['langCode'] = _langCode; + map['del'] = _del; + map['createTime'] = _createTime; + map['updateTime'] = _updateTime; + map['activeTime'] = _activeTime; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_famaily_leader_board_res.dart b/lib/chatvibe_domain/models/res/at_famaily_leader_board_res.dart new file mode 100644 index 0000000..a2c8334 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_famaily_leader_board_res.dart @@ -0,0 +1,172 @@ +/// hostWeekRank : [{"userId":"","nickname":"","avatar":"","exp":"","isHost":false}] +/// supporterWeekRank : [{"userId":"","nickname":"","avatar":"","exp":"","isHost":false}] + +class ATFamailyLeaderBoardRes { + ATFamailyLeaderBoardRes({ + List? hostWeekRank, + HostWeekRank? myRankInfo, + List? supporterWeekRank,}){ + _hostWeekRank = hostWeekRank; + _supporterWeekRank = supporterWeekRank; + _myRankInfo = myRankInfo; +} + + ATFamailyLeaderBoardRes.fromJson(dynamic json) { + if (json['hostWeekRank'] != null) { + _hostWeekRank = []; + json['hostWeekRank'].forEach((v) { + _hostWeekRank?.add(HostWeekRank.fromJson(v)); + }); + } + if (json['supporterWeekRank'] != null) { + _supporterWeekRank = []; + json['supporterWeekRank'].forEach((v) { + _supporterWeekRank?.add(SupporterWeekRank.fromJson(v)); + }); + } + _myRankInfo = json['myRankInfo'] != null ? HostWeekRank.fromJson(json['myRankInfo']) : null; + } + List? _hostWeekRank; + HostWeekRank? _myRankInfo; + List? _supporterWeekRank; + + List? get hostWeekRank => _hostWeekRank; + HostWeekRank? get myRankInfo => _myRankInfo; + List? get supporterWeekRank => _supporterWeekRank; + + Map toJson() { + final map = {}; + if (_hostWeekRank != null) { + map['hostWeekRank'] = _hostWeekRank?.map((v) => v.toJson()).toList(); + } + if (_supporterWeekRank != null) { + map['supporterWeekRank'] = _supporterWeekRank?.map((v) => v.toJson()).toList(); + } + + if (_myRankInfo != null) { + map['myRankInfo'] = _myRankInfo?.toJson(); + } + return map; + } + +} + +/// userId : "" +/// nickname : "" +/// avatar : "" +/// exp : "" +/// isHost : false + +class SupporterWeekRank { + SupporterWeekRank({ + String? userId, + String? account, + String? nickname, + String? avatar, + String? exp, + bool? isHost,}){ + _userId = userId; + _account = account; + _nickname = nickname; + _avatar = avatar; + _exp = exp; + _isHost = isHost; +} + + SupporterWeekRank.fromJson(dynamic json) { + _userId = json['userId']; + _account = json['account']; + _nickname = json['nickname']; + _avatar = json['avatar']; + _exp = json['exp']; + _isHost = json['isHost']; + } + String? _userId; + String? _account; + String? _nickname; + String? _avatar; + String? _exp; + bool? _isHost; + + String? get userId => _userId; + String? get nickname => _nickname; + String? get account => _account; + String? get avatar => _avatar; + String? get exp => _exp; + bool? get isHost => _isHost; + + Map toJson() { + final map = {}; + map['userId'] = _userId; + map['account'] = _account; + map['nickname'] = _nickname; + map['avatar'] = _avatar; + map['exp'] = _exp; + map['isHost'] = _isHost; + return map; + } + +} + +/// userId : "" +/// nickname : "" +/// avatar : "" +/// exp : "" +/// isHost : false + +class HostWeekRank { + HostWeekRank({ + String? userId, + String? account, + String? nickname, + String? avatar, + String? exp, + String? rank, + bool? isHost,}){ + _userId = userId; + _account = account; + _nickname = nickname; + _avatar = avatar; + _exp = exp; + _rank = rank; + _isHost = isHost; +} + + HostWeekRank.fromJson(dynamic json) { + _userId = json['userId']; + _account = json['account']; + _nickname = json['nickname']; + _avatar = json['avatar']; + _exp = json['exp']; + _rank = json['rank']; + _isHost = json['isHost']; + } + String? _userId; + String? _account; + String? _nickname; + String? _avatar; + String? _exp; + String? _rank; + bool? _isHost; + + String? get userId => _userId; + String? get account => _account; + String? get nickname => _nickname; + String? get avatar => _avatar; + String? get exp => _exp; + String? get rank => _rank; + bool? get isHost => _isHost; + + Map toJson() { + final map = {}; + map['userId'] = _userId; + map['account'] = _account; + map['nickname'] = _nickname; + map['avatar'] = _avatar; + map['exp'] = _exp; + map['rank'] = _rank; + map['isHost'] = _isHost; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_family_home_list_res.dart b/lib/chatvibe_domain/models/res/at_family_home_list_res.dart new file mode 100644 index 0000000..54b7828 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_family_home_list_res.dart @@ -0,0 +1,315 @@ +/// records : [{"familyId":"","familyName":"","familyAvatar":"","familyAccount":"","levelKey":"","memberCount":0,"onlineCount":0,"exp":"","avatarFrameCover":"","avatarFrameSvg":"","displayMembers":[{"userId":"","avatar":"","online":false,"familyRole":"","consumptionLevel":0}],"onlineRooms":[{"roomId":"","roomAccount":"","userId":"","userAvatar":"","userNickname":"","onlineQuantity":0}]}] +/// total : 0 +/// size : 0 +/// current : 0 + +class ATFamilyHomeListRes { + ATFamilyHomeListRes({ + List? records, + num? total, + num? size, + num? current,}){ + _records = records; + _total = total; + _size = size; + _current = current; +} + + ATFamilyHomeListRes.fromJson(dynamic json) { + if (json['records'] != null) { + _records = []; + json['records'].forEach((v) { + _records?.add(Records.fromJson(v)); + }); + } + _total = json['total']; + _size = json['size']; + _current = json['current']; + } + List? _records; + num? _total; + num? _size; + num? _current; + + List? get records => _records; + num? get total => _total; + num? get size => _size; + num? get current => _current; + + Map toJson() { + final map = {}; + if (_records != null) { + map['records'] = _records?.map((v) => v.toJson()).toList(); + } + map['total'] = _total; + map['size'] = _size; + map['current'] = _current; + return map; + } + +} + +/// familyId : "" +/// familyName : "" +/// familyAvatar : "" +/// familyAccount : "" +/// levelKey : "" +/// memberCount : 0 +/// onlineCount : 0 +/// exp : "" +/// avatarFrameCover : "" +/// avatarFrameSvg : "" +/// displayMembers : [{"userId":"","avatar":"","online":false,"familyRole":"","consumptionLevel":0}] +/// onlineRooms : [{"roomId":"","roomAccount":"","userId":"","userAvatar":"","userNickname":"","onlineQuantity":0}] + +class Records { + Records({ + String? familyId, + String? familyName, + String? familyAvatar, + String? familyAccount, + String? familyNotice, + String? myFamilyRole, + num? memberCount, + num? level, + num? onlineCount, + String? exp, + String? avatarFrameCover, + String? avatarFrameSvg, + List? displayMembers, + List? onlineRooms,}){ + _familyId = familyId; + _familyName = familyName; + _familyAvatar = familyAvatar; + _familyAccount = familyAccount; + _myFamilyRole = myFamilyRole; + _familyNotice = familyNotice; + _memberCount = memberCount; + _level = level; + _onlineCount = onlineCount; + _exp = exp; + _avatarFrameCover = avatarFrameCover; + _avatarFrameSvg = avatarFrameSvg; + _displayMembers = displayMembers; + _onlineRooms = onlineRooms; +} + + Records.fromJson(dynamic json) { + _familyId = json['familyId']; + _familyName = json['familyName']; + _familyAvatar = json['familyAvatar']; + _familyAccount = json['familyAccount']; + _myFamilyRole = json['myFamilyRole']; + _familyNotice = json['familyNotice']; + _memberCount = json['memberCount']; + _level = json['level']; + _onlineCount = json['onlineCount']; + _exp = json['exp']; + _avatarFrameCover = json['avatarFrameCover']; + _avatarFrameSvg = json['avatarFrameSvg']; + if (json['displayMembers'] != null) { + _displayMembers = []; + json['displayMembers'].forEach((v) { + _displayMembers?.add(DisplayMembers.fromJson(v)); + }); + } + if (json['onlineRooms'] != null) { + _onlineRooms = []; + json['onlineRooms'].forEach((v) { + _onlineRooms?.add(OnlineRooms.fromJson(v)); + }); + } + } + String? _familyId; + String? _familyName; + String? _familyAvatar; + String? _familyAccount; + String? _myFamilyRole; + String? _familyNotice; + num? _memberCount; + num? _level; + num? _onlineCount; + String? _exp; + String? _avatarFrameCover; + String? _avatarFrameSvg; + List? _displayMembers; + List? _onlineRooms; + + String? get familyId => _familyId; + String? get familyName => _familyName; + String? get familyAvatar => _familyAvatar; + String? get familyAccount => _familyAccount; + String? get familyNotice => _familyNotice; + String? get myFamilyRole => _myFamilyRole; + num? get memberCount => _memberCount; + num? get level => _level; + num? get onlineCount => _onlineCount; + String? get exp => _exp; + String? get avatarFrameCover => _avatarFrameCover; + String? get avatarFrameSvg => _avatarFrameSvg; + List? get displayMembers => _displayMembers; + List? get onlineRooms => _onlineRooms; + + Map toJson() { + final map = {}; + map['familyId'] = _familyId; + map['familyName'] = _familyName; + map['familyAvatar'] = _familyAvatar; + map['familyAccount'] = _familyAccount; + map['familyNotice'] = _familyNotice; + map['myFamilyRole'] = _myFamilyRole; + map['memberCount'] = _memberCount; + map['level'] = _level; + map['onlineCount'] = _onlineCount; + map['exp'] = _exp; + map['avatarFrameCover'] = _avatarFrameCover; + map['avatarFrameSvg'] = _avatarFrameSvg; + if (_displayMembers != null) { + map['displayMembers'] = _displayMembers?.map((v) => v.toJson()).toList(); + } + if (_onlineRooms != null) { + map['onlineRooms'] = _onlineRooms?.map((v) => v.toJson()).toList(); + } + return map; + } + +} + +/// roomId : "" +/// roomAccount : "" +/// userId : "" +/// userAvatar : "" +/// userNickname : "" +/// onlineQuantity : 0 + +class OnlineRooms { + OnlineRooms({ + String? roomId, + String? roomAccount, + String? roomName, + String? roomCover, + String? countryName, + String? countryCode, + String? userId, + bool? existsPassword, + String? userAvatar, + String? userNickname, + num? onlineQuantity,}){ + _roomId = roomId; + _roomName = roomName; + _roomCover = roomCover; + _existsPassword = existsPassword; + _roomAccount = roomAccount; + _countryName = countryName; + _countryCode = countryCode; + _userId = userId; + _userAvatar = userAvatar; + _userNickname = userNickname; + _onlineQuantity = onlineQuantity; +} + + OnlineRooms.fromJson(dynamic json) { + _roomId = json['roomId']; + _roomName = json['roomName']; + _roomCover = json['roomCover']; + _existsPassword = json['existsPassword']; + _roomAccount = json['roomAccount']; + _countryName = json['countryName']; + _countryCode = json['countryCode']; + _userId = json['userId']; + _userAvatar = json['userAvatar']; + _userNickname = json['userNickname']; + _onlineQuantity = json['onlineQuantity']; + } + String? _roomId; + String? _roomName; + String? _roomCover; + bool? _existsPassword; + String? _roomAccount; + String? _countryName; + String? _countryCode; + String? _userId; + String? _userAvatar; + String? _userNickname; + num? _onlineQuantity; + + String? get roomId => _roomId; + String? get roomName => _roomName; + String? get roomCover => _roomCover; + bool? get existsPassword => _existsPassword; + String? get roomAccount => _roomAccount; + String? get countryName => _countryName; + String? get countryCode => _countryCode; + String? get userId => _userId; + String? get userAvatar => _userAvatar; + String? get userNickname => _userNickname; + num? get onlineQuantity => _onlineQuantity; + + Map toJson() { + final map = {}; + map['roomId'] = _roomId; + map['roomName'] = _roomName; + map['roomCover'] = _roomCover; + map['existsPassword'] = _existsPassword; + map['roomAccount'] = _roomAccount; + map['countryName'] = _countryName; + map['countryCode'] = _countryCode; + map['userId'] = _userId; + map['userAvatar'] = _userAvatar; + map['userNickname'] = _userNickname; + map['onlineQuantity'] = _onlineQuantity; + return map; + } + +} + +/// userId : "" +/// avatar : "" +/// online : false +/// familyRole : "" +/// consumptionLevel : 0 + +class DisplayMembers { + DisplayMembers({ + String? userId, + String? avatar, + bool? online, + String? familyRole, + num? consumptionLevel,}){ + _userId = userId; + _avatar = avatar; + _online = online; + _familyRole = familyRole; + _consumptionLevel = consumptionLevel; +} + + DisplayMembers.fromJson(dynamic json) { + _userId = json['userId']; + _avatar = json['avatar']; + _online = json['online']; + _familyRole = json['familyRole']; + _consumptionLevel = json['consumptionLevel']; + } + String? _userId; + String? _avatar; + bool? _online; + String? _familyRole; + num? _consumptionLevel; + + String? get userId => _userId; + String? get avatar => _avatar; + bool? get online => _online; + String? get familyRole => _familyRole; + num? get consumptionLevel => _consumptionLevel; + + Map toJson() { + final map = {}; + map['userId'] = _userId; + map['avatar'] = _avatar; + map['online'] = _online; + map['familyRole'] = _familyRole; + map['consumptionLevel'] = _consumptionLevel; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_family_level_res.dart b/lib/chatvibe_domain/models/res/at_family_level_res.dart new file mode 100644 index 0000000..0e70c33 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_family_level_res.dart @@ -0,0 +1,106 @@ +/// levelKey : "" +/// levelExp : "" +/// avatarFrameCover : "" +/// avatarFrameSvg : "" +/// badgeCover : "" +/// badgeSvg : "" +/// giftCover : "" +/// giftSvg : "" +/// maxMember : 0 +/// maxManager : 0 +/// levelBackgroundPicture : "" +/// levelType : "" +/// sort : 0 + +class ATFamilyLevelRes { + ATFamilyLevelRes({ + String? level, + int? levelExp, + String? avatarFrameCover, + String? avatarFrameSvg, + String? badgeCover, + String? badgeSvg, + String? giftCover, + String? giftSvg, + num? maxMember, + num? maxManager, + String? levelBackgroundPicture, + String? levelType, + num? sort,}){ + _level = level; + _levelExp = levelExp; + _avatarFrameCover = avatarFrameCover; + _avatarFrameSvg = avatarFrameSvg; + _badgeCover = badgeCover; + _badgeSvg = badgeSvg; + _giftCover = giftCover; + _giftSvg = giftSvg; + _maxMember = maxMember; + _maxManager = maxManager; + _levelBackgroundPicture = levelBackgroundPicture; + _levelType = levelType; + _sort = sort; +} + + ATFamilyLevelRes.fromJson(dynamic json) { + _level = json['level']; + _levelExp = json['levelExp']; + _avatarFrameCover = json['avatarFrameCover']; + _avatarFrameSvg = json['avatarFrameSvg']; + _badgeCover = json['badgeCover']; + _badgeSvg = json['badgeSvg']; + _giftCover = json['giftCover']; + _giftSvg = json['giftSvg']; + _maxMember = json['maxMember']; + _maxManager = json['maxManager']; + _levelBackgroundPicture = json['levelBackgroundPicture']; + _levelType = json['levelType']; + _sort = json['sort']; + } + String? _level; + int? _levelExp; + String? _avatarFrameCover; + String? _avatarFrameSvg; + String? _badgeCover; + String? _badgeSvg; + String? _giftCover; + String? _giftSvg; + num? _maxMember; + num? _maxManager; + String? _levelBackgroundPicture; + String? _levelType; + num? _sort; + + String? get level => _level; + int? get levelExp => _levelExp; + String? get avatarFrameCover => _avatarFrameCover; + String? get avatarFrameSvg => _avatarFrameSvg; + String? get badgeCover => _badgeCover; + String? get badgeSvg => _badgeSvg; + String? get giftCover => _giftCover; + String? get giftSvg => _giftSvg; + num? get maxMember => _maxMember; + num? get maxManager => _maxManager; + String? get levelBackgroundPicture => _levelBackgroundPicture; + String? get levelType => _levelType; + num? get sort => _sort; + + Map toJson() { + final map = {}; + map['level'] = _level; + map['levelExp'] = _levelExp; + map['avatarFrameCover'] = _avatarFrameCover; + map['avatarFrameSvg'] = _avatarFrameSvg; + map['badgeCover'] = _badgeCover; + map['badgeSvg'] = _badgeSvg; + map['giftCover'] = _giftCover; + map['giftSvg'] = _giftSvg; + map['maxMember'] = _maxMember; + map['maxManager'] = _maxManager; + map['levelBackgroundPicture'] = _levelBackgroundPicture; + map['levelType'] = _levelType; + map['sort'] = _sort; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_family_list_member_res.dart b/lib/chatvibe_domain/models/res/at_family_list_member_res.dart new file mode 100644 index 0000000..5d5b525 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_family_list_member_res.dart @@ -0,0 +1,157 @@ +/// familyId : "" +/// maxMember : 0 +/// maxManager : 0 +/// members : [{"familyId":"","memberId":"","memberUserId":"","account":"","specialAccount":"","memberAvatar":"","memberNickname":"","memberAge":0,"memberSex":false,"memberExp":"","memberRole":"","agent":false,"anchor":false}] + +class ATFamilyListMemberRes { + ATFamilyListMemberRes({ + String? familyId, + num? maxMember, + num? maxManager, + List? members,}){ + _familyId = familyId; + _maxMember = maxMember; + _maxManager = maxManager; + _members = members; +} + + ATFamilyListMemberRes.fromJson(dynamic json) { + _familyId = json['familyId']; + _maxMember = json['maxMember']; + _maxManager = json['maxManager']; + if (json['members'] != null) { + _members = []; + json['members'].forEach((v) { + _members?.add(Members.fromJson(v)); + }); + } + } + String? _familyId; + num? _maxMember; + num? _maxManager; + List? _members; + + String? get familyId => _familyId; + num? get maxMember => _maxMember; + num? get maxManager => _maxManager; + List? get members => _members; + + Map toJson() { + final map = {}; + map['familyId'] = _familyId; + map['maxMember'] = _maxMember; + map['maxManager'] = _maxManager; + if (_members != null) { + map['members'] = _members?.map((v) => v.toJson()).toList(); + } + return map; + } + +} + +/// familyId : "" +/// memberId : "" +/// memberUserId : "" +/// account : "" +/// specialAccount : "" +/// memberAvatar : "" +/// memberNickname : "" +/// memberAge : 0 +/// memberSex : false +/// memberExp : "" +/// memberRole : "" +/// agent : false +/// anchor : false + +class Members { + Members({ + String? familyId, + String? memberId, + String? memberUserId, + String? account, + String? specialAccount, + String? memberAvatar, + String? memberNickname, + num? memberAge, + bool? memberSex, + String? memberExp, + String? memberRole, + bool? agent, + bool? anchor,}){ + _familyId = familyId; + _memberId = memberId; + _memberUserId = memberUserId; + _account = account; + _specialAccount = specialAccount; + _memberAvatar = memberAvatar; + _memberNickname = memberNickname; + _memberAge = memberAge; + _memberSex = memberSex; + _memberExp = memberExp; + _memberRole = memberRole; + _agent = agent; + _anchor = anchor; +} + + Members.fromJson(dynamic json) { + _familyId = json['familyId']; + _memberId = json['memberId']; + _memberUserId = json['memberUserId']; + _account = json['account']; + _specialAccount = json['specialAccount']; + _memberAvatar = json['memberAvatar']; + _memberNickname = json['memberNickname']; + _memberAge = json['memberAge']; + _memberSex = json['memberSex']; + _memberExp = json['memberExp']; + _memberRole = json['memberRole']; + _agent = json['agent']; + _anchor = json['anchor']; + } + String? _familyId; + String? _memberId; + String? _memberUserId; + String? _account; + String? _specialAccount; + String? _memberAvatar; + String? _memberNickname; + num? _memberAge; + bool? _memberSex; + String? _memberExp; + String? _memberRole; + bool? _agent; + bool? _anchor; + + String? get familyId => _familyId; + String? get memberId => _memberId; + String? get memberUserId => _memberUserId; + String? get account => _account; + String? get specialAccount => _specialAccount; + String? get memberAvatar => _memberAvatar; + String? get memberNickname => _memberNickname; + num? get memberAge => _memberAge; + bool? get memberSex => _memberSex; + String? get memberExp => _memberExp; + String? get memberRole => _memberRole; + bool? get agent => _agent; + bool? get anchor => _anchor; + + Map toJson() { + final map = {}; + map['familyId'] = _familyId; + map['memberId'] = _memberId; + map['memberUserId'] = _memberUserId; + map['account'] = _account; + map['specialAccount'] = _specialAccount; + map['memberAvatar'] = _memberAvatar; + map['memberNickname'] = _memberNickname; + map['memberAge'] = _memberAge; + map['memberSex'] = _memberSex; + map['memberExp'] = _memberExp; + map['memberRole'] = _memberRole; + map['agent'] = _agent; + map['anchor'] = _anchor; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_family_list_message_res.dart b/lib/chatvibe_domain/models/res/at_family_list_message_res.dart new file mode 100644 index 0000000..f7f4d39 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_family_list_message_res.dart @@ -0,0 +1,89 @@ +/// msgId : "" +/// familyUser : {"id":"","account":"","specialAccount":"","userAvatar":"","userNickname":""} +/// createTime : 0 + +class ATFamilyListMessageRes { + ATFamilyListMessageRes({ + String? msgId, + FamilyUser? familyUser, + num? createTime,}){ + _msgId = msgId; + _familyUser = familyUser; + _createTime = createTime; +} + + ATFamilyListMessageRes.fromJson(dynamic json) { + _msgId = json['msgId']; + _familyUser = json['familyUser'] != null ? FamilyUser.fromJson(json['familyUser']) : null; + _createTime = json['createTime']; + } + String? _msgId; + FamilyUser? _familyUser; + num? _createTime; + + String? get msgId => _msgId; + FamilyUser? get familyUser => _familyUser; + num? get createTime => _createTime; + + Map toJson() { + final map = {}; + map['msgId'] = _msgId; + if (_familyUser != null) { + map['familyUser'] = _familyUser?.toJson(); + } + map['createTime'] = _createTime; + return map; + } + +} + +/// id : "" +/// account : "" +/// specialAccount : "" +/// userAvatar : "" +/// userNickname : "" + +class FamilyUser { + FamilyUser({ + String? id, + String? account, + String? specialAccount, + String? userAvatar, + String? userNickname,}){ + _id = id; + _account = account; + _specialAccount = specialAccount; + _userAvatar = userAvatar; + _userNickname = userNickname; +} + + FamilyUser.fromJson(dynamic json) { + _id = json['id']; + _account = json['account']; + _specialAccount = json['specialAccount']; + _userAvatar = json['userAvatar']; + _userNickname = json['userNickname']; + } + String? _id; + String? _account; + String? _specialAccount; + String? _userAvatar; + String? _userNickname; + + String? get id => _id; + String? get account => _account; + String? get specialAccount => _specialAccount; + String? get userAvatar => _userAvatar; + String? get userNickname => _userNickname; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['account'] = _account; + map['specialAccount'] = _specialAccount; + map['userAvatar'] = _userAvatar; + map['userNickname'] = _userNickname; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_family_my_apply_list_res.dart b/lib/chatvibe_domain/models/res/at_family_my_apply_list_res.dart new file mode 100644 index 0000000..be11796 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_family_my_apply_list_res.dart @@ -0,0 +1,78 @@ +/// msgId : "" +/// familyId : "" +/// familyName : "" +/// familyAvatar : "" +/// maxMember : 0 +/// status : 0 +/// createTime : 0 + +class ATFamilyMyApplyListRes { + ATFamilyMyApplyListRes({ + num? msgId, + num? familyId, + String? familyName, + String? familyAvatar, + String? familyAccount, + num? maxMember, + num? status, + num? createTime, + }) { + _msgId = msgId; + _familyId = familyId; + _familyName = familyName; + _familyAvatar = familyAvatar; + _familyAccount = familyAccount; + _maxMember = maxMember; + _status = status; + _createTime = createTime; + } + + ATFamilyMyApplyListRes.fromJson(dynamic json) { + _msgId = json['msgId']; + _familyId = json['familyId']; + _familyName = json['familyName']; + _familyAccount = json['familyAccount']; + _familyAvatar = json['familyAvatar']; + _maxMember = json['maxMember']; + _status = json['status']; + _createTime = json['createTime']; + } + + num? _msgId; + num? _familyId; + String? _familyName; + String? _familyAccount; + String? _familyAvatar; + num? _maxMember; + num? _status; + num? _createTime; + + num? get msgId => _msgId; + + num? get familyId => _familyId; + + String? get familyName => _familyName; + + String? get familyAccount => _familyAccount; + + String? get familyAvatar => _familyAvatar; + + num? get maxMember => _maxMember; + + num? get status => _status; + + num? get createTime => _createTime; + + Map toJson() { + final map = {}; + map['msgId'] = _msgId; + map['familyId'] = _familyId; + map['familyAccount'] = _familyAccount; + map['familyName'] = _familyName; + map['familyAvatar'] = _familyAvatar; + map['maxMember'] = _maxMember; + map['status'] = _status; + map['createTime'] = _createTime; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/at_family_news_list_res.dart b/lib/chatvibe_domain/models/res/at_family_news_list_res.dart new file mode 100644 index 0000000..e73dee3 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_family_news_list_res.dart @@ -0,0 +1,146 @@ +/// records : [{"id":"","newsType":"","operatorId":"","operatorName":"","targetUserId":"","targetUserName":"","content":"","createTime":""}] +/// total : 0 +/// size : 0 +/// current : 0 + +class ATFamilyNewsListRes { + ATFamilyNewsListRes({ + List? records, + num? total, + num? size, + num? current,}){ + _records = records; + _total = total; + _size = size; + _current = current; +} + + ATFamilyNewsListRes.fromJson(dynamic json) { + if (json['records'] != null) { + _records = []; + json['records'].forEach((v) { + _records?.add(Records.fromJson(v)); + }); + } + _total = json['total']; + _size = json['size']; + _current = json['current']; + } + List? _records; + num? _total; + num? _size; + num? _current; +ATFamilyNewsListRes copyWith({ List? records, + num? total, + num? size, + num? current, +}) => ATFamilyNewsListRes( records: records ?? _records, + total: total ?? _total, + size: size ?? _size, + current: current ?? _current, +); + List? get records => _records; + num? get total => _total; + num? get size => _size; + num? get current => _current; + + Map toJson() { + final map = {}; + if (_records != null) { + map['records'] = _records?.map((v) => v.toJson()).toList(); + } + map['total'] = _total; + map['size'] = _size; + map['current'] = _current; + return map; + } + +} + +/// id : "" +/// newsType : "" +/// operatorId : "" +/// operatorName : "" +/// targetUserId : "" +/// targetUserName : "" +/// content : "" +/// createTime : "" + +class Records { + Records({ + String? id, + String? newsType, + String? operatorId, + String? operatorName, + String? targetUserId, + String? targetUserName, + String? content, + int? createTime,}){ + _id = id; + _newsType = newsType; + _operatorId = operatorId; + _operatorName = operatorName; + _targetUserId = targetUserId; + _targetUserName = targetUserName; + _content = content; + _createTime = createTime; +} + + Records.fromJson(dynamic json) { + _id = json['id']; + _newsType = json['newsType']; + _operatorId = json['operatorId']; + _operatorName = json['operatorName']; + _targetUserId = json['targetUserId']; + _targetUserName = json['targetUserName']; + _content = json['content']; + _createTime = json['createTime']; + } + String? _id; + String? _newsType; + String? _operatorId; + String? _operatorName; + String? _targetUserId; + String? _targetUserName; + String? _content; + int? _createTime; +Records copyWith({ String? id, + String? newsType, + String? operatorId, + String? operatorName, + String? targetUserId, + String? targetUserName, + String? content, + int? createTime, +}) => Records( id: id ?? _id, + newsType: newsType ?? _newsType, + operatorId: operatorId ?? _operatorId, + operatorName: operatorName ?? _operatorName, + targetUserId: targetUserId ?? _targetUserId, + targetUserName: targetUserName ?? _targetUserName, + content: content ?? _content, + createTime: createTime ?? _createTime, +); + String? get id => _id; + String? get newsType => _newsType; + String? get operatorId => _operatorId; + String? get operatorName => _operatorName; + String? get targetUserId => _targetUserId; + String? get targetUserName => _targetUserName; + String? get content => _content; + int? get createTime => _createTime; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['newsType'] = _newsType; + map['operatorId'] = _operatorId; + map['operatorName'] = _operatorName; + map['targetUserId'] = _targetUserId; + map['targetUserName'] = _targetUserName; + map['content'] = _content; + map['createTime'] = _createTime; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_game_ranking_res.dart b/lib/chatvibe_domain/models/res/at_game_ranking_res.dart new file mode 100644 index 0000000..96a8edb --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_game_ranking_res.dart @@ -0,0 +1,148 @@ +/// records : [{"rank":0,"gameId":"","gameName":"","gameCover":"","gameOrigin":"","sysOrigin":"","totalPrizeAmount":0,"periodType":""}] +/// total : 0 +/// size : 0 +/// current : 0 + +class ATGameRankingRes { + ATGameRankingRes({ + List? records, + num? total, + num? size, + num? current, + }) { + _records = records; + _total = total; + _size = size; + _current = current; + } + + ATGameRankingRes.fromJson(dynamic json) { + if (json['records'] != null) { + _records = []; + json['records'].forEach((v) { + _records?.add(Records.fromJson(v)); + }); + } + _total = json['total']; + _size = json['size']; + _current = json['current']; + } + + List? _records; + num? _total; + num? _size; + num? _current; + + List? get records => _records; + + num? get total => _total; + + num? get size => _size; + + num? get current => _current; + + Map toJson() { + final map = {}; + if (_records != null) { + map['records'] = _records?.map((v) => v.toJson()).toList(); + } + map['total'] = _total; + map['size'] = _size; + map['current'] = _current; + return map; + } +} + +/// rank : 0 +/// gameId : "" +/// gameName : "" +/// gameCover : "" +/// gameOrigin : "" +/// sysOrigin : "" +/// totalPrizeAmount : 0 +/// periodType : "" + +class Records { + Records({ + num? rank, + String? gameId, + String? gameName, + String? gameUrl, + String? gameCover, + String? gameOrigin, + String? sysOrigin, + bool? latest, + num? totalPrizeAmount, + String? periodType, + }) { + _rank = rank; + _latest = latest; + _gameId = gameId; + _gameName = gameName; + _gameUrl = gameUrl; + _gameCover = gameCover; + _gameOrigin = gameOrigin; + _sysOrigin = sysOrigin; + _totalPrizeAmount = totalPrizeAmount; + _periodType = periodType; + } + + Records.fromJson(dynamic json) { + _rank = json['rank']; + _latest = json['latest']; + _gameId = json['gameId']; + _gameName = json['gameName']; + _gameUrl = json['gameUrl']; + _gameCover = json['gameCover']; + _gameOrigin = json['gameOrigin']; + _sysOrigin = json['sysOrigin']; + _totalPrizeAmount = json['totalPrizeAmount']; + _periodType = json['periodType']; + } + + num? _rank; + bool? _latest; + String? _gameId; + String? _gameName; + String? _gameUrl; + String? _gameCover; + String? _gameOrigin; + String? _sysOrigin; + num? _totalPrizeAmount; + String? _periodType; + + num? get rank => _rank; + + bool? get latest => _latest; + + String? get gameId => _gameId; + + String? get gameName => _gameName; + + String? get gameUrl => _gameUrl; + + String? get gameCover => _gameCover; + + String? get gameOrigin => _gameOrigin; + + String? get sysOrigin => _sysOrigin; + + num? get totalPrizeAmount => _totalPrizeAmount; + + String? get periodType => _periodType; + + Map toJson() { + final map = {}; + map['rank'] = _rank; + map['latest'] = _latest; + map['gameId'] = _gameId; + map['gameName'] = _gameName; + map['gameUrl'] = _gameUrl; + map['gameCover'] = _gameCover; + map['gameOrigin'] = _gameOrigin; + map['sysOrigin'] = _sysOrigin; + map['totalPrizeAmount'] = _totalPrizeAmount; + map['periodType'] = _periodType; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/at_get_game_config_data.dart b/lib/chatvibe_domain/models/res/at_get_game_config_data.dart new file mode 100644 index 0000000..aca28ec --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_get_game_config_data.dart @@ -0,0 +1,148 @@ +/// appChannel : "2" +/// appId : 10 +/// code : "3232" +/// gameConfig : {"currencyIcon":"14","sceneMode":100} +/// gameMode : "3" +/// gsp : 101 +/// language : "2" +/// roomId : "4444" +/// userId : "eeew" + +class ATGetGameConfigData { + ATGetGameConfigData({ + String? appChannel, + num? appId, + String? code, + GameConfig? gameConfig, + String? gameMode, + num? gsp, + String? language, + String? roomId, + String? gameRoomId, + String? role, + String? userId,}){ + _appChannel = appChannel; + _appId = appId; + _code = code; + _gameConfig = gameConfig; + _gameMode = gameMode; + _gsp = gsp; + _language = language; + _roomId = roomId; + _gameRoomId = gameRoomId; + _userId = userId; + _role = role; +} + + ATGetGameConfigData.fromJson(dynamic json) { + _appChannel = json['appChannel']; + _appId = json['appId']; + _code = json['code']; + _gameConfig = json['gameConfig'] != null ? GameConfig.fromJson(json['gameConfig']) : null; + _gameMode = json['gameMode']; + _gsp = json['gsp']; + _language = json['language']; + _roomId = json['roomId']; + _gameRoomId = json['gameRoomId']; + _userId = json['userId']; + _role = json['role']; + } + String? _appChannel; + num? _appId; + String? _code; + GameConfig? _gameConfig; + String? _gameMode; + num? _gsp; + String? _language; + String? _roomId; + String? _gameRoomId; + String? _userId; + String? _role; +ATGetGameConfigData copyWith({ String? appChannel, + num? appId, + String? code, + GameConfig? gameConfig, + String? gameMode, + num? gsp, + String? language, + String? roomId, + String? gameRoomId, + String? userId, + String? role, +}) => ATGetGameConfigData( appChannel: appChannel ?? _appChannel, + appId: appId ?? _appId, + code: code ?? _code, + gameConfig: gameConfig ?? _gameConfig, + gameMode: gameMode ?? _gameMode, + gsp: gsp ?? _gsp, + language: language ?? _language, + roomId: roomId ?? _roomId, + gameRoomId: gameRoomId ?? _gameRoomId, + userId: userId ?? _userId, + role: role ?? _role, +); + String? get appChannel => _appChannel; + num? get appId => _appId; + String? get code => _code; + GameConfig? get gameConfig => _gameConfig; + String? get gameMode => _gameMode; + num? get gsp => _gsp; + String? get language => _language; + String? get roomId => _roomId; + String? get gameRoomId => _gameRoomId; + String? get userId => _userId; + String? get role => _role; + + Map toJson() { + final map = {}; + map['appChannel'] = _appChannel; + map['appId'] = _appId; + map['code'] = _code; + if (_gameConfig != null) { + map['gameConfig'] = _gameConfig?.toJson(); + } + map['gameMode'] = _gameMode; + map['gsp'] = _gsp; + map['language'] = _language; + map['roomId'] = _roomId; + map['gameRoomId'] = _gameRoomId; + map['userId'] = _userId; + map['role'] = _role; + return map; + } + +} + +/// currencyIcon : "14" +/// sceneMode : 100 + +class GameConfig { + GameConfig({ + String? currencyIcon, + num? sceneMode,}){ + _currencyIcon = currencyIcon; + _sceneMode = sceneMode; +} + + GameConfig.fromJson(dynamic json) { + _currencyIcon = json['currencyIcon']; + _sceneMode = json['sceneMode']; + } + String? _currencyIcon; + num? _sceneMode; +GameConfig copyWith({ String? currencyIcon, + num? sceneMode, +}) => GameConfig( currencyIcon: currencyIcon ?? _currencyIcon, + sceneMode: sceneMode ?? _sceneMode, +); + String? get currencyIcon => _currencyIcon; + num? get sceneMode => _sceneMode; + + Map toJson() { + final map = {}; + map['currencyIcon'] = _currencyIcon; + map['sceneMode'] = _sceneMode; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_get_list_game_config_res.dart b/lib/chatvibe_domain/models/res/at_get_list_game_config_res.dart new file mode 100644 index 0000000..9abb075 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_get_list_game_config_res.dart @@ -0,0 +1,156 @@ +/// id : 0 +/// name : "" +/// gameCode : "" +/// gameOrigin : "" +/// cover : "" +/// amounts : "" +/// height : 0.0 +/// width : 0.0 +/// fullScreen : false +/// clientOrigin : "" +/// regions : "" + +class ATGetListGameConfigRes { + ATGetListGameConfigRes({ + String? id, + String? name, + String? gameCode, + String? gameOrigin, + String? cover, + String? amounts, + String? category, + double? height, + double? width, + bool? fullScreen, + bool? latest, + String? clientOrigin, + String? gameMode, + String? regions, + }) { + _id = id; + _name = name; + _gameCode = gameCode; + _gameOrigin = gameOrigin; + _cover = cover; + _amounts = amounts; + _category = category; + _height = height; + _width = width; + _fullScreen = fullScreen; + _latest = latest; + _clientOrigin = clientOrigin; + _gameMode = gameMode; + _regions = regions; + } + + ATGetListGameConfigRes.fromJson(dynamic json) { + _id = json['id']; + _name = json['name']; + _gameCode = json['gameCode']; + _gameOrigin = json['gameOrigin']; + _cover = json['cover']; + _amounts = json['amounts']; + _category = json['category']; + _height = json['height']; + _width = json['width']; + _fullScreen = json['fullScreen']; + _latest = json['latest']; + _clientOrigin = json['clientOrigin']; + _gameMode = json['gameMode']; + _regions = json['regions']; + } + + String? _id; + String? _name; + String? _gameCode; + String? _gameOrigin; + String? _cover; + String? _amounts; + String? _category; + double? _height; + double? _width; + bool? _fullScreen; + bool? _latest; + String? _clientOrigin; + String? _gameMode; + String? _regions; + + ATGetListGameConfigRes copyWith({ + String? id, + String? name, + String? gameCode, + String? gameOrigin, + String? cover, + String? amounts, + String? category, + double? height, + double? width, + bool? fullScreen, + bool? latest, + String? clientOrigin, + String? gameMode, + String? regions, + }) => ATGetListGameConfigRes( + id: id ?? _id, + name: name ?? _name, + gameCode: gameCode ?? _gameCode, + gameOrigin: gameOrigin ?? _gameOrigin, + cover: cover ?? _cover, + amounts: amounts ?? _amounts, + category: category ?? _category, + height: height ?? _height, + width: width ?? _width, + fullScreen: fullScreen ?? _fullScreen, + latest: latest ?? _latest, + clientOrigin: clientOrigin ?? _clientOrigin, + gameMode: gameMode ?? _gameMode, + regions: regions ?? _regions, + ); + + String? get id => _id; + + String? get name => _name; + + String? get gameCode => _gameCode; + + String? get gameOrigin => _gameOrigin; + + String? get cover => _cover; + + String? get amounts => _amounts; + + String? get category => _category; + + double? get height => _height; + + double? get width => _width; + + bool? get fullScreen => _fullScreen; + + bool? get latest => _latest; + + String? get clientOrigin => _clientOrigin; + + String? get gameMode => _gameMode; + + String? get regions => _regions; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['name'] = _name; + map['gameCode'] = _gameCode; + map['gameOrigin'] = _gameOrigin; + map['cover'] = _cover; + map['amounts'] = _amounts; + map['category'] = _category; + map['height'] = _height; + map['width'] = _width; + map['fullScreen'] = _fullScreen; + map['latest'] = _latest; + map['clientOrigin'] = _clientOrigin; + map['gameMode'] = _gameMode; + map['regions'] = _regions; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/at_gold_record_res.dart b/lib/chatvibe_domain/models/res/at_gold_record_res.dart new file mode 100644 index 0000000..d2e2595 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_gold_record_res.dart @@ -0,0 +1,69 @@ +/// id : 0 +/// title : "" +/// type : 0 +/// quantity : 0.0 +/// createTime : "" + +class ATGoldRecordRes { + ATGoldRecordRes({ + String? id, + String? title, + num? type, + num? quantity, + int? createTime, + }) { + _id = id; + _title = title; + _type = type; + _quantity = quantity; + _createTime = createTime; + } + + ATGoldRecordRes.fromJson(dynamic json) { + _id = json['id']; + _title = json['title']; + _type = json['type']; + _quantity = json['quantity']; + _createTime = json['createTime']; + } + + String? _id; + String? _title; + num? _type; + num? _quantity; + int? _createTime; + + ATGoldRecordRes copyWith({ + String? id, + String? title, + num? type, + num? quantity, + int? createTime, + }) => ATGoldRecordRes( + id: id ?? _id, + title: title ?? _title, + type: type ?? _type, + quantity: quantity ?? _quantity, + createTime: createTime ?? _createTime, + ); + + String? get id => _id; + + String? get title => _title; + + num? get type => _type; + + num? get quantity => _quantity; + + int? get createTime => _createTime; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['title'] = _title; + map['type'] = _type; + map['quantity'] = _quantity; + map['createTime'] = _createTime; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/at_google_pay_res.dart b/lib/chatvibe_domain/models/res/at_google_pay_res.dart new file mode 100644 index 0000000..40259cb --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_google_pay_res.dart @@ -0,0 +1,51 @@ +/// productId : "" +/// productGroup : "" +/// obtainAmount : 0.0 +/// balanceAmount : 0.0 + +class ATGooglePayRes { + ATGooglePayRes({ + String? productId, + String? productGroup, + num? obtainAmount, + num? balanceAmount,}){ + _productId = productId; + _productGroup = productGroup; + _obtainAmount = obtainAmount; + _balanceAmount = balanceAmount; +} + + ATGooglePayRes.fromJson(dynamic json) { + _productId = json['productId']; + _productGroup = json['productGroup']; + _obtainAmount = json['obtainAmount']; + _balanceAmount = json['balanceAmount']; + } + String? _productId; + String? _productGroup; + num? _obtainAmount; + num? _balanceAmount; +ATGooglePayRes copyWith({ String? productId, + String? productGroup, + num? obtainAmount, + num? balanceAmount, +}) => ATGooglePayRes( productId: productId ?? _productId, + productGroup: productGroup ?? _productGroup, + obtainAmount: obtainAmount ?? _obtainAmount, + balanceAmount: balanceAmount ?? _balanceAmount, +); + String? get productId => _productId; + String? get productGroup => _productGroup; + num? get obtainAmount => _obtainAmount; + num? get balanceAmount => _balanceAmount; + + Map toJson() { + final map = {}; + map['productId'] = _productId; + map['productGroup'] = _productGroup; + map['obtainAmount'] = _obtainAmount; + map['balanceAmount'] = _balanceAmount; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_index_banner_res.dart b/lib/chatvibe_domain/models/res/at_index_banner_res.dart new file mode 100644 index 0000000..0487ff9 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_index_banner_res.dart @@ -0,0 +1,105 @@ +/// alertCover : "" +/// content : "" +/// cover : "" +/// displayPosition : "" +/// expiredTime : 0 +/// id : 0 +/// params : "" +/// smallCover : "" +/// sysOrigin : "" +/// type : "" + +class ATIndexBannerRes { + ATIndexBannerRes({ + String? alertCover, + String? content, + String? cover, + String? displayPosition, + num? expiredTime, + String? id, + String? params, + String? smallCover, + String? sysOrigin, + String? type,}){ + _alertCover = alertCover; + _content = content; + _cover = cover; + _displayPosition = displayPosition; + _expiredTime = expiredTime; + _id = id; + _params = params; + _smallCover = smallCover; + _sysOrigin = sysOrigin; + _type = type; +} + + ATIndexBannerRes.fromJson(dynamic json) { + _alertCover = json['alertCover']; + _content = json['content']; + _cover = json['cover']; + _displayPosition = json['displayPosition']; + _expiredTime = json['expiredTime']; + _id = json['id']; + _params = json['params']; + _smallCover = json['smallCover']; + _sysOrigin = json['sysOrigin']; + _type = json['type']; + } + String? _alertCover; + String? _content; + String? _cover; + String? _displayPosition; + num? _expiredTime; + String? _id; + String? _params; + String? _smallCover; + String? _sysOrigin; + String? _type; +ATIndexBannerRes copyWith({ String? alertCover, + String? content, + String? cover, + String? displayPosition, + num? expiredTime, + String? id, + String? params, + String? smallCover, + String? sysOrigin, + String? type, +}) => ATIndexBannerRes( alertCover: alertCover ?? _alertCover, + content: content ?? _content, + cover: cover ?? _cover, + displayPosition: displayPosition ?? _displayPosition, + expiredTime: expiredTime ?? _expiredTime, + id: id ?? _id, + params: params ?? _params, + smallCover: smallCover ?? _smallCover, + sysOrigin: sysOrigin ?? _sysOrigin, + type: type ?? _type, +); + String? get alertCover => _alertCover; + String? get content => _content; + String? get cover => _cover; + String? get displayPosition => _displayPosition; + num? get expiredTime => _expiredTime; + String? get id => _id; + String? get params => _params; + String? get smallCover => _smallCover; + String? get sysOrigin => _sysOrigin; + String? get type => _type; + + Map toJson() { + final map = {}; + map['alertCover'] = _alertCover; + map['content'] = _content; + map['cover'] = _cover; + map['displayPosition'] = _displayPosition; + map['expiredTime'] = _expiredTime; + map['id'] = _id; + map['params'] = _params; + map['smallCover'] = _smallCover; + map['sysOrigin'] = _sysOrigin; + map['type'] = _type; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_is_follow_room_res.dart b/lib/chatvibe_domain/models/res/at_is_follow_room_res.dart new file mode 100644 index 0000000..85a4025 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_is_follow_room_res.dart @@ -0,0 +1,33 @@ +/// joinRoom : false +/// followRoom : false + +class ATIsFollowRoomRes { + ATIsFollowRoomRes({ + bool? joinRoom, + bool? followRoom,}){ + _joinRoom = joinRoom; + _followRoom = followRoom; +} + + ATIsFollowRoomRes.fromJson(dynamic json) { + _joinRoom = json['joinRoom']; + _followRoom = json['followRoom']; + } + bool? _joinRoom; + bool? _followRoom; +ATIsFollowRoomRes copyWith({ bool? joinRoom, + bool? followRoom, +}) => ATIsFollowRoomRes( joinRoom: joinRoom ?? _joinRoom, + followRoom: followRoom ?? _followRoom, +); + bool? get joinRoom => _joinRoom; + bool? get followRoom => _followRoom; + + Map toJson() { + final map = {}; + map['joinRoom'] = _joinRoom; + map['followRoom'] = _followRoom; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_level_config_res.dart b/lib/chatvibe_domain/models/res/at_level_config_res.dart new file mode 100644 index 0000000..a382792 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_level_config_res.dart @@ -0,0 +1,180 @@ +/// charm : {"backCover":"","resources":[{"levelIcon":"","levelRange":"","levelSpecial":""}],"ribbon":""} +/// wealth : {"backCover":"","resources":[{"levelIcon":"","levelRange":"","levelSpecial":""}],"ribbon":""} + +class ATLevelConfigRes { + ATLevelConfigRes({ + Charm? charm, + Wealth? wealth,}){ + _charm = charm; + _wealth = wealth; +} + + ATLevelConfigRes.fromJson(dynamic json) { + _charm = json['charm'] != null ? Charm.fromJson(json['charm']) : null; + _wealth = json['wealth'] != null ? Wealth.fromJson(json['wealth']) : null; + } + Charm? _charm; + Wealth? _wealth; +ATLevelConfigRes copyWith({ Charm? charm, + Wealth? wealth, +}) => ATLevelConfigRes( charm: charm ?? _charm, + wealth: wealth ?? _wealth, +); + Charm? get charm => _charm; + Wealth? get wealth => _wealth; + + Map toJson() { + final map = {}; + if (_charm != null) { + map['charm'] = _charm?.toJson(); + } + if (_wealth != null) { + map['wealth'] = _wealth?.toJson(); + } + return map; + } + +} + +/// backCover : "" +/// resources : [{"levelIcon":"","levelRange":"","levelSpecial":""}] +/// ribbon : "" + +class Wealth { + Wealth({ + String? backCover, + List? resources, + String? ribbon,}){ + _backCover = backCover; + _resources = resources; + _ribbon = ribbon; +} + + Wealth.fromJson(dynamic json) { + _backCover = json['backCover']; + if (json['resources'] != null) { + _resources = []; + json['resources'].forEach((v) { + _resources?.add(Resources.fromJson(v)); + }); + } + _ribbon = json['ribbon']; + } + String? _backCover; + List? _resources; + String? _ribbon; +Wealth copyWith({ String? backCover, + List? resources, + String? ribbon, +}) => Wealth( backCover: backCover ?? _backCover, + resources: resources ?? _resources, + ribbon: ribbon ?? _ribbon, +); + String? get backCover => _backCover; + List? get resources => _resources; + String? get ribbon => _ribbon; + + Map toJson() { + final map = {}; + map['backCover'] = _backCover; + if (_resources != null) { + map['resources'] = _resources?.map((v) => v.toJson()).toList(); + } + map['ribbon'] = _ribbon; + return map; + } + +} + +/// levelIcon : "" +/// levelRange : "" +/// levelSpecial : "" + +class Resources { + Resources({ + String? levelIcon, + String? levelRange, + String? levelSpecial,}){ + _levelIcon = levelIcon; + _levelRange = levelRange; + _levelSpecial = levelSpecial; +} + + Resources.fromJson(dynamic json) { + _levelIcon = json['levelIcon']; + _levelRange = json['levelRange']; + _levelSpecial = json['levelSpecial']; + } + String? _levelIcon; + String? _levelRange; + String? _levelSpecial; +Resources copyWith({ String? levelIcon, + String? levelRange, + String? levelSpecial, +}) => Resources( levelIcon: levelIcon ?? _levelIcon, + levelRange: levelRange ?? _levelRange, + levelSpecial: levelSpecial ?? _levelSpecial, +); + String? get levelIcon => _levelIcon; + String? get levelRange => _levelRange; + String? get levelSpecial => _levelSpecial; + + Map toJson() { + final map = {}; + map['levelIcon'] = _levelIcon; + map['levelRange'] = _levelRange; + map['levelSpecial'] = _levelSpecial; + return map; + } + +} + +/// backCover : "" +/// resources : [{"levelIcon":"","levelRange":"","levelSpecial":""}] +/// ribbon : "" + +class Charm { + Charm({ + String? backCover, + List? resources, + String? ribbon,}){ + _backCover = backCover; + _resources = resources; + _ribbon = ribbon; +} + + Charm.fromJson(dynamic json) { + _backCover = json['backCover']; + if (json['resources'] != null) { + _resources = []; + json['resources'].forEach((v) { + _resources?.add(Resources.fromJson(v)); + }); + } + _ribbon = json['ribbon']; + } + String? _backCover; + List? _resources; + String? _ribbon; +Charm copyWith({ String? backCover, + List? resources, + String? ribbon, +}) => Charm( backCover: backCover ?? _backCover, + resources: resources ?? _resources, + ribbon: ribbon ?? _ribbon, +); + String? get backCover => _backCover; + List? get resources => _resources; + String? get ribbon => _ribbon; + + Map toJson() { + final map = {}; + map['backCover'] = _backCover; + if (_resources != null) { + map['resources'] = _resources?.map((v) => v.toJson()).toList(); + } + map['ribbon'] = _ribbon; + return map; + } + +} diff --git a/lib/chatvibe_domain/models/res/at_material_badge_res.dart b/lib/chatvibe_domain/models/res/at_material_badge_res.dart new file mode 100644 index 0000000..23d7b7e --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_material_badge_res.dart @@ -0,0 +1,192 @@ +/// badgeKey : "" +/// badgeTables : [{"activation":false,"badgeConfig":{"id":0,"badgeLevel":0,"milestone":0,"badgeName":"","type":"","badgeKey":"","selectUrl":"","notSelectUrl":"","animationUrl":""}}] +/// badgeTable : {"activation":false,"badgeConfig":{"id":0,"badgeLevel":0,"milestone":0,"badgeName":"","type":"","badgeKey":"","selectUrl":"","notSelectUrl":"","animationUrl":""}} + +class ATMaterialBadgeRes { + ATMaterialBadgeRes({ + String? badgeKey, + List? badgeTables,}){ + _badgeKey = badgeKey; + _badgeTables = badgeTables; +} + + ATMaterialBadgeRes.fromJson(dynamic json) { + _badgeKey = json['badgeKey']; + if (json['badgeTables'] != null) { + _badgeTables = []; + json['badgeTables'].forEach((v) { + _badgeTables?.add(BadgeTables.fromJson(v)); + }); + } + } + String? _badgeKey; + List? _badgeTables; +ATMaterialBadgeRes copyWith({ String? badgeKey, + List? badgeTables, +}) => ATMaterialBadgeRes( badgeKey: badgeKey ?? _badgeKey, + badgeTables: badgeTables ?? _badgeTables, +); + String? get badgeKey => _badgeKey; + List? get badgeTables => _badgeTables; + + Map toJson() { + final map = {}; + map['badgeKey'] = _badgeKey; + if (_badgeTables != null) { + map['badgeTables'] = _badgeTables?.map((v) => v.toJson()).toList(); + } + return map; + } + +} + +/// id : 0 +/// badgeLevel : 0 +/// milestone : 0 +/// badgeName : "" +/// type : "" +/// badgeKey : "" +/// selectUrl : "" +/// notSelectUrl : "" +/// animationUrl : "" + +class BadgeConfig { + BadgeConfig({ + String? id, + num? badgeLevel, + int? expireTime, + String? milestone, + String? badgeName, + String? type, + String? badgeKey, + String? selectUrl, + String? notSelectUrl, + String? animationUrl,}){ + _id = id; + _badgeLevel = badgeLevel; + _milestone = milestone; + _badgeName = badgeName; + _type = type; + _badgeKey = badgeKey; + _selectUrl = selectUrl; + _notSelectUrl = notSelectUrl; + _animationUrl = animationUrl; +} + + BadgeConfig.fromJson(dynamic json) { + _id = json['id']; + _badgeLevel = json['badgeLevel']; + _milestone = json['milestone']; + _badgeName = json['badgeName']; + _type = json['type']; + _badgeKey = json['badgeKey']; + _selectUrl = json['selectUrl']; + _notSelectUrl = json['notSelectUrl']; + _animationUrl = json['animationUrl']; + } + String? _id; + num? _badgeLevel; + String? _milestone; + String? _badgeName; + String? _type; + String? _badgeKey; + String? _selectUrl; + String? _notSelectUrl; + String? _animationUrl; +BadgeConfig copyWith({ String? id, + num? badgeLevel, + String? milestone, + int? expireTime, + String? badgeName, + String? type, + String? badgeKey, + String? selectUrl, + String? notSelectUrl, + String? animationUrl, +}) => BadgeConfig( id: id ?? _id, + badgeLevel: badgeLevel ?? _badgeLevel, + milestone: milestone ?? _milestone, + badgeName: badgeName ?? _badgeName, + type: type ?? _type, + badgeKey: badgeKey ?? _badgeKey, + selectUrl: selectUrl ?? _selectUrl, + notSelectUrl: notSelectUrl ?? _notSelectUrl, + animationUrl: animationUrl ?? _animationUrl, +); + String? get id => _id; + num? get badgeLevel => _badgeLevel; + String? get milestone => _milestone; + String? get badgeName => _badgeName; + String? get type => _type; + String? get badgeKey => _badgeKey; + String? get selectUrl => _selectUrl; + String? get notSelectUrl => _notSelectUrl; + String? get animationUrl => _animationUrl; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['badgeLevel'] = _badgeLevel; + map['milestone'] = _milestone; + map['badgeName'] = _badgeName; + map['type'] = _type; + map['badgeKey'] = _badgeKey; + map['selectUrl'] = _selectUrl; + map['notSelectUrl'] = _notSelectUrl; + map['animationUrl'] = _animationUrl; + return map; + } + +} + +/// activation : false +/// badgeConfig : {"id":0,"badgeLevel":0,"milestone":0,"badgeName":"","type":"","badgeKey":"","selectUrl":"","notSelectUrl":"","animationUrl":""} + +class BadgeTables { + BadgeTables({ + bool? activation, + int? expireTime, + BadgeConfig? badgeConfig,}){ + _activation = activation; + _expireTime = expireTime; + _badgeConfig = badgeConfig; +} + + BadgeTables.fromJson(dynamic json) { + _activation = json['activation']; + _expireTime = json['expireTime']; + _badgeConfig = json['badgeConfig'] != null ? BadgeConfig.fromJson(json['badgeConfig']) : null; + } + bool? _activation; + int? _expireTime; + BadgeConfig? _badgeConfig; +BadgeTables copyWith({ bool? activation, + BadgeConfig? badgeConfig, +}) => BadgeTables( activation: activation ?? _activation, + badgeConfig: badgeConfig ?? _badgeConfig, +); + bool? get activation => _activation; + int? get expireTime => _expireTime; + BadgeConfig? get badgeConfig => _badgeConfig; + + Map toJson() { + final map = {}; + map['activation'] = _activation; + map['expireTime'] = _expireTime; + if (_badgeConfig != null) { + map['badgeConfig'] = _badgeConfig?.toJson(); + } + return map; + } + +} + +/// id : 0 +/// badgeLevel : 0 +/// milestone : 0 +/// badgeName : "" +/// type : "" +/// badgeKey : "" +/// selectUrl : "" +/// notSelectUrl : "" +/// animationUrl : "" diff --git a/lib/chatvibe_domain/models/res/at_mic_go_up_res.dart b/lib/chatvibe_domain/models/res/at_mic_go_up_res.dart new file mode 100644 index 0000000..68fbfb3 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_mic_go_up_res.dart @@ -0,0 +1,353 @@ +/// micIndex : 0 +/// micLock : false +/// micMute : false +/// roomId : 0 +/// roomToken : "" +/// user : {"account":"","charmLevel":0,"heartbeatVal":0,"id":0,"ownSpecialId":{"account":"","expiredTime":0},"roles":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0} + +class ATMicGoUpRes { + ATMicGoUpRes({ + num? micIndex, + bool? micLock, + bool? micMute, + String? roomId, + String? roomToken, + User? user,}){ + _micIndex = micIndex; + _micLock = micLock; + _micMute = micMute; + _roomId = roomId; + _roomToken = roomToken; + _user = user; +} + + ATMicGoUpRes.fromJson(dynamic json) { + _micIndex = json['micIndex']; + _micLock = json['micLock']; + _micMute = json['micMute']; + _roomId = json['roomId']; + _roomToken = json['roomToken']; + _user = json['user'] != null ? User.fromJson(json['user']) : null; + } + num? _micIndex; + bool? _micLock; + bool? _micMute; + String? _roomId; + String? _roomToken; + User? _user; +ATMicGoUpRes copyWith({ num? micIndex, + bool? micLock, + bool? micMute, + String? roomId, + String? roomToken, + User? user, +}) => ATMicGoUpRes( micIndex: micIndex ?? _micIndex, + micLock: micLock ?? _micLock, + micMute: micMute ?? _micMute, + roomId: roomId ?? _roomId, + roomToken: roomToken ?? _roomToken, + user: user ?? _user, +); + num? get micIndex => _micIndex; + bool? get micLock => _micLock; + bool? get micMute => _micMute; + String? get roomId => _roomId; + String? get roomToken => _roomToken; + User? get user => _user; + + Map toJson() { + final map = {}; + map['micIndex'] = _micIndex; + map['micLock'] = _micLock; + map['micMute'] = _micMute; + map['roomId'] = _roomId; + map['roomToken'] = _roomToken; + if (_user != null) { + map['user'] = _user?.toJson(); + } + return map; + } + +} + +/// account : "" +/// charmLevel : 0 +/// heartbeatVal : 0 +/// id : 0 +/// ownSpecialId : {"account":"","expiredTime":0} +/// roles : "" +/// useProps : [{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}] +/// userAvatar : "" +/// userNickname : "" +/// userSex : 0 + +class User { + User({ + String? account, + num? charmLevel, + num? heartbeatVal, + String? id, + OwnSpecialId? ownSpecialId, + String? roles, + List? useProps, + String? userAvatar, + String? userNickname, + num? userSex,}){ + _account = account; + _charmLevel = charmLevel; + _heartbeatVal = heartbeatVal; + _id = id; + _ownSpecialId = ownSpecialId; + _roles = roles; + _useProps = useProps; + _userAvatar = userAvatar; + _userNickname = userNickname; + _userSex = userSex; +} + + User.fromJson(dynamic json) { + _account = json['account']; + _charmLevel = json['charmLevel']; + _heartbeatVal = json['heartbeatVal']; + _id = json['id']; + _ownSpecialId = json['ownSpecialId'] != null ? OwnSpecialId.fromJson(json['ownSpecialId']) : null; + _roles = json['roles']; + if (json['useProps'] != null) { + _useProps = []; + json['useProps'].forEach((v) { + _useProps?.add(UseProps.fromJson(v)); + }); + } + _userAvatar = json['userAvatar']; + _userNickname = json['userNickname']; + _userSex = json['userSex']; + } + String? _account; + num? _charmLevel; + num? _heartbeatVal; + String? _id; + OwnSpecialId? _ownSpecialId; + String? _roles; + List? _useProps; + String? _userAvatar; + String? _userNickname; + num? _userSex; +User copyWith({ String? account, + num? charmLevel, + num? heartbeatVal, + String? id, + OwnSpecialId? ownSpecialId, + String? roles, + List? useProps, + String? userAvatar, + String? userNickname, + num? userSex, +}) => User( account: account ?? _account, + charmLevel: charmLevel ?? _charmLevel, + heartbeatVal: heartbeatVal ?? _heartbeatVal, + id: id ?? _id, + ownSpecialId: ownSpecialId ?? _ownSpecialId, + roles: roles ?? _roles, + useProps: useProps ?? _useProps, + userAvatar: userAvatar ?? _userAvatar, + userNickname: userNickname ?? _userNickname, + userSex: userSex ?? _userSex, +); + String? get account => _account; + num? get charmLevel => _charmLevel; + num? get heartbeatVal => _heartbeatVal; + String? get id => _id; + OwnSpecialId? get ownSpecialId => _ownSpecialId; + String? get roles => _roles; + List? get useProps => _useProps; + String? get userAvatar => _userAvatar; + String? get userNickname => _userNickname; + num? get userSex => _userSex; + + Map toJson() { + final map = {}; + map['account'] = _account; + map['charmLevel'] = _charmLevel; + map['heartbeatVal'] = _heartbeatVal; + map['id'] = _id; + if (_ownSpecialId != null) { + map['ownSpecialId'] = _ownSpecialId?.toJson(); + } + map['roles'] = _roles; + if (_useProps != null) { + map['useProps'] = _useProps?.map((v) => v.toJson()).toList(); + } + map['userAvatar'] = _userAvatar; + map['userNickname'] = _userNickname; + map['userSex'] = _userSex; + return map; + } + +} + +/// expireTime : 0 +/// propsResources : {"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""} +/// userId : 0 + +class UseProps { + UseProps({ + String? expireTime, + PropsResources? propsResources, + String? userId,}){ + _expireTime = expireTime; + _propsResources = propsResources; + _userId = userId; +} + + UseProps.fromJson(dynamic json) { + _expireTime = json['expireTime']; + _propsResources = json['propsResources'] != null ? PropsResources.fromJson(json['propsResources']) : null; + _userId = json['userId']; + } + String? _expireTime; + PropsResources? _propsResources; + String? _userId; +UseProps copyWith({ String? expireTime, + PropsResources? propsResources, + String? userId, +}) => UseProps( expireTime: expireTime ?? _expireTime, + propsResources: propsResources ?? _propsResources, + userId: userId ?? _userId, +); + String? get expireTime => _expireTime; + PropsResources? get propsResources => _propsResources; + String? get userId => _userId; + + Map toJson() { + final map = {}; + map['expireTime'] = _expireTime; + if (_propsResources != null) { + map['propsResources'] = _propsResources?.toJson(); + } + map['userId'] = _userId; + return map; + } + +} + +/// amount : 0.0 +/// code : "" +/// cover : "" +/// expand : "" +/// id : 0 +/// name : "" +/// sourceUrl : "" +/// type : "" + +class PropsResources { + PropsResources({ + num? amount, + String? code, + String? cover, + String? expand, + num? id, + String? name, + String? sourceUrl, + String? type,}){ + _amount = amount; + _code = code; + _cover = cover; + _expand = expand; + _id = id; + _name = name; + _sourceUrl = sourceUrl; + _type = type; +} + + PropsResources.fromJson(dynamic json) { + _amount = json['amount']; + _code = json['code']; + _cover = json['cover']; + _expand = json['expand']; + _id = json['id']; + _name = json['name']; + _sourceUrl = json['sourceUrl']; + _type = json['type']; + } + num? _amount; + String? _code; + String? _cover; + String? _expand; + num? _id; + String? _name; + String? _sourceUrl; + String? _type; +PropsResources copyWith({ num? amount, + String? code, + String? cover, + String? expand, + num? id, + String? name, + String? sourceUrl, + String? type, +}) => PropsResources( amount: amount ?? _amount, + code: code ?? _code, + cover: cover ?? _cover, + expand: expand ?? _expand, + id: id ?? _id, + name: name ?? _name, + sourceUrl: sourceUrl ?? _sourceUrl, + type: type ?? _type, +); + num? get amount => _amount; + String? get code => _code; + String? get cover => _cover; + String? get expand => _expand; + num? get id => _id; + String? get name => _name; + String? get sourceUrl => _sourceUrl; + String? get type => _type; + + Map toJson() { + final map = {}; + map['amount'] = _amount; + map['code'] = _code; + map['cover'] = _cover; + map['expand'] = _expand; + map['id'] = _id; + map['name'] = _name; + map['sourceUrl'] = _sourceUrl; + map['type'] = _type; + return map; + } + +} + +/// account : "" +/// expiredTime : 0 + +class OwnSpecialId { + OwnSpecialId({ + String? account, + num? expiredTime,}){ + _account = account; + _expiredTime = expiredTime; +} + + OwnSpecialId.fromJson(dynamic json) { + _account = json['account']; + _expiredTime = json['expiredTime']; + } + String? _account; + num? _expiredTime; +OwnSpecialId copyWith({ String? account, + num? expiredTime, +}) => OwnSpecialId( account: account ?? _account, + expiredTime: expiredTime ?? _expiredTime, +); + String? get account => _account; + num? get expiredTime => _expiredTime; + + Map toJson() { + final map = {}; + map['account'] = _account; + map['expiredTime'] = _expiredTime; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_product_config_res.dart b/lib/chatvibe_domain/models/res/at_product_config_res.dart new file mode 100644 index 0000000..c03d766 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_product_config_res.dart @@ -0,0 +1,69 @@ +/// id : 0 +/// sysOrigin : "" +/// productPackage : "" +/// unitPrice : 0.0 +/// obtainCandy : 0.0 +/// rewardCandy : 0.0 + +class ATProductConfigRes { + ATProductConfigRes({ + String? id, + String? sysOrigin, + String? productPackage, + num? unitPrice, + num? obtainCandy, + num? rewardCandy,}){ + _id = id; + _sysOrigin = sysOrigin; + _productPackage = productPackage; + _unitPrice = unitPrice; + _obtainCandy = obtainCandy; + _rewardCandy = rewardCandy; +} + + ATProductConfigRes.fromJson(dynamic json) { + _id = json['id']; + _sysOrigin = json['sysOrigin']; + _productPackage = json['productPackage']; + _unitPrice = json['unitPrice']; + _obtainCandy = json['obtainCandy']; + _rewardCandy = json['rewardCandy']; + } + String? _id; + String? _sysOrigin; + String? _productPackage; + num? _unitPrice; + num? _obtainCandy; + num? _rewardCandy; +ATProductConfigRes copyWith({ String? id, + String? sysOrigin, + String? productPackage, + num? unitPrice, + num? obtainCandy, + num? rewardCandy, +}) => ATProductConfigRes( id: id ?? _id, + sysOrigin: sysOrigin ?? _sysOrigin, + productPackage: productPackage ?? _productPackage, + unitPrice: unitPrice ?? _unitPrice, + obtainCandy: obtainCandy ?? _obtainCandy, + rewardCandy: rewardCandy ?? _rewardCandy, +); + String? get id => _id; + String? get sysOrigin => _sysOrigin; + String? get productPackage => _productPackage; + num? get unitPrice => _unitPrice; + num? get obtainCandy => _obtainCandy; + num? get rewardCandy => _rewardCandy; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['sysOrigin'] = _sysOrigin; + map['productPackage'] = _productPackage; + map['unitPrice'] = _unitPrice; + map['obtainCandy'] = _obtainCandy; + map['rewardCandy'] = _rewardCandy; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_prop_coupon_list_res.dart b/lib/chatvibe_domain/models/res/at_prop_coupon_list_res.dart new file mode 100644 index 0000000..16f75df --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_prop_coupon_list_res.dart @@ -0,0 +1,177 @@ +/// records : [{"id":"0","couponNo":"","couponType":0,"couponTypeName":"","propId":0,"propName":"","propIcon":"","validDays":0,"status":0,"statusName":"","source":0,"sourceName":"","expireTime":"","useTime":"","createTime":""}] +/// total : 0 +/// size : 0 +/// current : 0 + +class ATPropCouponListRes { + ATPropCouponListRes({ + List? records, + num? total, + num? size, + num? current,}){ + _records = records; + _total = total; + _size = size; + _current = current; +} + + ATPropCouponListRes.fromJson(dynamic json) { + if (json['records'] != null) { + _records = []; + json['records'].forEach((v) { + _records?.add(Records.fromJson(v)); + }); + } + _total = json['total']; + _size = json['size']; + _current = json['current']; + } + List? _records; + num? _total; + num? _size; + num? _current; + + List? get records => _records; + num? get total => _total; + num? get size => _size; + num? get current => _current; + + Map toJson() { + final map = {}; + if (_records != null) { + map['records'] = _records?.map((v) => v.toJson()).toList(); + } + map['total'] = _total; + map['size'] = _size; + map['current'] = _current; + return map; + } + +} + +/// id : "0" +/// couponNo : "" +/// couponType : 0 +/// couponTypeName : "" +/// propId : 0 +/// propName : "" +/// propIcon : "" +/// validDays : 0 +/// status : 0 +/// statusName : "" +/// source : 0 +/// sourceName : "" +/// expireTime : "" +/// useTime : "" +/// createTime : "" + +class Records { + Records({ + num? id, + String? couponNo, + num? couponType, + String? couponTypeName, + num? propId, + String? propName, + String? propIcon, + num? validDays, + num? propDays, + num? status, + String? statusName, + num? source, + String? sourceName, + int? expireTime, + int? useTime, + int? createTime,}){ + _id = id; + _couponNo = couponNo; + _couponType = couponType; + _couponTypeName = couponTypeName; + _propId = propId; + _propName = propName; + _propIcon = propIcon; + _validDays = validDays; + _propDays = propDays; + _status = status; + _statusName = statusName; + _source = source; + _sourceName = sourceName; + _expireTime = expireTime; + _useTime = useTime; + _createTime = createTime; +} + + Records.fromJson(dynamic json) { + _id = json['id']; + _couponNo = json['couponNo']; + _couponType = json['couponType']; + _couponTypeName = json['couponTypeName']; + _propId = json['propId']; + _propName = json['propName']; + _propIcon = json['propIcon']; + _validDays = json['validDays']; + _propDays = json['propDays']; + _status = json['status']; + _statusName = json['statusName']; + _source = json['source']; + _sourceName = json['sourceName']; + _expireTime = json['expireTime']; + _useTime = json['useTime']; + _createTime = json['createTime']; + } + num? _id; + String? _couponNo; + num? _couponType; + String? _couponTypeName; + num? _propId; + String? _propName; + String? _propIcon; + num? _validDays; + num? _propDays; + num? _status; + String? _statusName; + num? _source; + String? _sourceName; + int? _expireTime; + int? _useTime; + int? _createTime; + + num? get id => _id; + String? get couponNo => _couponNo; + num? get couponType => _couponType; + String? get couponTypeName => _couponTypeName; + num? get propId => _propId; + String? get propName => _propName; + String? get propIcon => _propIcon; + num? get validDays => _validDays; + num? get propDays => _propDays; + num? get status => _status; + String? get statusName => _statusName; + num? get source => _source; + String? get sourceName => _sourceName; + int? get expireTime => _expireTime; + int? get useTime => _useTime; + int? get createTime => _createTime; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['couponNo'] = _couponNo; + map['couponType'] = _couponType; + map['couponTypeName'] = _couponTypeName; + map['propId'] = _propId; + map['propName'] = _propName; + map['propIcon'] = _propIcon; + map['validDays'] = _validDays; + map['propDays'] = _propDays; + map['status'] = _status; + map['statusName'] = _statusName; + map['source'] = _source; + map['sourceName'] = _sourceName; + map['expireTime'] = _expireTime; + map['useTime'] = _useTime; + map['createTime'] = _createTime; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_prop_coupon_record_list_res.dart b/lib/chatvibe_domain/models/res/at_prop_coupon_record_list_res.dart new file mode 100644 index 0000000..c2de2a5 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_prop_coupon_record_list_res.dart @@ -0,0 +1,179 @@ +/// records : [{"id":0,"couponNo":"","couponType":0,"couponTypeName":"","propId":0,"propName":"","validDays":0,"actionType":0,"actionTypeName":"","receiverId":0,"receiverNickname":"","remark":"","createTime":""}] +/// total : 0 +/// size : 0 +/// current : 0 + +class ATPropCouponRecordListRes { + ATPropCouponRecordListRes({ + List? records, + num? total, + num? size, + num? current,}){ + _records = records; + _total = total; + _size = size; + _current = current; +} + + ATPropCouponRecordListRes.fromJson(dynamic json) { + if (json['records'] != null) { + _records = []; + json['records'].forEach((v) { + _records?.add(Records.fromJson(v)); + }); + } + _total = json['total']; + _size = json['size']; + _current = json['current']; + } + List? _records; + num? _total; + num? _size; + num? _current; + + List? get records => _records; + num? get total => _total; + num? get size => _size; + num? get current => _current; + + Map toJson() { + final map = {}; + if (_records != null) { + map['records'] = _records?.map((v) => v.toJson()).toList(); + } + map['total'] = _total; + map['size'] = _size; + map['current'] = _current; + return map; + } + +} + +/// id : 0 +/// couponNo : "" +/// couponType : 0 +/// couponTypeName : "" +/// propId : 0 +/// propName : "" +/// validDays : 0 +/// actionType : 0 +/// actionTypeName : "" +/// receiverId : 0 +/// receiverNickname : "" +/// remark : "" +/// createTime : "" + +class Records { + Records({ + num? id, + String? couponNo, + String? propIcon, + num? couponType, + String? couponTypeName, + num? propId, + String? propName, + num? validDays, + num? propDays, + num? actionType, + String? actionTypeName, + num? receiverId, + String? receiverAccount, + String? receiverNickname, + String? remark, + int? createTime, + int? expireTime,}){ + _id = id; + _couponNo = couponNo; + _propIcon = propIcon; + _couponType = couponType; + _couponTypeName = couponTypeName; + _propId = propId; + _propName = propName; + _validDays = validDays; + _propDays = propDays; + _actionType = actionType; + _actionTypeName = actionTypeName; + _receiverId = receiverId; + _receiverAccount = receiverAccount; + _receiverNickname = receiverNickname; + _remark = remark; + _createTime = createTime; + _expireTime = expireTime; +} + + Records.fromJson(dynamic json) { + _id = json['id']; + _couponNo = json['couponNo']; + _propIcon = json['propIcon']; + _couponType = json['couponType']; + _couponTypeName = json['couponTypeName']; + _propId = json['propId']; + _propName = json['propName']; + _propDays = json['propDays']; + _actionType = json['actionType']; + _actionTypeName = json['actionTypeName']; + _receiverId = json['receiverId']; + _receiverAccount = json['receiverAccount']; + _receiverNickname = json['receiverNickname']; + _remark = json['remark']; + _createTime = json['createTime']; + _expireTime = json['expireTime']; + } + num? _id; + String? _couponNo; + String? _propIcon; + num? _couponType; + String? _couponTypeName; + num? _propId; + String? _propName; + num? _validDays; + num? _propDays; + num? _actionType; + String? _actionTypeName; + num? _receiverId; + String? _receiverAccount; + String? _receiverNickname; + String? _remark; + int? _createTime; + int? _expireTime; + + num? get id => _id; + String? get propIcon => _propIcon; + num? get couponType => _couponType; + String? get couponTypeName => _couponTypeName; + num? get propId => _propId; + String? get propName => _propName; + num? get validDays => _validDays; + num? get propDays => _propDays; + num? get actionType => _actionType; + String? get actionTypeName => _actionTypeName; + num? get receiverId => _receiverId; + String? get receiverAccount => _receiverAccount; + String? get receiverNickname => _receiverNickname; + String? get remark => _remark; + int? get createTime => _createTime; + int? get expireTime => _expireTime; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['couponNo'] = _couponNo; + map['propIcon'] = _propIcon; + map['couponType'] = _couponType; + map['couponTypeName'] = _couponTypeName; + map['propId'] = _propId; + map['propName'] = _propName; + map['validDays'] = _validDays; + map['propDays'] = _propDays; + map['actionType'] = _actionType; + map['actionTypeName'] = _actionTypeName; + map['receiverId'] = _receiverId; + map['receiverAccount'] = _receiverAccount; + map['receiverNickname'] = _receiverNickname; + map['remark'] = _remark; + map['createTime'] = _createTime; + map['expireTime'] = _expireTime; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_public_message_page_res.dart b/lib/chatvibe_domain/models/res/at_public_message_page_res.dart new file mode 100644 index 0000000..837aaae --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_public_message_page_res.dart @@ -0,0 +1,175 @@ +/// records : [{"id":"","messageId":"","type":"","title":"","content":"","imageUrl":"","extraData":{"":{}},"createdAt":0,"expiresAt":0,"status":""}] +/// total : 0 +/// size : 0 +/// current : 0 + +class ATPublicMessagePageRes { + ATPublicMessagePageRes({ + List? records, + num? total, + num? size, + num? current, + }) { + _records = records; + _total = total; + _size = size; + _current = current; + } + + ATPublicMessagePageRes.fromJson(dynamic json) { + if (json['records'] != null) { + _records = []; + json['records'].forEach((v) { + _records?.add(Records.fromJson(v)); + }); + } + _total = json['total']; + _size = json['size']; + _current = json['current']; + } + + List? _records; + num? _total; + num? _size; + num? _current; + + List? get records => _records; + + num? get total => _total; + + num? get size => _size; + + num? get current => _current; + + Map toJson() { + final map = {}; + if (_records != null) { + map['records'] = _records?.map((v) => v.toJson()).toList(); + } + map['total'] = _total; + map['size'] = _size; + map['current'] = _current; + return map; + } +} + +/// id : "" +/// messageId : "" +/// type : "" +/// title : "" +/// content : "" +/// imageUrl : "" +/// extraData : {"":{}} +/// createdAt : 0 +/// expiresAt : 0 +/// status : "" + +class Records { + Records({ + String? id, + String? messageId, + String? type, + String? title, + String? content, + String? imageUrl, + String? createdAt, + String? expiresAt, + String? status, + ExtraData? extraData, + }) { + _id = id; + _messageId = messageId; + _type = type; + _title = title; + _content = content; + _imageUrl = imageUrl; + _createdAt = createdAt; + _expiresAt = expiresAt; + _status = status; + _extraData = extraData; + } + + Records.fromJson(dynamic json) { + _id = json['id']; + _messageId = json['messageId']; + _type = json['type']; + _title = json['title']; + _content = json['content']; + _imageUrl = json['imageUrl']; + _createdAt = json['createdAt']; + _expiresAt = json['expiresAt']; + _status = json['status']; + _extraData = + json['extraData'] != null + ? ExtraData.fromJson(json['extraData']) + : null; + } + + String? _id; + String? _messageId; + String? _type; + String? _title; + String? _content; + String? _imageUrl; + String? _createdAt; + String? _expiresAt; + String? _status; + ExtraData? _extraData; + + String? get id => _id; + + String? get messageId => _messageId; + + String? get type => _type; + + String? get title => _title; + + String? get content => _content; + + String? get imageUrl => _imageUrl; + + String? get createdAt => _createdAt; + + String? get expiresAt => _expiresAt; + + String? get status => _status; + + ExtraData? get extraData => _extraData; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['messageId'] = _messageId; + map['type'] = _type; + map['title'] = _title; + map['content'] = _content; + map['imageUrl'] = _imageUrl; + map['createdAt'] = _createdAt; + map['expiresAt'] = _expiresAt; + map['status'] = _status; + if (_extraData != null) { + map['extraData'] = _extraData?.toJson(); + } + return map; + } +} + +class ExtraData { + ExtraData({String? link}) { + _link = link; + } + + String? _link; + + String? get link => _link; + + ExtraData.fromJson(dynamic json) { + _link = json['link']; + } + + Map toJson() { + final map = {}; + map['link'] = _link; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/at_room_contribute_level_res.dart b/lib/chatvibe_domain/models/res/at_room_contribute_level_res.dart new file mode 100644 index 0000000..6cf480e --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_room_contribute_level_res.dart @@ -0,0 +1,87 @@ +/// level : 0 +/// thatExperience : 0 +/// thatExperienceFormat : "" +/// nextExperience : 0 +/// nextExperienceFormat : "" + +class ATRoomContributeLevelRes { + ATRoomContributeLevelRes({ + num? level, + String? thatExperience, + String? thatExperienceFormat, + num? thisWeekAmount, + String? thisWeekIntegral, + String? nextExperience, + String? nextExperienceFormat, + }) { + _level = level; + _thatExperience = thatExperience; + _thatExperienceFormat = thatExperienceFormat; + _thisWeekAmount = thisWeekAmount; + _thisWeekIntegral = thisWeekIntegral; + _nextExperience = nextExperience; + _nextExperienceFormat = nextExperienceFormat; + } + + ATRoomContributeLevelRes.fromJson(dynamic json) { + _level = json['level']; + _thatExperience = json['thatExperience']; + _thatExperienceFormat = json['thatExperienceFormat']; + _thisWeekAmount = json['thisWeekAmount']; + _thisWeekIntegral = json['thisWeekIntegral']; + _nextExperience = json['nextExperience']; + _nextExperienceFormat = json['nextExperienceFormat']; + } + + num? _level; + num? _thisWeekAmount; + String? _thatExperience; + String? _thatExperienceFormat; + String? _thisWeekIntegral; + String? _nextExperience; + String? _nextExperienceFormat; + + ATRoomContributeLevelRes copyWith({ + num? level, + num? thisWeekAmount, + String? thatExperience, + String? thatExperienceFormat, + String? thisWeekIntegral, + String? nextExperience, + String? nextExperienceFormat, + }) => ATRoomContributeLevelRes( + level: level ?? _level, + thisWeekAmount: thisWeekAmount ?? _thisWeekAmount, + thatExperience: thatExperience ?? _thatExperience, + thatExperienceFormat: thatExperienceFormat ?? _thatExperienceFormat, + thisWeekIntegral: thisWeekIntegral ?? _thisWeekIntegral, + nextExperience: nextExperience ?? _nextExperience, + nextExperienceFormat: nextExperienceFormat ?? _nextExperienceFormat, + ); + + num? get level => _level; + + num? get thisWeekAmount => _thisWeekAmount; + + String? get thatExperience => _thatExperience; + + String? get thatExperienceFormat => _thatExperienceFormat; + + String? get thisWeekIntegral => _thisWeekIntegral; + + String? get nextExperience => _nextExperience; + + String? get nextExperienceFormat => _nextExperienceFormat; + + Map toJson() { + final map = {}; + map['level'] = _level; + map['thatExperience'] = _thatExperience; + map['thatExperienceFormat'] = _thatExperienceFormat; + map['thisWeekAmount'] = _thisWeekAmount; + map['thisWeekIntegral'] = _thisWeekIntegral; + map['nextExperience'] = _nextExperience; + map['nextExperienceFormat'] = _nextExperienceFormat; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/at_room_emoji_res.dart b/lib/chatvibe_domain/models/res/at_room_emoji_res.dart new file mode 100644 index 0000000..a3ca07e --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_room_emoji_res.dart @@ -0,0 +1,146 @@ +/// id : 0 +/// sysOrigin : "" +/// groupCode : "" +/// groupName : "" +/// cover : "" +/// have : false +/// amount : 0.0 +/// emojis : [{"sourceUrl":"","sourceType":0,"coverUrl":"","type":""}] + +class ATRoomEmojiRes { + ATRoomEmojiRes({ + String? id, + String? sysOrigin, + String? groupCode, + String? groupName, + String? cover, + bool? have, + num? amount, + List? emojis,}){ + _id = id; + _sysOrigin = sysOrigin; + _groupCode = groupCode; + _groupName = groupName; + _cover = cover; + _have = have; + _amount = amount; + _emojis = emojis; +} + + ATRoomEmojiRes.fromJson(dynamic json) { + _id = json['id']; + _sysOrigin = json['sysOrigin']; + _groupCode = json['groupCode']; + _groupName = json['groupName']; + _cover = json['cover']; + _have = json['have']; + _amount = json['amount']; + if (json['emojis'] != null) { + _emojis = []; + json['emojis'].forEach((v) { + _emojis?.add(Emojis.fromJson(v)); + }); + } + } + String? _id; + String? _sysOrigin; + String? _groupCode; + String? _groupName; + String? _cover; + bool? _have; + num? _amount; + List? _emojis; +ATRoomEmojiRes copyWith({ String? id, + String? sysOrigin, + String? groupCode, + String? groupName, + String? cover, + bool? have, + num? amount, + List? emojis, +}) => ATRoomEmojiRes( id: id ?? _id, + sysOrigin: sysOrigin ?? _sysOrigin, + groupCode: groupCode ?? _groupCode, + groupName: groupName ?? _groupName, + cover: cover ?? _cover, + have: have ?? _have, + amount: amount ?? _amount, + emojis: emojis ?? _emojis, +); + String? get id => _id; + String? get sysOrigin => _sysOrigin; + String? get groupCode => _groupCode; + String? get groupName => _groupName; + String? get cover => _cover; + bool? get have => _have; + num? get amount => _amount; + List? get emojis => _emojis; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['sysOrigin'] = _sysOrigin; + map['groupCode'] = _groupCode; + map['groupName'] = _groupName; + map['cover'] = _cover; + map['have'] = _have; + map['amount'] = _amount; + if (_emojis != null) { + map['emojis'] = _emojis?.map((v) => v.toJson()).toList(); + } + return map; + } + +} + +/// sourceUrl : "" +/// sourceType : 0 +/// coverUrl : "" +/// type : "" + +class Emojis { + Emojis({ + String? sourceUrl, + num? sourceType, + String? coverUrl, + String? type,}){ + _sourceUrl = sourceUrl; + _sourceType = sourceType; + _coverUrl = coverUrl; + _type = type; +} + + Emojis.fromJson(dynamic json) { + _sourceUrl = json['sourceUrl']; + _sourceType = json['sourceType']; + _coverUrl = json['coverUrl']; + _type = json['type']; + } + String? _sourceUrl; + num? _sourceType; + String? _coverUrl; + String? _type; +Emojis copyWith({ String? sourceUrl, + num? sourceType, + String? coverUrl, + String? type, +}) => Emojis( sourceUrl: sourceUrl ?? _sourceUrl, + sourceType: sourceType ?? _sourceType, + coverUrl: coverUrl ?? _coverUrl, + type: type ?? _type, +); + String? get sourceUrl => _sourceUrl; + num? get sourceType => _sourceType; + String? get coverUrl => _coverUrl; + String? get type => _type; + + Map toJson() { + final map = {}; + map['sourceUrl'] = _sourceUrl; + map['sourceType'] = _sourceType; + map['coverUrl'] = _coverUrl; + map['type'] = _type; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_room_join_black_list_res.dart b/lib/chatvibe_domain/models/res/at_room_join_black_list_res.dart new file mode 100644 index 0000000..225fdee --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_room_join_black_list_res.dart @@ -0,0 +1,42 @@ +/// roomId : 0 +/// roles : "" +/// remark : "" + +class ATRoomJoinBlackListRes { + ATRoomJoinBlackListRes({ + String? roomId, + String? roles, + String? remark,}){ + _roomId = roomId; + _roles = roles; + _remark = remark; +} + + ATRoomJoinBlackListRes.fromJson(dynamic json) { + _roomId = json['roomId']; + _roles = json['roles']; + _remark = json['remark']; + } + String? _roomId; + String? _roles; + String? _remark; +ATRoomJoinBlackListRes copyWith({ String? roomId, + String? roles, + String? remark, +}) => ATRoomJoinBlackListRes( roomId: roomId ?? _roomId, + roles: roles ?? _roles, + remark: remark ?? _remark, +); + String? get roomId => _roomId; + String? get roles => _roles; + String? get remark => _remark; + + Map toJson() { + final map = {}; + map['roomId'] = _roomId; + map['roles'] = _roles; + map['remark'] = _remark; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_room_red_packet_detail_res.dart b/lib/chatvibe_domain/models/res/at_room_red_packet_detail_res.dart new file mode 100644 index 0000000..b8410d4 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_room_red_packet_detail_res.dart @@ -0,0 +1,198 @@ +/// packetId : "" +/// userId : 0 +/// userName : "" +/// userAvatar : "" +/// packetType : 0 +/// packetTypeDesc : "" +/// totalAmount : 0 +/// totalCount : 0 +/// remainAmount : 0 +/// remainCount : 0 +/// status : 0 +/// statusDesc : "" +/// myAmount : 0 +/// grabbed : false +/// records : [{"recordId":"","userId":0,"userName":"","userAvatar":"","amount":0,"grabTime":""}] +/// createTime : "" + +class ATRoomRedPacketDetailRes { + ATRoomRedPacketDetailRes({ + String? packetId, + num? userId, + String? userName, + String? userAvatar, + num? packetType, + String? packetTypeDesc, + num? totalAmount, + num? totalCount, + num? remainAmount, + num? remainCount, + num? status, + String? statusDesc, + num? myAmount, + bool? grabbed, + List? records, + String? createTime,}){ + _packetId = packetId; + _userId = userId; + _userName = userName; + _userAvatar = userAvatar; + _packetType = packetType; + _packetTypeDesc = packetTypeDesc; + _totalAmount = totalAmount; + _totalCount = totalCount; + _remainAmount = remainAmount; + _remainCount = remainCount; + _status = status; + _statusDesc = statusDesc; + _myAmount = myAmount; + _grabbed = grabbed; + _records = records; + _createTime = createTime; +} + + ATRoomRedPacketDetailRes.fromJson(dynamic json) { + _packetId = json['packetId']; + _userId = json['userId']; + _userName = json['userName']; + _userAvatar = json['userAvatar']; + _packetType = json['packetType']; + _packetTypeDesc = json['packetTypeDesc']; + _totalAmount = json['totalAmount']; + _totalCount = json['totalCount']; + _remainAmount = json['remainAmount']; + _remainCount = json['remainCount']; + _status = json['status']; + _statusDesc = json['statusDesc']; + _myAmount = json['myAmount']; + _grabbed = json['grabbed']; + if (json['records'] != null) { + _records = []; + json['records'].forEach((v) { + _records?.add(Records.fromJson(v)); + }); + } + _createTime = json['createTime']; + } + String? _packetId; + num? _userId; + String? _userName; + String? _userAvatar; + num? _packetType; + String? _packetTypeDesc; + num? _totalAmount; + num? _totalCount; + num? _remainAmount; + num? _remainCount; + num? _status; + String? _statusDesc; + num? _myAmount; + bool? _grabbed; + List? _records; + String? _createTime; + + String? get packetId => _packetId; + num? get userId => _userId; + String? get userName => _userName; + String? get userAvatar => _userAvatar; + num? get packetType => _packetType; + String? get packetTypeDesc => _packetTypeDesc; + num? get totalAmount => _totalAmount; + num? get totalCount => _totalCount; + num? get remainAmount => _remainAmount; + num? get remainCount => _remainCount; + num? get status => _status; + String? get statusDesc => _statusDesc; + num? get myAmount => _myAmount; + bool? get grabbed => _grabbed; + List? get records => _records; + String? get createTime => _createTime; + + Map toJson() { + final map = {}; + map['packetId'] = _packetId; + map['userId'] = _userId; + map['userName'] = _userName; + map['userAvatar'] = _userAvatar; + map['packetType'] = _packetType; + map['packetTypeDesc'] = _packetTypeDesc; + map['totalAmount'] = _totalAmount; + map['totalCount'] = _totalCount; + map['remainAmount'] = _remainAmount; + map['remainCount'] = _remainCount; + map['status'] = _status; + map['statusDesc'] = _statusDesc; + map['myAmount'] = _myAmount; + map['grabbed'] = _grabbed; + if (_records != null) { + map['records'] = _records?.map((v) => v.toJson()).toList(); + } + map['createTime'] = _createTime; + return map; + } + +} + +/// recordId : "" +/// userId : 0 +/// userName : "" +/// userAvatar : "" +/// amount : 0 +/// grabTime : "" + +class Records { + Records({ + String? recordId, + num? userId, + String? userName, + String? account, + String? userAvatar, + num? amount, + String? grabTime,}){ + _recordId = recordId; + _userId = userId; + _userName = userName; + _account = account; + _userAvatar = userAvatar; + _amount = amount; + _grabTime = grabTime; +} + + Records.fromJson(dynamic json) { + _recordId = json['recordId']; + _userId = json['userId']; + _userName = json['userName']; + _account = json['account']; + _userAvatar = json['userAvatar']; + _amount = json['amount']; + _grabTime = json['grabTime']; + } + String? _recordId; + num? _userId; + String? _userName; + String? _account; + String? _userAvatar; + num? _amount; + String? _grabTime; + + String? get recordId => _recordId; + num? get userId => _userId; + String? get userName => _userName; + String? get account => _account; + String? get userAvatar => _userAvatar; + num? get amount => _amount; + String? get grabTime => _grabTime; + + Map toJson() { + final map = {}; + map['recordId'] = _recordId; + map['userId'] = _userId; + map['userName'] = _userName; + map['account'] = _account; + map['userAvatar'] = _userAvatar; + map['amount'] = _amount; + map['grabTime'] = _grabTime; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_room_red_packet_grab_res.dart b/lib/chatvibe_domain/models/res/at_room_red_packet_grab_res.dart new file mode 100644 index 0000000..d4f0aa7 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_room_red_packet_grab_res.dart @@ -0,0 +1,50 @@ +/// packetId : "" +/// amount : 0 +/// grabTime : "" +/// remainCount : 0 +/// totalCount : 0 + +class ATRoomRedPacketGrabRes { + ATRoomRedPacketGrabRes({ + String? packetId, + num? amount, + String? grabTime, + num? remainCount, + num? totalCount,}){ + _packetId = packetId; + _amount = amount; + _grabTime = grabTime; + _remainCount = remainCount; + _totalCount = totalCount; +} + + ATRoomRedPacketGrabRes.fromJson(dynamic json) { + _packetId = json['packetId']; + _amount = json['amount']; + _grabTime = json['grabTime']; + _remainCount = json['remainCount']; + _totalCount = json['totalCount']; + } + String? _packetId; + num? _amount; + String? _grabTime; + num? _remainCount; + num? _totalCount; + + String? get packetId => _packetId; + num? get amount => _amount; + String? get grabTime => _grabTime; + num? get remainCount => _remainCount; + num? get totalCount => _totalCount; + + Map toJson() { + final map = {}; + map['packetId'] = _packetId; + map['amount'] = _amount; + map['grabTime'] = _grabTime; + map['remainCount'] = _remainCount; + map['totalCount'] = _totalCount; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_room_red_packet_list_res.dart b/lib/chatvibe_domain/models/res/at_room_red_packet_list_res.dart new file mode 100644 index 0000000..3d1e93d --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_room_red_packet_list_res.dart @@ -0,0 +1,167 @@ +/// packetId : "" +/// roomId : 0 +/// userId : 0 +/// userName : "" +/// userAvatar : "" +/// packetType : 0 +/// packetTypeDesc : "" +/// sourceType : 0 +/// totalAmount : 0 +/// totalCount : 0 +/// remainAmount : 0 +/// remainCount : 0 +/// expireMinutes : 0 +/// expireTime : "" +/// countdown : 0 +/// status : 0 +/// statusDesc : "" +/// grabbed : false +/// createTime : "" + +class ATRoomRedPacketListRes { + ATRoomRedPacketListRes({ + String? packetId, + String? roomId, + num? userId, + String? userName, + String? userAvatar, + num? packetType, + String? packetTypeDesc, + num? sourceType, + num? totalAmount, + num? totalCount, + num? remainAmount, + num? remainCount, + num? expireMinutes, + String? expireTime, + int? countdown, + num? status, + String? statusDesc, + bool? grabbed, + String? createTime, + }) { + _packetId = packetId; + _roomId = roomId; + _userId = userId; + _userName = userName; + _userAvatar = userAvatar; + _packetType = packetType; + _packetTypeDesc = packetTypeDesc; + _sourceType = sourceType; + _totalAmount = totalAmount; + _totalCount = totalCount; + _remainAmount = remainAmount; + _remainCount = remainCount; + _expireMinutes = expireMinutes; + _expireTime = expireTime; + _countdown = countdown; + _status = status; + _statusDesc = statusDesc; + _grabbed = grabbed; + _createTime = createTime; + } + + ATRoomRedPacketListRes.fromJson(dynamic json) { + _packetId = json['packetId']; + _roomId = json['roomId']; + _userId = json['userId']; + _userName = json['userName']; + _userAvatar = json['userAvatar']; + _packetType = json['packetType']; + _packetTypeDesc = json['packetTypeDesc']; + _sourceType = json['sourceType']; + _totalAmount = json['totalAmount']; + _totalCount = json['totalCount']; + _remainAmount = json['remainAmount']; + _remainCount = json['remainCount']; + _expireMinutes = json['expireMinutes']; + _expireTime = json['expireTime']; + _countdown = json['countdown']; + _status = json['status']; + _statusDesc = json['statusDesc']; + _grabbed = json['grabbed']; + _createTime = json['createTime']; + } + + String? _packetId; + String? _roomId; + num? _userId; + String? _userName; + String? _userAvatar; + num? _packetType; + String? _packetTypeDesc; + num? _sourceType; + num? _totalAmount; + num? _totalCount; + num? _remainAmount; + num? _remainCount; + num? _expireMinutes; + String? _expireTime; + int? _countdown; + num? _status; + String? _statusDesc; + bool? _grabbed; + String? _createTime; + + String? get packetId => _packetId; + + String? get roomId => _roomId; + + num? get userId => _userId; + + String? get userName => _userName; + + String? get userAvatar => _userAvatar; + + num? get packetType => _packetType; + + String? get packetTypeDesc => _packetTypeDesc; + + num? get sourceType => _sourceType; + + num? get totalAmount => _totalAmount; + + num? get totalCount => _totalCount; + + num? get remainAmount => _remainAmount; + + num? get remainCount => _remainCount; + + num? get expireMinutes => _expireMinutes; + + String? get expireTime => _expireTime; + + int? get countdown => _countdown; + + num? get status => _status; + + String? get statusDesc => _statusDesc; + + bool? get grabbed => _grabbed; + + String? get createTime => _createTime; + + Map toJson() { + final map = {}; + map['packetId'] = _packetId; + map['roomId'] = _roomId; + map['userId'] = _userId; + map['userName'] = _userName; + map['userAvatar'] = _userAvatar; + map['packetType'] = _packetType; + map['packetTypeDesc'] = _packetTypeDesc; + map['sourceType'] = _sourceType; + map['totalAmount'] = _totalAmount; + map['totalCount'] = _totalCount; + map['remainAmount'] = _remainAmount; + map['remainCount'] = _remainCount; + map['expireMinutes'] = _expireMinutes; + map['expireTime'] = _expireTime; + map['countdown'] = _countdown; + map['status'] = _status; + map['statusDesc'] = _statusDesc; + map['grabbed'] = _grabbed; + map['createTime'] = _createTime; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/at_room_reward_info_res.dart b/lib/chatvibe_domain/models/res/at_room_reward_info_res.dart new file mode 100644 index 0000000..0545ce4 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_room_reward_info_res.dart @@ -0,0 +1,142 @@ +/// countdown : 0 +/// currentProgress : {"amount":0,"level":0} +/// lastWeekProgress : {"amount":0,"level":0,"ownerIncome":0,"ownerIncomeReceived":false,"rewardCoins":0,"rewardCoinsSent":false} + +class ATRoomRewardInfoRes { + ATRoomRewardInfoRes({ + int? countdown, + CurrentProgress? currentProgress, + LastWeekProgress? lastWeekProgress, + }) { + _countdown = countdown; + _currentProgress = currentProgress; + _lastWeekProgress = lastWeekProgress; + } + + ATRoomRewardInfoRes.fromJson(dynamic json) { + _countdown = json['countdown']; + _currentProgress = + json['currentProgress'] != null + ? CurrentProgress.fromJson(json['currentProgress']) + : null; + _lastWeekProgress = + json['lastWeekProgress'] != null + ? LastWeekProgress.fromJson(json['lastWeekProgress']) + : null; + } + + int? _countdown; + CurrentProgress? _currentProgress; + LastWeekProgress? _lastWeekProgress; + + int? get countdown => _countdown; + + CurrentProgress? get currentProgress => _currentProgress; + + LastWeekProgress? get lastWeekProgress => _lastWeekProgress; + + Map toJson() { + final map = {}; + map['countdown'] = _countdown; + if (_currentProgress != null) { + map['currentProgress'] = _currentProgress?.toJson(); + } + if (_lastWeekProgress != null) { + map['lastWeekProgress'] = _lastWeekProgress?.toJson(); + } + return map; + } +} + +/// amount : 0 +/// level : 0 +/// ownerIncome : 0 +/// ownerIncomeReceived : false +/// rewardCoins : 0 +/// rewardCoinsSent : false + +class LastWeekProgress { + LastWeekProgress({ + num? amount, + num? level, + num? ownerIncome, + bool? ownerIncomeReceived, + num? rewardCoins, + bool? rewardCoinsSent, + }) { + _amount = amount; + _level = level; + _ownerIncome = ownerIncome; + _ownerIncomeReceived = ownerIncomeReceived; + _rewardCoins = rewardCoins; + _rewardCoinsSent = rewardCoinsSent; + } + + LastWeekProgress.fromJson(dynamic json) { + _amount = json['amount']; + _level = json['level']; + _ownerIncome = json['ownerIncome']; + _ownerIncomeReceived = json['ownerIncomeReceived']; + _rewardCoins = json['rewardCoins']; + _rewardCoinsSent = json['rewardCoinsSent']; + } + + num? _amount; + num? _level; + num? _ownerIncome; + bool? _ownerIncomeReceived; + num? _rewardCoins; + bool? _rewardCoinsSent; + + num? get amount => _amount; + + num? get level => _level; + + num? get ownerIncome => _ownerIncome; + + bool? get ownerIncomeReceived => _ownerIncomeReceived; + + num? get rewardCoins => _rewardCoins; + + bool? get rewardCoinsSent => _rewardCoinsSent; + + Map toJson() { + final map = {}; + map['amount'] = _amount; + map['level'] = _level; + map['ownerIncome'] = _ownerIncome; + map['ownerIncomeReceived'] = _ownerIncomeReceived; + map['rewardCoins'] = _rewardCoins; + map['rewardCoinsSent'] = _rewardCoinsSent; + return map; + } +} + +/// amount : 0 +/// level : 0 + +class CurrentProgress { + CurrentProgress({num? amount, num? level}) { + _amount = amount; + _level = level; + } + + CurrentProgress.fromJson(dynamic json) { + _amount = json['amount']; + _level = json['level']; + } + + num? _amount; + num? _level; + + num? get amount => _amount; + + num? get level => _level; + + Map toJson() { + final map = {}; + map['amount'] = _amount; + map['level'] = _level; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/at_room_rocket_config_res.dart b/lib/chatvibe_domain/models/res/at_room_rocket_config_res.dart new file mode 100644 index 0000000..11ca626 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_room_rocket_config_res.dart @@ -0,0 +1,186 @@ +/// enabled : true +/// id : 3 +/// level : 3 +/// levelName : "三级火箭" +/// maxEnergy : 1000000 +/// normalRewardConfig : [] +/// rewardConfig : [{"amount":1969014438069481474,"content":"1969014438069481474","cover":"https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/svga_cover/manager-5058b89a-8654-4895-866e-fe4295e88408.png","createTime":1758286290000,"detailType":"AVATAR_FRAME","groupId":1966024426660499457,"id":1969021529953873921,"name":"atu-KingTOP1头饰","quantity":7,"remark":"","sort":1,"sourceUrl":"https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/svgasource/manager-975e24c5-e87a-4236-9256-799c127a06bc.svga","type":"PROPS","updateTime":1758286290000},{"amount":1969014872674873345,"badgeName":"atu-King'Medal","content":"1969014872674873345","cover":"https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/other/manager-fc5ad0a4-5e4a-498b-8494-2aee2efe5b99.png","createTime":1758286290000,"detailType":"BADGE","groupId":1966024426660499457,"id":1969021529953873922,"quantity":7,"remark":"","sort":2,"sourceUrl":"https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/other/manager-176fcf7e-787b-4bf6-bdb4-238d8388a5e2.svga","type":"BADGE","updateTime":1758286290000},{"amount":30000,"content":"30000","createTime":1758286290000,"detailType":"GOLD","groupId":1966024426660499457,"id":1969021529953873923,"quantity":0,"remark":"","sort":3,"type":"GOLD","updateTime":1758286290000},{"amount":1962463978629165057,"content":"1962463978629165057","cover":"https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/svga_cover/manager-6633f4cb-58b8-4b7d-8075-7f0a6f29fae0.png","createTime":1758286290000,"detailType":"RIDE","groupId":1966024426660499457,"id":1969021529953873924,"name":"后续活动座驾","quantity":7,"remark":"","sort":4,"sourceUrl":"https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/svgasource/manager-536ac482-8e9d-4a21-acff-531d51ff5b66.mp4","type":"PROPS","updateTime":1758286290000}] +/// status : 1 + +class ATRoomRocketConfigRes { + ATRoomRocketConfigRes({ + bool? enabled, + String? id, + num? level, + String? levelName, + int? maxEnergy, + List? rewardConfig, + num? status,}){ + _enabled = enabled; + _id = id; + _level = level; + _levelName = levelName; + _maxEnergy = maxEnergy; + _rewardConfig = rewardConfig; + _status = status; +} + + ATRoomRocketConfigRes.fromJson(dynamic json) { + _enabled = json['enabled']; + _id = json['id']; + _level = json['level']; + _levelName = json['levelName']; + _maxEnergy = json['maxEnergy']; + if (json['rewardConfig'] != null) { + _rewardConfig = []; + json['rewardConfig'].forEach((v) { + _rewardConfig?.add(RewardConfig.fromJson(v)); + }); + } + _status = json['status']; + } + bool? _enabled; + String? _id; + num? _level; + String? _levelName; + int? _maxEnergy; + List? _rewardConfig; + num? _status; + + bool? get enabled => _enabled; + String? get id => _id; + num? get level => _level; + String? get levelName => _levelName; + int? get maxEnergy => _maxEnergy; + List? get rewardConfig => _rewardConfig; + num? get status => _status; + + Map toJson() { + final map = {}; + map['enabled'] = _enabled; + map['id'] = _id; + map['level'] = _level; + map['levelName'] = _levelName; + map['maxEnergy'] = _maxEnergy; + if (_rewardConfig != null) { + map['rewardConfig'] = _rewardConfig?.map((v) => v.toJson()).toList(); + } + map['status'] = _status; + return map; + } + +} + +/// amount : 1969014438069481474 +/// content : "1969014438069481474" +/// cover : "https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/svga_cover/manager-5058b89a-8654-4895-866e-fe4295e88408.png" +/// createTime : 1758286290000 +/// detailType : "AVATAR_FRAME" +/// groupId : 1966024426660499457 +/// id : 1969021529953873921 +/// name : "atu-KingTOP1头饰" +/// quantity : 7 +/// remark : "" +/// sort : 1 +/// sourceUrl : "https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/svgasource/manager-975e24c5-e87a-4236-9256-799c127a06bc.svga" +/// type : "PROPS" +/// updateTime : 1758286290000 + +class RewardConfig { + RewardConfig({ + num? amount, + String? content, + String? cover, + num? createTime, + String? detailType, + String? groupId, + String? id, + String? name, + num? quantity, + String? remark, + num? sort, + String? sourceUrl, + String? type, + num? updateTime,}){ + _amount = amount; + _content = content; + _cover = cover; + _createTime = createTime; + _detailType = detailType; + _groupId = groupId; + _id = id; + _name = name; + _quantity = quantity; + _remark = remark; + _sort = sort; + _sourceUrl = sourceUrl; + _type = type; + _updateTime = updateTime; +} + + RewardConfig.fromJson(dynamic json) { + _amount = json['amount']; + _content = json['content']; + _cover = json['cover']; + _createTime = json['createTime']; + _detailType = json['detailType']; + _groupId = json['groupId']; + _id = json['id']; + _name = json['name']; + _quantity = json['quantity']; + _remark = json['remark']; + _sort = json['sort']; + _sourceUrl = json['sourceUrl']; + _type = json['type']; + _updateTime = json['updateTime']; + } + num? _amount; + String? _content; + String? _cover; + num? _createTime; + String? _detailType; + String? _groupId; + String? _id; + String? _name; + num? _quantity; + String? _remark; + num? _sort; + String? _sourceUrl; + String? _type; + num? _updateTime; + + num? get amount => _amount; + String? get content => _content; + String? get cover => _cover; + num? get createTime => _createTime; + String? get detailType => _detailType; + String? get groupId => _groupId; + String? get id => _id; + String? get name => _name; + num? get quantity => _quantity; + String? get remark => _remark; + num? get sort => _sort; + String? get sourceUrl => _sourceUrl; + String? get type => _type; + num? get updateTime => _updateTime; + + Map toJson() { + final map = {}; + map['amount'] = _amount; + map['content'] = _content; + map['cover'] = _cover; + map['createTime'] = _createTime; + map['detailType'] = _detailType; + map['groupId'] = _groupId; + map['id'] = _id; + map['name'] = _name; + map['quantity'] = _quantity; + map['remark'] = _remark; + map['sort'] = _sort; + map['sourceUrl'] = _sourceUrl; + map['type'] = _type; + map['updateTime'] = _updateTime; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_room_rocket_status_res.dart b/lib/chatvibe_domain/models/res/at_room_rocket_status_res.dart new file mode 100644 index 0000000..5f4f04a --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_room_rocket_status_res.dart @@ -0,0 +1,145 @@ +/// roomId : 0 +/// date : "" +/// level : 0 +/// levelName : "" +/// currentEnergy : 0 +/// maxEnergy : 0 +/// energyPercent : 0.0 +/// status : "" +/// statusDesc : "" +/// totalContributors : 0 +/// launchTime : "" +/// claimExpireTime : "" +/// claimCount : 0 +/// canClaim : false +/// hasClaimed : false +/// myContribution : 0 +/// myContributionRate : 0.0 + +class ATRoomRocketStatusRes { + ATRoomRocketStatusRes({ + String? roomId, + String? date, + num? level, + String? levelName, + int? currentEnergy, + int? maxEnergy, + num? energyPercent, + String? status, + String? statusDesc, + num? totalContributors, + String? launchTime, + String? claimExpireTime, + num? claimCount, + bool? canClaim, + bool? hasClaimed, + num? myContribution, + num? myContributionRate, + }) { + _roomId = roomId; + _date = date; + _level = level; + _levelName = levelName; + _currentEnergy = currentEnergy; + _maxEnergy = maxEnergy; + _energyPercent = energyPercent; + _status = status; + _statusDesc = statusDesc; + _totalContributors = totalContributors; + _claimExpireTime = claimExpireTime; + _claimCount = claimCount; + _canClaim = canClaim; + _hasClaimed = hasClaimed; + _myContribution = myContribution; + _myContributionRate = myContributionRate; + } + + ATRoomRocketStatusRes.fromJson(dynamic json) { + _roomId = json['roomId']; + _date = json['date']; + _level = json['level']; + _levelName = json['levelName']; + _currentEnergy = json['currentEnergy']; + _maxEnergy = json['maxEnergy']; + _energyPercent = json['energyPercent']; + _status = json['status']; + _statusDesc = json['statusDesc']; + _totalContributors = json['totalContributors']; + _claimExpireTime = json['claimExpireTime']; + _claimCount = json['claimCount']; + _canClaim = json['canClaim']; + _hasClaimed = json['hasClaimed']; + _myContribution = json['myContribution']; + _myContributionRate = json['myContributionRate']; + } + + String? _roomId; + String? _date; + num? _level; + String? _levelName; + int? _currentEnergy; + int? _maxEnergy; + num? _energyPercent; + String? _status; + String? _statusDesc; + num? _totalContributors; + String? _claimExpireTime; + num? _claimCount; + bool? _canClaim; + bool? _hasClaimed; + num? _myContribution; + num? _myContributionRate; + + String? get roomId => _roomId; + + String? get date => _date; + + num? get level => _level; + + String? get levelName => _levelName; + + int? get currentEnergy => _currentEnergy; + + int? get maxEnergy => _maxEnergy; + + num? get energyPercent => _energyPercent; + + String? get status => _status; + + String? get statusDesc => _statusDesc; + + num? get totalContributors => _totalContributors; + + String? get claimExpireTime => _claimExpireTime; + + num? get claimCount => _claimCount; + + bool? get canClaim => _canClaim; + + bool? get hasClaimed => _hasClaimed; + + num? get myContribution => _myContribution; + + num? get myContributionRate => _myContributionRate; + + Map toJson() { + final map = {}; + map['roomId'] = _roomId; + map['date'] = _date; + map['level'] = _level; + map['levelName'] = _levelName; + map['currentEnergy'] = _currentEnergy; + map['maxEnergy'] = _maxEnergy; + map['energyPercent'] = _energyPercent; + map['status'] = _status; + map['statusDesc'] = _statusDesc; + map['totalContributors'] = _totalContributors; + map['claimExpireTime'] = _claimExpireTime; + map['claimCount'] = _claimCount; + map['canClaim'] = _canClaim; + map['hasClaimed'] = _hasClaimed; + map['myContribution'] = _myContribution; + map['myContributionRate'] = _myContributionRate; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/at_room_task_claimable_count_res.dart b/lib/chatvibe_domain/models/res/at_room_task_claimable_count_res.dart new file mode 100644 index 0000000..98911e7 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_room_task_claimable_count_res.dart @@ -0,0 +1,22 @@ +/// claimableCount : 0 + +class ATRoomTaskClaimableCountRes { + ATRoomTaskClaimableCountRes({ + num? claimableCount,}){ + _claimableCount = claimableCount; +} + + ATRoomTaskClaimableCountRes.fromJson(dynamic json) { + _claimableCount = json['claimableCount']; + } + num? _claimableCount; + + num? get claimableCount => _claimableCount; + + Map toJson() { + final map = {}; + map['claimableCount'] = _claimableCount; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_room_task_list_res.dart b/lib/chatvibe_domain/models/res/at_room_task_list_res.dart new file mode 100644 index 0000000..b79c905 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_room_task_list_res.dart @@ -0,0 +1,166 @@ +/// countdownSeconds : 0 +/// tasks : [{"taskId":0,"taskCode":"string","taskName":"string","taskCategory":0,"iconUrl":"string","currentValue":0,"tiers":[{"tierIndex":0,"targetValue":0,"targetUnit":"string","rewardValue":0,"isCompleted":true,"isClaimed":true}]}] + +class ATRoomTaskListRes { + ATRoomTaskListRes({ + int? countdownSeconds, + List? tasks,}){ + _countdown = countdown; + _tasks = tasks; +} + + ATRoomTaskListRes.fromJson(dynamic json) { + _countdown = json['countdown']; + if (json['tasks'] != null) { + _tasks = []; + json['tasks'].forEach((v) { + _tasks?.add(Tasks.fromJson(v)); + }); + } + } + int? _countdown; + List? _tasks; + + int? get countdown => _countdown; + List? get tasks => _tasks; + + Map toJson() { + final map = {}; + map['countdown'] = _countdown; + if (_tasks != null) { + map['tasks'] = _tasks?.map((v) => v.toJson()).toList(); + } + return map; + } + +} + +/// taskId : 0 +/// taskCode : "string" +/// taskName : "string" +/// taskCategory : 0 +/// iconUrl : "string" +/// currentValue : 0 +/// tiers : [{"tierIndex":0,"targetValue":0,"targetUnit":"string","rewardValue":0,"isCompleted":true,"isClaimed":true}] + +class Tasks { + Tasks({ + num? taskId, + String? taskCode, + String? taskName, + num? taskCategory, + String? iconUrl, + int? currentValue, + List? tiers,}){ + _taskId = taskId; + _taskCode = taskCode; + _taskName = taskName; + _taskCategory = taskCategory; + _iconUrl = iconUrl; + _currentValue = currentValue; + _tiers = tiers; +} + + Tasks.fromJson(dynamic json) { + _taskId = json['taskId']; + _taskCode = json['taskCode']; + _taskName = json['taskName']; + _taskCategory = json['taskCategory']; + _iconUrl = json['iconUrl']; + _currentValue = json['currentValue']; + if (json['tiers'] != null) { + _tiers = []; + json['tiers'].forEach((v) { + _tiers?.add(Tiers.fromJson(v)); + }); + } + } + num? _taskId; + String? _taskCode; + String? _taskName; + num? _taskCategory; + String? _iconUrl; + int? _currentValue; + List? _tiers; + + num? get taskId => _taskId; + String? get taskCode => _taskCode; + String? get taskName => _taskName; + num? get taskCategory => _taskCategory; + String? get iconUrl => _iconUrl; + int? get currentValue => _currentValue; + List? get tiers => _tiers; + + Map toJson() { + final map = {}; + map['taskId'] = _taskId; + map['taskCode'] = _taskCode; + map['taskName'] = _taskName; + map['taskCategory'] = _taskCategory; + map['iconUrl'] = _iconUrl; + map['currentValue'] = _currentValue; + if (_tiers != null) { + map['tiers'] = _tiers?.map((v) => v.toJson()).toList(); + } + return map; + } + +} + +/// tierIndex : 0 +/// targetValue : 0 +/// targetUnit : "string" +/// rewardValue : 0 +/// isCompleted : true +/// isClaimed : true + +class Tiers { + Tiers({ + num? tierIndex, + int? targetValue, + String? targetUnit, + num? rewardValue, + bool? isCompleted, + bool? isClaimed,}){ + _tierIndex = tierIndex; + _targetValue = targetValue; + _targetUnit = targetUnit; + _rewardValue = rewardValue; + _isCompleted = isCompleted; + _isClaimed = isClaimed; +} + + Tiers.fromJson(dynamic json) { + _tierIndex = json['tierIndex']; + _targetValue = json['targetValue']; + _targetUnit = json['targetUnit']; + _rewardValue = json['rewardValue']; + _isCompleted = json['isCompleted']; + _isClaimed = json['isClaimed']; + } + num? _tierIndex; + int? _targetValue; + String? _targetUnit; + num? _rewardValue; + bool? _isCompleted; + bool? _isClaimed; + + num? get tierIndex => _tierIndex; + int? get targetValue => _targetValue; + String? get targetUnit => _targetUnit; + num? get rewardValue => _rewardValue; + bool? get isCompleted => _isCompleted; + bool? get isClaimed => _isClaimed; + + Map toJson() { + final map = {}; + map['tierIndex'] = _tierIndex; + map['targetValue'] = _targetValue; + map['targetUnit'] = _targetUnit; + map['rewardValue'] = _rewardValue; + map['isCompleted'] = _isCompleted; + map['isClaimed'] = _isClaimed; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_room_theme_list_res.dart b/lib/chatvibe_domain/models/res/at_room_theme_list_res.dart new file mode 100644 index 0000000..cb07f78 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_room_theme_list_res.dart @@ -0,0 +1,85 @@ +/// id : "" +/// userId : "" +/// themeBack : "" +/// themeStatus : "" +/// themeMoney : 0.0 +/// sysOrigin : "" + +class ATRoomThemeListRes { + ATRoomThemeListRes({ + String? id, + String? userId, + String? themeBack, + String? themeStatus, + num? themeMoney, + int? expireTime, + bool? useTheme, + String? sysOrigin,}){ + _id = id; + _userId = userId; + _themeBack = themeBack; + _themeStatus = themeStatus; + _themeMoney = themeMoney; + _expireTime = expireTime; + _useTheme = useTheme; + _sysOrigin = sysOrigin; +} + + ATRoomThemeListRes.fromJson(dynamic json) { + _id = json['id']; + _userId = json['userId']; + _themeBack = json['themeBack']; + _themeStatus = json['themeStatus']; + _themeMoney = json['themeMoney']; + _expireTime = json['expireTime']; + _sysOrigin = json['sysOrigin']; + _useTheme = json['useTheme']; + } + String? _id; + String? _userId; + String? _themeBack; + String? _themeStatus; + num? _themeMoney; + int? _expireTime; + bool? _useTheme; + String? _sysOrigin; +ATRoomThemeListRes copyWith({ String? id, + String? userId, + String? themeBack, + String? themeStatus, + num? themeMoney, + int? expireTime, + bool? useTheme, + String? sysOrigin, +}) => ATRoomThemeListRes( id: id ?? _id, + userId: userId ?? _userId, + themeBack: themeBack ?? _themeBack, + themeStatus: themeStatus ?? _themeStatus, + themeMoney: themeMoney ?? _themeMoney, + expireTime: expireTime ?? _expireTime, + useTheme: useTheme ?? _useTheme, + sysOrigin: sysOrigin ?? _sysOrigin, +); + String? get id => _id; + String? get userId => _userId; + String? get themeBack => _themeBack; + String? get themeStatus => _themeStatus; + num? get themeMoney => _themeMoney; + int? get expireTime => _expireTime; + bool? get useTheme => _useTheme; + String? get sysOrigin => _sysOrigin; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['userId'] = _userId; + map['themeBack'] = _themeBack; + map['themeStatus'] = _themeStatus; + map['themeMoney'] = _themeMoney; + map['expireTime'] = _expireTime; + map['useTheme'] = _useTheme; + map['sysOrigin'] = _sysOrigin; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_rtc_token_res.dart b/lib/chatvibe_domain/models/res/at_rtc_token_res.dart new file mode 100644 index 0000000..6fc77c1 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_rtc_token_res.dart @@ -0,0 +1,33 @@ +/// newServiceToken : "" +/// rtcToken : "" + +class ATRtcTokenRes { + ATRtcTokenRes({ + String? newServiceToken, + String? rtcToken,}){ + _newServiceToken = newServiceToken; + _rtcToken = rtcToken; +} + + ATRtcTokenRes.fromJson(dynamic json) { + _newServiceToken = json['newServiceToken']; + _rtcToken = json['rtcToken']; + } + String? _newServiceToken; + String? _rtcToken; +ATRtcTokenRes copyWith({ String? newServiceToken, + String? rtcToken, +}) => ATRtcTokenRes( newServiceToken: newServiceToken ?? _newServiceToken, + rtcToken: rtcToken ?? _rtcToken, +); + String? get newServiceToken => _newServiceToken; + String? get rtcToken => _rtcToken; + + Map toJson() { + final map = {}; + map['newServiceToken'] = _newServiceToken; + map['rtcToken'] = _rtcToken; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_sign_in_res.dart b/lib/chatvibe_domain/models/res/at_sign_in_res.dart new file mode 100644 index 0000000..2b09dde --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_sign_in_res.dart @@ -0,0 +1,291 @@ +/// days : 0 +/// rewards : [{"extValues":{"":{}},"rule":{"id":0,"sysOrigin":"","activityType":"","resourceGroupId":0,"jsonData":"","sort":0},"propsGroup":{"id":0,"name":"","shelfStatus":false,"activityRewardProps":[{"id":0,"groupId":0,"type":"","detailType":"","content":"","sort":0,"cover":"","quantity":0,"amount":0.0,"remark":""}]}}] + +class ATSignInRes { + ATSignInRes({num? days, List? rewards}) { + _days = days; + _rewards = rewards; + } + + ATSignInRes.fromJson(dynamic json) { + _days = json['days']; + if (json['rewards'] != null) { + _rewards = []; + json['rewards'].forEach((v) { + _rewards?.add(Rewards.fromJson(v)); + }); + } + } + + num? _days; + List? _rewards; + + ATSignInRes copyWith({num? days, List? rewards}) => + ATSignInRes(days: days ?? _days, rewards: rewards ?? _rewards); + + num? get days => _days; + + List? get rewards => _rewards; + + Map toJson() { + final map = {}; + map['days'] = _days; + if (_rewards != null) { + map['rewards'] = _rewards?.map((v) => v.toJson()).toList(); + } + return map; + } +} + +/// extValues : {"":{}} +/// rule : {"id":0,"sysOrigin":"","activityType":"","resourceGroupId":0,"jsonData":"","sort":0} +/// propsGroup : {"id":0,"name":"","shelfStatus":false,"activityRewardProps":[{"id":0,"groupId":0,"type":"","detailType":"","content":"","sort":0,"cover":"","quantity":0,"amount":0.0,"remark":""}]} + +class Rewards { + Rewards({Rule? rule, PropsGroup? propsGroup}) { + _rule = rule; + _propsGroup = propsGroup; + } + + Rewards.fromJson(dynamic json) { + _rule = json['rule'] != null ? Rule.fromJson(json['rule']) : null; + _propsGroup = + json['propsGroup'] != null + ? PropsGroup.fromJson(json['propsGroup']) + : null; + } + + Rule? _rule; + PropsGroup? _propsGroup; + + Rule? get rule => _rule; + + PropsGroup? get propsGroup => _propsGroup; + + Map toJson() { + final map = {}; + if (_rule != null) { + map['rule'] = _rule?.toJson(); + } + if (_propsGroup != null) { + map['propsGroup'] = _propsGroup?.toJson(); + } + return map; + } +} + +/// id : 0 +/// name : "" +/// shelfStatus : false +/// activityRewardProps : [{"id":0,"groupId":0,"type":"","detailType":"","content":"","sort":0,"cover":"","quantity":0,"amount":0.0,"remark":""}] + +class PropsGroup { + PropsGroup({ + String? id, + String? name, + bool? shelfStatus, + List? activityRewardProps, + }) { + _id = id; + _name = name; + _shelfStatus = shelfStatus; + _activityRewardProps = activityRewardProps; + } + + PropsGroup.fromJson(dynamic json) { + _id = json['id']; + _name = json['name']; + _shelfStatus = json['shelfStatus']; + if (json['activityRewardProps'] != null) { + _activityRewardProps = []; + json['activityRewardProps'].forEach((v) { + _activityRewardProps?.add(ActivityRewardProps.fromJson(v)); + }); + } + } + + String? _id; + String? _name; + bool? _shelfStatus; + List? _activityRewardProps; + + String? get id => _id; + + String? get name => _name; + + bool? get shelfStatus => _shelfStatus; + + List? get activityRewardProps => _activityRewardProps; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['name'] = _name; + map['shelfStatus'] = _shelfStatus; + if (_activityRewardProps != null) { + map['activityRewardProps'] = + _activityRewardProps?.map((v) => v.toJson()).toList(); + } + return map; + } +} + +/// id : 0 +/// groupId : 0 +/// type : "" +/// detailType : "" +/// content : "" +/// sort : 0 +/// cover : "" +/// quantity : 0 +/// amount : 0.0 +/// remark : "" + +class ActivityRewardProps { + ActivityRewardProps({ + String? id, + String? groupId, + String? type, + String? detailType, + String? content, + num? sort, + String? cover, + num? quantity, + num? amount, + String? remark, + }) { + _id = id; + _groupId = groupId; + _type = type; + _detailType = detailType; + _content = content; + _sort = sort; + _cover = cover; + _quantity = quantity; + _amount = amount; + _remark = remark; + } + + ActivityRewardProps.fromJson(dynamic json) { + _id = json['id']; + _groupId = json['groupId']; + _type = json['type']; + _detailType = json['detailType']; + _content = json['content']; + _sort = json['sort']; + _cover = json['cover']; + _quantity = json['quantity']; + _amount = json['amount']; + _remark = json['remark']; + } + + String? _id; + String? _groupId; + String? _type; + String? _detailType; + String? _content; + num? _sort; + String? _cover; + num? _quantity; + num? _amount; + String? _remark; + + String? get id => _id; + + String? get groupId => _groupId; + + String? get type => _type; + + String? get detailType => _detailType; + + String? get content => _content; + + num? get sort => _sort; + + String? get cover => _cover; + + num? get quantity => _quantity; + + num? get amount => _amount; + + String? get remark => _remark; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['groupId'] = _groupId; + map['type'] = _type; + map['detailType'] = _detailType; + map['content'] = _content; + map['sort'] = _sort; + map['cover'] = _cover; + map['quantity'] = _quantity; + map['amount'] = _amount; + map['remark'] = _remark; + return map; + } +} + +/// id : 0 +/// sysOrigin : "" +/// activityType : "" +/// resourceGroupId : 0 +/// jsonData : "" +/// sort : 0 + +class Rule { + Rule({ + String? id, + String? sysOrigin, + String? activityType, + String? resourceGroupId, + String? jsonData, + num? sort, + }) { + _id = id; + _sysOrigin = sysOrigin; + _activityType = activityType; + _resourceGroupId = resourceGroupId; + _jsonData = jsonData; + _sort = sort; + } + + Rule.fromJson(dynamic json) { + _id = json['id']; + _sysOrigin = json['sysOrigin']; + _activityType = json['activityType']; + _resourceGroupId = json['resourceGroupId']; + _jsonData = json['jsonData']; + _sort = json['sort']; + } + + String? _id; + String? _sysOrigin; + String? _activityType; + String? _resourceGroupId; + String? _jsonData; + num? _sort; + + String? get id => _id; + + String? get sysOrigin => _sysOrigin; + + String? get activityType => _activityType; + + String? get resourceGroupId => _resourceGroupId; + + String? get jsonData => _jsonData; + + num? get sort => _sort; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['sysOrigin'] = _sysOrigin; + map['activityType'] = _activityType; + map['resourceGroupId'] = _resourceGroupId; + map['jsonData'] = _jsonData; + map['sort'] = _sort; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/at_start_page_res.dart b/lib/chatvibe_domain/models/res/at_start_page_res.dart new file mode 100644 index 0000000..08ba244 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_start_page_res.dart @@ -0,0 +1,124 @@ +/// cover : "" +/// expireTime : 0 +/// id : 0 +/// sysOrigin : "" +/// type : "" + +class ATStartPageRes { + ATStartPageRes({ + String? cover, + num? expireTime, + String? id, + String? sysOrigin, + String? type, + List? user, + }) { + _cover = cover; + _expireTime = expireTime; + _id = id; + _sysOrigin = sysOrigin; + _type = type; + _user = user; + } + + ATStartPageRes.fromJson(dynamic json) { + _cover = json['cover']; + _expireTime = json['expireTime']; + _id = json['id']; + _sysOrigin = json['sysOrigin']; + _type = json['type']; + if (json['user'] != null) { + _user = []; + json['user'].forEach((v) { + _user?.add(StartPageUser.fromJson(v)); + }); + } + } + + String? _cover; + num? _expireTime; + String? _id; + String? _sysOrigin; + String? _type; + List? _user; + + ATStartPageRes copyWith({ + String? cover, + num? expireTime, + String? id, + String? sysOrigin, + String? type, + List? user, + }) => ATStartPageRes( + cover: cover ?? _cover, + expireTime: expireTime ?? _expireTime, + id: id ?? _id, + sysOrigin: sysOrigin ?? _sysOrigin, + type: type ?? _type, + user: user ?? _user, + ); + + String? get cover => _cover; + + num? get expireTime => _expireTime; + + String? get id => _id; + + String? get sysOrigin => _sysOrigin; + + String? get type => _type; + + List? get user => _user; + + Map toJson() { + final map = {}; + map['cover'] = _cover; + map['expireTime'] = _expireTime; + map['id'] = _id; + map['sysOrigin'] = _sysOrigin; + map['type'] = _type; + if (_user != null) { + map['user'] = _user?.map((v) => v.toJson()).toList(); + } + return map; + } +} + +class StartPageUser { + StartPageUser({String? account, String? nickName, String? avatar}) { + _account = account; + _nickName = nickName; + _avatar = avatar; + } + + StartPageUser.fromJson(dynamic json) { + _account = json['account']; + _nickName = json['nickName']; + _avatar = json['avatar']; + } + + String? _account; + String? _nickName; + String? _avatar; + + StartPageUser copyWith({String? account, String? nickName, String? avatar}) => + StartPageUser( + account: account ?? _account, + nickName: nickName ?? _nickName, + avatar: avatar ?? _avatar, + ); + + String? get account => _account; + + String? get nickName => _nickName; + + String? get avatar => _avatar; + + Map toJson() { + final map = {}; + map['account'] = _account; + map['nickName'] = _nickName; + map['avatar'] = _avatar; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/at_system_invit_message_res.dart b/lib/chatvibe_domain/models/res/at_system_invit_message_res.dart new file mode 100644 index 0000000..9819918 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_system_invit_message_res.dart @@ -0,0 +1,69 @@ +/// account : "" +/// countryCode : "" +/// countryName : "" +/// userAvatar : "" +/// userNickname : "" +/// actualAccount : "" + +class ATSystemInvitMessageRes { + ATSystemInvitMessageRes({ + String? account, + String? countryCode, + String? countryName, + String? userAvatar, + String? userNickname, + String? applyType, + String? content, + String? actualAccount,}){ + _account = account; + _countryCode = countryCode; + _countryName = countryName; + _userAvatar = userAvatar; + _userNickname = userNickname; + _actualAccount = actualAccount; + _applyType = applyType; + _content = content; +} + + ATSystemInvitMessageRes.fromJson(dynamic json) { + _account = json['account']; + _countryCode = json['countryCode']; + _countryName = json['countryName']; + _userAvatar = json['userAvatar']; + _userNickname = json['userNickname']; + _actualAccount = json['actualAccount']; + _applyType = json['applyType']; + _content = json['content']; + } + String? _account; + String? _countryCode; + String? _countryName; + String? _userAvatar; + String? _userNickname; + String? _actualAccount; + String? _applyType; + String? _content; + + String? get account => _account; + String? get countryCode => _countryCode; + String? get countryName => _countryName; + String? get userAvatar => _userAvatar; + String? get userNickname => _userNickname; + String? get actualAccount => _actualAccount; + String? get applyType => _applyType; + String? get content => _content; + + Map toJson() { + final map = {}; + map['account'] = _account; + map['countryCode'] = _countryCode; + map['countryName'] = _countryName; + map['userAvatar'] = _userAvatar; + map['userNickname'] = _userNickname; + map['actualAccount'] = _actualAccount; + map['applyType'] = _applyType; + map['content'] = _content; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_task_list_res.dart b/lib/chatvibe_domain/models/res/at_task_list_res.dart new file mode 100644 index 0000000..9abe488 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_task_list_res.dart @@ -0,0 +1,131 @@ +/// taskType : 0 +/// taskName : "" +/// taskDesc : "" +/// jumpPage : "" +/// conditionType : "" +/// sortOrder : 0 +/// taskId : 0 +/// taskStatus : 0 +/// cover : "" +/// taskIcon : "" +/// isRewardCollected : 0 +/// quantity : 0 + +class ATTaskListRes { + ATTaskListRes({ + num? taskType, + String? taskName, + String? taskDesc, + String? jumpPage, + String? conditionType, + num? sortOrder, + num? taskId, + num? taskStatus, + String? cover, + String? taskIcon, + String? conditionValue, + String? completedValue, + num? isRewardCollected, + num? targetValue, + num? quantity, + }) { + _taskType = taskType; + _taskName = taskName; + _taskDesc = taskDesc; + _jumpPage = jumpPage; + _conditionType = conditionType; + _sortOrder = sortOrder; + _taskId = taskId; + _taskStatus = taskStatus; + _cover = cover; + _taskIcon = taskIcon; + _conditionValue = conditionValue; + _completedValue = completedValue; + _isRewardCollected = isRewardCollected; + _quantity = quantity; + _targetValue = targetValue; + } + + ATTaskListRes.fromJson(dynamic json) { + _taskType = json['taskType']; + _taskName = json['taskName']; + _taskDesc = json['taskDesc']; + _jumpPage = json['jumpPage']; + _conditionType = json['conditionType']; + _sortOrder = json['sortOrder']; + _taskId = json['taskId']; + _taskStatus = json['taskStatus']; + _cover = json['cover']; + _taskIcon = json['taskIcon']; + _conditionValue = json['conditionValue']; + _completedValue = json['completedValue']; + _isRewardCollected = json['isRewardCollected']; + _quantity = json['quantity']; + _targetValue = json['targetValue']; + } + + num? _taskType; + String? _taskName; + String? _taskDesc; + String? _jumpPage; + String? _conditionType; + num? _sortOrder; + num? _taskId; + num? _taskStatus; + String? _cover; + String? _taskIcon; + String? _conditionValue; + String? _completedValue; + num? _isRewardCollected; + num? _quantity; + num? _targetValue; + + num? get taskType => _taskType; + + String? get taskName => _taskName; + + String? get taskDesc => _taskDesc; + + String? get jumpPage => _jumpPage; + + String? get conditionType => _conditionType; + + num? get sortOrder => _sortOrder; + + num? get taskId => _taskId; + + num? get taskStatus => _taskStatus; + + String? get cover => _cover; + + String? get taskIcon => _taskIcon; + + String? get conditionValue => _conditionValue; + + String? get completedValue => _completedValue; + + num? get isRewardCollected => _isRewardCollected; + + num? get quantity => _quantity; + num? get targetValue => _targetValue; + + Map toJson() { + final map = {}; + map['taskType'] = _taskType; + map['taskName'] = _taskName; + map['taskDesc'] = _taskDesc; + map['jumpPage'] = _jumpPage; + map['conditionType'] = _conditionType; + map['sortOrder'] = _sortOrder; + map['taskId'] = _taskId; + map['taskStatus'] = _taskStatus; + map['cover'] = _cover; + map['taskIcon'] = _taskIcon; + map['conditionValue'] = _conditionValue; + map['completedValue'] = _completedValue; + map['isRewardCollected'] = _isRewardCollected; + map['quantity'] = _quantity; + map['targetValue'] = _targetValue; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/at_top_four_with_reward_res.dart b/lib/chatvibe_domain/models/res/at_top_four_with_reward_res.dart new file mode 100644 index 0000000..96ae77b --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_top_four_with_reward_res.dart @@ -0,0 +1,57 @@ +/// userId : "1957345312961527809" +/// account : "8826602" +/// nickname : "8826602" +/// avatar : "https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/default_avatar/user.png" +/// rank : 1 +/// quantity : "6K" + +class ATTopFourWithRewardRes { + ATTopFourWithRewardRes({ + String? userId, + String? account, + String? nickname, + String? avatar, + num? rank, + String? quantity,}){ + _userId = userId; + _account = account; + _nickname = nickname; + _avatar = avatar; + _rank = rank; + _quantity = quantity; +} + + ATTopFourWithRewardRes.fromJson(dynamic json) { + _userId = json['userId']; + _account = json['account']; + _nickname = json['nickname']; + _avatar = json['avatar']; + _rank = json['rank']; + _quantity = json['quantity']; + } + String? _userId; + String? _account; + String? _nickname; + String? _avatar; + num? _rank; + String? _quantity; + + String? get userId => _userId; + String? get account => _account; + String? get nickname => _nickname; + String? get avatar => _avatar; + num? get rank => _rank; + String? get quantity => _quantity; + + Map toJson() { + final map = {}; + map['userId'] = _userId; + map['account'] = _account; + map['nickname'] = _nickname; + map['avatar'] = _avatar; + map['rank'] = _rank; + map['quantity'] = _quantity; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_user_counter_res.dart b/lib/chatvibe_domain/models/res/at_user_counter_res.dart new file mode 100644 index 0000000..aa98241 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_user_counter_res.dart @@ -0,0 +1,45 @@ +/// counterType : "" +/// quantity : 0 +/// formatQuantity : "" + +class ATUserCounterRes { + ATUserCounterRes({String? counterType, String? quantity, String? formatQuantity}) { + _counterType = counterType; + _quantity = quantity; + _formatQuantity = formatQuantity; + } + + ATUserCounterRes.fromJson(dynamic json) { + _counterType = json['counterType']; + _quantity = json['quantity']; + _formatQuantity = json['formatQuantity']; + } + + String? _counterType; + String? _quantity; + String? _formatQuantity; + + ATUserCounterRes copyWith({ + String? counterType, + String? quantity, + String? formatQuantity, + }) => ATUserCounterRes( + counterType: counterType ?? _counterType, + quantity: quantity ?? _quantity, + formatQuantity: formatQuantity ?? _formatQuantity, + ); + + String? get counterType => _counterType; + + String? get quantity => _quantity; + + String? get formatQuantity => _formatQuantity; + + Map toJson() { + final map = {}; + map['counterType'] = _counterType; + map['quantity'] = _quantity; + map['formatQuantity'] = _formatQuantity; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/at_user_freight_balance_page_res.dart b/lib/chatvibe_domain/models/res/at_user_freight_balance_page_res.dart new file mode 100644 index 0000000..53228b3 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_user_freight_balance_page_res.dart @@ -0,0 +1,250 @@ +import 'login_res.dart'; + +/// records : [{"extValues":{"":{}},"id":"","sysOrigin":"","userId":"","earnPoints":0.0,"consumptionPoints":0.0,"balance":0.0,"createTime":0,"updateTime":0,"close":false,"display":false,"dealer":false,"superDealer":false,"supportedCountries":"","contactInfo":"","sellerQuantity":0,"realSellerQuantity":0,"userBaseInfo":{"id":0,"account":"","userAvatar":"","userNickname":"","userSex":0,"age":0,"accountStatus":"","freezingTime":0,"isUpdateCountry":"","countryId":0,"countryName":"","countryCode":"","inRoomId":"","roomIcon":"","regionCode":"","wealthLevel":0,"charmLevel":0,"originSys":"","sysOriginChild":"","del":false,"createTime":0,"bornYear":0,"bornMonth":0,"bornDay":0,"ownSpecialId":{"account":"","expiredTime":0},"useProps":[{"userId":0,"propsResources":{"id":0,"type":"","code":"","name":"","cover":"","sourceUrl":"","expand":"","amount":0.0},"expireTime":0}],"sameRegion":false,"isFreightAgent":false,"isSuperFreightAgent":false,"wearBadge":[{"id":0,"userId":0,"badgeLevel":0,"milestone":0,"badgeName":"","type":"","badgeKey":"","selectUrl":"","notSelectUrl":"","animationUrl":"","expireTime":0}]}}] +/// total : 0 +/// size : 0 +/// current : 0 + +class AtUserFreightBalancePageRes { + AtUserFreightBalancePageRes({ + List? records, + num? total, + num? size, + num? current, + }) { + _records = records; + _total = total; + _size = size; + _current = current; + } + + AtUserFreightBalancePageRes.fromJson(dynamic json) { + if (json['records'] != null) { + _records = []; + json['records'].forEach((v) { + _records?.add(Records.fromJson(v)); + }); + } + _total = json['total']; + _size = json['size']; + _current = json['current']; + } + + List? _records; + num? _total; + num? _size; + num? _current; + + List? get records => _records; + + num? get total => _total; + + num? get size => _size; + + num? get current => _current; + + Map toJson() { + final map = {}; + if (_records != null) { + map['records'] = _records?.map((v) => v.toJson()).toList(); + } + map['total'] = _total; + map['size'] = _size; + map['current'] = _current; + return map; + } +} + +/// extValues : {"":{}} +/// id : "" +/// sysOrigin : "" +/// userId : "" +/// earnPoints : 0.0 +/// consumptionPoints : 0.0 +/// balance : 0.0 +/// createTime : 0 +/// updateTime : 0 +/// close : false +/// display : false +/// dealer : false +/// superDealer : false +/// supportedCountries : "" +/// contactInfo : "" +/// sellerQuantity : 0 +/// realSellerQuantity : 0 +/// userBaseInfo : {"id":0,"account":"","userAvatar":"","userNickname":"","userSex":0,"age":0,"accountStatus":"","freezingTime":0,"isUpdateCountry":"","countryId":0,"countryName":"","countryCode":"","inRoomId":"","roomIcon":"","regionCode":"","wealthLevel":0,"charmLevel":0,"originSys":"","sysOriginChild":"","del":false,"createTime":0,"bornYear":0,"bornMonth":0,"bornDay":0,"ownSpecialId":{"account":"","expiredTime":0},"useProps":[{"userId":0,"propsResources":{"id":0,"type":"","code":"","name":"","cover":"","sourceUrl":"","expand":"","amount":0.0},"expireTime":0}],"sameRegion":false,"isFreightAgent":false,"isSuperFreightAgent":false,"wearBadge":[{"id":0,"userId":0,"badgeLevel":0,"milestone":0,"badgeName":"","type":"","badgeKey":"","selectUrl":"","notSelectUrl":"","animationUrl":"","expireTime":0}]} + +class Records { + Records({ + String? id, + String? sysOrigin, + List? nationalFlag, + String? userId, + String? ownNationalFlag, + num? earnPoints, + num? consumptionPoints, + num? balance, + int? createTime, + int? updateTime, + int? transactionCount, + int? becomeDays, + bool? close, + bool? display, + bool? isFollow, + bool? dealer, + bool? superDealer, + String? supportedCountries, + String? contactInfo, + num? sellerQuantity, + num? realSellerQuantity, + ChatVibeUserProfile? userBaseInfo, + }) { + _id = id; + _sysOrigin = sysOrigin; + _nationalFlag = nationalFlag; + _ownNationalFlag = ownNationalFlag; + _userId = userId; + _earnPoints = earnPoints; + _consumptionPoints = consumptionPoints; + _balance = balance; + _createTime = createTime; + _updateTime = updateTime; + _transactionCount = transactionCount; + _becomeDays = becomeDays; + _close = close; + _display = display; + _isFollow = isFollow; + _dealer = dealer; + _superDealer = superDealer; + _supportedCountries = supportedCountries; + _contactInfo = contactInfo; + _sellerQuantity = sellerQuantity; + _realSellerQuantity = realSellerQuantity; + _userBaseInfo = userBaseInfo; + } + + Records.fromJson(dynamic json) { + _id = json['id']; + _sysOrigin = json['sysOrigin']; + _nationalFlag = + json['nationalFlag'] != null ? json['nationalFlag'].cast() : []; + _ownNationalFlag = json['ownNationalFlag']; + _userId = json['userId']; + _earnPoints = json['earnPoints']; + _consumptionPoints = json['consumptionPoints']; + _balance = json['balance']; + _createTime = json['createTime']; + _updateTime = json['updateTime']; + _transactionCount = json['transactionCount']; + _becomeDays = json['becomeDays']; + _close = json['close']; + _display = json['display']; + _isFollow = json['isFollow']; + _dealer = json['dealer']; + _superDealer = json['superDealer']; + _supportedCountries = json['supportedCountries']; + _contactInfo = json['contactInfo']; + _sellerQuantity = json['sellerQuantity']; + _realSellerQuantity = json['realSellerQuantity']; + _userBaseInfo = + json['userBaseInfo'] != null + ? ChatVibeUserProfile.fromJson(json['userBaseInfo']) + : null; + } + + String? _id; + String? _sysOrigin; + List? _nationalFlag; + String? _ownNationalFlag; + String? _userId; + num? _earnPoints; + num? _consumptionPoints; + num? _balance; + int? _createTime; + int? _updateTime; + int? _transactionCount; + int? _becomeDays; + bool? _close; + bool? _display; + bool? _isFollow; + bool? _dealer; + bool? _superDealer; + String? _supportedCountries; + String? _contactInfo; + num? _sellerQuantity; + num? _realSellerQuantity; + ChatVibeUserProfile? _userBaseInfo; + + String? get id => _id; + + String? get sysOrigin => _sysOrigin; + + List? get nationalFlag => _nationalFlag; + + String? get ownNationalFlag => _ownNationalFlag; + + String? get userId => _userId; + + num? get earnPoints => _earnPoints; + + num? get consumptionPoints => _consumptionPoints; + + num? get balance => _balance; + + int? get createTime => _createTime; + + int? get updateTime => _updateTime; + + int? get transactionCount => _transactionCount; + + int? get becomeDays => _becomeDays; + + bool? get close => _close; + + bool? get display => _display; + + bool? get isFollow => _isFollow; + + bool? get dealer => _dealer; + + bool? get superDealer => _superDealer; + + String? get supportedCountries => _supportedCountries; + + String? get contactInfo => _contactInfo; + + num? get sellerQuantity => _sellerQuantity; + + num? get realSellerQuantity => _realSellerQuantity; + + ChatVibeUserProfile? get userBaseInfo => _userBaseInfo; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['sysOrigin'] = _sysOrigin; + map['nationalFlag'] = _nationalFlag; + map['ownNationalFlag'] = _ownNationalFlag; + map['userId'] = _userId; + map['earnPoints'] = _earnPoints; + map['consumptionPoints'] = _consumptionPoints; + map['balance'] = _balance; + map['createTime'] = _createTime; + map['updateTime'] = _updateTime; + map['becomeDays'] = _becomeDays; + map['transactionCount'] = _transactionCount; + map['close'] = _close; + map['display'] = _display; + map['isFollow'] = _isFollow; + map['dealer'] = _dealer; + map['superDealer'] = _superDealer; + map['supportedCountries'] = _supportedCountries; + map['contactInfo'] = _contactInfo; + map['sellerQuantity'] = _sellerQuantity; + map['realSellerQuantity'] = _realSellerQuantity; + if (_userBaseInfo != null) { + map['userBaseInfo'] = _userBaseInfo?.toJson(); + } + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/at_user_identity_res.dart b/lib/chatvibe_domain/models/res/at_user_identity_res.dart new file mode 100644 index 0000000..6e2adb5 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_user_identity_res.dart @@ -0,0 +1,90 @@ +/// agent : true +/// anchor : true +/// bd : false +/// freightAgent : false + +class ATUserIdentityRes { + ATUserIdentityRes({ + bool? agent, + bool? anchor, + bool? bd, + bool? admin, + bool? superAdmin, + bool? bdLeader, + bool? superFreightAgent, + bool? manager, + bool? freightAgent,}){ + _agent = agent; + _superFreightAgent = superFreightAgent; + _anchor = anchor; + _bd = bd; + _manager = manager; + _admin = admin; + _superAdmin = superAdmin; + _bdLeader = bdLeader; + _freightAgent = freightAgent; +} + + ATUserIdentityRes.fromJson(dynamic json) { + _agent = json['agent']; + _anchor = json['anchor']; + _bd = json['bd']; + _superAdmin = json['superAdmin']; + _bdLeader = json['bdLeader']; + _superFreightAgent = json['superFreightAgent']; + _admin = json['admin']; + _freightAgent = json['freightAgent']; + _manager = json['manager']; + } + bool? _agent; + bool? _anchor; + bool? _bd; + bool? _admin; + bool? _superAdmin; + bool? _bdLeader; + bool? _freightAgent; + bool? _manager; + bool? _superFreightAgent; +ATUserIdentityRes copyWith({ bool? agent, + bool? anchor, + bool? bd, + bool? admin, + bool? superAdmin, + bool? manager, + bool? superFreightAgent, + bool? freightAgent, +}) => ATUserIdentityRes( agent: agent ?? _agent, + anchor: anchor ?? _anchor, + bd: bd ?? _bd, + superAdmin: superAdmin ?? _superAdmin, + bdLeader: bdLeader ?? _bdLeader, + admin: admin ?? _admin, + manager: manager ?? _manager, + superFreightAgent: superFreightAgent ?? _superFreightAgent, + freightAgent: freightAgent ?? _freightAgent, +); + bool? get agent => _agent; + bool? get anchor => _anchor; + bool? get bd => _bd; + bool? get admin => _admin; + bool? get manager => _manager; + bool? get superAdmin => _superAdmin; + bool? get bdLeader => _bdLeader; + bool? get superFreightAgent => _superFreightAgent; + bool? get freightAgent => _freightAgent; + + Map toJson() { + final map = {}; + map['agent'] = _agent; + map['anchor'] = _anchor; + map['bd'] = _bd; + map['manager'] = _manager; + map['admin'] = _admin; + map['superAdmin'] = _superAdmin; + map['bdLeader'] = _bdLeader; + map['superFreightAgent'] = _superFreightAgent; + map['freightAgent'] = _freightAgent; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_user_level_exp_res.dart b/lib/chatvibe_domain/models/res/at_user_level_exp_res.dart new file mode 100644 index 0000000..b87d9f3 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_user_level_exp_res.dart @@ -0,0 +1,96 @@ +/// level : 0 +/// thatExperience : 0 +/// thatExperienceFormat : "" +/// nextExperience : 0 +/// nextExperienceFormat : "" + +class ATUserLevelExpRes { + ATUserLevelExpRes({ + num? level, + num? vipDays, + num? idIconType, + String? thatExperience, + String? vipLevel, + String? thatExperienceFormat, + String? nextExperience, + String? nextExperienceFormat, + }) { + _level = level; + _thatExperience = thatExperience; + _thatExperienceFormat = thatExperienceFormat; + _vipDays = vipDays; + _idIconType = idIconType; + _vipLevel = vipLevel; + _nextExperience = nextExperience; + _nextExperienceFormat = nextExperienceFormat; + } + + ATUserLevelExpRes.fromJson(dynamic json) { + _level = json['level']; + _thatExperience = json['thatExperience']; + _thatExperienceFormat = json['thatExperienceFormat']; + _vipDays = json['vipDays']; + _idIconType = json['idIconType']; + _vipLevel = json['vipLevel']; + _nextExperience = json['nextExperience']; + _nextExperienceFormat = json['nextExperienceFormat']; + } + + num? _level; + num? _vipDays; + num? _idIconType; + String? _thatExperience; + String? _thatExperienceFormat; + String? _vipLevel; + String? _nextExperience; + String? _nextExperienceFormat; + + ATUserLevelExpRes copyWith({ + num? level, + String? thatExperience, + String? thatExperienceFormat, + num? vipDays, + num? idIconType, + String? vipLevel, + String? nextExperience, + String? nextExperienceFormat, + }) => ATUserLevelExpRes( + level: level ?? _level, + thatExperience: thatExperience ?? _thatExperience, + thatExperienceFormat: thatExperienceFormat ?? _thatExperienceFormat, + vipDays: vipDays ?? _vipDays, + idIconType: idIconType ?? _idIconType, + vipLevel: vipLevel ?? _vipLevel, + nextExperience: nextExperience ?? _nextExperience, + nextExperienceFormat: nextExperienceFormat ?? _nextExperienceFormat, + ); + + num? get level => _level; + + String? get thatExperience => _thatExperience; + + String? get thatExperienceFormat => _thatExperienceFormat; + + num? get vipDays => _vipDays; + + num? get idIconType => _idIconType; + + String? get vipLevel => _vipLevel; + + String? get nextExperience => _nextExperience; + + String? get nextExperienceFormat => _nextExperienceFormat; + + Map toJson() { + final map = {}; + map['level'] = _level; + map['thatExperience'] = _thatExperience; + map['thatExperienceFormat'] = _thatExperienceFormat; + map['vipDays'] = _vipDays; + map['idIconType'] = _idIconType; + map['vipLevel'] = _vipLevel; + map['nextExperience'] = _nextExperience; + map['nextExperienceFormat'] = _nextExperienceFormat; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/at_user_red_packet_grab_res.dart b/lib/chatvibe_domain/models/res/at_user_red_packet_grab_res.dart new file mode 100644 index 0000000..5d1ca80 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_user_red_packet_grab_res.dart @@ -0,0 +1,36 @@ +/// packetId : "" +/// amount : 0 +/// senderUserId : 0 + +class ATUserRedPacketGrabRes { + ATUserRedPacketGrabRes({ + String? packetId, + num? amount, + num? senderUserId,}){ + _packetId = packetId; + _amount = amount; + _senderUserId = senderUserId; +} + + ATUserRedPacketGrabRes.fromJson(dynamic json) { + _packetId = json['packetId']; + _amount = json['amount']; + _senderUserId = json['senderUserId']; + } + String? _packetId; + num? _amount; + num? _senderUserId; + + String? get packetId => _packetId; + num? get amount => _amount; + num? get senderUserId => _senderUserId; + + Map toJson() { + final map = {}; + map['packetId'] = _packetId; + map['amount'] = _amount; + map['senderUserId'] = _senderUserId; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_user_red_packet_send_res.dart b/lib/chatvibe_domain/models/res/at_user_red_packet_send_res.dart new file mode 100644 index 0000000..7ff7351 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_user_red_packet_send_res.dart @@ -0,0 +1,184 @@ +/// packetId : "" +/// senderUserId : "" +/// receiverUserId : "" +/// senderInfo : {"userId":"","account":"","nickName":"","avatar":""} +/// receiverInfo : {"userId":"","account":"","nickName":"","avatar":""} +/// totalAmount : 0 +/// handlingFee : 0 +/// status : 0 +/// expireTime : 0 +/// grabbedAt : 0 +/// createTime : 0 + +class ATUserRedPacketSendRes { + ATUserRedPacketSendRes({ + String? packetId, + num? senderUserId, + num? receiverUserId, + SenderInfo? senderInfo, + ReceiverInfo? receiverInfo, + num? totalAmount, + num? handlingFee, + num? status, + int? expireTime, + int? grabbedAt, + int? createTime,}){ + _packetId = packetId; + _senderUserId = senderUserId; + _receiverUserId = receiverUserId; + _senderInfo = senderInfo; + _receiverInfo = receiverInfo; + _totalAmount = totalAmount; + _handlingFee = handlingFee; + _status = status; + _expireTime = expireTime; + _grabbedAt = grabbedAt; + _createTime = createTime; +} + + ATUserRedPacketSendRes.fromJson(dynamic json) { + _packetId = json['packetId']; + _senderUserId = json['senderUserId']; + _receiverUserId = json['receiverUserId']; + _senderInfo = json['senderInfo'] != null ? SenderInfo.fromJson(json['senderInfo']) : null; + _receiverInfo = json['receiverInfo'] != null ? ReceiverInfo.fromJson(json['receiverInfo']) : null; + _totalAmount = json['totalAmount']; + _handlingFee = json['handlingFee']; + _status = json['status']; + _expireTime = json['expireTime']; + _grabbedAt = json['grabbedAt']; + _createTime = json['createTime']; + } + String? _packetId; + num? _senderUserId; + num? _receiverUserId; + SenderInfo? _senderInfo; + ReceiverInfo? _receiverInfo; + num? _totalAmount; + num? _handlingFee; + num? _status; + int? _expireTime; + int? _grabbedAt; + int? _createTime; + + String? get packetId => _packetId; + num? get senderUserId => _senderUserId; + num? get receiverUserId => _receiverUserId; + SenderInfo? get senderInfo => _senderInfo; + ReceiverInfo? get receiverInfo => _receiverInfo; + num? get totalAmount => _totalAmount; + num? get handlingFee => _handlingFee; + num? get status => _status; + int? get expireTime => _expireTime; + int? get grabbedAt => _grabbedAt; + int? get createTime => _createTime; + + Map toJson() { + final map = {}; + map['packetId'] = _packetId; + map['senderUserId'] = _senderUserId; + map['receiverUserId'] = _receiverUserId; + if (_senderInfo != null) { + map['senderInfo'] = _senderInfo?.toJson(); + } + if (_receiverInfo != null) { + map['receiverInfo'] = _receiverInfo?.toJson(); + } + map['totalAmount'] = _totalAmount; + map['handlingFee'] = _handlingFee; + map['status'] = _status; + map['expireTime'] = _expireTime; + map['grabbedAt'] = _grabbedAt; + map['createTime'] = _createTime; + return map; + } + +} + +/// userId : "" +/// account : "" +/// nickName : "" +/// avatar : "" + +class ReceiverInfo { + ReceiverInfo({ + num? userId, + String? account, + String? nickName, + String? avatar,}){ + _userId = userId; + _account = account; + _nickName = nickName; + _avatar = avatar; +} + + ReceiverInfo.fromJson(dynamic json) { + _userId = json['userId']; + _account = json['account']; + _nickName = json['nickName']; + _avatar = json['avatar']; + } + num? _userId; + String? _account; + String? _nickName; + String? _avatar; + + num? get userId => _userId; + String? get account => _account; + String? get nickName => _nickName; + String? get avatar => _avatar; + + Map toJson() { + final map = {}; + map['userId'] = _userId; + map['account'] = _account; + map['nickName'] = _nickName; + map['avatar'] = _avatar; + return map; + } + +} + +/// userId : "" +/// account : "" +/// nickName : "" +/// avatar : "" + +class SenderInfo { + SenderInfo({ + num? userId, + String? account, + String? nickName, + String? avatar,}){ + _userId = userId; + _account = account; + _nickName = nickName; + _avatar = avatar; +} + + SenderInfo.fromJson(dynamic json) { + _userId = json['userId']; + _account = json['account']; + _nickName = json['nickName']; + _avatar = json['avatar']; + } + num? _userId; + String? _account; + String? _nickName; + String? _avatar; + + num? get userId => _userId; + String? get account => _account; + String? get nickName => _nickName; + String? get avatar => _avatar; + + Map toJson() { + final map = {}; + map['userId'] = _userId; + map['account'] = _account; + map['nickName'] = _nickName; + map['avatar'] = _avatar; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_user_vip_ability_res.dart b/lib/chatvibe_domain/models/res/at_user_vip_ability_res.dart new file mode 100644 index 0000000..a42dc67 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_user_vip_ability_res.dart @@ -0,0 +1,57 @@ +/// userId : "2" +/// vipLevel : "string" +/// contactMode : "string" +/// kickPrevention : true +/// mysteriousInvisibility : true +/// antiBlock : true + +class ATUserVipAbilityRes { + ATUserVipAbilityRes({ + num? userId, + String? vipLevel, + String? contactMode, + bool? kickPrevention, + bool? mysteriousInvisibility, + bool? antiBlock,}){ + _userId = userId; + _vipLevel = vipLevel; + _contactMode = contactMode; + _kickPrevention = kickPrevention; + _mysteriousInvisibility = mysteriousInvisibility; + _antiBlock = antiBlock; +} + + ATUserVipAbilityRes.fromJson(dynamic json) { + _userId = json['userId']; + _vipLevel = json['vipLevel']; + _contactMode = json['contactMode']; + _kickPrevention = json['kickPrevention']; + _mysteriousInvisibility = json['mysteriousInvisibility']; + _antiBlock = json['antiBlock']; + } + num? _userId; + String? _vipLevel; + String? _contactMode; + bool? _kickPrevention; + bool? _mysteriousInvisibility; + bool? _antiBlock; + + num? get userId => _userId; + String? get vipLevel => _vipLevel; + String? get contactMode => _contactMode; + bool? get kickPrevention => _kickPrevention; + bool? get mysteriousInvisibility => _mysteriousInvisibility; + bool? get antiBlock => _antiBlock; + + Map toJson() { + final map = {}; + map['userId'] = _userId; + map['vipLevel'] = _vipLevel; + map['contactMode'] = _contactMode; + map['kickPrevention'] = _kickPrevention; + map['mysteriousInvisibility'] = _mysteriousInvisibility; + map['antiBlock'] = _antiBlock; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_version_manage_latest_res.dart b/lib/chatvibe_domain/models/res/at_version_manage_latest_res.dart new file mode 100644 index 0000000..3d0b859 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_version_manage_latest_res.dart @@ -0,0 +1,99 @@ +/// id : 0 +/// platform : "" +/// version : "" +/// forceUpdate : false +/// appType : "" +/// review : false +/// patch : false +/// updateDescribe : "" +/// updateWorshipDescribe : "" +/// downloadUrl : "" +/// buildVersion : 0 +/// createTime : 0 + +class ATVersionManageLatestRes { + ATVersionManageLatestRes({ + String? id, + String? platform, + String? version, + bool? forceUpdate, + String? appType, + bool? review, + bool? patch, + String? updateDescribe, + String? updateWorshipDescribe, + String? downloadUrl, + num? buildVersion, + num? createTime,}){ + _id = id; + _platform = platform; + _version = version; + _forceUpdate = forceUpdate; + _appType = appType; + _review = review; + _patch = patch; + _updateDescribe = updateDescribe; + _updateWorshipDescribe = updateWorshipDescribe; + _downloadUrl = downloadUrl; + _buildVersion = buildVersion; + _createTime = createTime; +} + + ATVersionManageLatestRes.fromJson(dynamic json) { + _id = json['id']; + _platform = json['platform']; + _version = json['version']; + _forceUpdate = json['forceUpdate']; + _appType = json['appType']; + _review = json['review']; + _patch = json['patch']; + _updateDescribe = json['updateDescribe']; + _updateWorshipDescribe = json['updateWorshipDescribe']; + _downloadUrl = json['downloadUrl']; + _buildVersion = json['buildVersion']; + _createTime = json['createTime']; + } + String? _id; + String? _platform; + String? _version; + bool? _forceUpdate; + String? _appType; + bool? _review; + bool? _patch; + String? _updateDescribe; + String? _updateWorshipDescribe; + String? _downloadUrl; + num? _buildVersion; + num? _createTime; + + String? get id => _id; + String? get platform => _platform; + String? get version => _version; + bool? get forceUpdate => _forceUpdate; + String? get appType => _appType; + bool? get review => _review; + bool? get patch => _patch; + String? get updateDescribe => _updateDescribe; + String? get updateWorshipDescribe => _updateWorshipDescribe; + String? get downloadUrl => _downloadUrl; + num? get buildVersion => _buildVersion; + num? get createTime => _createTime; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['platform'] = _platform; + map['version'] = _version; + map['forceUpdate'] = _forceUpdate; + map['appType'] = _appType; + map['review'] = _review; + map['patch'] = _patch; + map['updateDescribe'] = _updateDescribe; + map['updateWorshipDescribe'] = _updateWorshipDescribe; + map['downloadUrl'] = _downloadUrl; + map['buildVersion'] = _buildVersion; + map['createTime'] = _createTime; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_violation_handle_res.dart b/lib/chatvibe_domain/models/res/at_violation_handle_res.dart new file mode 100644 index 0000000..ab2ed78 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_violation_handle_res.dart @@ -0,0 +1,57 @@ +/// recordId : 0 +/// bizNo : "" +/// message : "" +/// success : false +/// originalContent : "" +/// adjustedContent : "" + +class ATViolationHandleRes { + ATViolationHandleRes({ + num? recordId, + String? bizNo, + String? message, + bool? success, + String? originalContent, + String? adjustedContent,}){ + _recordId = recordId; + _bizNo = bizNo; + _message = message; + _success = success; + _originalContent = originalContent; + _adjustedContent = adjustedContent; +} + + ATViolationHandleRes.fromJson(dynamic json) { + _recordId = json['recordId']; + _bizNo = json['bizNo']; + _message = json['message']; + _success = json['success']; + _originalContent = json['originalContent']; + _adjustedContent = json['adjustedContent']; + } + num? _recordId; + String? _bizNo; + String? _message; + bool? _success; + String? _originalContent; + String? _adjustedContent; + + num? get recordId => _recordId; + String? get bizNo => _bizNo; + String? get message => _message; + bool? get success => _success; + String? get originalContent => _originalContent; + String? get adjustedContent => _adjustedContent; + + Map toJson() { + final map = {}; + map['recordId'] = _recordId; + map['bizNo'] = _bizNo; + map['message'] = _message; + map['success'] = _success; + map['originalContent'] = _originalContent; + map['adjustedContent'] = _adjustedContent; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_web_pay_commodity_res.dart b/lib/chatvibe_domain/models/res/at_web_pay_commodity_res.dart new file mode 100644 index 0000000..a01732e --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_web_pay_commodity_res.dart @@ -0,0 +1,325 @@ +/// application : {"id":0,"appName":"string","appCode":"string","androidLink":"string","iosLink":"string"} +/// commodity : [{"id":0,"applicationId":0,"payCountryId":0,"content":"string","awardContent":"string","amountUsd":0,"amount":0,"currency":"string"}] +/// channels : [{"channel":{"id":0,"channelCode":"string","channelName":"string","channelIcon":"string","channelType":"string"},"details":{"id":0,"channelCode":"string","factoryMinLimit":0,"factoryMaxLimit":0,"factoryDailyLimit":0,"suggestScore":0,"factoryChannel":"string"}}] + +class ATWebPayCommodityRes { + WebPayCommodityRes({ + Application? application, + List? commodity, + List? channels,}){ + _application = application; + _commodity = commodity; + _channels = channels; +} + + ATWebPayCommodityRes.fromJson(dynamic json) { + _application = json['application'] != null ? Application.fromJson(json['application']) : null; + if (json['commodity'] != null) { + _commodity = []; + json['commodity'].forEach((v) { + _commodity?.add(Commodity.fromJson(v)); + }); + } + if (json['channels'] != null) { + _channels = []; + json['channels'].forEach((v) { + _channels?.add(Channels.fromJson(v)); + }); + } + } + Application? _application; + List? _commodity; + List? _channels; + + Application? get application => _application; + List? get commodity => _commodity; + List? get channels => _channels; + + Map toJson() { + final map = {}; + if (_application != null) { + map['application'] = _application?.toJson(); + } + if (_commodity != null) { + map['commodity'] = _commodity?.map((v) => v.toJson()).toList(); + } + if (_channels != null) { + map['channels'] = _channels?.map((v) => v.toJson()).toList(); + } + return map; + } + +} + +/// channel : {"id":0,"channelCode":"string","channelName":"string","channelIcon":"string","channelType":"string"} +/// details : {"id":0,"channelCode":"string","factoryMinLimit":0,"factoryMaxLimit":0,"factoryDailyLimit":0,"suggestScore":0,"factoryChannel":"string"} + +class Channels { + Channels({ + Channel? channel, + Details? details,}){ + _channel = channel; + _details = details; +} + + Channels.fromJson(dynamic json) { + _channel = json['channel'] != null ? Channel.fromJson(json['channel']) : null; + _details = json['details'] != null ? Details.fromJson(json['details']) : null; + } + Channel? _channel; + Details? _details; + + Channel? get channel => _channel; + Details? get details => _details; + + Map toJson() { + final map = {}; + if (_channel != null) { + map['channel'] = _channel?.toJson(); + } + if (_details != null) { + map['details'] = _details?.toJson(); + } + return map; + } + +} + +/// id : 0 +/// channelCode : "string" +/// factoryMinLimit : 0 +/// factoryMaxLimit : 0 +/// factoryDailyLimit : 0 +/// suggestScore : 0 +/// factoryChannel : "string" + +class Details { + Details({ + String? id, + String? channelCode, + num? factoryMinLimit, + num? factoryMaxLimit, + num? factoryDailyLimit, + num? suggestScore, + String? factoryChannel,}){ + _id = id; + _channelCode = channelCode; + _factoryMinLimit = factoryMinLimit; + _factoryMaxLimit = factoryMaxLimit; + _factoryDailyLimit = factoryDailyLimit; + _suggestScore = suggestScore; + _factoryChannel = factoryChannel; +} + + Details.fromJson(dynamic json) { + _id = json['id']; + _channelCode = json['channelCode']; + _factoryMinLimit = json['factoryMinLimit']; + _factoryMaxLimit = json['factoryMaxLimit']; + _factoryDailyLimit = json['factoryDailyLimit']; + _suggestScore = json['suggestScore']; + _factoryChannel = json['factoryChannel']; + } + String? _id; + String? _channelCode; + num? _factoryMinLimit; + num? _factoryMaxLimit; + num? _factoryDailyLimit; + num? _suggestScore; + String? _factoryChannel; + + String? get id => _id; + String? get channelCode => _channelCode; + num? get factoryMinLimit => _factoryMinLimit; + num? get factoryMaxLimit => _factoryMaxLimit; + num? get factoryDailyLimit => _factoryDailyLimit; + num? get suggestScore => _suggestScore; + String? get factoryChannel => _factoryChannel; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['channelCode'] = _channelCode; + map['factoryMinLimit'] = _factoryMinLimit; + map['factoryMaxLimit'] = _factoryMaxLimit; + map['factoryDailyLimit'] = _factoryDailyLimit; + map['suggestScore'] = _suggestScore; + map['factoryChannel'] = _factoryChannel; + return map; + } + +} + +/// id : 0 +/// channelCode : "string" +/// channelName : "string" +/// channelIcon : "string" +/// channelType : "string" + +class Channel { + Channel({ + String? id, + String? channelCode, + String? channelName, + String? channelIcon, + String? channelType,}){ + _id = id; + _channelCode = channelCode; + _channelName = channelName; + _channelIcon = channelIcon; + _channelType = channelType; +} + + Channel.fromJson(dynamic json) { + _id = json['id']; + _channelCode = json['channelCode']; + _channelName = json['channelName']; + _channelIcon = json['channelIcon']; + _channelType = json['channelType']; + } + String? _id; + String? _channelCode; + String? _channelName; + String? _channelIcon; + String? _channelType; + + String? get id => _id; + String? get channelCode => _channelCode; + String? get channelName => _channelName; + String? get channelIcon => _channelIcon; + String? get channelType => _channelType; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['channelCode'] = _channelCode; + map['channelName'] = _channelName; + map['channelIcon'] = _channelIcon; + map['channelType'] = _channelType; + return map; + } + +} + +/// id : 0 +/// applicationId : 0 +/// payCountryId : 0 +/// content : "string" +/// awardContent : "string" +/// amountUsd : 0 +/// amount : 0 +/// currency : "string" + +class Commodity { + Commodity({ + String? id, + String? applicationId, + String? payCountryId, + String? content, + String? awardContent, + num? amountUsd, + num? amount, + String? currency,}){ + _id = id; + _applicationId = applicationId; + _payCountryId = payCountryId; + _content = content; + _awardContent = awardContent; + _amountUsd = amountUsd; + _amount = amount; + _currency = currency; +} + + Commodity.fromJson(dynamic json) { + _id = json['id']; + _applicationId = json['applicationId']; + _payCountryId = json['payCountryId']; + _content = json['content']; + _awardContent = json['awardContent']; + _amountUsd = json['amountUsd']; + _amount = json['amount']; + _currency = json['currency']; + } + String? _id; + String? _applicationId; + String? _payCountryId; + String? _content; + String? _awardContent; + num? _amountUsd; + num? _amount; + String? _currency; + + String? get id => _id; + String? get applicationId => _applicationId; + String? get payCountryId => _payCountryId; + String? get content => _content; + String? get awardContent => _awardContent; + num? get amountUsd => _amountUsd; + num? get amount => _amount; + String? get currency => _currency; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['applicationId'] = _applicationId; + map['payCountryId'] = _payCountryId; + map['content'] = _content; + map['awardContent'] = _awardContent; + map['amountUsd'] = _amountUsd; + map['amount'] = _amount; + map['currency'] = _currency; + return map; + } + +} + +/// id : 0 +/// appName : "string" +/// appCode : "string" +/// androidLink : "string" +/// iosLink : "string" + +class Application { + Application({ + String? id, + String? appName, + String? appCode, + String? androidLink, + String? iosLink,}){ + _id = id; + _appName = appName; + _appCode = appCode; + _androidLink = androidLink; + _iosLink = iosLink; +} + + Application.fromJson(dynamic json) { + _id = json['id']; + _appName = json['appName']; + _appCode = json['appCode']; + _androidLink = json['androidLink']; + _iosLink = json['iosLink']; + } + String? _id; + String? _appName; + String? _appCode; + String? _androidLink; + String? _iosLink; + + String? get id => _id; + String? get appName => _appName; + String? get appCode => _appCode; + String? get androidLink => _androidLink; + String? get iosLink => _iosLink; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['appName'] = _appName; + map['appCode'] = _appCode; + map['androidLink'] = _androidLink; + map['iosLink'] = _iosLink; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_web_pay_recharge_res.dart b/lib/chatvibe_domain/models/res/at_web_pay_recharge_res.dart new file mode 100644 index 0000000..c77887e --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_web_pay_recharge_res.dart @@ -0,0 +1,64 @@ +/// tradeNo : "string" +/// orderId : "string" +/// factoryCode : "string" +/// factoryChannelCode : "string" +/// currency : "string" +/// countryCode : "string" +/// requestUrl : "string" + +class ATWebPayRechargeRes { + ATWebPayRechargeRes({ + String? tradeNo, + String? orderId, + String? factoryCode, + String? factoryChannelCode, + String? currency, + String? countryCode, + String? requestUrl,}){ + _tradeNo = tradeNo; + _orderId = orderId; + _factoryCode = factoryCode; + _factoryChannelCode = factoryChannelCode; + _currency = currency; + _countryCode = countryCode; + _requestUrl = requestUrl; +} + + ATWebPayRechargeRes.fromJson(dynamic json) { + _tradeNo = json['tradeNo']; + _orderId = json['orderId']; + _factoryCode = json['factoryCode']; + _factoryChannelCode = json['factoryChannelCode']; + _currency = json['currency']; + _countryCode = json['countryCode']; + _requestUrl = json['requestUrl']; + } + String? _tradeNo; + String? _orderId; + String? _factoryCode; + String? _factoryChannelCode; + String? _currency; + String? _countryCode; + String? _requestUrl; + + String? get tradeNo => _tradeNo; + String? get orderId => _orderId; + String? get factoryCode => _factoryCode; + String? get factoryChannelCode => _factoryChannelCode; + String? get currency => _currency; + String? get countryCode => _countryCode; + String? get requestUrl => _requestUrl; + + Map toJson() { + final map = {}; + map['tradeNo'] = _tradeNo; + map['orderId'] = _orderId; + map['factoryCode'] = _factoryCode; + map['factoryChannelCode'] = _factoryChannelCode; + map['currency'] = _currency; + map['countryCode'] = _countryCode; + map['requestUrl'] = _requestUrl; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/at_web_pay_user_profile_res.dart b/lib/chatvibe_domain/models/res/at_web_pay_user_profile_res.dart new file mode 100644 index 0000000..7a06f95 --- /dev/null +++ b/lib/chatvibe_domain/models/res/at_web_pay_user_profile_res.dart @@ -0,0 +1,93 @@ +import 'country_res.dart'; +import 'login_res.dart'; + +/// userProfile : {"id":0,"userAvatar":"string","userNickname":"string","account":"string","sysOrigin":"string"} +/// countryList : [{"id":0,"countryId":0,"country":{"id":0,"alphaTwo":"string","alphaThree":"string","countryNumeric":0,"countryName":"string","aliasName":"string","assignment":"string","nationalFlag":"string","sort":0,"open":true,"top":true,"phonePrefix":0,"createTime":0,"updateTime":0,"createUser":0,"updateUser":0}}] +/// regionId : "string" + +class ATWebPayUserProfileRes { + ATWebPayUserProfileRes({ + ChatVibeUserProfile? userProfile, + List? countryList, + String? regionId, + }) { + _userProfile = userProfile; + _countryList = countryList; + _regionId = regionId; + } + + ATWebPayUserProfileRes.fromJson(dynamic json) { + _userProfile = + json['userProfile'] != null + ? ChatVibeUserProfile.fromJson(json['userProfile']) + : null; + if (json['countryList'] != null) { + _countryList = []; + json['countryList'].forEach((v) { + _countryList?.add(CountryList.fromJson(v)); + }); + } + _regionId = json['regionId']; + } + + ChatVibeUserProfile? _userProfile; + List? _countryList; + String? _regionId; + + ChatVibeUserProfile? get userProfile => _userProfile; + + List? get countryList => _countryList; + + String? get regionId => _regionId; + + Map toJson() { + final map = {}; + if (_userProfile != null) { + map['userProfile'] = _userProfile?.toJson(); + } + if (_countryList != null) { + map['countryList'] = _countryList?.map((v) => v.toJson()).toList(); + } + map['regionId'] = _regionId; + return map; + } +} + +/// id : 0 +/// countryId : 0 +/// country : {"id":0,"alphaTwo":"string","alphaThree":"string","countryNumeric":0,"countryName":"string","aliasName":"string","assignment":"string","nationalFlag":"string","sort":0,"open":true,"top":true,"phonePrefix":0,"createTime":0,"updateTime":0,"createUser":0,"updateUser":0} + +class CountryList { + CountryList({String? id, String? countryId, Country? country}) { + _id = id; + _countryId = countryId; + _country = country; + } + + CountryList.fromJson(dynamic json) { + _id = json['id']; + _countryId = json['countryId']; + _country = + json['country'] != null ? Country.fromJson(json['country']) : null; + } + + String? _id; + String? _countryId; + Country? _country; + + String? get id => _id; + + String? get countryId => _countryId; + + Country? get country => _country; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['countryId'] = _countryId; + if (_country != null) { + map['country'] = _country?.toJson(); + } + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/badge_backpack_res.dart b/lib/chatvibe_domain/models/res/badge_backpack_res.dart new file mode 100644 index 0000000..d5b7bee --- /dev/null +++ b/lib/chatvibe_domain/models/res/badge_backpack_res.dart @@ -0,0 +1,38 @@ +import 'package:aslan/chatvibe_domain/models/res/at_material_badge_res.dart'; + +/// id : 0 +/// badgeConfig : {"id":0,"badgeLevel":0,"milestone":0,"badgeName":"","type":"","badgeKey":"","selectUrl":"","notSelectUrl":"","animationUrl":""} + +class BadgeBackpackRes { + BadgeBackpackRes({String? id, BadgeConfig? badgeConfig}) { + _id = id; + _badgeConfig = badgeConfig; + } + + BadgeBackpackRes.fromJson(dynamic json) { + _id = json['id']; + _badgeConfig = + json['badgeConfig'] != null + ? BadgeConfig.fromJson(json['badgeConfig']) + : null; + } + + String? _id; + BadgeConfig? _badgeConfig; + + BadgeBackpackRes copyWith({String? id, BadgeConfig? badgeConfig}) => + BadgeBackpackRes(id: id ?? _id, badgeConfig: badgeConfig ?? _badgeConfig); + + String? get id => _id; + + BadgeConfig? get badgeConfig => _badgeConfig; + + Map toJson() { + final map = {}; + map['id'] = _id; + if (_badgeConfig != null) { + map['badgeConfig'] = _badgeConfig?.toJson(); + } + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/bags_list_res.dart b/lib/chatvibe_domain/models/res/bags_list_res.dart new file mode 100644 index 0000000..05d22f9 --- /dev/null +++ b/lib/chatvibe_domain/models/res/bags_list_res.dart @@ -0,0 +1,86 @@ +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +/// userId : "" +/// type : "" +/// expireTime : 0 +/// useProps : false +/// propsResources : {"id":"","type":"","code":"","name":"","cover":"","sourceUrl":"","expand":"","amount":0.0} +/// allowGive : false + +class BagsListRes { + BagsListRes({ + String? userId, + String? type, + int? expireTime, + bool? useProps, + PropsResources? propsResources, + bool? allowGive, + }) { + _userId = userId; + _type = type; + _expireTime = expireTime; + _useProps = useProps; + _propsResources = propsResources; + _allowGive = allowGive; + } + + BagsListRes.fromJson(dynamic json) { + _userId = json['userId']; + _type = json['type']; + _expireTime = json['expireTime']; + _useProps = json['useProps']; + _propsResources = + json['propsResources'] != null + ? PropsResources.fromJson(json['propsResources']) + : null; + _allowGive = json['allowGive']; + } + + String? _userId; + String? _type; + int? _expireTime; + bool? _useProps; + PropsResources? _propsResources; + bool? _allowGive; + + BagsListRes copyWith({ + String? userId, + String? type, + int? expireTime, + bool? useProps, + PropsResources? propsResources, + bool? allowGive, + }) => BagsListRes( + userId: userId ?? _userId, + type: type ?? _type, + expireTime: expireTime ?? _expireTime, + useProps: useProps ?? _useProps, + propsResources: propsResources ?? _propsResources, + allowGive: allowGive ?? _allowGive, + ); + + String? get userId => _userId; + + String? get type => _type; + + int? get expireTime => _expireTime; + + bool? get useProps => _useProps; + + PropsResources? get propsResources => _propsResources; + + bool? get allowGive => _allowGive; + + Map toJson() { + final map = {}; + map['userId'] = _userId; + map['type'] = _type; + map['expireTime'] = _expireTime; + map['useProps'] = _useProps; + if (_propsResources != null) { + map['propsResources'] = _propsResources?.toJson(); + } + map['allowGive'] = _allowGive; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/broad_cast_mic_change_push.dart b/lib/chatvibe_domain/models/res/broad_cast_mic_change_push.dart new file mode 100644 index 0000000..25dbaa4 --- /dev/null +++ b/lib/chatvibe_domain/models/res/broad_cast_mic_change_push.dart @@ -0,0 +1,75 @@ +/// data : {"mics":[{"micIndex":0,"micLock":false,"micMute":false,"roomId":"1954021581748412417"},{"micIndex":1,"micLock":false,"micMute":false,"roomId":"1954021581748412417"},{"micIndex":2,"micLock":false,"micMute":false,"roomId":"1954021581748412417","user":{"account":"8826517","charmLevel":17,"cpUserId":"0","heartbeatVal":0,"id":"1954021558348390402","roles":"HOMEOWNER","useProps":[{"propsResources":{"sourceUrl":"https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/svgasource/manager-f77b1efa-ae00-4935-872d-cbb5db3d2fb0.svga","type":"AVATAR_FRAME"}},{"propsResources":{"code":"10026","cover":"https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/svga_cover/manager-c0508d8c-8393-46e8-84c2-3319be893ba7.png","type":"NOBLE_VIP"}}],"userAvatar":"https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/avatar/750355c0-cfa7-4447-b453-f622bd2e3859.jpg","userNickname":"JacksoUI","userSex":1}},{"micIndex":3,"micLock":false,"micMute":false,"roomId":"1954021581748412417"},{"micIndex":4,"micLock":false,"micMute":false,"roomId":"1954021581748412417"},{"micIndex":5,"micLock":false,"micMute":false,"roomId":"1954021581748412417"},{"micIndex":6,"micLock":false,"micMute":false,"roomId":"1954021581748412417"},{"micIndex":7,"micLock":false,"micMute":true,"roomId":"1954021581748412417"},{"micIndex":8,"micLock":false,"micMute":false,"roomId":"1954021581748412417"},{"micIndex":9,"micLock":false,"micMute":true,"roomId":"1954021581748412417"}],"timeId":"1968592383197442050"} +/// reqTraceId : "GL-f45a6e89405a2fff611dbb94f02a8d84" +/// type : "MIC_CHANGE" + +import 'package:aslan/chatvibe_domain/models/res/mic_res.dart'; + +class BroadCastMicChangePush { + BroadCastMicChangePush({ + Data? data, + String? reqTraceId, + String? type,}){ + _data = data; + _reqTraceId = reqTraceId; + _type = type; +} + + BroadCastMicChangePush.fromJson(dynamic json) { + _data = json['data'] != null ? Data.fromJson(json['data']) : null; + _reqTraceId = json['reqTraceId']; + _type = json['type']; + } + Data? _data; + String? _reqTraceId; + String? _type; + Data? get data => _data; + String? get reqTraceId => _reqTraceId; + String? get type => _type; + + Map toJson() { + final map = {}; + if (_data != null) { + map['data'] = _data?.toJson(); + } + map['reqTraceId'] = _reqTraceId; + map['type'] = _type; + return map; + } + +} + +/// mics : [{"micIndex":0,"micLock":false,"micMute":false,"roomId":"1954021581748412417"},{"micIndex":1,"micLock":false,"micMute":false,"roomId":"1954021581748412417"},{"micIndex":2,"micLock":false,"micMute":false,"roomId":"1954021581748412417","user":{"account":"8826517","charmLevel":17,"cpUserId":"0","heartbeatVal":0,"id":"1954021558348390402","roles":"HOMEOWNER","useProps":[{"propsResources":{"sourceUrl":"https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/svgasource/manager-f77b1efa-ae00-4935-872d-cbb5db3d2fb0.svga","type":"AVATAR_FRAME"}},{"propsResources":{"code":"10026","cover":"https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/svga_cover/manager-c0508d8c-8393-46e8-84c2-3319be893ba7.png","type":"NOBLE_VIP"}}],"userAvatar":"https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/avatar/750355c0-cfa7-4447-b453-f622bd2e3859.jpg","userNickname":"JacksoUI","userSex":1}},{"micIndex":3,"micLock":false,"micMute":false,"roomId":"1954021581748412417"},{"micIndex":4,"micLock":false,"micMute":false,"roomId":"1954021581748412417"},{"micIndex":5,"micLock":false,"micMute":false,"roomId":"1954021581748412417"},{"micIndex":6,"micLock":false,"micMute":false,"roomId":"1954021581748412417"},{"micIndex":7,"micLock":false,"micMute":true,"roomId":"1954021581748412417"},{"micIndex":8,"micLock":false,"micMute":false,"roomId":"1954021581748412417"},{"micIndex":9,"micLock":false,"micMute":true,"roomId":"1954021581748412417"}] +/// timeId : "1968592383197442050" + +class Data { + Data({ + List? mics, + String? timeId,}){ + _mics = mics; + _timeId = timeId; +} + + Data.fromJson(dynamic json) { + if (json['mics'] != null) { + _mics = []; + json['mics'].forEach((v) { + _mics?.add(MicRes.fromJson(v)); + }); + } + _timeId = json['timeId']; + } + List? _mics; + String? _timeId; + List? get mics => _mics; + String? get timeId => _timeId; + + Map toJson() { + final map = {}; + if (_mics != null) { + map['mics'] = _mics?.map((v) => v.toJson()).toList(); + } + map['timeId'] = _timeId; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/country_res.dart b/lib/chatvibe_domain/models/res/country_res.dart new file mode 100644 index 0000000..858356e --- /dev/null +++ b/lib/chatvibe_domain/models/res/country_res.dart @@ -0,0 +1,166 @@ +/// openCountry : [{"aliasName":"India","alphaThree":"IND","alphaTwo":"IN","countryName":"India","enName":"India","id":"1231833304232112130","nationalFlag":"https://dev.file.momooline.com/other/manager-2d956fa5-3a17-4af7-95c4-42d4b4f1161c.png","open":true,"phonePrefix":91,"top":false},{"aliasName":"Iran","alphaThree":"IRN","alphaTwo":"IR","countryName":"Iran, Islamic Republic of","enName":"Iran","id":"1231833306144714754","nationalFlag":"https://dev.file.momooline.com/other/manager-108be79c-0c8e-4b19-9147-fb2514e1828e.png","open":true,"phonePrefix":98,"top":false},{"aliasName":"Italy","alphaThree":"ITA","alphaTwo":"IT","countryName":"Italy","enName":"Italy","id":"1231833307491086337","nationalFlag":"https://dev.file.momooline.com/other/manager-0d7a19f5-e2e8-4b22-839f-791782112db0.png","open":true,"phonePrefix":39,"top":false},{"aliasName":"UAE 01- Abu Dhabi","alphaThree":"ARE","alphaTwo":"AE","countryName":"UAE 01- Abu Dhabi","enName":"UAE 01- Abu Dhabi","id":"1231833232115249154","nationalFlag":"https://dev.file.momooline.com/other/manager-41ffec46-d569-4235-89c8-a42f928b3f71.png","open":true,"phonePrefix":971,"top":false},{"aliasName":"Kuwait","alphaThree":"KWT","alphaTwo":"KW","countryName":"Kuwait","enName":"Kuwait","id":"1231833316571754497","nationalFlag":"https://dev.file.momooline.com/other/manager-9d7c61a8-c0a4-4923-9688-1f3eb65e1083.png","open":true,"phonePrefix":965,"top":false},{"aliasName":"Sri Lanka","alphaThree":"LKA","alphaTwo":"LK","countryName":"Sri Lanka","enName":"Sri Lanka","id":"1231833320958996482","nationalFlag":"https://dev.file.momooline.com/other/manager-369085b4-d012-4d49-b2df-85948ddd73d2.png","open":true,"phonePrefix":94,"top":false}] +/// tops : [{"aliasName":"KSA 02- Jeddah","alphaThree":"SAU","alphaTwo":"SP","countryName":"KSA 02- Jeddah","enName":"KSA 02- Jeddah","id":"1231833361190760450","nationalFlag":"https://dev.file.momooline.com/other/manager-5b5277f2-6ea7-4d2c-aa27-1435a6793905.png","open":true,"phonePrefix":966,"top":true},{"aliasName":"KSA 01- Abu Dhabi","alphaThree":"SAU","alphaTwo":"SA","countryName":"KSA 01- Abu Dhabi","enName":"KSA 01- Abu Dhabi","id":"1231833361190760449","nationalFlag":"https://dev.file.momooline.com/other/manager-5b5277f2-6ea7-4d2c-aa27-1435a6793905.png","open":true,"phonePrefix":966,"top":true},{"aliasName":"Egypt 02- Alexandria","alphaThree":"EGY","alphaTwo":"EY","countryName":"Egypt 02- Alexandria","enName":"Egypt 02- Alexandria","id":"1231833276281270275","nationalFlag":"https://dev.file.momooline.com/other/manager-2a1785b7-dd47-40ec-a1d9-18a505563dfe.png","open":true,"phonePrefix":20,"top":true},{"aliasName":"Egypt","alphaThree":"EGY","alphaTwo":"EG","countryName":"Egypt","enName":"Egypt","id":"1231833276281270274","nationalFlag":"https://dev.file.momooline.com/other/manager-2a1785b7-dd47-40ec-a1d9-18a505563dfe.png","open":true,"phonePrefix":20,"top":true}] + +class CountryRes { + CountryRes({ + List? openCountry, + List? tops,}){ + _openCountry = openCountry; + _tops = tops; + } + CountryRes.fromJson(dynamic json) { + if (json['openCountry'] != null) { + _openCountry = []; + json['openCountry'].forEach((v) { + _openCountry?.add(Country.fromJson(v)); + }); + } + if (json['tops'] != null) { + _tops = []; + json['tops'].forEach((v) { + _tops?.add(Country.fromJson(v)); + }); + } + } + List? _openCountry; + List? _tops; + CountryRes copyWith({ List? openCountry, + List? tops, + }) => CountryRes( openCountry: openCountry ?? _openCountry, + tops: tops ?? _tops, + ); + List? get openCountry => _openCountry; + List? get tops => _tops; + + Map toJson() { + final map = {}; + if (_openCountry != null) { + map['openCountry'] = _openCountry?.map((v) => v.toJson()).toList(); + } + if (_tops != null) { + map['tops'] = _tops?.map((v) => v.toJson()).toList(); + } + return map; + } + +} + +/// aliasName : "KSA 02- Jeddah" +/// alphaThree : "SAU" +/// alphaTwo : "SP" +/// countryName : "KSA 02- Jeddah" +/// enName : "KSA 02- Jeddah" +/// id : "1231833361190760450" +/// nationalFlag : "https://dev.file.momooline.com/other/manager-5b5277f2-6ea7-4d2c-aa27-1435a6793905.png" +/// open : true +/// phonePrefix : 966 +/// top : true + + +class Country { + Country({ + String? aliasName, + String? alphaThree, + String? alphaTwo, + String? countryName, + String? countryCode, + String? enName, + String? id, + String? nationalFlag, + bool? open, + num? phonePrefix, + bool isSelect = false, + bool? top,}){ + _aliasName = aliasName; + _alphaThree = alphaThree; + _alphaTwo = alphaTwo; + _countryName = countryName; + _countryCode = countryCode; + _enName = enName; + _id = id; + _nationalFlag = nationalFlag; + _open = open; + _phonePrefix = phonePrefix; + _top = top; + _isSelect = isSelect; + } + + Country.fromJson(dynamic json) { + _aliasName = json['aliasName']; + _alphaThree = json['alphaThree']; + _alphaTwo = json['alphaTwo']; + _countryName = json['countryName']; + _countryCode = json['countryCode']; + _enName = json['enName']; + _id = json['id']; + _nationalFlag = json['nationalFlag']; + _open = json['open']; + _phonePrefix = json['phonePrefix']; + _top = json['top']; + } + String? _aliasName; + String? _alphaThree; + String? _alphaTwo; + String? _countryName; + String? _countryCode; + String? _enName; + String? _id; + String? _nationalFlag; + bool? _open; + num? _phonePrefix; + bool? _top; + bool _isSelect = false; + Country copyWith({ String? aliasName, + String? alphaThree, + String? alphaTwo, + String? countryName, + String? countryCode, + String? enName, + String? id, + String? nationalFlag, + bool? open, + num? phonePrefix, + bool? top, + }) => Country( aliasName: aliasName ?? _aliasName, + alphaThree: alphaThree ?? _alphaThree, + alphaTwo: alphaTwo ?? _alphaTwo, + countryName: countryName ?? _countryName, + countryCode: countryCode ?? _countryCode, + enName: enName ?? _enName, + id: id ?? _id, + nationalFlag: nationalFlag ?? _nationalFlag, + open: open ?? _open, + phonePrefix: phonePrefix ?? _phonePrefix, + top: top ?? _top, + ); + String? get aliasName => _aliasName; + String? get alphaThree => _alphaThree; + String? get alphaTwo => _alphaTwo; + String? get countryName => _countryName; + String? get countryCode => _countryCode; + String? get enName => _enName; + String? get id => _id; + String? get nationalFlag => _nationalFlag; + bool? get open => _open; + num? get phonePrefix => _phonePrefix; + bool? get top => _top; + + + + Map toJson() { + final map = {}; + map['aliasName'] = _aliasName; + map['alphaThree'] = _alphaThree; + map['alphaTwo'] = _alphaTwo; + map['countryName'] = _countryName; + map['countryCode'] = _countryCode; + map['enName'] = _enName; + map['id'] = _id; + map['nationalFlag'] = _nationalFlag; + map['open'] = _open; + map['phonePrefix'] = _phonePrefix; + map['top'] = _top; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/create_dynamic_res.dart b/lib/chatvibe_domain/models/res/create_dynamic_res.dart new file mode 100644 index 0000000..28ecb60 --- /dev/null +++ b/lib/chatvibe_domain/models/res/create_dynamic_res.dart @@ -0,0 +1,36 @@ +/// dynamicId : 2018251459137736706 +/// content : 123456 +/// createTime : 1770023619815 + +class ChatVibeCreateDynamicRes { + ChatVibeCreateDynamicRes({ + String? dynamicId, + String? content, + int? createTime,}){ + _dynamicId = dynamicId; + _content = content; + _createTime = createTime; +} + + ChatVibeCreateDynamicRes.fromJson(dynamic json) { + _dynamicId = json['dynamicId']; + _content = json['content']; + _createTime = json['createTime']; + } + String? _dynamicId; + String? _content; + int? _createTime; + + String? get dynamicId => _dynamicId; + String? get content => _content; + int? get createTime => _createTime; + + Map toJson() { + final map = {}; + map['dynamicId'] = _dynamicId; + map['content'] = _content; + map['createTime'] = _createTime; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/dynamic_list_res.dart b/lib/chatvibe_domain/models/res/dynamic_list_res.dart new file mode 100644 index 0000000..dac534b --- /dev/null +++ b/lib/chatvibe_domain/models/res/dynamic_list_res.dart @@ -0,0 +1,391 @@ +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +/// records : [{"extValues":{"":{}},"dynamicId":0,"userAvatar":"","userNickname":"","userSex":0,"userId":0,"tag":"","tagId":0,"content":"","pictures":[{"extValues":{"":{}},"id":0,"dynamicId":0,"resourceUrl":"","miniResourceUrl":"","width":0,"height":0,"sort":0,"violation":""}],"subscription":false,"like":false,"commentStrQuantity":"","likeStrQuantity":"","createTime":0,"userProfile":{"id":0,"account":"","userAvatar":"","userNickname":"","userSex":0,"age":0,"accountStatus":"","freezingTime":0,"isUpdateCountry":"","countryId":0,"countryName":"","countryCode":"","autograph":"","hobby":"","cpList":[{"meUserId":0,"meAccount":"","meUserAvatar":"","meUserNickname":"","cpUserId":0,"cpAccount":"","cpUserAvatar":"","cpUserNickname":"","cpValId":0,"cpValue":0.0,"days":0,"firstDay":"","createTime":0}],"isCpRelation":false,"familyId":0,"backgroundPhotos":[{"url":"","status":0,"rejectReason":"","createTime":0}],"personalPhotos":[{"url":"","status":0,"rejectReason":"","createTime":0}],"inRoomId":"","roomIcon":"","regionCode":"","wealthLevel":0,"charmLevel":0,"originSys":"","sysOriginChild":"","del":false,"createTime":0,"bornYear":0,"bornMonth":0,"bornDay":0,"ownSpecialId":{"account":"","expiredTime":0},"useProps":[{"userId":0,"propsResources":{"id":0,"type":"","code":"","name":"","cover":"","sourceUrl":"","expand":"","amount":0.0},"expireTime":0}],"sameRegion":false,"isFreightAgent":false,"isSuperFreightAgent":false,"firstRecharge":false,"firstRechargeEndTime":0,"wearBadge":[{"id":0,"userId":0,"badgeLevel":0,"milestone":0,"badgeName":"","type":"","use":false,"badgeKey":"","selectUrl":"","notSelectUrl":"","animationUrl":"","expireTime":0}],"wearHonor":[{"id":0,"userId":0,"badgeLevel":0,"milestone":0,"badgeName":"","type":"","use":false,"badgeKey":"","selectUrl":"","notSelectUrl":"","animationUrl":"","expireTime":0}]},"top":false}] +/// total : 0 +/// size : 0 +/// current : 0 + +class ChatVibeDynamicListRes { + ChatVibeDynamicListRes({ + List? records, + num? total, + num? size, + num? current, + }) { + _records = records; + _total = total; + _size = size; + _current = current; + } + + ChatVibeDynamicListRes.fromJson(dynamic json) { + if (json['records'] != null) { + _records = []; + json['records'].forEach((v) { + _records?.add(Records.fromJson(v)); + }); + } + _total = json['total']; + _size = json['size']; + _current = json['current']; + } + + List? _records; + num? _total; + num? _size; + num? _current; + + List? get records => _records; + + num? get total => _total; + + num? get size => _size; + + num? get current => _current; + + Map toJson() { + final map = {}; + if (_records != null) { + map['records'] = _records?.map((v) => v.toJson()).toList(); + } + map['total'] = _total; + map['size'] = _size; + map['current'] = _current; + return map; + } +} + +/// extValues : {"":{}} +/// dynamicId : 0 +/// userAvatar : "" +/// userNickname : "" +/// userSex : 0 +/// userId : 0 +/// tag : "" +/// tagId : 0 +/// content : "" +/// pictures : [{"extValues":{"":{}},"id":0,"dynamicId":0,"resourceUrl":"","miniResourceUrl":"","width":0,"height":0,"sort":0,"violation":""}] +/// subscription : false +/// like : false +/// commentStrQuantity : "" +/// likeStrQuantity : "" +/// createTime : 0 +/// userProfile : {"id":0,"account":"","userAvatar":"","userNickname":"","userSex":0,"age":0,"accountStatus":"","freezingTime":0,"isUpdateCountry":"","countryId":0,"countryName":"","countryCode":"","autograph":"","hobby":"","cpList":[{"meUserId":0,"meAccount":"","meUserAvatar":"","meUserNickname":"","cpUserId":0,"cpAccount":"","cpUserAvatar":"","cpUserNickname":"","cpValId":0,"cpValue":0.0,"days":0,"firstDay":"","createTime":0}],"isCpRelation":false,"familyId":0,"backgroundPhotos":[{"url":"","status":0,"rejectReason":"","createTime":0}],"personalPhotos":[{"url":"","status":0,"rejectReason":"","createTime":0}],"inRoomId":"","roomIcon":"","regionCode":"","wealthLevel":0,"charmLevel":0,"originSys":"","sysOriginChild":"","del":false,"createTime":0,"bornYear":0,"bornMonth":0,"bornDay":0,"ownSpecialId":{"account":"","expiredTime":0},"useProps":[{"userId":0,"propsResources":{"id":0,"type":"","code":"","name":"","cover":"","sourceUrl":"","expand":"","amount":0.0},"expireTime":0}],"sameRegion":false,"isFreightAgent":false,"isSuperFreightAgent":false,"firstRecharge":false,"firstRechargeEndTime":0,"wearBadge":[{"id":0,"userId":0,"badgeLevel":0,"milestone":0,"badgeName":"","type":"","use":false,"badgeKey":"","selectUrl":"","notSelectUrl":"","animationUrl":"","expireTime":0}],"wearHonor":[{"id":0,"userId":0,"badgeLevel":0,"milestone":0,"badgeName":"","type":"","use":false,"badgeKey":"","selectUrl":"","notSelectUrl":"","animationUrl":"","expireTime":0}]} +/// top : false + +class Records { + Records({ + String? dynamicId, + String? giftId, + String? id, + String? type, + String? messageContent, + num? rootCommentId, + String? userAvatar, + String? giftUrl, + num? giftQuantity, + num? giftCount, + String? userNickname, + num? userSex, + String? userId, + String? tag, + num? tagId, + num? age, + String? content, + List? pictures, + bool? subscription, + bool? like, + String? commentStrQuantity, + String? likeStrQuantity, + String? giftStrQuantity, + int? createTime, + int? updateTime, + ChatVibeUserProfile? userProfile, + bool? top, + }) { + _dynamicId = dynamicId; + _giftId = giftId; + _id = id; + _type = type; + _messageContent = messageContent; + _rootCommentId = rootCommentId; + _userAvatar = userAvatar; + _giftUrl = giftUrl; + _giftQuantity = giftQuantity; + _giftCount = giftCount; + _userNickname = userNickname; + _userSex = userSex; + _age = age; + _userId = userId; + _tag = tag; + _tagId = tagId; + _content = content; + _pictures = pictures; + _subscription = subscription; + _like = like; + _commentStrQuantity = commentStrQuantity; + _likeStrQuantity = likeStrQuantity; + _giftStrQuantity = giftStrQuantity; + _createTime = createTime; + _updateTime = updateTime; + _userProfile = userProfile; + _top = top; + } + + Records.fromJson(dynamic json) { + _dynamicId = json['dynamicId']; + _giftId = json['giftId']; + _id = json['id']; + _type = json['type']; + _messageContent = json['messageContent']; + _rootCommentId = json['rootCommentId']; + _userAvatar = json['userAvatar']; + _giftUrl = json['giftUrl']; + _giftQuantity = json['giftQuantity']; + _giftCount = json['giftCount']; + _userNickname = json['userNickname']; + _userSex = json['userSex']; + _age = json['age']; + _userId = json['userId']; + _tag = json['tag']; + _tagId = json['tagId']; + _content = json['content']; + if (json['pictures'] != null) { + _pictures = []; + json['pictures'].forEach((v) { + _pictures?.add(Pictures.fromJson(v)); + }); + } + _subscription = json['subscription']; + _like = json['like']; + _commentStrQuantity = json['commentStrQuantity']; + _likeStrQuantity = json['likeStrQuantity']; + _giftStrQuantity = json['giftStrQuantity']; + _createTime = json['createTime']; + _updateTime = json['updateTime']; + _userProfile = + json['userProfile'] != null + ? ChatVibeUserProfile.fromJson(json['userProfile']) + : null; + _top = json['top']; + } + + String? _dynamicId; + num? _rootCommentId; + String? _giftId; + String? _id; + String? _type; + String? _messageContent; + String? _userAvatar; + String? _giftUrl; + num? _giftQuantity; + num? _giftCount; + String? _userNickname; + num? _userSex; + num? _age; + String? _userId; + String? _tag; + num? _tagId; + String? _content; + List? _pictures; + bool? _subscription; + bool? _like; + String? _commentStrQuantity; + String? _likeStrQuantity; + String? _giftStrQuantity; + int? _createTime; + int? _updateTime; + ChatVibeUserProfile? _userProfile; + bool? _top; + + String? get dynamicId => _dynamicId; + + String? get giftId => _giftId; + + String? get id => _id; + + String? get type => _type; + + String? get messageContent => _messageContent; + + num? get rootCommentId => _rootCommentId; + + String? get userAvatar => _userAvatar; + + String? get giftUrl => _giftUrl; + + num? get giftQuantity => _giftQuantity; + + num? get giftCount => _giftCount; + + String? get userNickname => _userNickname; + + num? get userSex => _userSex; + + num? get age => (_age ?? 0) < 1 ? 18 : _age; + + String? get userId => _userId; + + String? get tag => _tag; + + num? get tagId => _tagId; + + String? get content => _content; + + List? get pictures => _pictures; + + bool? get subscription => _subscription; + + bool? get like => _like; + + String? get commentStrQuantity => _commentStrQuantity; + + String? get likeStrQuantity => _likeStrQuantity; + + String? get giftStrQuantity => _giftStrQuantity; + + int? get createTime => _createTime; + + int? get updateTime => _updateTime; + + ChatVibeUserProfile? get userProfile => _userProfile; + + bool? get top => _top; + + setSubscription(bool subscription) { + _subscription = subscription; + } + + setLike(bool like) { + _like = like; + } + + setCommentStrQuantity(String nm) { + _commentStrQuantity = nm; + } + + setLikeStrQuantity(String nm) { + _likeStrQuantity = nm; + } + + setGiftStrQuantity(String nm) { + _giftStrQuantity = nm; + } + + Map toJson() { + final map = {}; + map['dynamicId'] = _dynamicId; + map['giftId'] = _giftId; + map['id'] = _id; + map['type'] = _type; + map['messageContent'] = _messageContent; + map['rootCommentId'] = _rootCommentId; + map['userAvatar'] = _userAvatar; + map['giftUrl'] = _giftUrl; + map['giftQuantity'] = _giftQuantity; + map['giftCount'] = _giftCount; + map['userNickname'] = _userNickname; + map['userSex'] = _userSex; + map['age'] = _age; + map['userId'] = _userId; + map['tag'] = _tag; + map['tagId'] = _tagId; + map['content'] = _content; + if (_pictures != null) { + map['pictures'] = _pictures?.map((v) => v.toJson()).toList(); + } + map['subscription'] = _subscription; + map['like'] = _like; + map['commentStrQuantity'] = _commentStrQuantity; + map['likeStrQuantity'] = _likeStrQuantity; + map['giftStrQuantity'] = _giftStrQuantity; + map['createTime'] = _createTime; + map['updateTime'] = _updateTime; + if (_userProfile != null) { + map['userProfile'] = _userProfile?.toJson(); + } + map['top'] = _top; + return map; + } +} + +/// extValues : {"":{}} +/// id : 0 +/// dynamicId : 0 +/// resourceUrl : "" +/// miniResourceUrl : "" +/// width : 0 +/// height : 0 +/// sort : 0 +/// violation : "" + +class Pictures { + Pictures({ + String? id, + String? dynamicId, + String? resourceUrl, + String? miniResourceUrl, + num? width, + num? height, + num? sort, + String? violation, + }) { + _id = id; + _dynamicId = dynamicId; + _resourceUrl = resourceUrl; + _miniResourceUrl = miniResourceUrl; + _width = width; + _height = height; + _sort = sort; + _violation = violation; + } + + Pictures.fromJson(dynamic json) { + _id = json['id']; + _dynamicId = json['dynamicId']; + _resourceUrl = json['resourceUrl']; + _miniResourceUrl = json['miniResourceUrl']; + _width = json['width']; + _height = json['height']; + _sort = json['sort']; + _violation = json['violation']; + } + + String? _id; + String? _dynamicId; + String? _resourceUrl; + String? _miniResourceUrl; + num? _width; + num? _height; + num? _sort; + String? _violation; + + String? get id => _id; + + String? get dynamicId => _dynamicId; + + String? get resourceUrl => _resourceUrl; + + String? get miniResourceUrl => _miniResourceUrl; + + num? get width => _width; + + num? get height => _height; + + num? get sort => _sort; + + String? get violation => _violation; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['dynamicId'] = _dynamicId; + map['resourceUrl'] = _resourceUrl; + map['miniResourceUrl'] = _miniResourceUrl; + map['width'] = _width; + map['height'] = _height; + map['sort'] = _sort; + map['violation'] = _violation; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/family_base_info_res.dart b/lib/chatvibe_domain/models/res/family_base_info_res.dart new file mode 100644 index 0000000..4836792 --- /dev/null +++ b/lib/chatvibe_domain/models/res/family_base_info_res.dart @@ -0,0 +1,427 @@ +/// familyId : "" +/// familyAccount : "" +/// familyAvatar : "" +/// familyName : "" +/// familyIntro : "" +/// familyNotice : "" +/// familyAdmin : {"userId":"","account":"","specialAccount":"","userAvatar":"","userNickName":""} +/// currentExp : "" +/// currentMember : 0 +/// familyLevel : 0 +/// levelExp : "" +/// avatarFrameCover : "" +/// avatarFrameSvg : "" +/// badgeCover : "" +/// badgeSvg : "" +/// maxMember : 0 +/// levelBackgroundPicture : "" +/// boxInfo : {"contributeCount":0,"userContributed":false,"chests":[{"level":0,"requiredCount":0,"rewardGold":0,"unlocked":false,"claimed":false}]} +/// memberList : {"hosts":[{"userId":"","userAvatar":""}],"supporters":[{"userId":"","userAvatar":""}]} +/// amount : 0.0 + +class ChatVibeFamilyBaseInfoRes { + ChatVibeFamilyBaseInfoRes({ + String? familyId, + String? familyAccount, + String? familyAvatar, + String? familyName, + String? familyIntro, + String? familyNotice, + FamilyAdmin? familyAdmin, + String? currentExp, + num? currentMember, + int? familyLevel, + bool? joinWaiting, + String? levelExp, + String? avatarFrameCover, + String? avatarFrameSvg, + String? badgeCover, + String? badgeSvg, + String? myFamilyRole, + num? maxMember, + String? levelBackgroundPicture, + BoxInfo? boxInfo, + MemberList? memberList, + num? amount,}){ + _familyId = familyId; + _familyAccount = familyAccount; + _familyAvatar = familyAvatar; + _familyName = familyName; + _familyIntro = familyIntro; + _familyNotice = familyNotice; + _familyAdmin = familyAdmin; + _currentExp = currentExp; + _currentMember = currentMember; + _familyLevel = familyLevel; + _levelExp = levelExp; + _joinWaiting = joinWaiting; + _avatarFrameCover = avatarFrameCover; + _avatarFrameSvg = avatarFrameSvg; + _badgeCover = badgeCover; + _badgeSvg = badgeSvg; + _myFamilyRole = myFamilyRole; + _maxMember = maxMember; + _levelBackgroundPicture = levelBackgroundPicture; + _boxInfo = boxInfo; + _memberList = memberList; + _amount = amount; +} + + ChatVibeFamilyBaseInfoRes.fromJson(dynamic json) { + _familyId = json['familyId']; + _familyAccount = json['familyAccount']; + _familyAvatar = json['familyAvatar']; + _familyName = json['familyName']; + _familyIntro = json['familyIntro']; + _familyNotice = json['familyNotice']; + _familyAdmin = json['familyAdmin'] != null ? FamilyAdmin.fromJson(json['familyAdmin']) : null; + _currentExp = json['currentExp']; + _currentMember = json['currentMember']; + _familyLevel = json['familyLevel']; + _levelExp = json['levelExp']; + _joinWaiting = json['joinWaiting']; + _avatarFrameCover = json['avatarFrameCover']; + _avatarFrameSvg = json['avatarFrameSvg']; + _badgeCover = json['badgeCover']; + _badgeSvg = json['badgeSvg']; + _myFamilyRole = json['myFamilyRole']; + _maxMember = json['maxMember']; + _maxAdmin = json['maxAdmin']; + _levelBackgroundPicture = json['levelBackgroundPicture']; + _boxInfo = json['boxInfo'] != null ? BoxInfo.fromJson(json['boxInfo']) : null; + _memberList = json['memberList'] != null ? MemberList.fromJson(json['memberList']) : null; + _amount = json['amount']; + } + String? _familyId; + String? _familyAccount; + String? _familyAvatar; + String? _familyName; + String? _familyIntro; + String? _familyNotice; + FamilyAdmin? _familyAdmin; + String? _currentExp; + num? _currentMember; + bool? _joinWaiting; + int? _familyLevel; + String? _levelExp; + String? _avatarFrameCover; + String? _avatarFrameSvg; + String? _badgeCover; + String? _badgeSvg; + String? _myFamilyRole; + num? _maxMember; + num? _maxAdmin; + String? _levelBackgroundPicture; + BoxInfo? _boxInfo; + MemberList? _memberList; + num? _amount; + + String? get familyId => _familyId; + bool? get joinWaiting => _joinWaiting; + String? get familyAccount => _familyAccount; + String? get familyAvatar => _familyAvatar; + String? get familyName => _familyName; + String? get familyIntro => _familyIntro; + String? get familyNotice => _familyNotice; + FamilyAdmin? get familyAdmin => _familyAdmin; + String? get currentExp => _currentExp; + num? get currentMember => _currentMember; + int? get familyLevel => _familyLevel; + String? get levelExp => _levelExp; + String? get avatarFrameCover => _avatarFrameCover; + String? get avatarFrameSvg => _avatarFrameSvg; + String? get badgeCover => _badgeCover; + String? get badgeSvg => _badgeSvg; + String? get myFamilyRole => _myFamilyRole; + num? get maxMember => _maxMember; + num? get maxAdmin => _maxAdmin; + String? get levelBackgroundPicture => _levelBackgroundPicture; + BoxInfo? get boxInfo => _boxInfo; + MemberList? get memberList => _memberList; + num? get amount => _amount; + + Map toJson() { + final map = {}; + map['familyId'] = _familyId; + map['familyAccount'] = _familyAccount; + map['familyAvatar'] = _familyAvatar; + map['familyName'] = _familyName; + map['joinWaiting'] = _joinWaiting; + map['familyIntro'] = _familyIntro; + map['familyNotice'] = _familyNotice; + if (_familyAdmin != null) { + map['familyAdmin'] = _familyAdmin?.toJson(); + } + map['currentExp'] = _currentExp; + map['currentMember'] = _currentMember; + map['familyLevel'] = _familyLevel; + map['levelExp'] = _levelExp; + map['avatarFrameCover'] = _avatarFrameCover; + map['avatarFrameSvg'] = _avatarFrameSvg; + map['badgeCover'] = _badgeCover; + map['badgeSvg'] = _badgeSvg; + map['myFamilyRole'] = _myFamilyRole; + map['maxMember'] = _maxMember; + map['maxAdmin'] = _maxAdmin; + map['levelBackgroundPicture'] = _levelBackgroundPicture; + if (_boxInfo != null) { + map['boxInfo'] = _boxInfo?.toJson(); + } + if (_memberList != null) { + map['memberList'] = _memberList?.toJson(); + } + map['amount'] = _amount; + return map; + } + +} + +/// hosts : [{"userId":"","userAvatar":""}] +/// supporters : [{"userId":"","userAvatar":""}] + +class MemberList { + MemberList({ + List? hosts, + List? supporters,}){ + _hosts = hosts; + _supporters = supporters; +} + + MemberList.fromJson(dynamic json) { + if (json['hosts'] != null) { + _hosts = []; + json['hosts'].forEach((v) { + _hosts?.add(Hosts.fromJson(v)); + }); + } + if (json['supporters'] != null) { + _supporters = []; + json['supporters'].forEach((v) { + _supporters?.add(Supporters.fromJson(v)); + }); + } + } + List? _hosts; + List? _supporters; + + List? get hosts => _hosts; + List? get supporters => _supporters; + + Map toJson() { + final map = {}; + if (_hosts != null) { + map['hosts'] = _hosts?.map((v) => v.toJson()).toList(); + } + if (_supporters != null) { + map['supporters'] = _supporters?.map((v) => v.toJson()).toList(); + } + return map; + } + +} + +/// userId : "" +/// userAvatar : "" + +class Supporters { + Supporters({ + String? userId, + String? userAvatar,}){ + _userId = userId; + _userAvatar = userAvatar; +} + + Supporters.fromJson(dynamic json) { + _userId = json['userId']; + _userAvatar = json['userAvatar']; + } + String? _userId; + String? _userAvatar; + + String? get userId => _userId; + String? get userAvatar => _userAvatar; + + Map toJson() { + final map = {}; + map['userId'] = _userId; + map['userAvatar'] = _userAvatar; + return map; + } + +} + +/// userId : "" +/// userAvatar : "" + +class Hosts { + Hosts({ + String? userId, + String? userAvatar,}){ + _userId = userId; + _userAvatar = userAvatar; +} + + Hosts.fromJson(dynamic json) { + _userId = json['userId']; + _userAvatar = json['userAvatar']; + } + String? _userId; + String? _userAvatar; + + String? get userId => _userId; + String? get userAvatar => _userAvatar; + + Map toJson() { + final map = {}; + map['userId'] = _userId; + map['userAvatar'] = _userAvatar; + return map; + } + +} + +/// contributeCount : 0 +/// userContributed : false +/// chests : [{"level":0,"requiredCount":0,"rewardGold":0,"unlocked":false,"claimed":false}] + +class BoxInfo { + BoxInfo({ + int? contributeCount, + bool? userContributed, + List? chests,}){ + _contributeCount = contributeCount; + _userContributed = userContributed; + _chests = chests; +} + + BoxInfo.fromJson(dynamic json) { + _contributeCount = json['contributeCount']; + _userContributed = json['userContributed']; + if (json['chests'] != null) { + _chests = []; + json['chests'].forEach((v) { + _chests?.add(Chests.fromJson(v)); + }); + } + } + int? _contributeCount; + bool? _userContributed; + List? _chests; + + int? get contributeCount => _contributeCount; + bool? get userContributed => _userContributed; + List? get chests => _chests; + + Map toJson() { + final map = {}; + map['contributeCount'] = _contributeCount; + map['userContributed'] = _userContributed; + if (_chests != null) { + map['chests'] = _chests?.map((v) => v.toJson()).toList(); + } + return map; + } + +} + +/// level : 0 +/// requiredCount : 0 +/// rewardGold : 0 +/// unlocked : false +/// claimed : false + +class Chests { + Chests({ + num? level, + int? requiredCount, + num? rewardGold, + bool? unlocked, + bool? claimed,}){ + _level = level; + _requiredCount = requiredCount; + _rewardGold = rewardGold; + _unlocked = unlocked; + _claimed = claimed; +} + + Chests.fromJson(dynamic json) { + _level = json['level']; + _requiredCount = json['requiredCount']; + _rewardGold = json['rewardGold']; + _unlocked = json['unlocked']; + _claimed = json['claimed']; + } + num? _level; + int? _requiredCount; + num? _rewardGold; + bool? _unlocked; + bool? _claimed; + + num? get level => _level; + int? get requiredCount => _requiredCount; + num? get rewardGold => _rewardGold; + bool? get unlocked => _unlocked; + bool? get claimed => _claimed; + + Map toJson() { + final map = {}; + map['level'] = _level; + map['requiredCount'] = _requiredCount; + map['rewardGold'] = _rewardGold; + map['unlocked'] = _unlocked; + map['claimed'] = _claimed; + return map; + } + +} + +/// userId : "" +/// account : "" +/// specialAccount : "" +/// userAvatar : "" +/// userNickName : "" + +class FamilyAdmin { + FamilyAdmin({ + String? userId, + String? account, + String? specialAccount, + String? userAvatar, + String? userNickName,}){ + _userId = userId; + _account = account; + _specialAccount = specialAccount; + _userAvatar = userAvatar; + _userNickName = userNickName; +} + + FamilyAdmin.fromJson(dynamic json) { + _userId = json['userId']; + _account = json['account']; + _specialAccount = json['specialAccount']; + _userAvatar = json['userAvatar']; + _userNickName = json['userNickName']; + } + String? _userId; + String? _account; + String? _specialAccount; + String? _userAvatar; + String? _userNickName; + + String? get userId => _userId; + String? get account => _account; + String? get specialAccount => _specialAccount; + String? get userAvatar => _userAvatar; + String? get userNickName => _userNickName; + + Map toJson() { + final map = {}; + map['userId'] = _userId; + map['account'] = _account; + map['specialAccount'] = _specialAccount; + map['userAvatar'] = _userAvatar; + map['userNickName'] = _userNickName; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/family_list_res.dart b/lib/chatvibe_domain/models/res/family_list_res.dart new file mode 100644 index 0000000..5115c7b --- /dev/null +++ b/lib/chatvibe_domain/models/res/family_list_res.dart @@ -0,0 +1,128 @@ +/// records : [{"familyId":"","familyAccount":"","familyAvatar":"","familyName":"","familyIntro":"","familyNotice":"","currentMember":0,"maxMember":0}] +/// total : 0 +/// size : 0 +/// current : 0 + +class ChatVibeFamilyListRes { + ChatVibeFamilyListRes({ + List? records, + num? total, + num? size, + num? current,}){ + _records = records; + _total = total; + _size = size; + _current = current; +} + + ChatVibeFamilyListRes.fromJson(dynamic json) { + if (json['records'] != null) { + _records = []; + json['records'].forEach((v) { + _records?.add(Records.fromJson(v)); + }); + } + _total = json['total']; + _size = json['size']; + _current = json['current']; + } + List? _records; + num? _total; + num? _size; + num? _current; + + List? get records => _records; + num? get total => _total; + num? get size => _size; + num? get current => _current; + + Map toJson() { + final map = {}; + if (_records != null) { + map['records'] = _records?.map((v) => v.toJson()).toList(); + } + map['total'] = _total; + map['size'] = _size; + map['current'] = _current; + return map; + } + +} + +/// familyId : "" +/// familyAccount : "" +/// familyAvatar : "" +/// familyName : "" +/// familyIntro : "" +/// familyNotice : "" +/// currentMember : 0 +/// maxMember : 0 + +class Records { + Records({ + String? familyId, + String? familyAccount, + String? familyAvatar, + String? familyName, + String? familyIntro, + String? familyNotice, + num? currentMember, + num? familyLevel, + num? maxMember,}){ + _familyId = familyId; + _familyAccount = familyAccount; + _familyAvatar = familyAvatar; + _familyName = familyName; + _familyIntro = familyIntro; + _familyNotice = familyNotice; + _currentMember = currentMember; + _familyLevel = familyLevel; + _maxMember = maxMember; +} + + Records.fromJson(dynamic json) { + _familyId = json['familyId']; + _familyAccount = json['familyAccount']; + _familyAvatar = json['familyAvatar']; + _familyName = json['familyName']; + _familyIntro = json['familyIntro']; + _familyNotice = json['familyNotice']; + _currentMember = json['currentMember']; + _familyLevel = json['familyLevel']; + _maxMember = json['maxMember']; + } + String? _familyId; + String? _familyAccount; + String? _familyAvatar; + String? _familyName; + String? _familyIntro; + String? _familyNotice; + num? _currentMember; + num? _familyLevel; + num? _maxMember; + + String? get familyId => _familyId; + String? get familyAccount => _familyAccount; + String? get familyAvatar => _familyAvatar; + String? get familyName => _familyName; + String? get familyIntro => _familyIntro; + String? get familyNotice => _familyNotice; + num? get currentMember => _currentMember; + num? get familyLevel => _familyLevel; + num? get maxMember => _maxMember; + + Map toJson() { + final map = {}; + map['familyId'] = _familyId; + map['familyAccount'] = _familyAccount; + map['familyAvatar'] = _familyAvatar; + map['familyName'] = _familyName; + map['familyIntro'] = _familyIntro; + map['familyNotice'] = _familyNotice; + map['currentMember'] = _currentMember; + map['familyLevel'] = _familyLevel; + map['maxMember'] = _maxMember; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/follow_room_res.dart b/lib/chatvibe_domain/models/res/follow_room_res.dart new file mode 100644 index 0000000..0d0e0a6 --- /dev/null +++ b/lib/chatvibe_domain/models/res/follow_room_res.dart @@ -0,0 +1,602 @@ +import 'package:aslan/chatvibe_domain/models/res/room_res.dart'; + +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +/// id : "0" +/// roomProfile : {"countryCode":"","countryName":"","event":"","hotRoom":false,"id":"0","layoutKey":"","nationalFlag":"","roomAccount":"","roomBadgeIcons":[""],"roomCover":"","roomDesc":"","roomGameIcon":"","roomName":"","sysOrigin":"","userId":"0","userProfile":{"account":"","accountStatus":"","age":0,"bornDay":0,"bornMonth":0,"bornYear":0,"countryCode":"","countryId":0,"countryName":"","createTime":0,"del":false,"freezingTime":0,"id":"s","originSys":"","ownSpecialId":{"account":"","expiredTime":0},"sameRegion":false,"sysOriginChild":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0,"wearBadge":[{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}]},"userSVipLevel":""} + +class FollowRoomRes { + FollowRoomRes({ + String? id, + RoomProfile? roomProfile, + List? onlineUsers, + }) { + _id = id; + _roomProfile = roomProfile; + _onlineUsers = onlineUsers; + } + + FollowRoomRes.fromJson(dynamic json) { + _id = json['id']; + _roomProfile = + json['roomProfile'] != null + ? RoomProfile.fromJson(json['roomProfile']) + : null; + if (json['onlineUsers'] != null) { + _onlineUsers = []; + json['onlineUsers'].forEach((v) { + _onlineUsers?.add(ChatVibeUserProfile.fromJson(v)); + }); + } + } + + String? _id; + RoomProfile? _roomProfile; + List? _onlineUsers; + + FollowRoomRes copyWith({ + String? id, + RoomProfile? roomProfile, + List? onlineUsers, + }) => FollowRoomRes( + id: id ?? _id, + roomProfile: roomProfile ?? _roomProfile, + onlineUsers: onlineUsers ?? _onlineUsers, + ); + + String? get id => _id; + + RoomProfile? get roomProfile => _roomProfile; + + List? get onlineUsers => _onlineUsers; + + Map toJson() { + final map = {}; + map['id'] = _id; + if (_roomProfile != null) { + map['roomProfile'] = _roomProfile?.toJson(); + } + if (_onlineUsers != null) { + map['onlineUsers'] = _onlineUsers?.map((v) => v.toJson()).toList(); + } + return map; + } +} + +/// countryCode : "" +/// countryName : "" +/// event : "" +/// hotRoom : false +/// id : "0" +/// layoutKey : "" +/// nationalFlag : "" +/// roomAccount : "" +/// roomBadgeIcons : [""] +/// roomCover : "" +/// roomDesc : "" +/// roomGameIcon : "" +/// roomName : "" +/// sysOrigin : "" +/// userId : "0" +/// userProfile : {"account":"","accountStatus":"","age":0,"bornDay":0,"bornMonth":0,"bornYear":0,"countryCode":"","countryId":0,"countryName":"","createTime":0,"del":false,"freezingTime":0,"id":"s","originSys":"","ownSpecialId":{"account":"","expiredTime":0},"sameRegion":false,"sysOriginChild":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0,"wearBadge":[{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}]} +/// userSVipLevel : "" + +class RoomProfile { + RoomProfile({ + String? countryCode, + String? countryName, + String? event, + bool? hotRoom, + String? id, + String? layoutKey, + String? nationalFlag, + String? roomAccount, + List? roomBadgeIcons, + String? roomCover, + String? roomDesc, + String? roomGameIcon, + String? roomName, + String? sysOrigin, + String? userId, + ChatVibeUserProfile? userProfile, + String? userSVipLevel, + ExtValues? extValues, + }) { + _countryCode = countryCode; + _countryName = countryName; + _event = event; + _hotRoom = hotRoom; + _id = id; + _layoutKey = layoutKey; + _nationalFlag = nationalFlag; + _roomAccount = roomAccount; + _roomBadgeIcons = roomBadgeIcons; + _roomCover = roomCover; + _roomDesc = roomDesc; + _roomGameIcon = roomGameIcon; + _roomName = roomName; + _sysOrigin = sysOrigin; + _userId = userId; + _userProfile = userProfile; + _userSVipLevel = userSVipLevel; + _extValues = extValues; + } + + RoomProfile.fromJson(dynamic json) { + _countryCode = json['countryCode']; + _countryName = json['countryName']; + _event = json['event']; + _hotRoom = json['hotRoom']; + _id = json['id']; + _layoutKey = json['layoutKey']; + _nationalFlag = json['nationalFlag']; + _roomAccount = json['roomAccount']; + _roomBadgeIcons = + json['roomBadgeIcons'] != null + ? json['roomBadgeIcons'].cast() + : []; + _roomCover = json['roomCover']; + _roomDesc = json['roomDesc']; + _roomGameIcon = json['roomGameIcon']; + _roomName = json['roomName']; + _sysOrigin = json['sysOrigin']; + _userId = json['userId']; + _userProfile = + json['userProfile'] != null + ? ChatVibeUserProfile.fromJson(json['userProfile']) + : null; + _userSVipLevel = json['userSVipLevel']; + _extValues = + json['extValues'] != null + ? ExtValues.fromJson(json['extValues']) + : null; + } + + String? _countryCode; + String? _countryName; + String? _event; + bool? _hotRoom; + String? _id; + String? _layoutKey; + String? _nationalFlag; + String? _roomAccount; + List? _roomBadgeIcons; + String? _roomCover; + String? _roomDesc; + String? _roomGameIcon; + String? _roomName; + String? _sysOrigin; + String? _userId; + ChatVibeUserProfile? _userProfile; + String? _userSVipLevel; + ExtValues? _extValues; + + RoomProfile copyWith({ + String? countryCode, + String? countryName, + String? event, + bool? hotRoom, + String? id, + String? layoutKey, + String? nationalFlag, + String? roomAccount, + List? roomBadgeIcons, + String? roomCover, + String? roomDesc, + String? roomGameIcon, + String? roomName, + String? sysOrigin, + String? userId, + ChatVibeUserProfile? userProfile, + String? userSVipLevel, + ExtValues? extValues, + }) => RoomProfile( + countryCode: countryCode ?? _countryCode, + countryName: countryName ?? _countryName, + event: event ?? _event, + hotRoom: hotRoom ?? _hotRoom, + id: id ?? _id, + layoutKey: layoutKey ?? _layoutKey, + nationalFlag: nationalFlag ?? _nationalFlag, + roomAccount: roomAccount ?? _roomAccount, + roomBadgeIcons: roomBadgeIcons ?? _roomBadgeIcons, + roomCover: roomCover ?? _roomCover, + roomDesc: roomDesc ?? _roomDesc, + roomGameIcon: roomGameIcon ?? _roomGameIcon, + roomName: roomName ?? _roomName, + sysOrigin: sysOrigin ?? _sysOrigin, + userId: userId ?? _userId, + userProfile: userProfile ?? _userProfile, + userSVipLevel: userSVipLevel ?? _userSVipLevel, + extValues: extValues ?? _extValues, + ); + + String? get countryCode => _countryCode; + + String? get countryName => _countryName; + + String? get event => _event; + + bool? get hotRoom => _hotRoom; + + String? get id => _id; + + String? get layoutKey => _layoutKey; + + String? get nationalFlag => _nationalFlag; + + String? get roomAccount => _roomAccount; + + List? get roomBadgeIcons => _roomBadgeIcons; + + String? get roomCover => _roomCover; + + String? get roomDesc => _roomDesc; + + String? get roomGameIcon => _roomGameIcon; + + String? get roomName => _roomName; + + String? get sysOrigin => _sysOrigin; + + String? get userId => _userId; + + ChatVibeUserProfile? get userProfile => _userProfile; + + String? get userSVipLevel => _userSVipLevel; + + ExtValues? get extValues => _extValues; + + Map toJson() { + final map = {}; + map['countryCode'] = _countryCode; + map['countryName'] = _countryName; + map['event'] = _event; + map['hotRoom'] = _hotRoom; + map['id'] = _id; + map['layoutKey'] = _layoutKey; + map['nationalFlag'] = _nationalFlag; + map['roomAccount'] = _roomAccount; + map['roomBadgeIcons'] = _roomBadgeIcons; + map['roomCover'] = _roomCover; + map['roomDesc'] = _roomDesc; + map['roomGameIcon'] = _roomGameIcon; + map['roomName'] = _roomName; + map['sysOrigin'] = _sysOrigin; + map['userId'] = _userId; + if (_userProfile != null) { + map['userProfile'] = _userProfile?.toJson(); + } + map['userSVipLevel'] = _userSVipLevel; + if (_extValues != null) { + map['extValues'] = _extValues?.toJson(); + } + return map; + } +} + +/// account : "" +/// accountStatus : "" +/// age : 0 +/// bornDay : 0 +/// bornMonth : 0 +/// bornYear : 0 +/// countryCode : "" +/// countryId : 0 +/// countryName : "" +/// createTime : 0 +/// del : false +/// freezingTime : 0 +/// id : "s" +/// originSys : "" +/// ownSpecialId : {"account":"","expiredTime":0} +/// sameRegion : false +/// sysOriginChild : "" +/// useProps : [{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}] +/// userAvatar : "" +/// userNickname : "" +/// userSex : 0 +/// wearBadge : [{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}] + +class WearBadge { + WearBadge({ + String? animationUrl, + String? badgeKey, + num? badgeLevel, + String? badgeName, + num? expireTime, + num? id, + num? milestone, + String? notSelectUrl, + String? selectUrl, + String? type, + num? userId, + }) { + _animationUrl = animationUrl; + _badgeKey = badgeKey; + _badgeLevel = badgeLevel; + _badgeName = badgeName; + _expireTime = expireTime; + _id = id; + _milestone = milestone; + _notSelectUrl = notSelectUrl; + _selectUrl = selectUrl; + _type = type; + _userId = userId; + } + + WearBadge.fromJson(dynamic json) { + _animationUrl = json['animationUrl']; + _badgeKey = json['badgeKey']; + _badgeLevel = json['badgeLevel']; + _badgeName = json['badgeName']; + _expireTime = json['expireTime']; + _id = json['id']; + _milestone = json['milestone']; + _notSelectUrl = json['notSelectUrl']; + _selectUrl = json['selectUrl']; + _type = json['type']; + _userId = json['userId']; + } + + String? _animationUrl; + String? _badgeKey; + num? _badgeLevel; + String? _badgeName; + num? _expireTime; + num? _id; + num? _milestone; + String? _notSelectUrl; + String? _selectUrl; + String? _type; + num? _userId; + + WearBadge copyWith({ + String? animationUrl, + String? badgeKey, + num? badgeLevel, + String? badgeName, + num? expireTime, + num? id, + num? milestone, + String? notSelectUrl, + String? selectUrl, + String? type, + num? userId, + }) => WearBadge( + animationUrl: animationUrl ?? _animationUrl, + badgeKey: badgeKey ?? _badgeKey, + badgeLevel: badgeLevel ?? _badgeLevel, + badgeName: badgeName ?? _badgeName, + expireTime: expireTime ?? _expireTime, + id: id ?? _id, + milestone: milestone ?? _milestone, + notSelectUrl: notSelectUrl ?? _notSelectUrl, + selectUrl: selectUrl ?? _selectUrl, + type: type ?? _type, + userId: userId ?? _userId, + ); + + String? get animationUrl => _animationUrl; + + String? get badgeKey => _badgeKey; + + num? get badgeLevel => _badgeLevel; + + String? get badgeName => _badgeName; + + num? get expireTime => _expireTime; + + num? get id => _id; + + num? get milestone => _milestone; + + String? get notSelectUrl => _notSelectUrl; + + String? get selectUrl => _selectUrl; + + String? get type => _type; + + num? get userId => _userId; + + Map toJson() { + final map = {}; + map['animationUrl'] = _animationUrl; + map['badgeKey'] = _badgeKey; + map['badgeLevel'] = _badgeLevel; + map['badgeName'] = _badgeName; + map['expireTime'] = _expireTime; + map['id'] = _id; + map['milestone'] = _milestone; + map['notSelectUrl'] = _notSelectUrl; + map['selectUrl'] = _selectUrl; + map['type'] = _type; + map['userId'] = _userId; + return map; + } +} + +/// expireTime : 0 +/// propsResources : {"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""} +/// userId : 0 + +class UseProps { + UseProps({num? expireTime, PropsResources? propsResources, num? userId}) { + _expireTime = expireTime; + _propsResources = propsResources; + _userId = userId; + } + + UseProps.fromJson(dynamic json) { + _expireTime = json['expireTime']; + _propsResources = + json['propsResources'] != null + ? PropsResources.fromJson(json['propsResources']) + : null; + _userId = json['userId']; + } + + num? _expireTime; + PropsResources? _propsResources; + num? _userId; + + UseProps copyWith({ + num? expireTime, + PropsResources? propsResources, + num? userId, + }) => UseProps( + expireTime: expireTime ?? _expireTime, + propsResources: propsResources ?? _propsResources, + userId: userId ?? _userId, + ); + + num? get expireTime => _expireTime; + + PropsResources? get propsResources => _propsResources; + + num? get userId => _userId; + + Map toJson() { + final map = {}; + map['expireTime'] = _expireTime; + if (_propsResources != null) { + map['propsResources'] = _propsResources?.toJson(); + } + map['userId'] = _userId; + return map; + } +} + +/// amount : 0.0 +/// code : "" +/// cover : "" +/// expand : "" +/// id : 0 +/// name : "" +/// sourceUrl : "" +/// type : "" + +class PropsResources { + PropsResources({ + num? amount, + String? code, + String? cover, + String? expand, + num? id, + String? name, + String? sourceUrl, + String? type, + }) { + _amount = amount; + _code = code; + _cover = cover; + _expand = expand; + _id = id; + _name = name; + _sourceUrl = sourceUrl; + _type = type; + } + + PropsResources.fromJson(dynamic json) { + _amount = json['amount']; + _code = json['code']; + _cover = json['cover']; + _expand = json['expand']; + _id = json['id']; + _name = json['name']; + _sourceUrl = json['sourceUrl']; + _type = json['type']; + } + + num? _amount; + String? _code; + String? _cover; + String? _expand; + num? _id; + String? _name; + String? _sourceUrl; + String? _type; + + PropsResources copyWith({ + num? amount, + String? code, + String? cover, + String? expand, + num? id, + String? name, + String? sourceUrl, + String? type, + }) => PropsResources( + amount: amount ?? _amount, + code: code ?? _code, + cover: cover ?? _cover, + expand: expand ?? _expand, + id: id ?? _id, + name: name ?? _name, + sourceUrl: sourceUrl ?? _sourceUrl, + type: type ?? _type, + ); + + num? get amount => _amount; + + String? get code => _code; + + String? get cover => _cover; + + String? get expand => _expand; + + num? get id => _id; + + String? get name => _name; + + String? get sourceUrl => _sourceUrl; + + String? get type => _type; + + Map toJson() { + final map = {}; + map['amount'] = _amount; + map['code'] = _code; + map['cover'] = _cover; + map['expand'] = _expand; + map['id'] = _id; + map['name'] = _name; + map['sourceUrl'] = _sourceUrl; + map['type'] = _type; + return map; + } +} + +/// account : "" +/// expiredTime : 0 + +class OwnSpecialId { + OwnSpecialId({String? account, num? expiredTime}) { + _account = account; + _expiredTime = expiredTime; + } + + OwnSpecialId.fromJson(dynamic json) { + _account = json['account']; + _expiredTime = json['expiredTime']; + } + + String? _account; + num? _expiredTime; + + OwnSpecialId copyWith({String? account, num? expiredTime}) => OwnSpecialId( + account: account ?? _account, + expiredTime: expiredTime ?? _expiredTime, + ); + + String? get account => _account; + + num? get expiredTime => _expiredTime; + + Map toJson() { + final map = {}; + map['account'] = _account; + map['expiredTime'] = _expiredTime; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/follow_user_res.dart b/lib/chatvibe_domain/models/res/follow_user_res.dart new file mode 100644 index 0000000..dd43bba --- /dev/null +++ b/lib/chatvibe_domain/models/res/follow_user_res.dart @@ -0,0 +1,245 @@ +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +/// id : 0 +/// mutualRelations : false +/// useProps : [{"allowGive":false,"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","createTime":0,"del":false,"expand":"","id":0,"imNotOpenedUrl":"","imOpenedUrl":"","imOpenedUrlTwo":"","imSendCoverUrl":"","name":"","roomNotOpenedUrl":"","roomOpenedUrl":"","roomSendCoverUrl":"","sourceUrl":"","sysOrigin":"","type":"","updateTime":0},"userId":0}] +/// userProfile : {"account":"","accountStatus":"","age":0,"bornDay":0,"bornMonth":0,"bornYear":0,"countryCode":"","countryId":0,"countryName":"","createTime":0,"del":false,"freezingTime":0,"id":0,"originSys":"","ownSpecialId":{"account":"","expiredTime":0},"sameRegion":false,"sysOriginChild":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0,"wearBadge":[{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}]} + +class FollowUserRes { + FollowUserRes({ + String? id, + bool? mutualRelations, + List? useProps, + ChatVibeUserProfile? userProfile,}){ + _id = id; + _mutualRelations = mutualRelations; + _useProps = useProps; + _userProfile = userProfile; +} + + FollowUserRes.fromJson(dynamic json) { + _id = json['id']; + _mutualRelations = json['mutualRelations']; + if (json['useProps'] != null) { + _useProps = []; + json['useProps'].forEach((v) { + _useProps?.add(UseProps.fromJson(v)); + }); + } + _userProfile = json['userProfile'] != null ? ChatVibeUserProfile.fromJson(json['userProfile']) : null; + } + String? _id; + bool? _mutualRelations; + List? _useProps; + ChatVibeUserProfile? _userProfile; +FollowUserRes copyWith({ String? id, + bool? mutualRelations, + List? useProps, + ChatVibeUserProfile? userProfile, +}) => FollowUserRes( id: id ?? _id, + mutualRelations: mutualRelations ?? _mutualRelations, + useProps: useProps ?? _useProps, + userProfile: userProfile ?? _userProfile, +); + String? get id => _id; + bool? get mutualRelations => _mutualRelations; + List? get useProps => _useProps; + ChatVibeUserProfile? get userProfile => _userProfile; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['mutualRelations'] = _mutualRelations; + if (_useProps != null) { + map['useProps'] = _useProps?.map((v) => v.toJson()).toList(); + } + if (_userProfile != null) { + map['userProfile'] = _userProfile?.toJson(); + } + return map; + } + +} + +/// account : "" +/// accountStatus : "" +/// age : 0 +/// bornDay : 0 +/// bornMonth : 0 +/// bornYear : 0 +/// countryCode : "" +/// countryId : 0 +/// countryName : "" +/// createTime : 0 +/// del : false +/// freezingTime : 0 +/// id : 0 +/// originSys : "" +/// ownSpecialId : {"account":"","expiredTime":0} +/// sameRegion : false +/// sysOriginChild : "" +/// useProps : [{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}] +/// userAvatar : "" +/// userNickname : "" +/// userSex : 0 +/// wearBadge : [{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}] + +/// animationUrl : "" +/// badgeKey : "" +/// badgeLevel : 0 +/// badgeName : "" +/// expireTime : 0 +/// id : 0 +/// milestone : 0 +/// notSelectUrl : "" +/// selectUrl : "" +/// type : "" +/// userId : 0 + +class WearBadge { + WearBadge({ + String? animationUrl, + String? badgeKey, + num? badgeLevel, + String? badgeName, + String? expireTime, + num? id, + num? milestone, + String? notSelectUrl, + String? selectUrl, + String? type, + String? userId,}){ + _animationUrl = animationUrl; + _badgeKey = badgeKey; + _badgeLevel = badgeLevel; + _badgeName = badgeName; + _expireTime = expireTime; + _id = id; + _milestone = milestone; + _notSelectUrl = notSelectUrl; + _selectUrl = selectUrl; + _type = type; + _userId = userId; +} + + WearBadge.fromJson(dynamic json) { + _animationUrl = json['animationUrl']; + _badgeKey = json['badgeKey']; + _badgeLevel = json['badgeLevel']; + _badgeName = json['badgeName']; + _expireTime = json['expireTime']; + _id = json['id']; + _milestone = json['milestone']; + _notSelectUrl = json['notSelectUrl']; + _selectUrl = json['selectUrl']; + _type = json['type']; + _userId = json['userId']; + } + String? _animationUrl; + String? _badgeKey; + num? _badgeLevel; + String? _badgeName; + String? _expireTime; + num? _id; + num? _milestone; + String? _notSelectUrl; + String? _selectUrl; + String? _type; + String? _userId; +WearBadge copyWith({ String? animationUrl, + String? badgeKey, + num? badgeLevel, + String? badgeName, + String? expireTime, + num? id, + num? milestone, + String? notSelectUrl, + String? selectUrl, + String? type, + String? userId, +}) => WearBadge( animationUrl: animationUrl ?? _animationUrl, + badgeKey: badgeKey ?? _badgeKey, + badgeLevel: badgeLevel ?? _badgeLevel, + badgeName: badgeName ?? _badgeName, + expireTime: expireTime ?? _expireTime, + id: id ?? _id, + milestone: milestone ?? _milestone, + notSelectUrl: notSelectUrl ?? _notSelectUrl, + selectUrl: selectUrl ?? _selectUrl, + type: type ?? _type, + userId: userId ?? _userId, +); + String? get animationUrl => _animationUrl; + String? get badgeKey => _badgeKey; + num? get badgeLevel => _badgeLevel; + String? get badgeName => _badgeName; + String? get expireTime => _expireTime; + num? get id => _id; + num? get milestone => _milestone; + String? get notSelectUrl => _notSelectUrl; + String? get selectUrl => _selectUrl; + String? get type => _type; + String? get userId => _userId; + + Map toJson() { + final map = {}; + map['animationUrl'] = _animationUrl; + map['badgeKey'] = _badgeKey; + map['badgeLevel'] = _badgeLevel; + map['badgeName'] = _badgeName; + map['expireTime'] = _expireTime; + map['id'] = _id; + map['milestone'] = _milestone; + map['notSelectUrl'] = _notSelectUrl; + map['selectUrl'] = _selectUrl; + map['type'] = _type; + map['userId'] = _userId; + return map; + } + +} + +/// expireTime : 0 +/// propsResources : {"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""} +/// userId : 0 + +class UseProps { + UseProps({ + String? expireTime, + PropsResources? propsResources, + String? userId,}){ + _expireTime = expireTime; + _propsResources = propsResources; + _userId = userId; +} + + UseProps.fromJson(dynamic json) { + _expireTime = json['expireTime']; + _propsResources = json['propsResources'] != null ? PropsResources.fromJson(json['propsResources']) : null; + _userId = json['userId']; + } + String? _expireTime; + PropsResources? _propsResources; + String? _userId; +UseProps copyWith({ String? expireTime, + PropsResources? propsResources, + String? userId, +}) => UseProps( expireTime: expireTime ?? _expireTime, + propsResources: propsResources ?? _propsResources, + userId: userId ?? _userId, +); + String? get expireTime => _expireTime; + PropsResources? get propsResources => _propsResources; + String? get userId => _userId; + + Map toJson() { + final map = {}; + map['expireTime'] = _expireTime; + if (_propsResources != null) { + map['propsResources'] = _propsResources?.toJson(); + } + map['userId'] = _userId; + return map; + } + +} diff --git a/lib/chatvibe_domain/models/res/game_ludo_res.dart b/lib/chatvibe_domain/models/res/game_ludo_res.dart new file mode 100644 index 0000000..6a4b8dc --- /dev/null +++ b/lib/chatvibe_domain/models/res/game_ludo_res.dart @@ -0,0 +1,207 @@ +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +/// id : "0" +/// sysOrigin : "string" +/// roomId : "0" +/// userId : "0" +/// gameStatus : "string" +/// gameStatusEvent : "string" +/// joinAmount : 0 +/// bonus : 0 +/// lockSeat : "string" +/// players : [{"consumeJoinAmount":0,"checkerboardColor":"string","initiator":true,"playerProfile":{"id":0,"account":"string","userAvatar":"string","userNickname":"string","userSex":0,"age":0,"accountStatus":"string","freezingTime":0,"isUpdateCountry":"string","countryId":0,"countryName":"string","countryCode":"string","autograph":"string","hobby":"string","cpList":[{"meUserId":0,"meAccount":"string","meUserAvatar":"string","meUserNickname":"string","cpUserId":0,"cpAccount":"string","cpUserAvatar":"string","cpUserNickname":"string","cpValId":0,"cpValue":0,"days":0,"firstDay":"string","createTime":0}],"isCpRelation":true,"familyId":0,"backgroundPhotos":[{"url":"string","status":0,"rejectReason":"string","createTime":0}],"personalPhotos":[{"url":"string","status":0,"rejectReason":"string","createTime":0}],"inRoomId":"string","roomIcon":"string","regionCode":"string","wealthLevel":0,"charmLevel":0,"originSys":"string","sysOriginChild":"string","del":true,"createTime":0,"bornYear":0,"bornMonth":0,"bornDay":0,"ownSpecialId":{"account":"string","expiredTime":0},"useProps":[{"userId":0,"propsResources":{"id":0,"type":"string","code":"string","name":"string","cover":"string","sourceUrl":"string","expand":"string","amount":0},"expireTime":0}],"sameRegion":true,"isFreightAgent":true,"isSuperFreightAgent":true,"firstRecharge":true,"firstRechargeAmount":0,"firstRechargeEndTime":0,"wearBadge":[{"id":0,"userId":0,"badgeLevel":0,"milestone":0,"badgeName":"string","type":"string","use":true,"badgeKey":"string","selectUrl":"string","notSelectUrl":"string","animationUrl":"string","expireTime":0}],"wearHonor":[{"id":0,"userId":0,"badgeLevel":0,"milestone":0,"badgeName":"string","type":"string","use":true,"badgeKey":"string","selectUrl":"string","notSelectUrl":"string","animationUrl":"string","expireTime":0}]}}] +/// createTime : 0 +/// gameInfo : {"id":"0","gameId":"string","name":"string","gameCode":"string","cover":"string"} + +class GameLudoRes { + GameLudoRes({ + String? id, + String? sysOrigin, + String? roomId, + String? userId, + String? gameStatus, + String? gameStatusEvent, + num? joinAmount, + num? bonus, + String? lockSeat, + List? players, + num? createTime, + GameInfo? gameInfo,}){ + _id = id; + _sysOrigin = sysOrigin; + _roomId = roomId; + _userId = userId; + _gameStatus = gameStatus; + _gameStatusEvent = gameStatusEvent; + _joinAmount = joinAmount; + _bonus = bonus; + _lockSeat = lockSeat; + _players = players; + _createTime = createTime; + _gameInfo = gameInfo; +} + + GameLudoRes.fromJson(dynamic json) { + _id = json['id']; + _sysOrigin = json['sysOrigin']; + _roomId = json['roomId']; + _userId = json['userId']; + _gameStatus = json['gameStatus']; + _gameStatusEvent = json['gameStatusEvent']; + _joinAmount = json['joinAmount']; + _bonus = json['bonus']; + _lockSeat = json['lockSeat']; + if (json['players'] != null) { + _players = []; + json['players'].forEach((v) { + _players?.add(Players.fromJson(v)); + }); + } + _createTime = json['createTime']; + _gameInfo = json['gameInfo'] != null ? GameInfo.fromJson(json['gameInfo']) : null; + } + String? _id; + String? _sysOrigin; + String? _roomId; + String? _userId; + String? _gameStatus; + String? _gameStatusEvent; + num? _joinAmount; + num? _bonus; + String? _lockSeat; + List? _players; + num? _createTime; + GameInfo? _gameInfo; + + String? get id => _id; + String? get sysOrigin => _sysOrigin; + String? get roomId => _roomId; + String? get userId => _userId; + String? get gameStatus => _gameStatus; + String? get gameStatusEvent => _gameStatusEvent; + num? get joinAmount => _joinAmount; + num? get bonus => _bonus; + String? get lockSeat => _lockSeat; + List? get players => _players; + num? get createTime => _createTime; + GameInfo? get gameInfo => _gameInfo; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['sysOrigin'] = _sysOrigin; + map['roomId'] = _roomId; + map['userId'] = _userId; + map['gameStatus'] = _gameStatus; + map['gameStatusEvent'] = _gameStatusEvent; + map['joinAmount'] = _joinAmount; + map['bonus'] = _bonus; + map['lockSeat'] = _lockSeat; + if (_players != null) { + map['players'] = _players?.map((v) => v.toJson()).toList(); + } + map['createTime'] = _createTime; + if (_gameInfo != null) { + map['gameInfo'] = _gameInfo?.toJson(); + } + return map; + } + +} + +/// id : "0" +/// gameId : "string" +/// name : "string" +/// gameCode : "string" +/// cover : "string" + +class GameInfo { + GameInfo({ + String? id, + String? gameId, + String? name, + String? gameCode, + String? cover,}){ + _id = id; + _gameId = gameId; + _name = name; + _gameCode = gameCode; + _cover = cover; +} + + GameInfo.fromJson(dynamic json) { + _id = json['id']; + _gameId = json['gameId']; + _name = json['name']; + _gameCode = json['gameCode']; + _cover = json['cover']; + } + String? _id; + String? _gameId; + String? _name; + String? _gameCode; + String? _cover; + + String? get id => _id; + String? get gameId => _gameId; + String? get name => _name; + String? get gameCode => _gameCode; + String? get cover => _cover; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['gameId'] = _gameId; + map['name'] = _name; + map['gameCode'] = _gameCode; + map['cover'] = _cover; + return map; + } + +} + +/// consumeJoinAmount : 0 +/// checkerboardColor : "string" +/// initiator : true +/// playerProfile : {"id":0,"account":"string","userAvatar":"string","userNickname":"string","userSex":0,"age":0,"accountStatus":"string","freezingTime":0,"isUpdateCountry":"string","countryId":0,"countryName":"string","countryCode":"string","autograph":"string","hobby":"string","cpList":[{"meUserId":0,"meAccount":"string","meUserAvatar":"string","meUserNickname":"string","cpUserId":0,"cpAccount":"string","cpUserAvatar":"string","cpUserNickname":"string","cpValId":0,"cpValue":0,"days":0,"firstDay":"string","createTime":0}],"isCpRelation":true,"familyId":0,"backgroundPhotos":[{"url":"string","status":0,"rejectReason":"string","createTime":0}],"personalPhotos":[{"url":"string","status":0,"rejectReason":"string","createTime":0}],"inRoomId":"string","roomIcon":"string","regionCode":"string","wealthLevel":0,"charmLevel":0,"originSys":"string","sysOriginChild":"string","del":true,"createTime":0,"bornYear":0,"bornMonth":0,"bornDay":0,"ownSpecialId":{"account":"string","expiredTime":0},"useProps":[{"userId":0,"propsResources":{"id":0,"type":"string","code":"string","name":"string","cover":"string","sourceUrl":"string","expand":"string","amount":0},"expireTime":0}],"sameRegion":true,"isFreightAgent":true,"isSuperFreightAgent":true,"firstRecharge":true,"firstRechargeAmount":0,"firstRechargeEndTime":0,"wearBadge":[{"id":0,"userId":0,"badgeLevel":0,"milestone":0,"badgeName":"string","type":"string","use":true,"badgeKey":"string","selectUrl":"string","notSelectUrl":"string","animationUrl":"string","expireTime":0}],"wearHonor":[{"id":0,"userId":0,"badgeLevel":0,"milestone":0,"badgeName":"string","type":"string","use":true,"badgeKey":"string","selectUrl":"string","notSelectUrl":"string","animationUrl":"string","expireTime":0}]} + +class Players { + Players({ + num? consumeJoinAmount, + String? checkerboardColor, + bool? initiator, + ChatVibeUserProfile? playerProfile,}){ + _consumeJoinAmount = consumeJoinAmount; + _checkerboardColor = checkerboardColor; + _initiator = initiator; + _playerProfile = playerProfile; +} + + Players.fromJson(dynamic json) { + _consumeJoinAmount = json['consumeJoinAmount']; + _checkerboardColor = json['checkerboardColor']; + _initiator = json['initiator']; + _playerProfile = json['playerProfile'] != null ? ChatVibeUserProfile.fromJson(json['playerProfile']) : null; + } + num? _consumeJoinAmount; + String? _checkerboardColor; + bool? _initiator; + ChatVibeUserProfile? _playerProfile; + + num? get consumeJoinAmount => _consumeJoinAmount; + String? get checkerboardColor => _checkerboardColor; + bool? get initiator => _initiator; + ChatVibeUserProfile? get playerProfile => _playerProfile; + + Map toJson() { + final map = {}; + map['consumeJoinAmount'] = _consumeJoinAmount; + map['checkerboardColor'] = _checkerboardColor; + map['initiator'] = _initiator; + if (_playerProfile != null) { + map['playerProfile'] = _playerProfile?.toJson(); + } + return map; + } + +} diff --git a/lib/chatvibe_domain/models/res/get_cp_relationship_pair_list_res.dart b/lib/chatvibe_domain/models/res/get_cp_relationship_pair_list_res.dart new file mode 100644 index 0000000..98b616f --- /dev/null +++ b/lib/chatvibe_domain/models/res/get_cp_relationship_pair_list_res.dart @@ -0,0 +1,112 @@ +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +/// extValues : {"":{}} +/// meUserProfile : {"id":0,"account":"","userAvatar":"","userNickname":"","userSex":0,"age":0,"accountStatus":"","freezingTime":0,"isUpdateCountry":"","countryId":0,"countryName":"","countryCode":"","inRoomId":"","roomIcon":"","regionCode":"","originSys":"","sysOriginChild":"","del":false,"createTime":0,"bornYear":0,"bornMonth":0,"bornDay":0,"ownSpecialId":{"account":"","expiredTime":0},"useProps":[{"userId":0,"propsResources":{"id":0,"type":"","code":"","name":"","cover":"","sourceUrl":"","expand":"","amount":0.0},"expireTime":0}],"sameRegion":false,"wearBadge":[{"id":0,"userId":0,"badgeLevel":0,"milestone":0,"badgeName":"","type":"","badgeKey":"","selectUrl":"","notSelectUrl":"","animationUrl":"","expireTime":0}]} +/// cpUserProfile : {"id":0,"account":"","userAvatar":"","userNickname":"","userSex":0,"age":0,"accountStatus":"","freezingTime":0,"isUpdateCountry":"","countryId":0,"countryName":"","countryCode":"","inRoomId":"","roomIcon":"","regionCode":"","originSys":"","sysOriginChild":"","del":false,"createTime":0,"bornYear":0,"bornMonth":0,"bornDay":0,"ownSpecialId":{"account":"","expiredTime":0},"useProps":[{"userId":0,"propsResources":{"id":0,"type":"","code":"","name":"","cover":"","sourceUrl":"","expand":"","amount":0.0},"expireTime":0}],"sameRegion":false,"wearBadge":[{"id":0,"userId":0,"badgeLevel":0,"milestone":0,"badgeName":"","type":"","badgeKey":"","selectUrl":"","notSelectUrl":"","animationUrl":"","expireTime":0}]} +/// days : 0 + +class GetCpRelationshipPairListRes { + GetCpRelationshipPairListRes({ + ChatVibeUserProfile? meUserProfile, + ChatVibeUserProfile? cpUserProfile, + double? cpValue, + int? nextLevelExp, + num? cpLevel, + num? loveHeartLevel, + num? profileCardLevel, + PropsResources? selfRing, + String? days, + String? firstDay, + String? status, + }) { + _meUserProfile = meUserProfile; + _cpUserProfile = cpUserProfile; + _cpValue = cpValue; + _nextLevelExp = nextLevelExp; + _cpLevel = cpLevel; + _loveHeartLevel = loveHeartLevel; + _profileCardLevel = profileCardLevel; + _days = days; + _firstDay = firstDay; + _status = status; + } + + GetCpRelationshipPairListRes.fromJson(dynamic json) { + _meUserProfile = + json['meUserProfile'] != null + ? ChatVibeUserProfile.fromJson(json['meUserProfile']) + : null; + _cpUserProfile = + json['cpUserProfile'] != null + ? ChatVibeUserProfile.fromJson(json['cpUserProfile']) + : null; + _selfRing = + json['selfRing'] != null + ? PropsResources.fromJson(json['selfRing']) + : null; + _days = json['days']; + _cpValue = json['cpValue']; + _nextLevelExp = json['nextLevelExp']; + _cpLevel = json['cpLevel']; + _loveHeartLevel = json['loveHeartLevel']; + _profileCardLevel = json['profileCardLevel']; + _firstDay = json['firstDay']; + _status = json['status']; + } + + ChatVibeUserProfile? _meUserProfile; + ChatVibeUserProfile? _cpUserProfile; + double? _cpValue; + int? _nextLevelExp; + num? _cpLevel; + num? _loveHeartLevel; + num? _profileCardLevel; + PropsResources? _selfRing; + String? _days; + String? _firstDay; + String? _status; + + ChatVibeUserProfile? get meUserProfile => _meUserProfile; + + ChatVibeUserProfile? get cpUserProfile => _cpUserProfile; + + String? get days => _days; + + String? get status => _status; + + double? get cpValue => _cpValue; + + int? get nextLevelExp => _nextLevelExp; + + num? get cpLevel => _cpLevel; + + num? get loveHeartLevel => _loveHeartLevel; + + num? get profileCardLevel => _profileCardLevel; + + PropsResources? get selfRing => _selfRing; + + String? get firstDay => _firstDay; + + Map toJson() { + final map = {}; + if (_meUserProfile != null) { + map['meUserProfile'] = _meUserProfile?.toJson(); + } + if (_cpUserProfile != null) { + map['cpUserProfile'] = _cpUserProfile?.toJson(); + } + if (_selfRing != null) { + map['selfRing'] = _selfRing?.toJson(); + } + map['days'] = _days; + map['cpValue'] = _cpValue; + map['nextLevelExp'] = _nextLevelExp; + map['cpLevel'] = _cpLevel; + map['loveHeartLevel'] = _loveHeartLevel; + map['profileCardLevel'] = _profileCardLevel; + map['firstDay'] = _firstDay; + map['status'] = _status; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/gift_backpack_res.dart b/lib/chatvibe_domain/models/res/gift_backpack_res.dart new file mode 100644 index 0000000..50383d4 --- /dev/null +++ b/lib/chatvibe_domain/models/res/gift_backpack_res.dart @@ -0,0 +1,117 @@ +import 'package:aslan/chatvibe_domain/models/res/gift_res.dart'; + +/// id : "0" +/// quantity : 0 +/// giftLimit : {"id":"0","userId":"0","backId":"0","giftTab":"","content":""} +/// giftConfig : {"id":"0","giftCode":"","giftPhoto":"","giftSourceUrl":"","giftCandy":0.0,"giftIntegral":0.0,"special":"","type":"","giftTab":"","standardId":"0","explanationGift":false,"account":"","userId":"0","giftName":"","expiredTime":0} + +class ChatVibeGiftBackpackRes { + ChatVibeGiftBackpackRes({ + String? id, + num? quantity, + GiftLimit? giftLimit, + ChatVibeGiftRes? giftConfig,}){ + _id = id; + _quantity = quantity; + _giftLimit = giftLimit; + _giftConfig = giftConfig; +} + + ChatVibeGiftBackpackRes.fromJson(dynamic json) { + _id = json['id']; + _quantity = json['quantity']; + _giftLimit = json['giftLimit'] != null ? GiftLimit.fromJson(json['giftLimit']) : null; + _giftConfig = json['giftConfig'] != null ? ChatVibeGiftRes.fromJson(json['giftConfig']) : null; + } + String? _id; + num? _quantity; + GiftLimit? _giftLimit; + ChatVibeGiftRes? _giftConfig; + + String? get id => _id; + num? get quantity => _quantity; + GiftLimit? get giftLimit => _giftLimit; + ChatVibeGiftRes? get giftConfig => _giftConfig; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['quantity'] = _quantity; + if (_giftLimit != null) { + map['giftLimit'] = _giftLimit?.toJson(); + } + if (_giftConfig != null) { + map['giftConfig'] = _giftConfig?.toJson(); + } + return map; + } + +} + +/// id : "0" +/// giftCode : "" +/// giftPhoto : "" +/// giftSourceUrl : "" +/// giftCandy : 0.0 +/// giftIntegral : 0.0 +/// special : "" +/// type : "" +/// giftTab : "" +/// standardId : "0" +/// explanationGift : false +/// account : "" +/// userId : "0" +/// giftName : "" +/// expiredTime : 0 + + +/// id : "0" +/// userId : "0" +/// backId : "0" +/// giftTab : "" +/// content : "" + +class GiftLimit { + GiftLimit({ + String? id, + String? userId, + String? backId, + String? giftTab, + String? content,}){ + _id = id; + _userId = userId; + _backId = backId; + _giftTab = giftTab; + _content = content; +} + + GiftLimit.fromJson(dynamic json) { + _id = json['id']; + _userId = json['userId']; + _backId = json['backId']; + _giftTab = json['giftTab']; + _content = json['content']; + } + String? _id; + String? _userId; + String? _backId; + String? _giftTab; + String? _content; + + String? get id => _id; + String? get userId => _userId; + String? get backId => _backId; + String? get giftTab => _giftTab; + String? get content => _content; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['userId'] = _userId; + map['backId'] = _backId; + map['giftTab'] = _giftTab; + map['content'] = _content; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/gift_by_group_res.dart b/lib/chatvibe_domain/models/res/gift_by_group_res.dart new file mode 100644 index 0000000..53d3f0b --- /dev/null +++ b/lib/chatvibe_domain/models/res/gift_by_group_res.dart @@ -0,0 +1,42 @@ +import 'package:aslan/chatvibe_domain/models/res/gift_res.dart'; + +/// giftConfigs : [{"account":"","expiredTime":0,"explanationGift":false,"giftCandy":0.0,"giftCode":"","giftIntegral":0.0,"giftName":"","giftPhoto":"","giftSourceUrl":"","giftTab":"","id":0,"special":"","standardId":0,"type":"","userId":0}] +/// giftTab : "" + +class GiftByGroupRes { + GiftByGroupRes({ + List? giftConfigs, + String? giftTab,}){ + _giftConfigs = giftConfigs; + _giftTab = giftTab; +} + + GiftByGroupRes.fromJson(dynamic json) { + if (json['giftConfigs'] != null) { + _giftConfigs = []; + json['giftConfigs'].forEach((v) { + _giftConfigs?.add(ChatVibeGiftRes.fromJson(v)); + }); + } + _giftTab = json['giftTab']; + } + List? _giftConfigs; + String? _giftTab; +GiftByGroupRes copyWith({ List? giftConfigs, + String? giftTab, +}) => GiftByGroupRes( giftConfigs: giftConfigs ?? _giftConfigs, + giftTab: giftTab ?? _giftTab, +); + List? get giftConfigs => _giftConfigs; + String? get giftTab => _giftTab; + + Map toJson() { + final map = {}; + if (_giftConfigs != null) { + map['giftConfigs'] = _giftConfigs?.map((v) => v.toJson()).toList(); + } + map['giftTab'] = _giftTab; + return map; + } + +} diff --git a/lib/chatvibe_domain/models/res/gift_res.dart b/lib/chatvibe_domain/models/res/gift_res.dart new file mode 100644 index 0000000..d96bbc0 --- /dev/null +++ b/lib/chatvibe_domain/models/res/gift_res.dart @@ -0,0 +1,182 @@ +/// id : 0 +/// giftCode : "" +/// giftPhoto : "" +/// giftSourceUrl : "" +/// giftCandy : 0.0 +/// giftIntegral : 0.0 +/// special : "" +/// type : "" +/// giftTab : "" +/// standardId : 0 +/// explanationGift : false +/// account : "" +/// userId : 0 +/// giftName : "" +/// expiredTime : 0 + +class ChatVibeGiftRes { + ChatVibeGiftRes({ + String? id, + String? giftCode, + String? giftPhoto, + String? giftSourceUrl, + num? giftCandy, + num? activityId, + num? giftIntegral, + String? special, + String? type, + String? giftTab, + String? standardId, + String? jumpUrl, + String? bannerUrl, + bool? explanationGift, + String? account, + String? userId, + String? giftName, + String? quantity, + num? expiredTime,}){ + _id = id; + _giftCode = giftCode; + _giftPhoto = giftPhoto; + _giftSourceUrl = giftSourceUrl; + _giftCandy = giftCandy; + _activityId = activityId; + _giftIntegral = giftIntegral; + _special = special; + _type = type; + _giftTab = giftTab; + _standardId = standardId; + _jumpUrl = jumpUrl; + _bannerUrl = bannerUrl; + _explanationGift = explanationGift; + _account = account; + _userId = userId; + _giftName = giftName; + _quantity = quantity; + _expiredTime = expiredTime; +} + + ChatVibeGiftRes.fromJson(dynamic json) { + _id = json['id']; + _giftCode = json['giftCode']; + _giftPhoto = json['giftPhoto']; + _giftSourceUrl = json['giftSourceUrl']; + _giftCandy = json['giftCandy']; + _activityId = json['activityId']; + _giftIntegral = json['giftIntegral']; + _special = json['special']; + _type = json['type']; + _giftTab = json['giftTab']; + _standardId = json['standardId']; + _jumpUrl = json['jumpUrl']; + _bannerUrl = json['bannerUrl']; + _explanationGift = json['explanationGift']; + _account = json['account']; + _userId = json['userId']; + _giftName = json['giftName']; + _quantity = json['quantity']; + _expiredTime = json['expiredTime']; + } + String? _id; + String? _giftCode; + String? _giftPhoto; + String? _giftSourceUrl; + num? _giftCandy; + num? _activityId; + num? _giftIntegral; + String? _special; + String? _type; + String? _giftTab; + String? _standardId; + String? _jumpUrl; + String? _bannerUrl; + bool? _explanationGift; + String? _account; + String? _userId; + String? _giftName; + String? _quantity; + num? _expiredTime; +ChatVibeGiftRes copyWith({ String? id, + String? giftCode, + String? giftPhoto, + String? giftSourceUrl, + num? giftCandy, + num? activityId, + num? giftIntegral, + String? special, + String? type, + String? giftTab, + String? standardId, + String? jumpUrl, + String? bannerUrl, + bool? explanationGift, + String? account, + String? userId, + String? giftName, + String? quantity, + num? expiredTime, +}) => ChatVibeGiftRes( id: id ?? _id, + giftCode: giftCode ?? _giftCode, + giftPhoto: giftPhoto ?? _giftPhoto, + giftSourceUrl: giftSourceUrl ?? _giftSourceUrl, + giftCandy: giftCandy ?? _giftCandy, + activityId: activityId ?? _activityId, + giftIntegral: giftIntegral ?? _giftIntegral, + special: special ?? _special, + type: type ?? _type, + giftTab: giftTab ?? _giftTab, + standardId: standardId ?? _standardId, + jumpUrl: jumpUrl ?? _jumpUrl, + bannerUrl: bannerUrl ?? _bannerUrl, + explanationGift: explanationGift ?? _explanationGift, + account: account ?? _account, + userId: userId ?? _userId, + giftName: giftName ?? _giftName, + quantity: quantity ?? _quantity, + expiredTime: expiredTime ?? _expiredTime, +); + String? get id => _id; + String? get giftCode => _giftCode; + String? get giftPhoto => _giftPhoto; + String? get giftSourceUrl => _giftSourceUrl; + num? get giftCandy => _giftCandy; + num? get activityId => _activityId; + num? get giftIntegral => _giftIntegral; + String? get special => _special; + String? get type => _type; + String? get giftTab => _giftTab; + String? get standardId => _standardId; + String? get jumpUrl => _jumpUrl; + String? get bannerUrl => _bannerUrl; + bool? get explanationGift => _explanationGift; + String? get account => _account; + String? get userId => _userId; + String? get giftName => _giftName; + String? get quantity => _quantity; + num? get expiredTime => _expiredTime; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['giftCode'] = _giftCode; + map['giftPhoto'] = _giftPhoto; + map['giftSourceUrl'] = _giftSourceUrl; + map['giftCandy'] = _giftCandy; + map['activityId'] = _activityId; + map['giftIntegral'] = _giftIntegral; + map['special'] = _special; + map['type'] = _type; + map['giftTab'] = _giftTab; + map['standardId'] = _standardId; + map['jumpUrl'] = _jumpUrl; + map['bannerUrl'] = _bannerUrl; + map['explanationGift'] = _explanationGift; + map['account'] = _account; + map['userId'] = _userId; + map['giftName'] = _giftName; + map['quantity'] = _quantity; + map['expiredTime'] = _expiredTime; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/join_room_res.dart b/lib/chatvibe_domain/models/res/join_room_res.dart new file mode 100644 index 0000000..d5c524f --- /dev/null +++ b/lib/chatvibe_domain/models/res/join_room_res.dart @@ -0,0 +1,959 @@ +import 'package:aslan/chatvibe_domain/models/res/room_res.dart'; + +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +/// entrants : {"level":{"charmLevel":0,"userId":0,"wealthLevel":0},"nobleVipAbility":[""],"roles":"","roomToken":""} +/// roomProfile : {"regionCode":"","roomCounter":{"adminCount":0,"memberCount":0},"roomProfile":{"activeTime":0,"countryCode":"","countryName":"","createTime":0,"del":false,"event":"","id":"0","langCode":"","nationalFlag":"","roomAccount":"","roomCover":"","roomDesc":"","roomName":"","sysOrigin":"","updateTime":0,"userId":"0"},"roomSetting":{"adminLockSeat":false,"allowMusic":false,"joinGolds":0,"lockLocation":"","maxAdmin":0,"maxMember":0,"mikeSize":0,"openKtvMode":false,"password":"","roomId":0,"roomSpecialMikeType":"","showHeartbeat":false,"takeMicRole":"","touristMike":false,"touristMsg":false,"userId":"0"},"userProfile":{"account":"","accountStatus":"","age":0,"bornDay":0,"bornMonth":0,"bornYear":0,"countryCode":"","countryId":0,"countryName":"","createTime":0,"del":false,"freezingTime":0,"id":"0","originSys":"","ownSpecialId":{"account":"","expiredTime":0},"sameRegion":false,"sysOriginChild":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0,"wearBadge":[{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}]}} +/// roomProps : {"layoutCode":"","roomTheme":{"expireTime":0,"id":0,"themeBack":"","themeStatus":"","useTheme":false}} + +class JoinRoomRes { + JoinRoomRes({ + Entrants? entrants, + RoomProfile? roomProfile, + RoomProps? roomProps, + }) { + _entrants = entrants; + _roomProfile = roomProfile; + _roomProps = roomProps; + } + + JoinRoomRes.fromJson(dynamic json) { + _entrants = + json['entrants'] != null ? Entrants.fromJson(json['entrants']) : null; + _roomProfile = + json['roomProfile'] != null + ? RoomProfile.fromJson(json['roomProfile']) + : null; + _roomProps = + json['roomProps'] != null + ? RoomProps.fromJson(json['roomProps']) + : null; + } + + Entrants? _entrants; + RoomProfile? _roomProfile; + RoomProps? _roomProps; + + JoinRoomRes copyWith({ + Entrants? entrants, + RoomProfile? roomProfile, + RoomProps? roomProps, + }) => JoinRoomRes( + entrants: entrants ?? _entrants, + roomProfile: roomProfile ?? _roomProfile, + roomProps: roomProps ?? _roomProps, + ); + + Entrants? get entrants => _entrants; + + RoomProfile? get roomProfile => _roomProfile; + + RoomProps? get roomProps => _roomProps; + + Map toJson() { + final map = {}; + if (_entrants != null) { + map['entrants'] = _entrants?.toJson(); + } + if (_roomProfile != null) { + map['roomProfile'] = _roomProfile?.toJson(); + } + if (_roomProps != null) { + map['roomProps'] = _roomProps?.toJson(); + } + return map; + } +} + +/// layoutCode : "" +/// roomTheme : {"expireTime":0,"id":0,"themeBack":"","themeStatus":"","useTheme":false} + +class RoomProps { + RoomProps({String? layoutCode, RoomTheme? roomTheme}) { + _layoutCode = layoutCode; + _roomTheme = roomTheme; + } + + RoomProps.fromJson(dynamic json) { + _layoutCode = json['layoutCode']; + _roomTheme = + json['roomTheme'] != null + ? RoomTheme.fromJson(json['roomTheme']) + : null; + } + + String? _layoutCode; + RoomTheme? _roomTheme; + + RoomProps copyWith({String? layoutCode, RoomTheme? roomTheme}) => RoomProps( + layoutCode: layoutCode ?? _layoutCode, + roomTheme: roomTheme ?? _roomTheme, + ); + + String? get layoutCode => _layoutCode; + + RoomTheme? get roomTheme => _roomTheme; + + Map toJson() { + final map = {}; + map['layoutCode'] = _layoutCode; + if (_roomTheme != null) { + map['roomTheme'] = _roomTheme?.toJson(); + } + return map; + } + + void setRoomTheme(RoomTheme? roomTheme) { + _roomTheme = roomTheme; + } +} + +/// expireTime : 0 +/// id : 0 +/// themeBack : "" +/// themeStatus : "" +/// useTheme : false + +class RoomTheme { + RoomTheme({ + int? expireTime, + String? id, + String? themeBack, + String? themeStatus, + bool? useTheme, + }) { + _expireTime = expireTime; + _id = id; + _themeBack = themeBack; + _themeStatus = themeStatus; + _useTheme = useTheme; + } + + RoomTheme.fromJson(dynamic json) { + _expireTime = json['expireTime']; + _id = json['id']; + _themeBack = json['themeBack']; + _themeStatus = json['themeStatus']; + _useTheme = json['useTheme']; + } + + int? _expireTime; + String? _id; + String? _themeBack; + String? _themeStatus; + bool? _useTheme; + + RoomTheme copyWith({ + int? expireTime, + String? id, + String? themeBack, + String? themeStatus, + bool? useTheme, + }) => RoomTheme( + expireTime: expireTime ?? _expireTime, + id: id ?? _id, + themeBack: themeBack ?? _themeBack, + themeStatus: themeStatus ?? _themeStatus, + useTheme: useTheme ?? _useTheme, + ); + + int? get expireTime => _expireTime; + + String? get id => _id; + + String? get themeBack => _themeBack; + + String? get themeStatus => _themeStatus; + + bool? get useTheme => _useTheme; + + Map toJson() { + final map = {}; + map['expireTime'] = _expireTime; + map['id'] = _id; + map['themeBack'] = _themeBack; + map['themeStatus'] = _themeStatus; + map['useTheme'] = _useTheme; + return map; + } +} + +/// regionCode : "" +/// roomCounter : {"adminCount":0,"memberCount":0} +/// roomProfile : {"activeTime":0,"countryCode":"","countryName":"","createTime":0,"del":false,"event":"","id":"0","langCode":"","nationalFlag":"","roomAccount":"","roomCover":"","roomDesc":"","roomName":"","sysOrigin":"","updateTime":0,"userId":"0"} +/// roomSetting : {"adminLockSeat":false,"allowMusic":false,"joinGolds":0,"lockLocation":"","maxAdmin":0,"maxMember":0,"mikeSize":0,"openKtvMode":false,"password":"","roomId":0,"roomSpecialMikeType":"","showHeartbeat":false,"takeMicRole":"","touristMike":false,"touristMsg":false,"userId":"0"} +/// userProfile : {"account":"","accountStatus":"","age":0,"bornDay":0,"bornMonth":0,"bornYear":0,"countryCode":"","countryId":0,"countryName":"","createTime":0,"del":false,"freezingTime":0,"id":"0","originSys":"","ownSpecialId":{"account":"","expiredTime":0},"sameRegion":false,"sysOriginChild":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0,"wearBadge":[{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}]} + +class RoomProfile { + RoomProfile({ + String? regionCode, + RoomCounter? roomCounter, + RoomProfile2? roomProfile, + RoomSetting? roomSetting, + ChatVibeUserProfile? userProfile, + }) { + _regionCode = regionCode; + _roomCounter = roomCounter; + _roomProfile = roomProfile; + _roomSetting = roomSetting; + _userProfile = userProfile; + } + + RoomProfile.fromJson(dynamic json) { + _regionCode = json['regionCode']; + _roomCounter = + json['roomCounter'] != null + ? RoomCounter.fromJson(json['roomCounter']) + : null; + _roomProfile = + json['roomProfile'] != null + ? RoomProfile2.fromJson(json['roomProfile']) + : null; + _roomSetting = + json['roomSetting'] != null + ? RoomSetting.fromJson(json['roomSetting']) + : null; + _userProfile = + json['userProfile'] != null + ? ChatVibeUserProfile.fromJson(json['userProfile']) + : null; + } + + String? _regionCode; + RoomCounter? _roomCounter; + RoomProfile2? _roomProfile; + RoomSetting? _roomSetting; + ChatVibeUserProfile? _userProfile; + + RoomProfile copyWith({ + String? regionCode, + RoomCounter? roomCounter, + RoomProfile2? roomProfile, + RoomSetting? roomSetting, + ChatVibeUserProfile? userProfile, + }) => RoomProfile( + regionCode: regionCode ?? _regionCode, + roomCounter: roomCounter ?? _roomCounter, + roomProfile: roomProfile ?? _roomProfile, + roomSetting: roomSetting ?? _roomSetting, + userProfile: userProfile ?? _userProfile, + ); + + String? get regionCode => _regionCode; + + RoomCounter? get roomCounter => _roomCounter; + + RoomProfile2? get roomProfile => _roomProfile; + + RoomSetting? get roomSetting => _roomSetting; + + ChatVibeUserProfile? get userProfile => _userProfile; + + Map toJson() { + final map = {}; + map['regionCode'] = _regionCode; + if (_roomCounter != null) { + map['roomCounter'] = _roomCounter?.toJson(); + } + if (_roomProfile != null) { + map['roomProfile'] = _roomProfile?.toJson(); + } + if (_roomSetting != null) { + map['roomSetting'] = _roomSetting?.toJson(); + } + if (_userProfile != null) { + map['userProfile'] = _userProfile?.toJson(); + } + return map; + } + + void setRoomSetting(RoomSetting roomSetting) { + _roomSetting = roomSetting; + } +} + +/// account : "" +/// accountStatus : "" +/// age : 0 +/// bornDay : 0 +/// bornMonth : 0 +/// bornYear : 0 +/// countryCode : "" +/// countryId : 0 +/// countryName : "" +/// createTime : 0 +/// del : false +/// freezingTime : 0 +/// id : "0" +/// originSys : "" +/// ownSpecialId : {"account":"","expiredTime":0} +/// sameRegion : false +/// sysOriginChild : "" +/// useProps : [{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}] +/// userAvatar : "" +/// userNickname : "" +/// userSex : 0 +/// wearBadge : [{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}] + +/// animationUrl : "" +/// badgeKey : "" +/// badgeLevel : 0 +/// badgeName : "" +/// expireTime : 0 +/// id : 0 +/// milestone : 0 +/// notSelectUrl : "" +/// selectUrl : "" +/// type : "" +/// userId : 0 + +class WearBadge { + WearBadge({ + String? animationUrl, + String? badgeKey, + num? badgeLevel, + String? badgeName, + num? expireTime, + num? id, + num? milestone, + String? notSelectUrl, + String? selectUrl, + String? type, + num? userId, + }) { + _animationUrl = animationUrl; + _badgeKey = badgeKey; + _badgeLevel = badgeLevel; + _badgeName = badgeName; + _expireTime = expireTime; + _id = id; + _milestone = milestone; + _notSelectUrl = notSelectUrl; + _selectUrl = selectUrl; + _type = type; + _userId = userId; + } + + WearBadge.fromJson(dynamic json) { + _animationUrl = json['animationUrl']; + _badgeKey = json['badgeKey']; + _badgeLevel = json['badgeLevel']; + _badgeName = json['badgeName']; + _expireTime = json['expireTime']; + _id = json['id']; + _milestone = json['milestone']; + _notSelectUrl = json['notSelectUrl']; + _selectUrl = json['selectUrl']; + _type = json['type']; + _userId = json['userId']; + } + + String? _animationUrl; + String? _badgeKey; + num? _badgeLevel; + String? _badgeName; + num? _expireTime; + num? _id; + num? _milestone; + String? _notSelectUrl; + String? _selectUrl; + String? _type; + num? _userId; + + WearBadge copyWith({ + String? animationUrl, + String? badgeKey, + num? badgeLevel, + String? badgeName, + num? expireTime, + num? id, + num? milestone, + String? notSelectUrl, + String? selectUrl, + String? type, + num? userId, + }) => WearBadge( + animationUrl: animationUrl ?? _animationUrl, + badgeKey: badgeKey ?? _badgeKey, + badgeLevel: badgeLevel ?? _badgeLevel, + badgeName: badgeName ?? _badgeName, + expireTime: expireTime ?? _expireTime, + id: id ?? _id, + milestone: milestone ?? _milestone, + notSelectUrl: notSelectUrl ?? _notSelectUrl, + selectUrl: selectUrl ?? _selectUrl, + type: type ?? _type, + userId: userId ?? _userId, + ); + + String? get animationUrl => _animationUrl; + + String? get badgeKey => _badgeKey; + + num? get badgeLevel => _badgeLevel; + + String? get badgeName => _badgeName; + + num? get expireTime => _expireTime; + + num? get id => _id; + + num? get milestone => _milestone; + + String? get notSelectUrl => _notSelectUrl; + + String? get selectUrl => _selectUrl; + + String? get type => _type; + + num? get userId => _userId; + + Map toJson() { + final map = {}; + map['animationUrl'] = _animationUrl; + map['badgeKey'] = _badgeKey; + map['badgeLevel'] = _badgeLevel; + map['badgeName'] = _badgeName; + map['expireTime'] = _expireTime; + map['id'] = _id; + map['milestone'] = _milestone; + map['notSelectUrl'] = _notSelectUrl; + map['selectUrl'] = _selectUrl; + map['type'] = _type; + map['userId'] = _userId; + return map; + } +} + +/// expireTime : 0 +/// propsResources : {"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""} +/// userId : 0 + +class UseProps { + UseProps({ + String? expireTime, + PropsResources? propsResources, + String? userId, + }) { + _expireTime = expireTime; + _propsResources = propsResources; + _userId = userId; + } + + UseProps.fromJson(dynamic json) { + _expireTime = json['expireTime']; + _propsResources = + json['propsResources'] != null + ? PropsResources.fromJson(json['propsResources']) + : null; + _userId = json['userId']; + } + + String? _expireTime; + PropsResources? _propsResources; + String? _userId; + + UseProps copyWith({ + String? expireTime, + PropsResources? propsResources, + String? userId, + }) => UseProps( + expireTime: expireTime ?? _expireTime, + propsResources: propsResources ?? _propsResources, + userId: userId ?? _userId, + ); + + String? get expireTime => _expireTime; + + PropsResources? get propsResources => _propsResources; + + String? get userId => _userId; + + Map toJson() { + final map = {}; + map['expireTime'] = _expireTime; + if (_propsResources != null) { + map['propsResources'] = _propsResources?.toJson(); + } + map['userId'] = _userId; + return map; + } +} + +/// amount : 0.0 +/// code : "" +/// cover : "" +/// expand : "" +/// id : 0 +/// name : "" +/// sourceUrl : "" +/// type : "" + +class PropsResources { + PropsResources({ + num? amount, + String? code, + String? cover, + String? expand, + String? id, + String? name, + String? sourceUrl, + String? type, + }) { + _amount = amount; + _code = code; + _cover = cover; + _expand = expand; + _id = id; + _name = name; + _sourceUrl = sourceUrl; + _type = type; + } + + PropsResources.fromJson(dynamic json) { + _amount = json['amount']; + _code = json['code']; + _cover = json['cover']; + _expand = json['expand']; + _id = json['id']; + _name = json['name']; + _sourceUrl = json['sourceUrl']; + _type = json['type']; + } + + num? _amount; + String? _code; + String? _cover; + String? _expand; + String? _id; + String? _name; + String? _sourceUrl; + String? _type; + + PropsResources copyWith({ + num? amount, + String? code, + String? cover, + String? expand, + String? id, + String? name, + String? sourceUrl, + String? type, + }) => PropsResources( + amount: amount ?? _amount, + code: code ?? _code, + cover: cover ?? _cover, + expand: expand ?? _expand, + id: id ?? _id, + name: name ?? _name, + sourceUrl: sourceUrl ?? _sourceUrl, + type: type ?? _type, + ); + + num? get amount => _amount; + + String? get code => _code; + + String? get cover => _cover; + + String? get expand => _expand; + + String? get id => _id; + + String? get name => _name; + + String? get sourceUrl => _sourceUrl; + + String? get type => _type; + + Map toJson() { + final map = {}; + map['amount'] = _amount; + map['code'] = _code; + map['cover'] = _cover; + map['expand'] = _expand; + map['id'] = _id; + map['name'] = _name; + map['sourceUrl'] = _sourceUrl; + map['type'] = _type; + return map; + } +} + +/// account : "" +/// expiredTime : 0 + +class OwnSpecialId { + OwnSpecialId({String? account, num? expiredTime}) { + _account = account; + _expiredTime = expiredTime; + } + + OwnSpecialId.fromJson(dynamic json) { + _account = json['account']; + _expiredTime = json['expiredTime']; + } + + String? _account; + num? _expiredTime; + + OwnSpecialId copyWith({String? account, num? expiredTime}) => OwnSpecialId( + account: account ?? _account, + expiredTime: expiredTime ?? _expiredTime, + ); + + String? get account => _account; + + num? get expiredTime => _expiredTime; + + Map toJson() { + final map = {}; + map['account'] = _account; + map['expiredTime'] = _expiredTime; + return map; + } +} + +/// adminLockSeat : false +/// allowMusic : false +/// joinGolds : 0 +/// lockLocation : "" +/// maxAdmin : 0 +/// maxMember : 0 +/// mikeSize : 0 +/// openKtvMode : false +/// password : "" +/// roomId : 0 +/// roomSpecialMikeType : "" +/// showHeartbeat : false +/// takeMicRole : "" +/// touristMike : false +/// touristMsg : false +/// userId : "0" + +/// activeTime : 0 +/// countryCode : "" +/// countryName : "" +/// createTime : 0 +/// del : false +/// event : "" +/// id : "0" +/// langCode : "" +/// nationalFlag : "" +/// roomAccount : "" +/// roomCover : "" +/// roomDesc : "" +/// roomName : "" +/// sysOrigin : "" +/// updateTime : 0 +/// userId : "0" + +class RoomProfile2 { + RoomProfile2({ + num? activeTime, + String? countryCode, + String? countryName, + num? createTime, + bool? del, + String? event, + String? id, + String? langCode, + String? nationalFlag, + String? roomAccount, + String? roomCover, + String? roomDesc, + String? roomName, + String? sysOrigin, + num? updateTime, + String? userId, + }) { + _activeTime = activeTime; + _countryCode = countryCode; + _countryName = countryName; + _createTime = createTime; + _del = del; + _event = event; + _id = id; + _langCode = langCode; + _nationalFlag = nationalFlag; + _roomAccount = roomAccount; + _roomCover = roomCover; + _roomDesc = roomDesc; + _roomName = roomName; + _sysOrigin = sysOrigin; + _updateTime = updateTime; + _userId = userId; + } + + RoomProfile2.fromJson(dynamic json) { + _activeTime = json['activeTime']; + _countryCode = json['countryCode']; + _countryName = json['countryName']; + _createTime = json['createTime']; + _del = json['del']; + _event = json['event']; + _id = json['id']; + _langCode = json['langCode']; + _nationalFlag = json['nationalFlag']; + _roomAccount = json['roomAccount']; + _roomCover = json['roomCover']; + _roomDesc = json['roomDesc']; + _roomName = json['roomName']; + _sysOrigin = json['sysOrigin']; + _updateTime = json['updateTime']; + _userId = json['userId']; + } + + num? _activeTime; + String? _countryCode; + String? _countryName; + num? _createTime; + bool? _del; + String? _event; + String? _id; + String? _langCode; + String? _nationalFlag; + String? _roomAccount; + String? _roomCover; + String? _roomDesc; + String? _roomName; + String? _sysOrigin; + num? _updateTime; + String? _userId; + + RoomProfile2 copyWith({ + num? activeTime, + String? countryCode, + String? countryName, + num? createTime, + bool? del, + String? event, + String? id, + String? langCode, + String? nationalFlag, + String? roomAccount, + String? roomCover, + String? roomDesc, + String? roomName, + String? sysOrigin, + num? updateTime, + String? userId, + }) => RoomProfile2( + activeTime: activeTime ?? _activeTime, + countryCode: countryCode ?? _countryCode, + countryName: countryName ?? _countryName, + createTime: createTime ?? _createTime, + del: del ?? _del, + event: event ?? _event, + id: id ?? _id, + langCode: langCode ?? _langCode, + nationalFlag: nationalFlag ?? _nationalFlag, + roomAccount: roomAccount ?? _roomAccount, + roomCover: roomCover ?? _roomCover, + roomDesc: roomDesc ?? _roomDesc, + roomName: roomName ?? _roomName, + sysOrigin: sysOrigin ?? _sysOrigin, + updateTime: updateTime ?? _updateTime, + userId: userId ?? _userId, + ); + + num? get activeTime => _activeTime; + + String? get countryCode => _countryCode; + + String? get countryName => _countryName; + + num? get createTime => _createTime; + + bool? get del => _del; + + String? get event => _event; + + String? get id => _id; + + String? get langCode => _langCode; + + String? get nationalFlag => _nationalFlag; + + String? get roomAccount => _roomAccount; + + String? get roomCover => _roomCover; + + String? get roomDesc => _roomDesc; + + String? get roomName => _roomName; + + String? get sysOrigin => _sysOrigin; + + num? get updateTime => _updateTime; + + String? get userId => _userId; + + Map toJson() { + final map = {}; + map['activeTime'] = _activeTime; + map['countryCode'] = _countryCode; + map['countryName'] = _countryName; + map['createTime'] = _createTime; + map['del'] = _del; + map['event'] = _event; + map['id'] = _id; + map['langCode'] = _langCode; + map['nationalFlag'] = _nationalFlag; + map['roomAccount'] = _roomAccount; + map['roomCover'] = _roomCover; + map['roomDesc'] = _roomDesc; + map['roomName'] = _roomName; + map['sysOrigin'] = _sysOrigin; + map['updateTime'] = _updateTime; + map['userId'] = _userId; + return map; + } +} + +/// adminCount : 0 +/// memberCount : 0 + +class RoomCounter { + RoomCounter({num? adminCount, num? memberCount}) { + _adminCount = adminCount; + _memberCount = memberCount; + } + + RoomCounter.fromJson(dynamic json) { + _adminCount = json['adminCount']; + _memberCount = json['memberCount']; + } + + num? _adminCount; + num? _memberCount; + + RoomCounter copyWith({num? adminCount, num? memberCount}) => RoomCounter( + adminCount: adminCount ?? _adminCount, + memberCount: memberCount ?? _memberCount, + ); + + num? get adminCount => _adminCount; + + num? get memberCount => _memberCount; + + Map toJson() { + final map = {}; + map['adminCount'] = _adminCount; + map['memberCount'] = _memberCount; + return map; + } +} + +/// level : {"charmLevel":0,"userId":0,"wealthLevel":0} +/// nobleVipAbility : [""] +/// roles : "" +/// roomToken : "" + +class Entrants { + Entrants({ + Level? level, + List? nobleVipAbility, + String? roles, + String? roomToken, + }) { + _level = level; + _nobleVipAbility = nobleVipAbility; + _roles = roles; + _roomToken = roomToken; + } + + Entrants.fromJson(dynamic json) { + _level = json['level'] != null ? Level.fromJson(json['level']) : null; + _nobleVipAbility = + json['nobleVipAbility'] != null + ? (json['nobleVipAbility'] as List) + .map((item) => item as String) + .toList() + : null; + _roles = json['roles']; + _roomToken = json['roomToken']; + } + + Level? _level; + List? _nobleVipAbility; + String? _roles; + String? _roomToken; + + Entrants copyWith({ + Level? level, + List? nobleVipAbility, + String? roles, + String? roomToken, + }) => Entrants( + level: level ?? _level, + nobleVipAbility: nobleVipAbility ?? _nobleVipAbility, + roles: roles ?? _roles, + roomToken: roomToken ?? _roomToken, + ); + + Level? get level => _level; + + List? get nobleVipAbility => _nobleVipAbility; + + String? get roles => _roles; + + String? get roomToken => _roomToken; + + Map toJson() { + final map = {}; + if (_level != null) { + map['level'] = _level?.toJson(); + } + map['nobleVipAbility'] = _nobleVipAbility; + map['roles'] = _roles; + map['roomToken'] = _roomToken; + return map; + } + + void setRoles(String? roles) { + _roles = roles; + } +} + +/// charmLevel : 0 +/// userId : 0 +/// wealthLevel : 0 + +class Level { + Level({num? charmLevel, num? userId, num? wealthLevel}) { + _charmLevel = charmLevel; + _userId = userId; + _wealthLevel = wealthLevel; + } + + Level.fromJson(dynamic json) { + _charmLevel = json['charmLevel']; + _userId = json['userId']; + _wealthLevel = json['wealthLevel']; + } + + num? _charmLevel; + num? _userId; + num? _wealthLevel; + + Level copyWith({num? charmLevel, num? userId, num? wealthLevel}) => Level( + charmLevel: charmLevel ?? _charmLevel, + userId: userId ?? _userId, + wealthLevel: wealthLevel ?? _wealthLevel, + ); + + num? get charmLevel => _charmLevel; + + num? get userId => _userId; + + num? get wealthLevel => _wealthLevel; + + Map toJson() { + final map = {}; + map['charmLevel'] = _charmLevel; + map['userId'] = _userId; + map['wealthLevel'] = _wealthLevel; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/login_res.dart b/lib/chatvibe_domain/models/res/login_res.dart new file mode 100644 index 0000000..31583ad --- /dev/null +++ b/lib/chatvibe_domain/models/res/login_res.dart @@ -0,0 +1,1307 @@ +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; + +import '../../../chatvibe_data/models/enum/at_props_type.dart'; +/// token : "" +/// userCredential : {"expireTime":0,"releaseTime":0,"sign":"","sysOrigin":"","userId":0,"version":""} +/// userProfile : {"account":"","accountStatus":"","age":0,"bornDay":0,"bornMonth":0,"bornYear":0,"countryCode":"","countryId":0,"countryName":"","createTime":0,"del":false,"freezingTime":0,"id":0,"originSys":"","ownSpecialId":{"account":"","expiredTime":0},"sameRegion":false,"sysOriginChild":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0,"wearBadge":[{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}]} +/// userSig : "" + +class ChatVibeLoginRes { + ChatVibeLoginRes({ + String? token, + ChatVibeUserCredential? userCredential, + ChatVibeUserProfile? userProfile, + String? userSig, + }) { + _token = token; + _userCredential = userCredential; + _userProfile = userProfile; + _userSig = userSig; + } + + ChatVibeLoginRes.fromJson(dynamic json) { + _token = json['token']; + _userCredential = + json['userCredential'] != null + ? ChatVibeUserCredential.fromJson(json['userCredential']) + : null; + _userProfile = + json['userProfile'] != null + ? ChatVibeUserProfile.fromJson(json['userProfile']) + : null; + _userSig = json['userSig']; + } + + String? _token; + ChatVibeUserCredential? _userCredential; + ChatVibeUserProfile? _userProfile; + String? _userSig; + + ChatVibeLoginRes copyWith({ + String? token, + ChatVibeUserCredential? userCredential, + ChatVibeUserProfile? userProfile, + String? userSig, + }) => ChatVibeLoginRes( + token: token ?? _token, + userCredential: userCredential ?? _userCredential, + userProfile: userProfile ?? _userProfile, + userSig: userSig ?? _userSig, + ); + + String? get token => _token; + + ChatVibeUserCredential? get userCredential => _userCredential; + + ChatVibeUserProfile? get userProfile => _userProfile; + + String? get userSig => _userSig; + + Map toJson() { + final map = {}; + map['token'] = _token; + if (_userCredential != null) { + map['userCredential'] = _userCredential?.toJson(); + } + if (_userProfile != null) { + map['userProfile'] = _userProfile?.toJson(); + } + map['userSig'] = _userSig; + return map; + } + + void setUserProfile(ChatVibeUserProfile userInfo) { + _userProfile = userInfo; + } +} + +/// account : "" +/// accountStatus : "" +/// age : 0 +/// bornDay : 0 +/// bornMonth : 0 +/// bornYear : 0 +/// countryCode : "" +/// countryId : 0 +/// countryName : "" +/// createTime : 0 +/// del : false +/// freezingTime : 0 +/// id : 0 +/// originSys : "" +/// ownSpecialId : {"account":"","expiredTime":0} +/// sameRegion : false +/// sysOriginChild : "" +/// useProps : [{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}] +/// userAvatar : "" +/// userNickname : "" +/// userSex : 0 +/// wearBadge : [{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}] + +class ChatVibeUserProfile { + ChatVibeUserProfile({ + String? account, + String? accountStatus, + num? age, + num? bornDay, + num? bornMonth, + num? bornYear, + String? countryCode, + String? countryId, + String? countryName, + num? createTime, + bool? del, + num? freezingTime, + String? id, + bool? firstRecharge, + num? charmLevel, + num? firstRechargeAmount, + num? wealthLevel, + num? heartbeatVal, + String? originSys, + OwnSpecialId? ownSpecialId, + bool? sameRegion, + int? firstRechargeEndTime, + String? sysOriginChild, + List? useProps, + String? userAvatar, + String? roles, + String? inRoomId, + String? userNickname, + String? hobby, + String? familyId, + String? autograph, + num? userSex, + List? wearBadge, + List? wearHonor, + List? backgroundPhotos, + List? personalPhotos, + List? cpList, + bool? isCpRelation, + }) { + _account = account; + _accountStatus = accountStatus; + _age = age; + _bornDay = bornDay; + _inRoomId = inRoomId; + _bornMonth = bornMonth; + _bornYear = bornYear; + _countryCode = countryCode; + _countryId = countryId; + _countryName = countryName; + _createTime = createTime; + _del = del; + _freezingTime = freezingTime; + _id = id; + _originSys = originSys; + _charmLevel = charmLevel; + _firstRechargeAmount = firstRechargeAmount; + _wealthLevel = wealthLevel; + _heartbeatVal = heartbeatVal; + _ownSpecialId = ownSpecialId; + _sameRegion = sameRegion; + _isCpRelation = isCpRelation; + _firstRechargeEndTime = firstRechargeEndTime; + _firstRecharge = firstRecharge; + _sysOriginChild = sysOriginChild; + _useProps = useProps; + _userAvatar = userAvatar; + _roles = roles; + _userNickname = userNickname; + _hobby = hobby; + _familyId = familyId; + _autograph = autograph; + _userSex = userSex; + _wearBadge = wearBadge; + _wearHonor = wearHonor; + _backgroundPhotos = backgroundPhotos; + _personalPhotos = personalPhotos; + _cpList = cpList; + } + + ChatVibeUserProfile.fromJson(dynamic json) { + _account = json['account']; + _accountStatus = json['accountStatus']; + _age = json['age']; + _bornDay = json['bornDay']; + _inRoomId = json['inRoomId']; + _bornMonth = json['bornMonth']; + _bornYear = json['bornYear']; + _countryCode = json['countryCode']; + _countryId = json['countryId']; + _countryName = json['countryName']; + _createTime = json['createTime']; + _del = json['del']; + _freezingTime = json['freezingTime']; + _id = json['id']; + _originSys = json['originSys']; + _charmLevel = json['charmLevel']; + _firstRechargeAmount = json['firstRechargeAmount']; + _wealthLevel = json['wealthLevel']; + _heartbeatVal = json['heartbeatVal']; + _ownSpecialId = + json['ownSpecialId'] != null + ? OwnSpecialId.fromJson(json['ownSpecialId']) + : null; + _sameRegion = json['sameRegion']; + _isCpRelation = json['isCpRelation']; + _firstRechargeEndTime = json['firstRechargeEndTime']; + _firstRecharge = json['firstRecharge']; + _sysOriginChild = json['sysOriginChild']; + if (json['useProps'] != null) { + _useProps = []; + json['useProps'].forEach((v) { + _useProps?.add(UseProps.fromJson(v)); + }); + } + _userAvatar = json['userAvatar']; + _roles = json['roles']; + _userNickname = json['userNickname']; + _hobby = json['hobby']; + _familyId = json['familyId']; + _autograph = json['autograph']; + _userSex = json['userSex']; + if (json['wearBadge'] != null) { + _wearBadge = []; + json['wearBadge'].forEach((v) { + _wearBadge?.add(WearBadge.fromJson(v)); + }); + } + if (json['wearHonor'] != null) { + _wearHonor = []; + json['wearHonor'].forEach((v) { + _wearHonor?.add(WearBadge.fromJson(v)); + }); + } + if (json['backgroundPhotos'] != null) { + _backgroundPhotos = []; + json['backgroundPhotos'].forEach((v) { + _backgroundPhotos?.add(PersonPhoto.fromJson(v)); + }); + } + if (json['personalPhotos'] != null) { + _personalPhotos = []; + json['personalPhotos'].forEach((v) { + _personalPhotos?.add(PersonPhoto.fromJson(v)); + }); + } + if (json['cpList'] != null) { + _cpList = []; + json['cpList'].forEach((v) { + _cpList?.add(CPRes.fromJson(v)); + }); + } + } + + String? _account; + String? _accountStatus; + num? _age; + num? _bornDay; + num? _bornMonth; + num? _bornYear; + String? _countryCode; + String? _countryId; + String? _inRoomId; + String? _countryName; + num? _createTime; + bool? _del; + num? _freezingTime; + String? _id; + String? _originSys; + num? _charmLevel; + num? _firstRechargeAmount; + num? _wealthLevel; + num? _heartbeatVal; + OwnSpecialId? _ownSpecialId; + bool? _sameRegion; + bool? _isCpRelation; + int? _firstRechargeEndTime; + bool? _firstRecharge; + String? _sysOriginChild; + List? _useProps; + String? _userAvatar; + String? _roles; + String? _userNickname; + String? _hobby; + String? _familyId; + String? _autograph; + num? _userSex; + List? _wearBadge; + List? _wearHonor; + List? _backgroundPhotos; + List? _personalPhotos; + List? _cpList; + + ChatVibeUserProfile copyWith({ + String? account, + String? accountStatus, + num? age, + num? bornDay, + num? bornMonth, + num? bornYear, + String? countryCode, + String? countryId, + String? countryName, + num? createTime, + bool? del, + num? freezingTime, + String? id, + String? originSys, + String? inRoomId, + num? charmLevel, + num? firstRechargeAmount, + num? wealthLevel, + num? heartbeatVal, + OwnSpecialId? ownSpecialId, + bool? sameRegion, + bool? isCpRelation, + int? firstRechargeEndTime, + bool? firstRecharge, + String? sysOriginChild, + List? useProps, + String? userAvatar, + String? roles, + String? userNickname, + String? hobby, + String? familyId, + String? autograph, + num? userSex, + List? wearBadge, + List? wearHonor, + List? backgroundPhotos, + List? personalPhotos, + List? cpList, + }) => ChatVibeUserProfile( + account: account ?? _account, + accountStatus: accountStatus ?? _accountStatus, + age: age ?? _age, + bornDay: bornDay ?? _bornDay, + bornMonth: bornMonth ?? _bornMonth, + bornYear: bornYear ?? _bornYear, + inRoomId: inRoomId ?? _inRoomId, + countryCode: countryCode ?? _countryCode, + countryId: countryId ?? _countryId, + countryName: countryName ?? _countryName, + createTime: createTime ?? _createTime, + del: del ?? _del, + freezingTime: freezingTime ?? _freezingTime, + id: id ?? _id, + originSys: originSys ?? _originSys, + charmLevel: charmLevel ?? _charmLevel, + firstRechargeAmount: firstRechargeAmount ?? _firstRechargeAmount, + wealthLevel: wealthLevel ?? _wealthLevel, + heartbeatVal: heartbeatVal ?? _heartbeatVal, + ownSpecialId: ownSpecialId ?? _ownSpecialId, + sameRegion: sameRegion ?? _sameRegion, + isCpRelation: isCpRelation ?? _isCpRelation, + firstRechargeEndTime: firstRechargeEndTime ?? _firstRechargeEndTime, + firstRecharge: firstRecharge ?? _firstRecharge, + sysOriginChild: sysOriginChild ?? _sysOriginChild, + useProps: useProps ?? _useProps, + userAvatar: userAvatar ?? _userAvatar, + roles: roles ?? _roles, + userNickname: userNickname ?? _userNickname, + hobby: hobby ?? _hobby, + familyId: familyId ?? _familyId, + autograph: autograph ?? _autograph, + userSex: userSex ?? _userSex, + wearBadge: wearBadge ?? _wearBadge, + wearHonor: wearHonor ?? _wearHonor, + backgroundPhotos: backgroundPhotos ?? _backgroundPhotos, + personalPhotos: personalPhotos ?? _personalPhotos, + cpList: cpList ?? _cpList, + ); + + String? get account => _account; + + String? get inRoomId => _inRoomId; + + String? get accountStatus => _accountStatus; + + num? get age => (_age ?? 0) < 1 ? 18 : _age; + + num? get bornDay => _bornDay; + + num? get bornMonth => _bornMonth; + + num? get bornYear => _bornYear; + + String? get countryCode => _countryCode; + + String? get countryId => _countryId; + + String? get countryName => _countryName; + + num? get createTime => _createTime; + + bool? get del => _del; + + num? get freezingTime => _freezingTime; + + String? get id => _id; + + String? get originSys => _originSys; + + num? get charmLevel => _charmLevel; + + num? get firstRechargeAmount => _firstRechargeAmount; + + num? get wealthLevel => _wealthLevel; + + num? get heartbeatVal => _heartbeatVal; + + OwnSpecialId? get ownSpecialId => _ownSpecialId; + + bool? get sameRegion => _sameRegion; + + bool? get isCpRelation => _isCpRelation; + + int? get firstRechargeEndTime => _firstRechargeEndTime; + + bool? get firstRecharge => _firstRecharge; + + String? get sysOriginChild => _sysOriginChild; + + List? get useProps => _useProps; + + String? get userAvatar => _userAvatar; + + String? get roles => _roles; + + String? get userNickname => _userNickname; + + String? get hobby => _hobby; + + String? get familyId => _familyId; + + String? get autograph => _autograph; + + num? get userSex => _userSex; + + List? get wearBadge => _wearBadge; + + List? get wearHonor => _wearHonor; + + List? get backgroundPhotos => _backgroundPhotos; + + List? get personalPhotos => _personalPhotos; + + List? get cpList => _cpList; + + void setFamilyId(String? familyId) { + _familyId = familyId; + AccountStorage().setCurrentUser(AccountStorage().getCurrentUser()!); + } + + ///清除荣耀信息,避免发送消息个人信息数据太多超12kb,注意这个只在房间发消息的时候才用,因为荣耀这个属性数据在房间里面不用展示 + void cleanWearHonor() { + _wearHonor?.clear(); + } + + ///同上 + void cleanWearBadge() { + _wearBadge?.clear(); + } + void cleanUseProps() { + _useProps?.clear(); + } + + void cleanPhotos() { + _backgroundPhotos?.clear(); + _personalPhotos?.clear(); + _cpList?.clear(); + } + + PropsResources? getHeaddress() { + PropsResources? pr; + _useProps?.forEach((value) { + if (value.propsResources?.type == ATPropsType.AVATAR_FRAME.name) { + pr = value.propsResources; + } + }); + return pr; + } + + PropsResources? getChatBox() { + PropsResources? pr; + _useProps?.forEach((value) { + if (value.propsResources?.type == ATPropsType.CHAT_BUBBLE.name) { + pr = value.propsResources; + } + }); + return pr; + } + + PropsResources? getMountains() { + PropsResources? pr; + _useProps?.forEach((value) { + if (value.propsResources?.type == ATPropsType.RIDE.name) { + pr = value.propsResources; + } + }); + return pr; + } + + PropsResources? getVIP() { + PropsResources? pr; + _useProps?.forEach((value) { + if (value.propsResources?.type == ATPropsType.NOBLE_VIP.name) { + pr = value.propsResources; + } + }); + return pr; + } + + PropsResources? getDataCard() { + PropsResources? pr; + _useProps?.forEach((value) { + if (value.propsResources?.type == ATPropsType.DATA_CARD.name) { + pr = value.propsResources; + } + }); + return pr; + } + + Map toJson() { + final map = {}; + map['account'] = _account; + map['accountStatus'] = _accountStatus; + map['age'] = _age; + map['bornDay'] = _bornDay; + map['bornMonth'] = _bornMonth; + map['bornYear'] = _bornYear; + map['countryCode'] = _countryCode; + map['countryId'] = _countryId; + map['countryName'] = _countryName; + map['createTime'] = _createTime; + map['del'] = _del; + map['freezingTime'] = _freezingTime; + map['id'] = _id; + map['originSys'] = _originSys; + map['charmLevel'] = _charmLevel; + map['firstRechargeAmount'] = _firstRechargeAmount; + map['wealthLevel'] = _wealthLevel; + map['heartbeatVal'] = _heartbeatVal; + if (_ownSpecialId != null) { + map['ownSpecialId'] = _ownSpecialId?.toJson(); + } + map['sameRegion'] = _sameRegion; + map['isCpRelation'] = _isCpRelation; + map['firstRecharge'] = _firstRecharge; + map['firstRechargeEndTime'] = _firstRechargeEndTime; + map['sysOriginChild'] = _sysOriginChild; + if (_useProps != null) { + map['useProps'] = _useProps?.map((v) => v.toJson()).toList(); + } + map['userAvatar'] = _userAvatar; + map['roles'] = _roles; + map['userNickname'] = _userNickname; + map['hobby'] = _hobby; + map['familyId'] = _familyId; + map['autograph'] = _autograph; + map['userSex'] = _userSex; + if (_wearBadge != null) { + map['wearBadge'] = _wearBadge?.map((v) => v.toJson()).toList(); + } + if (_wearHonor != null) { + map['wearHonor'] = _wearHonor?.map((v) => v.toJson()).toList(); + } + return map; + } + + ///获取Id,有靓号显示靓号 + String getID() { + String uid = account ?? ""; + if (_ownSpecialId != null) { + uid = _ownSpecialId?.account ?? ""; + } + return uid; + } + + ///是否有靓号 + bool hasSpecialId() { + if (_ownSpecialId != null) { + if ((_ownSpecialId?.expiredTime ?? 0) > + DateTime.now().millisecondsSinceEpoch) { + return true; + } + } + return false; + } + + void setHeartbeatVal(num? heartbeatVal) { + _heartbeatVal = (_heartbeatVal ?? 0) + (heartbeatVal ?? 0); + } +} + +/// animationUrl : "" +/// badgeKey : "" +/// badgeLevel : 0 +/// badgeName : "" +/// expireTime : 0 +/// id : 0 +/// milestone : 0 +/// notSelectUrl : "" +/// selectUrl : "" +/// type : "" +/// userId : 0 + +class WearBadge { + WearBadge({ + String? animationUrl, + String? badgeKey, + num? badgeLevel, + String? badgeName, + int? expireTime, + String? id, + String? milestone, + String? notSelectUrl, + String? selectUrl, + String? type, + String? userId, + bool? use, + }) { + _animationUrl = animationUrl; + _badgeKey = badgeKey; + _badgeLevel = badgeLevel; + _badgeName = badgeName; + _expireTime = expireTime; + _id = id; + _use = use; + _milestone = milestone; + _notSelectUrl = notSelectUrl; + _selectUrl = selectUrl; + _type = type; + _userId = userId; + } + + WearBadge.fromJson(dynamic json) { + _animationUrl = json['animationUrl']; + _badgeKey = json['badgeKey']; + _badgeLevel = json['badgeLevel']; + _badgeName = json['badgeName']; + _expireTime = json['expireTime']; + _id = json['id']; + _use = json['use']; + _milestone = json['milestone']; + _notSelectUrl = json['notSelectUrl']; + _selectUrl = json['selectUrl']; + _type = json['type']; + _userId = json['userId']; + } + + String? _animationUrl; + String? _badgeKey; + num? _badgeLevel; + String? _badgeName; + int? _expireTime; + String? _id; + bool? _use; + String? _milestone; + String? _notSelectUrl; + String? _selectUrl; + String? _type; + String? _userId; + + WearBadge copyWith({ + String? animationUrl, + String? badgeKey, + num? badgeLevel, + String? badgeName, + int? expireTime, + String? id, + bool? use, + String? milestone, + String? notSelectUrl, + String? selectUrl, + String? type, + String? userId, + }) => WearBadge( + animationUrl: animationUrl ?? _animationUrl, + badgeKey: badgeKey ?? _badgeKey, + badgeLevel: badgeLevel ?? _badgeLevel, + badgeName: badgeName ?? _badgeName, + expireTime: expireTime ?? _expireTime, + id: id ?? _id, + use: use ?? _use, + milestone: milestone ?? _milestone, + notSelectUrl: notSelectUrl ?? _notSelectUrl, + selectUrl: selectUrl ?? _selectUrl, + type: type ?? _type, + userId: userId ?? _userId, + ); + + String? get animationUrl => _animationUrl; + + String? get badgeKey => _badgeKey; + + num? get badgeLevel => _badgeLevel; + + bool? get use => _use; + + String? get badgeName => _badgeName; + + int? get expireTime => _expireTime; + + String? get id => _id; + + String? get milestone => _milestone; + + String? get notSelectUrl => _notSelectUrl; + + String? get selectUrl => _selectUrl; + + String? get type => _type; + + String? get userId => _userId; + + Map toJson() { + final map = {}; + map['animationUrl'] = _animationUrl; + map['badgeKey'] = _badgeKey; + map['badgeLevel'] = _badgeLevel; + map['badgeName'] = _badgeName; + map['expireTime'] = _expireTime; + map['id'] = _id; + map['use'] = _use; + map['milestone'] = _milestone; + map['notSelectUrl'] = _notSelectUrl; + map['selectUrl'] = _selectUrl; + map['type'] = _type; + map['userId'] = _userId; + return map; + } +} + +class PersonPhoto { + PersonPhoto({String? url, num? status}) { + _url = url; + _status = status; + } + + PersonPhoto.fromJson(dynamic json) { + _url = json['url']; + _status = json['status']; + } + + String? _url; + num? _status; + + PersonPhoto copyWith({String? url, num? status}) => + PersonPhoto(url: url ?? _url, status: status ?? _status); + + String? get url => _url; + + num? get status => _status; + + Map toJson() { + final map = {}; + map['url'] = _url; + map['status'] = _status; + return map; + } +} + +class CPRes { + CPRes({ + String? meUserId, + String? meAccount, + String? meUserAvatar, + String? meUserNickname, + String? cpUserId, + num? cpValId, + num? cpLevel, + bool? hasNonMicEffect, + num? micEffectLevel, + num? loveHeartLevel, + num? profileCardLevel, + num? nextLevelExp, + PropsResources? selfRing, + String? cpUserNickname, + String? cpAccount, + String? cpUserAvatar, + String? cpUserAvatarFrame, + String? days, + String? firstDay, + double? cpValue, + }) { + _meUserId = meUserId; + _meAccount = meAccount; + _meUserAvatar = meUserAvatar; + _meUserNickname = meUserNickname; + _cpUserId = cpUserId; + _cpValId = cpValId; + _cpLevel = cpLevel; + _hasNonMicEffect = hasNonMicEffect; + _micEffectLevel = micEffectLevel; + _loveHeartLevel = loveHeartLevel; + _profileCardLevel = profileCardLevel; + _nextLevelExp = nextLevelExp; + _selfRing = selfRing; + _cpUserNickname = cpUserNickname; + _cpUserAvatarFrame = cpUserAvatarFrame; + _cpAccount = cpAccount; + _cpUserAvatar = cpUserAvatar; + _days = days; + _firstDay = firstDay; + _cpValue = cpValue; + } + + CPRes.fromJson(dynamic json) { + _meUserId = json['meUserId']; + _meAccount = json['meAccount']; + _meUserAvatar = json['meUserAvatar']; + _meUserNickname = json['meUserNickname']; + _cpUserId = json['cpUserId']; + _cpValId = json['cpValId']; + _cpLevel = json['cpLevel']; + _hasNonMicEffect = json['hasNonMicEffect'] ?? json['hasEffectLevel']; + _micEffectLevel = json['micEffectLevel']; + _loveHeartLevel = json['loveHeartLevel']; + _profileCardLevel = json['profileCardLevel']; + _nextLevelExp = json['nextLevelExp']; + _selfRing = + json['selfRing'] != null + ? PropsResources.fromJson(json['selfRing']) + : null; + _cpUserNickname = json['cpUserNickname']; + _cpUserAvatarFrame = json['cpUserAvatarFrame']; + _cpAccount = json['cpAccount']; + _cpUserAvatar = json['cpUserAvatar']; + _days = json['days']; + _firstDay = json['firstDay']; + _cpValue = json['cpValue']; + } + + String? _meUserId; + String? _meAccount; + String? _meUserAvatar; + String? _meUserNickname; + String? _cpUserId; + num? _cpValId; + num? _cpLevel; + bool? _hasNonMicEffect; + num? _micEffectLevel; + num? _loveHeartLevel; + num? _profileCardLevel; + num? _nextLevelExp; + PropsResources? _selfRing; + String? _cpUserNickname; + String? _cpUserAvatarFrame; + String? _cpAccount; + String? _cpUserAvatar; + String? _days; + String? _firstDay; + double? _cpValue; + + String? get meUserId => _meUserId; + + String? get meAccount => _meAccount; + + String? get meUserAvatar => _meUserAvatar; + + String? get meUserNickname => _meUserNickname; + + String? get cpUserId => _cpUserId; + + num? get cpValId => _cpValId; + + num? get cpLevel => _cpLevel; + + bool? get hasNonMicEffect => _hasNonMicEffect; + + bool? get hasEffectLevel => _hasNonMicEffect; + + num? get micEffectLevel => _micEffectLevel; + + num? get loveHeartLevel => _loveHeartLevel; + + num? get profileCardLevel => _profileCardLevel; + + num? get nextLevelExp => _nextLevelExp; + + PropsResources? get selfRing => _selfRing; + + String? get cpUserNickname => _cpUserNickname; + + String? get cpUserAvatarFrame => _cpUserAvatarFrame; + + String? get cpAccount => _cpAccount; + + String? get cpUserAvatar => _cpUserAvatar; + + String? get days => _days; + + String? get firstDay => _firstDay; + + double? get cpValue => _cpValue; + + Map toJson() { + final map = {}; + map['meUserId'] = _meUserId; + map['meAccount'] = _meAccount; + map['meUserAvatar'] = _meUserAvatar; + map['meUserNickname'] = _meUserNickname; + map['cpUserId'] = _cpUserId; + map['cpValId'] = _cpValId; + map['cpLevel'] = _cpLevel; + map['hasNonMicEffect'] = _hasNonMicEffect; + map['hasEffectLevel'] = _hasNonMicEffect; + map['micEffectLevel'] = _micEffectLevel; + map['loveHeartLevel'] = _loveHeartLevel; + map['profileCardLevel'] = _profileCardLevel; + map['nextLevelExp'] = _nextLevelExp; + map['cpUserNickname'] = _cpUserNickname; + map['cpUserAvatarFrame'] = _cpUserAvatarFrame; + map['cpAccount'] = _cpAccount; + map['cpUserAvatar'] = _cpUserAvatar; + map['days'] = _days; + map['firstDay'] = _firstDay; + map['cpValue'] = _cpValue; + if (_selfRing != null) { + map['selfRing'] = _selfRing?.toJson(); + } + return map; + } +} + +/// expireTime : 0 +/// propsResources : {"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""} +/// userId : 0 + +class UseProps { + UseProps({ + bool? allowGive, + String? expireTime, + PropsResources? propsResources, + String? userId, + }) { + _allowGive = allowGive; + _expireTime = expireTime; + _propsResources = propsResources; + _userId = userId; + } + + UseProps.fromJson(dynamic json) { + _allowGive = json['allowGive']; + _expireTime = json['expireTime']; + _propsResources = + json['propsResources'] != null + ? PropsResources.fromJson(json['propsResources']) + : null; + _userId = json['userId']; + } + + bool? _allowGive; + String? _expireTime; + PropsResources? _propsResources; + String? _userId; + + UseProps copyWith({ + bool? allowGive, + String? expireTime, + PropsResources? propsResources, + String? userId, + }) => UseProps( + allowGive: allowGive ?? _allowGive, + expireTime: expireTime ?? _expireTime, + propsResources: propsResources ?? _propsResources, + userId: userId ?? _userId, + ); + + bool? get allowGive => _allowGive; + + String? get expireTime => _expireTime; + + PropsResources? get propsResources => _propsResources; + + String? get userId => _userId; + + Map toJson() { + final map = {}; + map['allowGive'] = _allowGive; + map['expireTime'] = _expireTime; + if (_propsResources != null) { + map['propsResources'] = _propsResources?.toJson(); + } + map['userId'] = _userId; + return map; + } + + void setPropsResources(PropsResources? pr) { + _propsResources = pr; + } +} + +/// amount : 0.0 +/// code : "" +/// cover : "" +/// expand : "" +/// id : 0 +/// name : "" +/// sourceUrl : "" +/// type : "" + +class PropsResources { + PropsResources({ + num? amount, + String? code, + String? cover, + num? createTime, + bool? del, + String? expand, + String? id, + String? imNotOpenedUrl, + String? imOpenedUrl, + String? imOpenedUrlTwo, + String? imSendCoverUrl, + String? name, + String? roomNotOpenedUrl, + String? roomOpenedUrl, + String? roomSendCoverUrl, + String? sourceUrl, + String? sysOrigin, + String? type, + num? updateTime, + }) { + _amount = amount; + _code = code; + _cover = cover; + _createTime = createTime; + _del = del; + _expand = expand; + _id = id; + _imNotOpenedUrl = imNotOpenedUrl; + _imOpenedUrl = imOpenedUrl; + _imOpenedUrlTwo = imOpenedUrlTwo; + _imSendCoverUrl = imSendCoverUrl; + _name = name; + _roomNotOpenedUrl = roomNotOpenedUrl; + _roomOpenedUrl = roomOpenedUrl; + _roomSendCoverUrl = roomSendCoverUrl; + _sourceUrl = sourceUrl; + _sysOrigin = sysOrigin; + _type = type; + _updateTime = updateTime; + } + + PropsResources.fromJson(dynamic json) { + _amount = json['amount']; + _code = json['code']; + _cover = json['cover']; + _createTime = json['createTime']; + _del = json['del']; + _expand = json['expand']; + _id = json['id']; + _imNotOpenedUrl = json['imNotOpenedUrl']; + _imOpenedUrl = json['imOpenedUrl']; + _imOpenedUrlTwo = json['imOpenedUrlTwo']; + _imSendCoverUrl = json['imSendCoverUrl']; + _name = json['name']; + _roomNotOpenedUrl = json['roomNotOpenedUrl']; + _roomOpenedUrl = json['roomOpenedUrl']; + _roomSendCoverUrl = json['roomSendCoverUrl']; + _sourceUrl = json['sourceUrl']; + _sysOrigin = json['sysOrigin']; + _type = json['type']; + _updateTime = json['updateTime']; + } + + num? _amount; + String? _code; + String? _cover; + num? _createTime; + bool? _del; + String? _expand; + String? _id; + String? _imNotOpenedUrl; + String? _imOpenedUrl; + String? _imOpenedUrlTwo; + String? _imSendCoverUrl; + String? _name; + String? _roomNotOpenedUrl; + String? _roomOpenedUrl; + String? _roomSendCoverUrl; + String? _sourceUrl; + String? _sysOrigin; + String? _type; + num? _updateTime; + + PropsResources copyWith({ + num? amount, + String? code, + String? cover, + num? createTime, + bool? del, + String? expand, + String? id, + String? imNotOpenedUrl, + String? imOpenedUrl, + String? imOpenedUrlTwo, + String? imSendCoverUrl, + String? name, + String? roomNotOpenedUrl, + String? roomOpenedUrl, + String? roomSendCoverUrl, + String? sourceUrl, + String? sysOrigin, + String? type, + num? updateTime, + }) => PropsResources( + amount: amount ?? _amount, + code: code ?? _code, + cover: cover ?? _cover, + createTime: createTime ?? _createTime, + del: del ?? _del, + expand: expand ?? _expand, + id: id ?? _id, + imNotOpenedUrl: imNotOpenedUrl ?? _imNotOpenedUrl, + imOpenedUrl: imOpenedUrl ?? _imOpenedUrl, + imOpenedUrlTwo: imOpenedUrlTwo ?? _imOpenedUrlTwo, + imSendCoverUrl: imSendCoverUrl ?? _imSendCoverUrl, + name: name ?? _name, + roomNotOpenedUrl: roomNotOpenedUrl ?? _roomNotOpenedUrl, + roomOpenedUrl: roomOpenedUrl ?? _roomOpenedUrl, + roomSendCoverUrl: roomSendCoverUrl ?? _roomSendCoverUrl, + sourceUrl: sourceUrl ?? _sourceUrl, + sysOrigin: sysOrigin ?? _sysOrigin, + type: type ?? _type, + updateTime: updateTime ?? _updateTime, + ); + + num? get amount => _amount; + + String? get code => _code; + + String? get cover => _cover; + + num? get createTime => _createTime; + + bool? get del => _del; + + String? get expand => _expand; + + String? get id => _id; + + String? get imNotOpenedUrl => _imNotOpenedUrl; + + String? get imOpenedUrl => _imOpenedUrl; + + String? get imOpenedUrlTwo => _imOpenedUrlTwo; + + String? get imSendCoverUrl => _imSendCoverUrl; + + String? get name => _name; + + String? get roomNotOpenedUrl => _roomNotOpenedUrl; + + String? get roomOpenedUrl => _roomOpenedUrl; + + String? get roomSendCoverUrl => _roomSendCoverUrl; + + String? get sourceUrl => _sourceUrl; + + String? get sysOrigin => _sysOrigin; + + String? get type => _type; + + num? get updateTime => _updateTime; + + Map toJson() { + final map = {}; + map['amount'] = _amount; + map['code'] = _code; + map['cover'] = _cover; + map['createTime'] = _createTime; + map['del'] = _del; + map['expand'] = _expand; + map['id'] = _id; + map['imNotOpenedUrl'] = _imNotOpenedUrl; + map['imOpenedUrl'] = _imOpenedUrl; + map['imOpenedUrlTwo'] = _imOpenedUrlTwo; + map['imSendCoverUrl'] = _imSendCoverUrl; + map['name'] = _name; + map['roomNotOpenedUrl'] = _roomNotOpenedUrl; + map['roomOpenedUrl'] = _roomOpenedUrl; + map['roomSendCoverUrl'] = _roomSendCoverUrl; + map['sourceUrl'] = _sourceUrl; + map['sysOrigin'] = _sysOrigin; + map['type'] = _type; + map['updateTime'] = _updateTime; + return map; + } +} + +/// account : "" +/// expiredTime : 0 + +class OwnSpecialId { + OwnSpecialId({String? account, num? expiredTime}) { + _account = account; + _expiredTime = expiredTime; + } + + OwnSpecialId.fromJson(dynamic json) { + _account = json['account']; + _expiredTime = json['expiredTime']; + } + + String? _account; + num? _expiredTime; + + OwnSpecialId copyWith({String? account, num? expiredTime}) => OwnSpecialId( + account: account ?? _account, + expiredTime: expiredTime ?? _expiredTime, + ); + + String? get account => _account; + + num? get expiredTime => _expiredTime; + + Map toJson() { + final map = {}; + map['account'] = _account; + map['expiredTime'] = _expiredTime; + return map; + } +} + +/// expireTime : 0 +/// releaseTime : 0 +/// sign : "" +/// sysOrigin : "" +/// userId : 0 +/// version : "" + +class ChatVibeUserCredential { + ChatVibeUserCredential({ + String? expireTime, + String? releaseTime, + String? sign, + String? sysOrigin, + String? userId, + String? version, + }) { + _expireTime = expireTime; + _releaseTime = releaseTime; + _sign = sign; + _sysOrigin = sysOrigin; + _userId = userId; + _version = version; + } + + ChatVibeUserCredential.fromJson(dynamic json) { + _expireTime = json['expireTime']; + _releaseTime = json['releaseTime']; + _sign = json['sign']; + _sysOrigin = json['sysOrigin']; + _userId = json['userId']; + _version = json['version']; + } + + String? _expireTime; + String? _releaseTime; + String? _sign; + String? _sysOrigin; + String? _userId; + String? _version; + + ChatVibeUserCredential copyWith({ + String? expireTime, + String? releaseTime, + String? sign, + String? sysOrigin, + String? userId, + String? version, + }) => ChatVibeUserCredential( + expireTime: expireTime ?? _expireTime, + releaseTime: releaseTime ?? _releaseTime, + sign: sign ?? _sign, + sysOrigin: sysOrigin ?? _sysOrigin, + userId: userId ?? _userId, + version: version ?? _version, + ); + + String? get expireTime => _expireTime; + + String? get releaseTime => _releaseTime; + + String? get sign => _sign; + + String? get sysOrigin => _sysOrigin; + + String? get userId => _userId; + + String? get version => _version; + + Map toJson() { + final map = {}; + map['expireTime'] = _expireTime; + map['releaseTime'] = _releaseTime; + map['sign'] = _sign; + map['sysOrigin'] = _sysOrigin; + map['userId'] = _userId; + map['version'] = _version; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/message_friend_user_res.dart b/lib/chatvibe_domain/models/res/message_friend_user_res.dart new file mode 100644 index 0000000..c6dfa23 --- /dev/null +++ b/lib/chatvibe_domain/models/res/message_friend_user_res.dart @@ -0,0 +1,40 @@ +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +class MessageFriendUserRes{ + ChatVibeLoginRes({ + String? id, + ChatVibeUserProfile? userProfile, + + }) { + _userProfile = userProfile; + _id = id; + } + + MessageFriendUserRes.fromJson(dynamic json) { + _id = json['id']; + _userProfile = + json['userProfile'] != null + ? ChatVibeUserProfile.fromJson(json['userProfile']) + : null; + } + ChatVibeUserProfile? _userProfile; + String? _id; + + + String? get id => _id; + ChatVibeUserProfile? get userProfile => _userProfile; + + + Map toJson() { + final map = {}; + map['id'] = _id; + if (_userProfile != null) { + map['userProfile'] = _userProfile?.toJson(); + } + return map; + } + + void setUserProfile(ChatVibeUserProfile userInfo) { + _userProfile = userInfo; + } +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/mic_res.dart b/lib/chatvibe_domain/models/res/mic_res.dart new file mode 100644 index 0000000..816ee1c --- /dev/null +++ b/lib/chatvibe_domain/models/res/mic_res.dart @@ -0,0 +1,115 @@ +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +/// roomId : 0 +/// micIndex : 0 +/// micLock : false +/// micMute : false +/// user : {"id":"0","account":"","userAvatar":"","userNickname":"","userSex":0,"ownSpecialId":{"account":"","expiredTime":0},"useProps":[{"userId":"0","propsResources":{"id":"0","type":"","code":"","name":"","cover":"","sourceUrl":"","expand":"","amount":0.0},"expireTime":0}],"roles":"","charmLevel":0,"heartbeatVal":0} +/// roomToken : "" + +class MicRes { + MicRes({ + String? roomId, + num? micIndex, + bool? micLock, + bool? micMute, + String? emojiPath, + ChatVibeUserProfile? user, + String? type, + String? roomToken, + String?number + }) { + _roomId = roomId; + _micIndex = micIndex; + _micLock = micLock; + _micMute = micMute; + _user = user; + _roomToken = roomToken; + _emojiPath = emojiPath; + _type = type; + _number = number; + } + + MicRes.fromJson(dynamic json) { + _roomId = json['roomId']; + _micIndex = json['micIndex']; + _micLock = json['micLock']; + _micMute = json['micMute']; + _user = json['user'] != null ? ChatVibeUserProfile.fromJson(json['user']) : null; + _roomToken = json['roomToken']; + _type = json['type']; + _number = json['number']; + } + + String? _roomId; + num? _micIndex; + bool? _micLock; + bool? _micMute; + ChatVibeUserProfile? _user; + String? _roomToken; + int? _volume = 0; + String? _type = ""; + String? _emojiPath; + String? _number; + + MicRes copyWith({ + String? roomId, + num? micIndex, + bool? micLock, + bool? micMute, + String? type, + ChatVibeUserProfile? user, + String? roomToken, + String? emojiPath, + String? number, + }) => MicRes( + roomId: roomId ?? _roomId, + micIndex: micIndex ?? _micIndex, + micLock: micLock ?? _micLock, + micMute: micMute ?? _micMute, + user: user ?? _user, + roomToken: roomToken ?? _roomToken, + emojiPath: emojiPath ?? _emojiPath, + type: type ?? _type, + number: number ?? _number, + ); + + String? get roomId => _roomId; + + num? get micIndex => _micIndex; + + bool? get micLock => _micLock; + + bool? get micMute => _micMute; + + ChatVibeUserProfile? get user => _user; + + String? get roomToken => _roomToken; + + String? get type => _type; + + String? get emojiPath => _emojiPath; + + String? get number => _number; + + set setUser(ChatVibeUserProfile? value) => _user = value; + + set setVolume(int? value) => _volume = value; + + set setEmojiPath(String? value) => _emojiPath = value; + + Map toJson() { + final map = {}; + map['roomId'] = _roomId; + map['micIndex'] = _micIndex; + map['micLock'] = _micLock; + map['micMute'] = _micMute; + map['type'] = _type; + map['number'] = _number; + if (_user != null) { + map['user'] = _user?.toJson(); + } + map['roomToken'] = _roomToken; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/my_room_res.dart b/lib/chatvibe_domain/models/res/my_room_res.dart new file mode 100644 index 0000000..15272b6 --- /dev/null +++ b/lib/chatvibe_domain/models/res/my_room_res.dart @@ -0,0 +1,320 @@ +import 'package:aslan/chatvibe_domain/models/res/room_res.dart'; + +import 'join_room_res.dart' show RoomCounter; + +/// id : 0 +/// roomAccount : "" +/// userId : 0 +/// roomCover : "" +/// roomName : "" +/// roomDesc : "" +/// event : "" +/// sysOrigin : "" +/// countryCode : "" +/// countryName : "" +/// nationalFlag : "" +/// langCode : "" +/// del : false +/// createTime : 0 +/// updateTime : 0 +/// activeTime : 0 +/// setting : {"password":"","takeMicRole":"","touristMike":false,"touristMsg":false,"maxMember":0,"maxAdmin":0,"joinGolds":0,"mikeSize":0,"allowMusic":false,"adminLockSeat":false,"showHeartbeat":false,"roomSpecialMikeType":"","roomSpecialMikeExpireTime":0,"roomSpecialMikeExpireType":""} +/// counter : {"memberCount":0,"adminCount":0} +/// customizeSetting : {"":""} +/// useBadges : [{"id":0,"userId":0,"badgeLevel":0,"milestone":0,"badgeName":"","type":"","badgeKey":"","selectUrl":"","notSelectUrl":"","animationUrl":"","expireTime":0}] + +class MyRoomRes { + MyRoomRes({ + String? id, + String? roomAccount, + String? userId, + String? roomCover, + String? roomName, + String? roomDesc, + String? event, + String? sysOrigin, + String? countryCode, + String? countryName, + String? nationalFlag, + String? langCode, + bool? del, + num? createTime, + num? updateTime, + num? activeTime, + RoomSetting? setting, + RoomCounter? counter, + List? useBadges,}){ + _id = id; + _roomAccount = roomAccount; + _userId = userId; + _roomCover = roomCover; + _roomName = roomName; + _roomDesc = roomDesc; + _event = event; + _sysOrigin = sysOrigin; + _countryCode = countryCode; + _countryName = countryName; + _nationalFlag = nationalFlag; + _langCode = langCode; + _del = del; + _createTime = createTime; + _updateTime = updateTime; + _activeTime = activeTime; + _setting = setting; + _counter = counter; + _useBadges = useBadges; + } + + MyRoomRes.fromJson(dynamic json) { + _id = json['id']; + _roomAccount = json['roomAccount']; + _userId = json['userId']; + _roomCover = json['roomCover']; + _roomName = json['roomName']; + _roomDesc = json['roomDesc']; + _event = json['event']; + _sysOrigin = json['sysOrigin']; + _countryCode = json['countryCode']; + _countryName = json['countryName']; + _nationalFlag = json['nationalFlag']; + _langCode = json['langCode']; + _del = json['del']; + _createTime = json['createTime']; + _updateTime = json['updateTime']; + _activeTime = json['activeTime']; + _setting = json['setting'] != null ? RoomSetting.fromJson(json['setting']) : null; + _counter = json['counter'] != null ? RoomCounter.fromJson(json['counter']) : null; + if (json['useBadges'] != null) { + _useBadges = []; + json['useBadges'].forEach((v) { + _useBadges?.add(UseBadges.fromJson(v)); + }); + } + } + String? _id; + String? _roomAccount; + String? _userId; + String? _roomCover; + String? _roomName; + String? _roomDesc; + String? _event; + String? _sysOrigin; + String? _countryCode; + String? _countryName; + String? _nationalFlag; + String? _langCode; + bool? _del; + num? _createTime; + num? _updateTime; + num? _activeTime; + RoomSetting? _setting; + RoomCounter? _counter; + List? _useBadges; + MyRoomRes copyWith({ String? id, + String? roomAccount, + String? userId, + String? roomCover, + String? roomName, + String? roomDesc, + String? event, + String? sysOrigin, + String? countryCode, + String? countryName, + String? nationalFlag, + String? langCode, + bool? del, + num? createTime, + num? updateTime, + num? activeTime, + RoomSetting? setting, + RoomCounter? counter, + List? useBadges, + }) => MyRoomRes( id: id ?? _id, + roomAccount: roomAccount ?? _roomAccount, + userId: userId ?? _userId, + roomCover: roomCover ?? _roomCover, + roomName: roomName ?? _roomName, + roomDesc: roomDesc ?? _roomDesc, + event: event ?? _event, + sysOrigin: sysOrigin ?? _sysOrigin, + countryCode: countryCode ?? _countryCode, + countryName: countryName ?? _countryName, + nationalFlag: nationalFlag ?? _nationalFlag, + langCode: langCode ?? _langCode, + del: del ?? _del, + createTime: createTime ?? _createTime, + updateTime: updateTime ?? _updateTime, + activeTime: activeTime ?? _activeTime, + setting: setting ?? _setting, + counter: counter ?? _counter, + useBadges: useBadges ?? _useBadges, + ); + String? get id => _id; + String? get roomAccount => _roomAccount; + String? get userId => _userId; + String? get roomCover => _roomCover; + String? get roomName => _roomName; + String? get roomDesc => _roomDesc; + String? get event => _event; + String? get sysOrigin => _sysOrigin; + String? get countryCode => _countryCode; + String? get countryName => _countryName; + String? get nationalFlag => _nationalFlag; + String? get langCode => _langCode; + bool? get del => _del; + num? get createTime => _createTime; + num? get updateTime => _updateTime; + num? get activeTime => _activeTime; + RoomSetting? get setting => _setting; + RoomCounter? get counter => _counter; + List? get useBadges => _useBadges; + set setRoomCover(String value) => _roomCover = value; + set setRoomName(String value) => _roomName = value; + set setRoomDesc(String value) => _roomDesc = value; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['roomAccount'] = _roomAccount; + map['userId'] = _userId; + map['roomCover'] = _roomCover; + map['roomName'] = _roomName; + map['roomDesc'] = _roomDesc; + map['event'] = _event; + map['sysOrigin'] = _sysOrigin; + map['countryCode'] = _countryCode; + map['countryName'] = _countryName; + map['nationalFlag'] = _nationalFlag; + map['langCode'] = _langCode; + map['del'] = _del; + map['createTime'] = _createTime; + map['updateTime'] = _updateTime; + map['activeTime'] = _activeTime; + if (_setting != null) { + map['setting'] = _setting?.toJson(); + } + if (_counter != null) { + map['counter'] = _counter?.toJson(); + } + if (_useBadges != null) { + map['useBadges'] = _useBadges?.map((v) => v.toJson()).toList(); + } + return map; + } + +} + +/// id : 0 +/// userId : 0 +/// badgeLevel : 0 +/// milestone : 0 +/// badgeName : "" +/// type : "" +/// badgeKey : "" +/// selectUrl : "" +/// notSelectUrl : "" +/// animationUrl : "" +/// expireTime : 0 + +class UseBadges { + UseBadges({ + num? id, + num? userId, + num? badgeLevel, + num? milestone, + String? badgeName, + String? type, + String? badgeKey, + String? selectUrl, + String? notSelectUrl, + String? animationUrl, + String? expireTime,}){ + _id = id; + _userId = userId; + _badgeLevel = badgeLevel; + _milestone = milestone; + _badgeName = badgeName; + _type = type; + _badgeKey = badgeKey; + _selectUrl = selectUrl; + _notSelectUrl = notSelectUrl; + _animationUrl = animationUrl; + _expireTime = expireTime; + } + + UseBadges.fromJson(dynamic json) { + _id = json['id']; + _userId = json['userId']; + _badgeLevel = json['badgeLevel']; + _milestone = json['milestone']; + _badgeName = json['badgeName']; + _type = json['type']; + _badgeKey = json['badgeKey']; + _selectUrl = json['selectUrl']; + _notSelectUrl = json['notSelectUrl']; + _animationUrl = json['animationUrl']; + _expireTime = json['expireTime']; + } + num? _id; + num? _userId; + num? _badgeLevel; + num? _milestone; + String? _badgeName; + String? _type; + String? _badgeKey; + String? _selectUrl; + String? _notSelectUrl; + String? _animationUrl; + String? _expireTime; + UseBadges copyWith({ num? id, + num? userId, + num? badgeLevel, + num? milestone, + String? badgeName, + String? type, + String? badgeKey, + String? selectUrl, + String? notSelectUrl, + String? animationUrl, + String? expireTime, + }) => UseBadges( id: id ?? _id, + userId: userId ?? _userId, + badgeLevel: badgeLevel ?? _badgeLevel, + milestone: milestone ?? _milestone, + badgeName: badgeName ?? _badgeName, + type: type ?? _type, + badgeKey: badgeKey ?? _badgeKey, + selectUrl: selectUrl ?? _selectUrl, + notSelectUrl: notSelectUrl ?? _notSelectUrl, + animationUrl: animationUrl ?? _animationUrl, + expireTime: expireTime ?? _expireTime, + ); + num? get id => _id; + num? get userId => _userId; + num? get badgeLevel => _badgeLevel; + num? get milestone => _milestone; + String? get badgeName => _badgeName; + String? get type => _type; + String? get badgeKey => _badgeKey; + String? get selectUrl => _selectUrl; + String? get notSelectUrl => _notSelectUrl; + String? get animationUrl => _animationUrl; + String? get expireTime => _expireTime; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['userId'] = _userId; + map['badgeLevel'] = _badgeLevel; + map['milestone'] = _milestone; + map['badgeName'] = _badgeName; + map['type'] = _type; + map['badgeKey'] = _badgeKey; + map['selectUrl'] = _selectUrl; + map['notSelectUrl'] = _notSelectUrl; + map['animationUrl'] = _animationUrl; + map['expireTime'] = _expireTime; + return map; + } + +} diff --git a/lib/chatvibe_domain/models/res/room_black_list_res.dart b/lib/chatvibe_domain/models/res/room_black_list_res.dart new file mode 100644 index 0000000..622d186 --- /dev/null +++ b/lib/chatvibe_domain/models/res/room_black_list_res.dart @@ -0,0 +1,72 @@ +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +/// blacklistUserProfile : {"account":"","accountStatus":"","age":0,"bornDay":0,"bornMonth":0,"bornYear":0,"countryCode":"","countryId":0,"countryName":"","createTime":0,"del":false,"freezingTime":0,"id":0,"inRoomId":"","isUpdateCountry":"","originSys":"","ownSpecialId":{"account":"","expiredTime":0},"regionCode":"","roomIcon":"","sameRegion":false,"sysOriginChild":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0,"wearBadge":[{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}]} +/// createTime : 0 +/// expiredTime : 0 +/// id : 0 +/// optUserProfile : {"account":"","accountStatus":"","age":0,"bornDay":0,"bornMonth":0,"bornYear":0,"countryCode":"","countryId":0,"countryName":"","createTime":0,"del":false,"freezingTime":0,"id":0,"inRoomId":"","isUpdateCountry":"","originSys":"","ownSpecialId":{"account":"","expiredTime":0},"regionCode":"","roomIcon":"","sameRegion":false,"sysOriginChild":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0,"wearBadge":[{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}]} +/// roomId : 0 + +class RoomBlackListRes { + RoomBlackListRes({ + ChatVibeUserProfile? blacklistUserProfile, + num? createTime, + num? expiredTime, + String? id, + ChatVibeUserProfile? optUserProfile, + String? roomId,}){ + _blacklistUserProfile = blacklistUserProfile; + _createTime = createTime; + _expiredTime = expiredTime; + _id = id; + _optUserProfile = optUserProfile; + _roomId = roomId; +} + + RoomBlackListRes.fromJson(dynamic json) { + _blacklistUserProfile = json['blacklistUserProfile'] != null ? ChatVibeUserProfile.fromJson(json['blacklistUserProfile']) : null; + _createTime = json['createTime']; + _expiredTime = json['expiredTime']; + _id = json['id']; + _optUserProfile = json['optUserProfile'] != null ? ChatVibeUserProfile.fromJson(json['optUserProfile']) : null; + _roomId = json['roomId']; + } + ChatVibeUserProfile? _blacklistUserProfile; + num? _createTime; + num? _expiredTime; + String? _id; + ChatVibeUserProfile? _optUserProfile; + String? _roomId; +RoomBlackListRes copyWith({ ChatVibeUserProfile? blacklistUserProfile, + num? createTime, + num? expiredTime, + String? id, + ChatVibeUserProfile? optUserProfile, + String? roomId, +}) => RoomBlackListRes( blacklistUserProfile: blacklistUserProfile ?? _blacklistUserProfile, + createTime: createTime ?? _createTime, + expiredTime: expiredTime ?? _expiredTime, + id: id ?? _id, + optUserProfile: optUserProfile ?? _optUserProfile, + roomId: roomId ?? _roomId, +); + ChatVibeUserProfile? get blacklistUserProfile => _blacklistUserProfile; + num? get createTime => _createTime; + num? get expiredTime => _expiredTime; + String? get id => _id; + ChatVibeUserProfile? get optUserProfile => _optUserProfile; + String? get roomId => _roomId; + + Map toJson() { + final map = {}; + map['blacklistUserProfile'] = _blacklistUserProfile; + map['createTime'] = _createTime; + map['expiredTime'] = _expiredTime; + map['id'] = _id; + if (_optUserProfile != null) { + map['optUserProfile'] = _optUserProfile?.toJson(); + } + map['roomId'] = _roomId; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/room_gift_rank_res.dart b/lib/chatvibe_domain/models/res/room_gift_rank_res.dart new file mode 100644 index 0000000..54ce3c1 --- /dev/null +++ b/lib/chatvibe_domain/models/res/room_gift_rank_res.dart @@ -0,0 +1,46 @@ +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +/// userProfile : {"id":0,"account":"","userAvatar":"","userNickname":"","userSex":0,"age":0,"accountStatus":"","freezingTime":0,"countryId":0,"countryName":"","countryCode":"","originSys":"","sysOriginChild":"","del":false,"createTime":0,"bornYear":0,"bornMonth":0,"bornDay":0,"ownSpecialId":{"account":"","expiredTime":0},"useProps":[{"userId":0,"propsResources":{"id":0,"type":"","code":"","name":"","cover":"","sourceUrl":"","expand":"","amount":0.0},"expireTime":0}],"sameRegion":false,"wearBadge":[{"id":0,"userId":0,"badgeLevel":0,"milestone":0,"badgeName":"","type":"","badgeKey":"","selectUrl":"","notSelectUrl":"","animationUrl":"","expireTime":0}]} +/// totalFormat : "" +/// total : 0 + +class RoomGiftRankRes { + RoomGiftRankRes({ + ChatVibeUserProfile? userProfile, + String? totalFormat, + String? total,}){ + _userProfile = userProfile; + _totalFormat = totalFormat; + _total = total; +} + + RoomGiftRankRes.fromJson(dynamic json) { + _userProfile = json['userProfile'] != null ? ChatVibeUserProfile.fromJson(json['userProfile']) : null; + _totalFormat = json['totalFormat']; + _total = json['total']; + } + ChatVibeUserProfile? _userProfile; + String? _totalFormat; + String? _total; +RoomGiftRankRes copyWith({ ChatVibeUserProfile? userProfile, + String? totalFormat, + String? total, +}) => RoomGiftRankRes( userProfile: userProfile ?? _userProfile, + totalFormat: totalFormat ?? _totalFormat, + total: total ?? _total, +); + ChatVibeUserProfile? get userProfile => _userProfile; + String? get totalFormat => _totalFormat; + String? get total => _total; + + Map toJson() { + final map = {}; + if (_userProfile != null) { + map['userProfile'] = _userProfile?.toJson(); + } + map['totalFormat'] = _totalFormat; + map['total'] = _total; + return map; + } + +} diff --git a/lib/chatvibe_domain/models/res/room_member_res.dart b/lib/chatvibe_domain/models/res/room_member_res.dart new file mode 100644 index 0000000..7705422 --- /dev/null +++ b/lib/chatvibe_domain/models/res/room_member_res.dart @@ -0,0 +1,52 @@ +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +/// id : 0 +/// userProfile : {"id":0,"account":"","userAvatar":"","userNickname":"","userSex":0,"age":0,"accountStatus":"","freezingTime":0,"countryId":0,"countryName":"","countryCode":"","originSys":"","sysOriginChild":"","del":false,"createTime":0,"bornYear":0,"bornMonth":0,"bornDay":0,"ownSpecialId":{"account":"","expiredTime":0},"useProps":[{"userId":0,"propsResources":{"id":0,"type":"","code":"","name":"","cover":"","sourceUrl":"","expand":"","amount":0.0},"expireTime":0}],"sameRegion":false,"wearBadge":[{"id":0,"userId":0,"badgeLevel":0,"milestone":0,"badgeName":"","type":"","badgeKey":"","selectUrl":"","notSelectUrl":"","animationUrl":"","expireTime":0}]} +/// roles : "" + +class ChatVibeRoomMemberRes { + ChatVibeRoomMemberRes({String? id, ChatVibeUserProfile? userProfile, String? roles}) { + _id = id; + _userProfile = userProfile; + _roles = roles; + } + + ChatVibeRoomMemberRes.fromJson(dynamic json) { + _id = json['id']; + _userProfile = + json['userProfile'] != null + ? ChatVibeUserProfile.fromJson(json['userProfile']) + : null; + _roles = json['roles']; + } + + String? _id; + ChatVibeUserProfile? _userProfile; + String? _roles; + + ChatVibeRoomMemberRes copyWith({ + String? id, + ChatVibeUserProfile? userProfile, + String? roles, + }) => ChatVibeRoomMemberRes( + id: id ?? _id, + userProfile: userProfile ?? _userProfile, + roles: roles ?? _roles, + ); + + String? get id => _id; + + ChatVibeUserProfile? get userProfile => _userProfile; + + String? get roles => _roles; + + Map toJson() { + final map = {}; + map['id'] = _id; + if (_userProfile != null) { + map['userProfile'] = _userProfile?.toJson(); + } + map['roles'] = _roles; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/room_res.dart b/lib/chatvibe_domain/models/res/room_res.dart new file mode 100644 index 0000000..7b897c1 --- /dev/null +++ b/lib/chatvibe_domain/models/res/room_res.dart @@ -0,0 +1,758 @@ +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +/// countryCode : "" +/// countryName : "" +/// event : "" +/// hotRoom : false +/// id : 0 +/// layoutKey : "" +/// nationalFlag : "" +/// roomAccount : "" +/// roomBadgeIcons : [""] +/// roomCover : "" +/// roomDesc : "" +/// roomGameIcon : "" +/// roomName : "" +/// sysOrigin : "" +/// userId : 0 +/// userProfile : {"account":"","accountStatus":"","age":0,"bornDay":0,"bornMonth":0,"bornYear":0,"countryCode":"","countryId":0,"countryName":"","createTime":0,"del":false,"freezingTime":0,"id":0,"originSys":"","ownSpecialId":{"account":"","expiredTime":0},"sameRegion":false,"sysOriginChild":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0,"wearBadge":[{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}]} +/// userSVipLevel : "" + +class ChatVibeRoomRes { + ChatVibeRoomRes({ + String? countryCode, + String? countryName, + String? event, + bool? hotRoom, + String? id, + String? layoutKey, + String? nationalFlag, + String? roomAccount, + List? roomBadgeIcons, + List? onlineUsers, + String? roomCover, + String? roomDesc, + String? roomGameIcon, + String? roomName, + String? sysOrigin, + String? userId, + ChatVibeUserProfile? userProfile, + String? userSVipLevel, + ExtValues? extValues, + }) { + _countryCode = countryCode; + _countryName = countryName; + _event = event; + _hotRoom = hotRoom; + _id = id; + _layoutKey = layoutKey; + _nationalFlag = nationalFlag; + _roomAccount = roomAccount; + _roomBadgeIcons = roomBadgeIcons; + _onlineUsers = onlineUsers; + _roomCover = roomCover; + _roomDesc = roomDesc; + _roomGameIcon = roomGameIcon; + _roomName = roomName; + _sysOrigin = sysOrigin; + _userId = userId; + _userProfile = userProfile; + _userSVipLevel = userSVipLevel; + _extValues = extValues; + } + + ChatVibeRoomRes.fromJson(dynamic json) { + _countryCode = json['countryCode']; + _countryName = json['countryName']; + _event = json['event']; + _hotRoom = json['hotRoom']; + _id = json['id']; + _layoutKey = json['layoutKey']; + _nationalFlag = json['nationalFlag']; + _roomAccount = json['roomAccount']; + _roomBadgeIcons = + json['roomBadgeIcons'] != null + ? (json['roomBadgeIcons'] as List) + .map((item) => item as String) + .toList() + : null; + if (json['onlineUsers'] != null) { + _onlineUsers = []; + json['onlineUsers'].forEach((v) { + _onlineUsers?.add(ChatVibeUserProfile.fromJson(v)); + }); + } + _roomCover = json['roomCover']; + _roomDesc = json['roomDesc']; + _roomGameIcon = json['roomGameIcon']; + _roomName = json['roomName']; + _sysOrigin = json['sysOrigin']; + _userId = json['userId']; + _userProfile = + json['userProfile'] != null + ? ChatVibeUserProfile.fromJson(json['userProfile']) + : null; + _userSVipLevel = json['userSVipLevel']; + _extValues = + json['extValues'] != null + ? ExtValues.fromJson(json['extValues']) + : null; + } + + String? _countryCode; + String? _countryName; + String? _event; + bool? _hotRoom; + String? _id; + String? _layoutKey; + String? _nationalFlag; + String? _roomAccount; + List? _roomBadgeIcons; + List? _onlineUsers; + String? _roomCover; + String? _roomDesc; + String? _roomGameIcon; + String? _roomName; + String? _sysOrigin; + String? _userId; + ChatVibeUserProfile? _userProfile; + String? _userSVipLevel; + ExtValues? _extValues; + + ChatVibeRoomRes copyWith({ + String? countryCode, + String? countryName, + String? event, + bool? hotRoom, + String? id, + String? layoutKey, + String? nationalFlag, + String? roomAccount, + List? roomBadgeIcons, + List? onlineUsers, + String? roomCover, + String? roomDesc, + String? roomGameIcon, + String? roomName, + String? sysOrigin, + String? userId, + ChatVibeUserProfile? userProfile, + String? userSVipLevel, + ExtValues? extValues, + }) => ChatVibeRoomRes( + countryCode: countryCode ?? _countryCode, + countryName: countryName ?? _countryName, + event: event ?? _event, + hotRoom: hotRoom ?? _hotRoom, + id: id ?? _id, + layoutKey: layoutKey ?? _layoutKey, + nationalFlag: nationalFlag ?? _nationalFlag, + roomAccount: roomAccount ?? _roomAccount, + roomBadgeIcons: roomBadgeIcons ?? _roomBadgeIcons, + onlineUsers: onlineUsers ?? _onlineUsers, + roomCover: roomCover ?? _roomCover, + roomDesc: roomDesc ?? _roomDesc, + roomGameIcon: roomGameIcon ?? _roomGameIcon, + roomName: roomName ?? _roomName, + sysOrigin: sysOrigin ?? _sysOrigin, + userId: userId ?? _userId, + userProfile: userProfile ?? _userProfile, + userSVipLevel: userSVipLevel ?? _userSVipLevel, + extValues: extValues ?? _extValues, + ); + + String? get countryCode => _countryCode; + + String? get countryName => _countryName; + + String? get event => _event; + + bool? get hotRoom => _hotRoom; + + String? get id => _id; + + String? get layoutKey => _layoutKey; + + String? get nationalFlag => _nationalFlag; + + String? get roomAccount => _roomAccount; + + List? get roomBadgeIcons => _roomBadgeIcons; + + List? get onlineUsers => _onlineUsers; + + String? get roomCover => _roomCover; + + String? get roomDesc => _roomDesc; + + String? get roomGameIcon => _roomGameIcon; + + String? get roomName => _roomName; + + String? get sysOrigin => _sysOrigin; + + String? get userId => _userId; + + ChatVibeUserProfile? get userProfile => _userProfile; + + String? get userSVipLevel => _userSVipLevel; + + ExtValues? get extValues => _extValues; + + Map toJson() { + final map = {}; + map['countryCode'] = _countryCode; + map['countryName'] = _countryName; + map['event'] = _event; + map['hotRoom'] = _hotRoom; + map['id'] = _id; + map['layoutKey'] = _layoutKey; + map['nationalFlag'] = _nationalFlag; + map['roomAccount'] = _roomAccount; + map['roomBadgeIcons'] = _roomBadgeIcons; + if (_onlineUsers != null) { + map['onlineUsers'] = _onlineUsers?.map((v) => v.toJson()).toList(); + } + map['roomCover'] = _roomCover; + map['roomDesc'] = _roomDesc; + map['roomGameIcon'] = _roomGameIcon; + map['roomName'] = _roomName; + map['sysOrigin'] = _sysOrigin; + map['userId'] = _userId; + if (_userProfile != null) { + map['userProfile'] = _userProfile?.toJson(); + } + map['userSVipLevel'] = _userSVipLevel; + if (_extValues != null) { + map['extValues'] = _extValues?.toJson(); + } + return map; + } +} + +/// account : "" +/// accountStatus : "" +/// age : 0 +/// bornDay : 0 +/// bornMonth : 0 +/// bornYear : 0 +/// countryCode : "" +/// countryId : 0 +/// countryName : "" +/// createTime : 0 +/// del : false +/// freezingTime : 0 +/// id : 0 +/// originSys : "" +/// ownSpecialId : {"account":"","expiredTime":0} +/// sameRegion : false +/// sysOriginChild : "" +/// useProps : [{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}] +/// userAvatar : "" +/// userNickname : "" +/// userSex : 0 +/// wearBadge : [{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}] +/// animationUrl : "" +/// badgeKey : "" +/// badgeLevel : 0 +/// badgeName : "" +/// expireTime : 0 +/// id : 0 +/// milestone : 0 +/// notSelectUrl : "" +/// selectUrl : "" +/// type : "" +/// userId : 0 + +class WearBadge { + WearBadge({ + String? animationUrl, + String? badgeKey, + num? badgeLevel, + String? badgeName, + num? expireTime, + String? id, + String? milestone, + String? notSelectUrl, + String? selectUrl, + String? type, + String? userId, + }) { + _animationUrl = animationUrl; + _badgeKey = badgeKey; + _badgeLevel = badgeLevel; + _badgeName = badgeName; + _expireTime = expireTime; + _id = id; + _milestone = milestone; + _notSelectUrl = notSelectUrl; + _selectUrl = selectUrl; + _type = type; + _userId = userId; + } + + WearBadge.fromJson(dynamic json) { + _animationUrl = json['animationUrl']; + _badgeKey = json['badgeKey']; + _badgeLevel = json['badgeLevel']; + _badgeName = json['badgeName']; + _expireTime = json['expireTime']; + _id = json['id']; + _milestone = json['milestone']; + _notSelectUrl = json['notSelectUrl']; + _selectUrl = json['selectUrl']; + _type = json['type']; + _userId = json['userId']; + } + + String? _animationUrl; + String? _badgeKey; + num? _badgeLevel; + String? _badgeName; + num? _expireTime; + String? _id; + String? _milestone; + String? _notSelectUrl; + String? _selectUrl; + String? _type; + String? _userId; + + WearBadge copyWith({ + String? animationUrl, + String? badgeKey, + num? badgeLevel, + String? badgeName, + num? expireTime, + String? id, + String? milestone, + String? notSelectUrl, + String? selectUrl, + String? type, + String? userId, + }) => WearBadge( + animationUrl: animationUrl ?? _animationUrl, + badgeKey: badgeKey ?? _badgeKey, + badgeLevel: badgeLevel ?? _badgeLevel, + badgeName: badgeName ?? _badgeName, + expireTime: expireTime ?? _expireTime, + id: id ?? _id, + milestone: milestone ?? _milestone, + notSelectUrl: notSelectUrl ?? _notSelectUrl, + selectUrl: selectUrl ?? _selectUrl, + type: type ?? _type, + userId: userId ?? _userId, + ); + + String? get animationUrl => _animationUrl; + + String? get badgeKey => _badgeKey; + + num? get badgeLevel => _badgeLevel; + + String? get badgeName => _badgeName; + + num? get expireTime => _expireTime; + + String? get id => _id; + + String? get milestone => _milestone; + + String? get notSelectUrl => _notSelectUrl; + + String? get selectUrl => _selectUrl; + + String? get type => _type; + + String? get userId => _userId; + + Map toJson() { + final map = {}; + map['animationUrl'] = _animationUrl; + map['badgeKey'] = _badgeKey; + map['badgeLevel'] = _badgeLevel; + map['badgeName'] = _badgeName; + map['expireTime'] = _expireTime; + map['id'] = _id; + map['milestone'] = _milestone; + map['notSelectUrl'] = _notSelectUrl; + map['selectUrl'] = _selectUrl; + map['type'] = _type; + map['userId'] = _userId; + return map; + } +} + +class ExtValues { + ExtValues({ + bool? existsPassword, + num? rocketLevel, + String? memberQuantity, + RoomSetting? roomSetting, + }) { + _existsPassword = existsPassword; + _rocketLevel = rocketLevel; + _memberQuantity = memberQuantity; + _roomSetting = roomSetting; + } + + ExtValues.fromJson(dynamic json) { + _existsPassword = json['existsPassword']; + _rocketLevel = json['rocketLevel']; + _memberQuantity = json['memberQuantity']; + _roomSetting = + json['roomSetting'] != null + ? RoomSetting.fromJson(json['roomSetting']) + : null; + } + + bool? _existsPassword; + num? _rocketLevel; + String? _memberQuantity; + RoomSetting? _roomSetting; + + ExtValues copyWith({ + bool? existsPassword, + num? rocketLevel, + String? memberQuantity, + RoomSetting? roomSetting, + }) => ExtValues( + existsPassword: existsPassword ?? _existsPassword, + rocketLevel: rocketLevel ?? _rocketLevel, + memberQuantity: memberQuantity ?? _memberQuantity, + roomSetting: roomSetting ?? _roomSetting, + ); + + bool? get existsPassword => _existsPassword; + + num? get rocketLevel => _rocketLevel; + + String? get memberQuantity => _memberQuantity; + + RoomSetting? get roomSetting => _roomSetting; + + Map toJson() { + final map = {}; + map['existsPassword'] = _existsPassword; + map['rocketLevel'] = _rocketLevel; + map['memberQuantity'] = _memberQuantity; + if (_roomSetting != null) { + map['roomSetting'] = _roomSetting?.toJson(); + } + return map; + } +} + +class RoomSetting { + RoomSetting({ + String? password, + String? takeMicRole, + bool? touristMike, + bool? touristMsg, + num? maxMember, + num? maxAdmin, + num? joinGolds, + num? mikeSize, + bool? allowMusic, + bool? adminLockSeat, + bool? showHeartbeat, + String? roomSpecialMikeType, + num? roomSpecialMikeExpireTime, + String? roomSpecialMikeExpireType,}){ + _password = password; + _takeMicRole = takeMicRole; + _touristMike = touristMike; + _touristMsg = touristMsg; + _maxMember = maxMember; + _maxAdmin = maxAdmin; + _joinGolds = joinGolds; + _mikeSize = mikeSize; + _allowMusic = allowMusic; + _adminLockSeat = adminLockSeat; + _showHeartbeat = showHeartbeat; + _roomSpecialMikeType = roomSpecialMikeType; + _roomSpecialMikeExpireTime = roomSpecialMikeExpireTime; + _roomSpecialMikeExpireType = roomSpecialMikeExpireType; + } + + RoomSetting.fromJson(dynamic json) { + _password = json['password']; + _takeMicRole = json['takeMicRole']; + _touristMike = json['touristMike']; + _touristMsg = json['touristMsg']; + _maxMember = json['maxMember']; + _maxAdmin = json['maxAdmin']; + _joinGolds = json['joinGolds']; + _mikeSize = json['mikeSize']; + _allowMusic = json['allowMusic']; + _adminLockSeat = json['adminLockSeat']; + _showHeartbeat = json['showHeartbeat']; + _roomSpecialMikeType = json['roomSpecialMikeType']; + _roomSpecialMikeExpireTime = json['roomSpecialMikeExpireTime']; + _roomSpecialMikeExpireType = json['roomSpecialMikeExpireType']; + } + String? _password; + String? _takeMicRole; + bool? _touristMike; + bool? _touristMsg; + num? _maxMember; + num? _maxAdmin; + num? _joinGolds; + num? _mikeSize; + bool? _allowMusic; + bool? _adminLockSeat; + bool? _showHeartbeat; + String? _roomSpecialMikeType; + num? _roomSpecialMikeExpireTime; + String? _roomSpecialMikeExpireType; + RoomSetting copyWith({ String? password, + String? takeMicRole, + bool? touristMike, + bool? touristMsg, + num? maxMember, + num? maxAdmin, + num? joinGolds, + num? mikeSize, + bool? allowMusic, + bool? adminLockSeat, + bool? showHeartbeat, + String? roomSpecialMikeType, + num? roomSpecialMikeExpireTime, + String? roomSpecialMikeExpireType, + }) => RoomSetting( password: password ?? _password, + takeMicRole: takeMicRole ?? _takeMicRole, + touristMike: touristMike ?? _touristMike, + touristMsg: touristMsg ?? _touristMsg, + maxMember: maxMember ?? _maxMember, + maxAdmin: maxAdmin ?? _maxAdmin, + joinGolds: joinGolds ?? _joinGolds, + mikeSize: mikeSize ?? _mikeSize, + allowMusic: allowMusic ?? _allowMusic, + adminLockSeat: adminLockSeat ?? _adminLockSeat, + showHeartbeat: showHeartbeat ?? _showHeartbeat, + roomSpecialMikeType: roomSpecialMikeType ?? _roomSpecialMikeType, + roomSpecialMikeExpireTime: roomSpecialMikeExpireTime ?? _roomSpecialMikeExpireTime, + roomSpecialMikeExpireType: roomSpecialMikeExpireType ?? _roomSpecialMikeExpireType, + ); + String? get password => _password; + String? get takeMicRole => _takeMicRole; + bool? get touristMike => _touristMike; + bool? get touristMsg => _touristMsg; + num? get maxMember => _maxMember; + num? get maxAdmin => _maxAdmin; + num? get joinGolds => _joinGolds; + num? get mikeSize => _mikeSize; + bool? get allowMusic => _allowMusic; + bool? get adminLockSeat => _adminLockSeat; + bool? get showHeartbeat => _showHeartbeat; + String? get roomSpecialMikeType => _roomSpecialMikeType; + num? get roomSpecialMikeExpireTime => _roomSpecialMikeExpireTime; + String? get roomSpecialMikeExpireType => _roomSpecialMikeExpireType; + + Map toJson() { + final map = {}; + map['password'] = _password; + map['takeMicRole'] = _takeMicRole; + map['touristMike'] = _touristMike; + map['touristMsg'] = _touristMsg; + map['maxMember'] = _maxMember; + map['maxAdmin'] = _maxAdmin; + map['joinGolds'] = _joinGolds; + map['mikeSize'] = _mikeSize; + map['allowMusic'] = _allowMusic; + map['adminLockSeat'] = _adminLockSeat; + map['showHeartbeat'] = _showHeartbeat; + map['roomSpecialMikeType'] = _roomSpecialMikeType; + map['roomSpecialMikeExpireTime'] = _roomSpecialMikeExpireTime; + map['roomSpecialMikeExpireType'] = _roomSpecialMikeExpireType; + return map; + } + +} +/// expireTime : 0 +/// propsResources : {"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""} +/// userId : 0 + +class UseProps { + UseProps({ + String? expireTime, + PropsResources? propsResources, + String? userId, + }) { + _expireTime = expireTime; + _propsResources = propsResources; + _userId = userId; + } + + UseProps.fromJson(dynamic json) { + _expireTime = json['expireTime']; + _propsResources = + json['propsResources'] != null + ? PropsResources.fromJson(json['propsResources']) + : null; + _userId = json['userId']; + } + + String? _expireTime; + PropsResources? _propsResources; + String? _userId; + + UseProps copyWith({ + String? expireTime, + PropsResources? propsResources, + String? userId, + }) => UseProps( + expireTime: expireTime ?? _expireTime, + propsResources: propsResources ?? _propsResources, + userId: userId ?? _userId, + ); + + String? get expireTime => _expireTime; + + PropsResources? get propsResources => _propsResources; + + String? get userId => _userId; + + Map toJson() { + final map = {}; + map['expireTime'] = _expireTime; + if (_propsResources != null) { + map['propsResources'] = _propsResources?.toJson(); + } + map['userId'] = _userId; + return map; + } +} + +/// amount : 0.0 +/// code : "" +/// cover : "" +/// expand : "" +/// id : 0 +/// name : "" +/// sourceUrl : "" +/// type : "" + +class PropsResources { + PropsResources({ + num? amount, + String? code, + String? cover, + String? expand, + String? id, + String? name, + String? sourceUrl, + String? type, + }) { + _amount = amount; + _code = code; + _cover = cover; + _expand = expand; + _id = id; + _name = name; + _sourceUrl = sourceUrl; + _type = type; + } + + PropsResources.fromJson(dynamic json) { + _amount = json['amount']; + _code = json['code']; + _cover = json['cover']; + _expand = json['expand']; + _id = json['id']; + _name = json['name']; + _sourceUrl = json['sourceUrl']; + _type = json['type']; + } + + num? _amount; + String? _code; + String? _cover; + String? _expand; + String? _id; + String? _name; + String? _sourceUrl; + String? _type; + + PropsResources copyWith({ + num? amount, + String? code, + String? cover, + String? expand, + String? id, + String? name, + String? sourceUrl, + String? type, + }) => PropsResources( + amount: amount ?? _amount, + code: code ?? _code, + cover: cover ?? _cover, + expand: expand ?? _expand, + id: id ?? _id, + name: name ?? _name, + sourceUrl: sourceUrl ?? _sourceUrl, + type: type ?? _type, + ); + + num? get amount => _amount; + + String? get code => _code; + + String? get cover => _cover; + + String? get expand => _expand; + + String? get id => _id; + + String? get name => _name; + + String? get sourceUrl => _sourceUrl; + + String? get type => _type; + + Map toJson() { + final map = {}; + map['amount'] = _amount; + map['code'] = _code; + map['cover'] = _cover; + map['expand'] = _expand; + map['id'] = _id; + map['name'] = _name; + map['sourceUrl'] = _sourceUrl; + map['type'] = _type; + return map; + } +} + +/// account : "" +/// expiredTime : 0 + +class OwnSpecialId { + OwnSpecialId({String? account, num? expiredTime}) { + _account = account; + _expiredTime = expiredTime; + } + + OwnSpecialId.fromJson(dynamic json) { + _account = json['account']; + _expiredTime = json['expiredTime']; + } + + String? _account; + num? _expiredTime; + + OwnSpecialId copyWith({String? account, num? expiredTime}) => OwnSpecialId( + account: account ?? _account, + expiredTime: expiredTime ?? _expiredTime, + ); + + String? get account => _account; + + num? get expiredTime => _expiredTime; + + Map toJson() { + final map = {}; + map['account'] = _account; + map['expiredTime'] = _expiredTime; + return map; + } +} diff --git a/lib/chatvibe_domain/models/res/room_user_card_res.dart b/lib/chatvibe_domain/models/res/room_user_card_res.dart new file mode 100644 index 0000000..20e409a --- /dev/null +++ b/lib/chatvibe_domain/models/res/room_user_card_res.dart @@ -0,0 +1,406 @@ +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +/// agent : false +/// anchor : false +/// follow : false +/// friend : false +/// ktvIntegral : "" +/// roomRole : "" +/// supperVip : "" +/// useBadge : [{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}] +/// useProps : [{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}] +/// userLevel : {"charmLevel":0,"extValues":{"":{}},"wealthLevel":0} +/// userProfile : {"account":"","accountStatus":"","age":0,"bornDay":0,"bornMonth":0,"bornYear":0,"countryCode":"","countryId":0,"countryName":"","createTime":0,"del":false,"freezingTime":0,"id":"0","originSys":"","ownSpecialId":{"account":"","expiredTime":0},"sameRegion":false,"sysOriginChild":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0,"wearBadge":[{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}]} + +class RoomUserCardRes { + RoomUserCardRes({ + bool? agent, + bool? anchor, + bool? follow, + bool? friend, + String? ktvIntegral, + String? roomRole, + String? supperVip, + List? useBadge, + List? useProps, + UserLevel? userLevel, + ChatVibeUserProfile? userProfile,}){ + _agent = agent; + _anchor = anchor; + _follow = follow; + _friend = friend; + _ktvIntegral = ktvIntegral; + _roomRole = roomRole; + _supperVip = supperVip; + _useBadge = useBadge; + _useProps = useProps; + _userLevel = userLevel; + _userProfile = userProfile; +} + + RoomUserCardRes.fromJson(dynamic json) { + _agent = json['agent']; + _anchor = json['anchor']; + _follow = json['follow']; + _friend = json['friend']; + _ktvIntegral = json['ktvIntegral']; + _roomRole = json['roomRole']; + _supperVip = json['supperVip']; + if (json['useBadge'] != null) { + _useBadge = []; + json['useBadge'].forEach((v) { + _useBadge?.add(UseBadge.fromJson(v)); + }); + } + if (json['useProps'] != null) { + _useProps = []; + json['useProps'].forEach((v) { + _useProps?.add(UseProps.fromJson(v)); + }); + } + _userLevel = json['userLevel'] != null ? UserLevel.fromJson(json['userLevel']) : null; + _userProfile = json['userProfile'] != null ? ChatVibeUserProfile.fromJson(json['userProfile']) : null; + } + bool? _agent; + bool? _anchor; + bool? _follow; + bool? _friend; + String? _ktvIntegral; + String? _roomRole; + String? _supperVip; + List? _useBadge; + List? _useProps; + UserLevel? _userLevel; + ChatVibeUserProfile? _userProfile; +RoomUserCardRes copyWith({ bool? agent, + bool? anchor, + bool? follow, + bool? friend, + String? ktvIntegral, + String? roomRole, + String? supperVip, + List? useBadge, + List? useProps, + UserLevel? userLevel, + ChatVibeUserProfile? userProfile, +}) => RoomUserCardRes( agent: agent ?? _agent, + anchor: anchor ?? _anchor, + follow: follow ?? _follow, + friend: friend ?? _friend, + ktvIntegral: ktvIntegral ?? _ktvIntegral, + roomRole: roomRole ?? _roomRole, + supperVip: supperVip ?? _supperVip, + useBadge: useBadge ?? _useBadge, + useProps: useProps ?? _useProps, + userLevel: userLevel ?? _userLevel, + userProfile: userProfile ?? _userProfile, +); + bool? get agent => _agent; + bool? get anchor => _anchor; + bool? get follow => _follow; + bool? get friend => _friend; + String? get ktvIntegral => _ktvIntegral; + String? get roomRole => _roomRole; + String? get supperVip => _supperVip; + List? get useBadge => _useBadge; + List? get useProps => _useProps; + UserLevel? get userLevel => _userLevel; + ChatVibeUserProfile? get userProfile => _userProfile; + + Map toJson() { + final map = {}; + map['agent'] = _agent; + map['anchor'] = _anchor; + map['follow'] = _follow; + map['friend'] = _friend; + map['ktvIntegral'] = _ktvIntegral; + map['roomRole'] = _roomRole; + map['supperVip'] = _supperVip; + if (_useBadge != null) { + map['useBadge'] = _useBadge?.map((v) => v.toJson()).toList(); + } + if (_useProps != null) { + map['useProps'] = _useProps?.map((v) => v.toJson()).toList(); + } + if (_userLevel != null) { + map['userLevel'] = _userLevel?.toJson(); + } + if (_userProfile != null) { + map['userProfile'] = _userProfile?.toJson(); + } + return map; + } + +} + +/// account : "" +/// accountStatus : "" +/// age : 0 +/// bornDay : 0 +/// bornMonth : 0 +/// bornYear : 0 +/// countryCode : "" +/// countryId : 0 +/// countryName : "" +/// createTime : 0 +/// del : false +/// freezingTime : 0 +/// id : "0" +/// originSys : "" +/// ownSpecialId : {"account":"","expiredTime":0} +/// sameRegion : false +/// sysOriginChild : "" +/// useProps : [{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}] +/// userAvatar : "" +/// userNickname : "" +/// userSex : 0 +/// wearBadge : [{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}] + +/// animationUrl : "" +/// badgeKey : "" +/// badgeLevel : 0 +/// badgeName : "" +/// expireTime : 0 +/// id : 0 +/// milestone : 0 +/// notSelectUrl : "" +/// selectUrl : "" +/// type : "" +/// userId : 0 + +class WearBadge { + WearBadge({ + String? animationUrl, + String? badgeKey, + num? badgeLevel, + String? badgeName, + num? expireTime, + num? id, + num? milestone, + String? notSelectUrl, + String? selectUrl, + String? type, + num? userId,}){ + _animationUrl = animationUrl; + _badgeKey = badgeKey; + _badgeLevel = badgeLevel; + _badgeName = badgeName; + _expireTime = expireTime; + _id = id; + _milestone = milestone; + _notSelectUrl = notSelectUrl; + _selectUrl = selectUrl; + _type = type; + _userId = userId; +} + + WearBadge.fromJson(dynamic json) { + _animationUrl = json['animationUrl']; + _badgeKey = json['badgeKey']; + _badgeLevel = json['badgeLevel']; + _badgeName = json['badgeName']; + _expireTime = json['expireTime']; + _id = json['id']; + _milestone = json['milestone']; + _notSelectUrl = json['notSelectUrl']; + _selectUrl = json['selectUrl']; + _type = json['type']; + _userId = json['userId']; + } + String? _animationUrl; + String? _badgeKey; + num? _badgeLevel; + String? _badgeName; + num? _expireTime; + num? _id; + num? _milestone; + String? _notSelectUrl; + String? _selectUrl; + String? _type; + num? _userId; +WearBadge copyWith({ String? animationUrl, + String? badgeKey, + num? badgeLevel, + String? badgeName, + num? expireTime, + num? id, + num? milestone, + String? notSelectUrl, + String? selectUrl, + String? type, + num? userId, +}) => WearBadge( animationUrl: animationUrl ?? _animationUrl, + badgeKey: badgeKey ?? _badgeKey, + badgeLevel: badgeLevel ?? _badgeLevel, + badgeName: badgeName ?? _badgeName, + expireTime: expireTime ?? _expireTime, + id: id ?? _id, + milestone: milestone ?? _milestone, + notSelectUrl: notSelectUrl ?? _notSelectUrl, + selectUrl: selectUrl ?? _selectUrl, + type: type ?? _type, + userId: userId ?? _userId, +); + String? get animationUrl => _animationUrl; + String? get badgeKey => _badgeKey; + num? get badgeLevel => _badgeLevel; + String? get badgeName => _badgeName; + num? get expireTime => _expireTime; + num? get id => _id; + num? get milestone => _milestone; + String? get notSelectUrl => _notSelectUrl; + String? get selectUrl => _selectUrl; + String? get type => _type; + num? get userId => _userId; + + Map toJson() { + final map = {}; + map['animationUrl'] = _animationUrl; + map['badgeKey'] = _badgeKey; + map['badgeLevel'] = _badgeLevel; + map['badgeName'] = _badgeName; + map['expireTime'] = _expireTime; + map['id'] = _id; + map['milestone'] = _milestone; + map['notSelectUrl'] = _notSelectUrl; + map['selectUrl'] = _selectUrl; + map['type'] = _type; + map['userId'] = _userId; + return map; + } + +} + +class UserLevel { + UserLevel({ + num? charmLevel, + num? wealthLevel,}){ + _charmLevel = charmLevel; + _wealthLevel = wealthLevel; +} + + UserLevel.fromJson(dynamic json) { + _charmLevel = json['charmLevel']; + _wealthLevel = json['wealthLevel']; + } + num? _charmLevel; + num? _wealthLevel; +UserLevel copyWith({ num? charmLevel, + num? wealthLevel, +}) => UserLevel( charmLevel: charmLevel ?? _charmLevel, + wealthLevel: wealthLevel ?? _wealthLevel, +); + num? get charmLevel => _charmLevel; + num? get wealthLevel => _wealthLevel; + + Map toJson() { + final map = {}; + map['charmLevel'] = _charmLevel; + map['wealthLevel'] = _wealthLevel; + return map; + } + +} + +class UseBadge { + UseBadge({ + String? animationUrl, + String? badgeKey, + num? badgeLevel, + String? badgeName, + num? expireTime, + String? id, + String? milestone, + String? notSelectUrl, + String? selectUrl, + String? type, + String? userId,}){ + _animationUrl = animationUrl; + _badgeKey = badgeKey; + _badgeLevel = badgeLevel; + _badgeName = badgeName; + _expireTime = expireTime; + _id = id; + _milestone = milestone; + _notSelectUrl = notSelectUrl; + _selectUrl = selectUrl; + _type = type; + _userId = userId; +} + + UseBadge.fromJson(dynamic json) { + _animationUrl = json['animationUrl']; + _badgeKey = json['badgeKey']; + _badgeLevel = json['badgeLevel']; + _badgeName = json['badgeName']; + _expireTime = json['expireTime']; + _id = json['id']; + _milestone = json['milestone']; + _notSelectUrl = json['notSelectUrl']; + _selectUrl = json['selectUrl']; + _type = json['type']; + _userId = json['userId']; + } + String? _animationUrl; + String? _badgeKey; + num? _badgeLevel; + String? _badgeName; + num? _expireTime; + String? _id; + String? _milestone; + String? _notSelectUrl; + String? _selectUrl; + String? _type; + String? _userId; +UseBadge copyWith({ String? animationUrl, + String? badgeKey, + num? badgeLevel, + String? badgeName, + num? expireTime, + String? id, + String? milestone, + String? notSelectUrl, + String? selectUrl, + String? type, + String? userId, +}) => UseBadge( animationUrl: animationUrl ?? _animationUrl, + badgeKey: badgeKey ?? _badgeKey, + badgeLevel: badgeLevel ?? _badgeLevel, + badgeName: badgeName ?? _badgeName, + expireTime: expireTime ?? _expireTime, + id: id ?? _id, + milestone: milestone ?? _milestone, + notSelectUrl: notSelectUrl ?? _notSelectUrl, + selectUrl: selectUrl ?? _selectUrl, + type: type ?? _type, + userId: userId ?? _userId, +); + String? get animationUrl => _animationUrl; + String? get badgeKey => _badgeKey; + num? get badgeLevel => _badgeLevel; + String? get badgeName => _badgeName; + num? get expireTime => _expireTime; + String? get id => _id; + String? get milestone => _milestone; + String? get notSelectUrl => _notSelectUrl; + String? get selectUrl => _selectUrl; + String? get type => _type; + String? get userId => _userId; + + Map toJson() { + final map = {}; + map['animationUrl'] = _animationUrl; + map['badgeKey'] = _badgeKey; + map['badgeLevel'] = _badgeLevel; + map['badgeName'] = _badgeName; + map['expireTime'] = _expireTime; + map['id'] = _id; + map['milestone'] = _milestone; + map['notSelectUrl'] = _notSelectUrl; + map['selectUrl'] = _selectUrl; + map['type'] = _type; + map['userId'] = _userId; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/store_list_res.dart b/lib/chatvibe_domain/models/res/store_list_res.dart new file mode 100644 index 0000000..2db8e11 --- /dev/null +++ b/lib/chatvibe_domain/models/res/store_list_res.dart @@ -0,0 +1,174 @@ +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +/// id : 0 +/// label : "" +/// discount : 0.0 +/// propsResources : {"id":0,"sysOrigin":"","type":"","code":"","name":"","cover":"","sourceUrl":"","expand":"","amount":0.0,"del":false,"roomSendCoverUrl":"","roomOpenedUrl":"","roomNotOpenedUrl":"","imSendCoverUrl":"","imOpenedUrl":"","imNotOpenedUrl":"","imOpenedUrlTwo":"","createTime":0,"updateTime":0} +/// propsPrices : [{"days":0,"amount":0.0,"discountAmount":0.0}] +/// diamondPrices : [{"days":0,"amount":0.0,"discountAmount":0.0}] + +class StoreListRes { + StoreListRes({ + String? id, + String? label, + num? discount, + PropsResources? propsResources, + List? propsPrices, + List? diamondPrices,}){ + _id = id; + _label = label; + _discount = discount; + _propsResources = propsResources; + _propsPrices = propsPrices; + _diamondPrices = diamondPrices; +} + + StoreListRes.fromJson(dynamic json) { + _id = json['id']; + _label = json['label']; + _discount = json['discount']; + _propsResources = json['propsResources'] != null ? PropsResources.fromJson(json['propsResources']) : null; + if (json['propsPrices'] != null) { + _propsPrices = []; + json['propsPrices'].forEach((v) { + _propsPrices?.add(PropsPrices.fromJson(v)); + }); + } + if (json['diamondPrices'] != null) { + _diamondPrices = []; + json['diamondPrices'].forEach((v) { + _diamondPrices?.add(DiamondPrices.fromJson(v)); + }); + } + } + String? _id; + String? _label; + num? _discount; + PropsResources? _propsResources; + List? _propsPrices; + List? _diamondPrices; +StoreListRes copyWith({ String? id, + String? label, + num? discount, + PropsResources? propsResources, + List? propsPrices, + List? diamondPrices, +}) => StoreListRes( id: id ?? _id, + label: label ?? _label, + discount: discount ?? _discount, + propsResources: propsResources ?? _propsResources, + propsPrices: propsPrices ?? _propsPrices, + diamondPrices: diamondPrices ?? _diamondPrices, +); + String? get id => _id; + String? get label => _label; + num? get discount => _discount; + PropsResources? get propsResources => _propsResources; + List? get propsPrices => _propsPrices; + List? get diamondPrices => _diamondPrices; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['label'] = _label; + map['discount'] = _discount; + if (_propsResources != null) { + map['propsResources'] = _propsResources?.toJson(); + } + if (_propsPrices != null) { + map['propsPrices'] = _propsPrices?.map((v) => v.toJson()).toList(); + } + if (_diamondPrices != null) { + map['diamondPrices'] = _diamondPrices?.map((v) => v.toJson()).toList(); + } + return map; + } + +} + +/// days : 0 +/// amount : 0.0 +/// discountAmount : 0.0 + +class DiamondPrices { + DiamondPrices({ + num? days, + num? amount, + num? discountAmount,}){ + _days = days; + _amount = amount; + _discountAmount = discountAmount; +} + + DiamondPrices.fromJson(dynamic json) { + _days = json['days']; + _amount = json['amount']; + _discountAmount = json['discountAmount']; + } + num? _days; + num? _amount; + num? _discountAmount; +DiamondPrices copyWith({ num? days, + num? amount, + num? discountAmount, +}) => DiamondPrices( days: days ?? _days, + amount: amount ?? _amount, + discountAmount: discountAmount ?? _discountAmount, +); + num? get days => _days; + num? get amount => _amount; + num? get discountAmount => _discountAmount; + + Map toJson() { + final map = {}; + map['days'] = _days; + map['amount'] = _amount; + map['discountAmount'] = _discountAmount; + return map; + } + +} + +/// days : 0 +/// amount : 0.0 +/// discountAmount : 0.0 + +class PropsPrices { + PropsPrices({ + num? days, + num? amount, + num? discountAmount,}){ + _days = days; + _amount = amount; + _discountAmount = discountAmount; +} + + PropsPrices.fromJson(dynamic json) { + _days = json['days']; + _amount = json['amount']; + _discountAmount = json['discountAmount']; + } + num? _days; + num? _amount; + num? _discountAmount; +PropsPrices copyWith({ num? days, + num? amount, + num? discountAmount, +}) => PropsPrices( days: days ?? _days, + amount: amount ?? _amount, + discountAmount: discountAmount ?? _discountAmount, +); + num? get days => _days; + num? get amount => _amount; + num? get discountAmount => _discountAmount; + + Map toJson() { + final map = {}; + map['days'] = _days; + map['amount'] = _amount; + map['discountAmount'] = _discountAmount; + return map; + } + +} + diff --git a/lib/chatvibe_domain/models/res/task_checkIn_latest_vip_record_res.dart b/lib/chatvibe_domain/models/res/task_checkIn_latest_vip_record_res.dart new file mode 100644 index 0000000..eb16600 --- /dev/null +++ b/lib/chatvibe_domain/models/res/task_checkIn_latest_vip_record_res.dart @@ -0,0 +1,127 @@ +/// id : "2022053273708990466" +/// sysOrigin : "atu" +/// userId : "2013018245987610626" +/// pennyAmount : "240000" +/// amount : 2400.00 +/// pennyBalance : "260000" +/// type : 0 +/// eventType : "PURCHASE_NOBLE_VIP_GIVEAWAY" +/// eventDesc : "Give Noble" +/// eventId : "1965318639453310977" +/// opUserType : 0 +/// createTime : 1770930043042 +/// updateTime : 1770930043059 +/// ATVIPType : "VISCOUNT" +/// vipLevel : 1 +/// loginRewardsAmount : 2400.00 + +class TaskCheckInLatestVipRecordRes { + TaskCheckInLatestVipRecordRes({ + String? id, + String? sysOrigin, + String? userId, + String? pennyAmount, + num? amount, + String? pennyBalance, + num? type, + String? eventType, + String? eventDesc, + String? eventId, + num? opUserType, + num? createTime, + num? updateTime, + String? ATVIPType, + num? vipLevel, + num? loginRewardsAmount,}){ + _id = id; + _sysOrigin = sysOrigin; + _userId = userId; + _pennyAmount = pennyAmount; + _amount = amount; + _pennyBalance = pennyBalance; + _type = type; + _eventType = eventType; + _eventDesc = eventDesc; + _eventId = eventId; + _opUserType = opUserType; + _createTime = createTime; + _updateTime = updateTime; + _ATVIPType = ATVIPType; + _vipLevel = vipLevel; + _loginRewardsAmount = loginRewardsAmount; +} + + TaskCheckInLatestVipRecordRes.fromJson(dynamic json) { + _id = json['id']; + _sysOrigin = json['sysOrigin']; + _userId = json['userId']; + _pennyAmount = json['pennyAmount']; + _amount = json['amount']; + _pennyBalance = json['pennyBalance']; + _type = json['type']; + _eventType = json['eventType']; + _eventDesc = json['eventDesc']; + _eventId = json['eventId']; + _opUserType = json['opUserType']; + _createTime = json['createTime']; + _updateTime = json['updateTime']; + _ATVIPType = json['ATVIPType']; + _vipLevel = json['vipLevel']; + _loginRewardsAmount = json['loginRewardsAmount']; + } + String? _id; + String? _sysOrigin; + String? _userId; + String? _pennyAmount; + num? _amount; + String? _pennyBalance; + num? _type; + String? _eventType; + String? _eventDesc; + String? _eventId; + num? _opUserType; + num? _createTime; + num? _updateTime; + String? _ATVIPType; + num? _vipLevel; + num? _loginRewardsAmount; + + String? get id => _id; + String? get sysOrigin => _sysOrigin; + String? get userId => _userId; + String? get pennyAmount => _pennyAmount; + num? get amount => _amount; + String? get pennyBalance => _pennyBalance; + num? get type => _type; + String? get eventType => _eventType; + String? get eventDesc => _eventDesc; + String? get eventId => _eventId; + num? get opUserType => _opUserType; + num? get createTime => _createTime; + num? get updateTime => _updateTime; + String? get ATVIPType => _ATVIPType; + num? get vipLevel => _vipLevel; + num? get loginRewardsAmount => _loginRewardsAmount; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['sysOrigin'] = _sysOrigin; + map['userId'] = _userId; + map['pennyAmount'] = _pennyAmount; + map['amount'] = _amount; + map['pennyBalance'] = _pennyBalance; + map['type'] = _type; + map['eventType'] = _eventType; + map['eventDesc'] = _eventDesc; + map['eventId'] = _eventId; + map['opUserType'] = _opUserType; + map['createTime'] = _createTime; + map['updateTime'] = _updateTime; + map['ATVIPType'] = _ATVIPType; + map['vipLevel'] = _vipLevel; + map['loginRewardsAmount'] = _loginRewardsAmount; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/models/res/user_count_guard_res.dart b/lib/chatvibe_domain/models/res/user_count_guard_res.dart new file mode 100644 index 0000000..d24812b --- /dev/null +++ b/lib/chatvibe_domain/models/res/user_count_guard_res.dart @@ -0,0 +1,47 @@ +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +/// userProfile : {"id":0,"account":"","userAvatar":"","userNickname":"","userSex":0,"age":0,"accountStatus":"","freezingTime":0,"countryId":0,"countryName":"","countryCode":"","originSys":"","sysOriginChild":"","del":false,"createTime":0,"bornYear":0,"bornMonth":0,"bornDay":0,"ownSpecialId":{"account":"","expiredTime":0},"useProps":[{"userId":0,"propsResources":{"id":0,"type":"","code":"","name":"","cover":"","sourceUrl":"","expand":"","amount":0.0},"expireTime":0}],"sameRegion":false,"wearBadge":[{"id":0,"userId":0,"badgeLevel":0,"milestone":0,"badgeName":"","type":"","badgeKey":"","selectUrl":"","notSelectUrl":"","animationUrl":"","expireTime":0}]} +/// quantity : 0 +/// quantityFormat : "" + +class UserCountGuardRes { + UserCountGuardRes({ + ChatVibeUserProfile? userProfile, + String? quantity, + String? quantityFormat,}){ + _userProfile = userProfile; + _quantity = quantity; + _quantityFormat = quantityFormat; +} + + UserCountGuardRes.fromJson(dynamic json) { + _userProfile = json['userProfile'] != null ? ChatVibeUserProfile.fromJson(json['userProfile']) : null; + _quantity = json['quantity']; + _quantityFormat = json['quantityFormat']; + } + ChatVibeUserProfile? _userProfile; + String? _quantity; + String? _quantityFormat; +UserCountGuardRes copyWith({ ChatVibeUserProfile? userProfile, + String? quantity, + String? quantityFormat, +}) => UserCountGuardRes( userProfile: userProfile ?? _userProfile, + quantity: quantity ?? _quantity, + quantityFormat: quantityFormat ?? _quantityFormat, +); + ChatVibeUserProfile? get userProfile => _userProfile; + String? get quantity => _quantity; + String? get quantityFormat => _quantityFormat; + + Map toJson() { + final map = {}; + if (_userProfile != null) { + map['userProfile'] = _userProfile?.toJson(); + } + map['quantity'] = _quantity; + map['quantityFormat'] = _quantityFormat; + return map; + } + +} + diff --git a/lib/chatvibe_domain/models/res/version_manage_lates_review_res.dart b/lib/chatvibe_domain/models/res/version_manage_lates_review_res.dart new file mode 100644 index 0000000..595abad --- /dev/null +++ b/lib/chatvibe_domain/models/res/version_manage_lates_review_res.dart @@ -0,0 +1,36 @@ +import 'package:aslan/chatvibe_domain/models/res/at_version_manage_latest_res.dart'; + +/// latest : {"id":0,"platform":"","version":"","forceUpdate":false,"appType":"","review":false,"patch":false,"updateDescribe":"","updateWorshipDescribe":"","downloadUrl":"","buildVersion":0,"createTime":0} +/// review : {"id":0,"platform":"","version":"","forceUpdate":false,"appType":"","review":false,"patch":false,"updateDescribe":"","updateWorshipDescribe":"","downloadUrl":"","buildVersion":0,"createTime":0} + +class VersionManageLatesReviewRes { + VersionManageLatesReviewRes({ + ATVersionManageLatestRes? latest, + ATVersionManageLatestRes? review,}){ + _latest = latest; + _review = review; +} + + VersionManageLatesReviewRes.fromJson(dynamic json) { + _latest = json['latest'] != null ? ATVersionManageLatestRes.fromJson(json['latest']) : null; + _review = json['review'] != null ? ATVersionManageLatestRes.fromJson(json['review']) : null; + } + ATVersionManageLatestRes? _latest; + ATVersionManageLatestRes? _review; + + ATVersionManageLatestRes? get latest => _latest; + ATVersionManageLatestRes? get review => _review; + + Map toJson() { + final map = {}; + if (_latest != null) { + map['latest'] = _latest?.toJson(); + } + if (_review != null) { + map['review'] = _review?.toJson(); + } + return map; + } + +} + diff --git a/lib/chatvibe_domain/models/res/vip_list_res.dart b/lib/chatvibe_domain/models/res/vip_list_res.dart new file mode 100644 index 0000000..30a4d2a --- /dev/null +++ b/lib/chatvibe_domain/models/res/vip_list_res.dart @@ -0,0 +1,943 @@ +/// id : 0 +/// nobleVipAbility : {"ATVIPType":"","vipLevel":0,"adminNumber":0,"loginRewardsAmount":0.0,"roomMaxMember":0,"avatarFrameId":0,"carId":0} +/// propsResources : {"id":0,"sysOrigin":"","type":"","code":"","name":"","cover":"","sourceUrl":"","expand":"","amount":0.0,"del":false,"roomSendCoverUrl":"","roomOpenedUrl":"","roomNotOpenedUrl":"","imSendCoverUrl":"","imOpenedUrl":"","imNotOpenedUrl":"","imOpenedUrlTwo":"","createTime":0,"updateTime":0} +/// avatarFrame : {"id":0,"sysOrigin":"","type":"","code":"","name":"","cover":"","sourceUrl":"","expand":"","amount":0.0,"del":false,"roomSendCoverUrl":"","roomOpenedUrl":"","roomNotOpenedUrl":"","imSendCoverUrl":"","imOpenedUrl":"","imNotOpenedUrl":"","imOpenedUrlTwo":"","createTime":0,"updateTime":0} +/// car : {"id":0,"sysOrigin":"","type":"","code":"","name":"","cover":"","sourceUrl":"","expand":"","amount":0.0,"del":false,"roomSendCoverUrl":"","roomOpenedUrl":"","roomNotOpenedUrl":"","imSendCoverUrl":"","imOpenedUrl":"","imNotOpenedUrl":"","imOpenedUrlTwo":"","createTime":0,"updateTime":0} +/// chatBubble : {"id":0,"sysOrigin":"","type":"","code":"","name":"","cover":"","sourceUrl":"","expand":"","amount":0.0,"del":false,"roomSendCoverUrl":"","roomOpenedUrl":"","roomNotOpenedUrl":"","imSendCoverUrl":"","imOpenedUrl":"","imNotOpenedUrl":"","imOpenedUrlTwo":"","createTime":0,"updateTime":0} +/// dataCard : {"id":0,"sysOrigin":"","type":"","code":"","name":"","cover":"","sourceUrl":"","expand":"","amount":0.0,"del":false,"roomSendCoverUrl":"","roomOpenedUrl":"","roomNotOpenedUrl":"","imSendCoverUrl":"","imOpenedUrl":"","imNotOpenedUrl":"","imOpenedUrlTwo":"","createTime":0,"updateTime":0} +/// propsPrices : [{"days":0,"amount":0.0,"discountAmount":0.0}] +/// own : false +/// ownArbitrary : false + +class VipListRes { + VipListRes({ + String? id, + NobleVipAbility? nobleVipAbility, + PropsResources? propsResources, + AvatarFrame? avatarFrame, + Car? car, + ChatBubble? chatBubble, + DataCard? dataCard, + List? propsPrices, + bool? own, + bool? ownArbitrary,}){ + _id = id; + _nobleVipAbility = nobleVipAbility; + _propsResources = propsResources; + _avatarFrame = avatarFrame; + _car = car; + _chatBubble = chatBubble; + _dataCard = dataCard; + _propsPrices = propsPrices; + _own = own; + _ownArbitrary = ownArbitrary; +} + + VipListRes.fromJson(dynamic json) { + _id = json['id']; + _nobleVipAbility = json['nobleVipAbility'] != null ? NobleVipAbility.fromJson(json['nobleVipAbility']) : null; + _propsResources = json['propsResources'] != null ? PropsResources.fromJson(json['propsResources']) : null; + _avatarFrame = json['avatarFrame'] != null ? AvatarFrame.fromJson(json['avatarFrame']) : null; + _car = json['car'] != null ? Car.fromJson(json['car']) : null; + _chatBubble = json['chatBubble'] != null ? ChatBubble.fromJson(json['chatBubble']) : null; + _dataCard = json['dataCard'] != null ? DataCard.fromJson(json['dataCard']) : null; + if (json['propsPrices'] != null) { + _propsPrices = []; + json['propsPrices'].forEach((v) { + _propsPrices?.add(PropsPrices.fromJson(v)); + }); + } + _own = json['own']; + _ownArbitrary = json['ownArbitrary']; + } + String? _id; + NobleVipAbility? _nobleVipAbility; + PropsResources? _propsResources; + AvatarFrame? _avatarFrame; + Car? _car; + ChatBubble? _chatBubble; + DataCard? _dataCard; + List? _propsPrices; + bool? _own; + bool? _ownArbitrary; + String? get id => _id; + NobleVipAbility? get nobleVipAbility => _nobleVipAbility; + PropsResources? get propsResources => _propsResources; + AvatarFrame? get avatarFrame => _avatarFrame; + Car? get car => _car; + ChatBubble? get chatBubble => _chatBubble; + DataCard? get dataCard => _dataCard; + List? get propsPrices => _propsPrices; + bool? get own => _own; + bool? get ownArbitrary => _ownArbitrary; + + Map toJson() { + final map = {}; + map['id'] = _id; + if (_nobleVipAbility != null) { + map['nobleVipAbility'] = _nobleVipAbility?.toJson(); + } + if (_propsResources != null) { + map['propsResources'] = _propsResources?.toJson(); + } + if (_avatarFrame != null) { + map['avatarFrame'] = _avatarFrame?.toJson(); + } + if (_car != null) { + map['car'] = _car?.toJson(); + } + if (_chatBubble != null) { + map['chatBubble'] = _chatBubble?.toJson(); + } + if (_dataCard != null) { + map['dataCard'] = _dataCard?.toJson(); + } + if (_propsPrices != null) { + map['propsPrices'] = _propsPrices?.map((v) => v.toJson()).toList(); + } + map['own'] = _own; + map['ownArbitrary'] = _ownArbitrary; + return map; + } + +} + +/// days : 0 +/// amount : 0.0 +/// discountAmount : 0.0 + +class PropsPrices { + PropsPrices({ + num? days, + num? amount, + num? discountAmount,}){ + _days = days; + _amount = amount; + _discountAmount = discountAmount; +} + + PropsPrices.fromJson(dynamic json) { + _days = json['days']; + _amount = json['amount']; + _discountAmount = json['discountAmount']; + } + num? _days; + num? _amount; + num? _discountAmount; + num? get days => _days; + num? get amount => _amount; + num? get discountAmount => _discountAmount; + + Map toJson() { + final map = {}; + map['days'] = _days; + map['amount'] = _amount; + map['discountAmount'] = _discountAmount; + return map; + } + +} + +/// id : 0 +/// sysOrigin : "" +/// type : "" +/// code : "" +/// name : "" +/// cover : "" +/// sourceUrl : "" +/// expand : "" +/// amount : 0.0 +/// del : false +/// roomSendCoverUrl : "" +/// roomOpenedUrl : "" +/// roomNotOpenedUrl : "" +/// imSendCoverUrl : "" +/// imOpenedUrl : "" +/// imNotOpenedUrl : "" +/// imOpenedUrlTwo : "" +/// createTime : 0 +/// updateTime : 0 + +class DataCard { + DataCard({ + String? id, + String? sysOrigin, + String? type, + String? code, + String? name, + String? cover, + String? sourceUrl, + String? expand, + num? amount, + bool? del, + String? roomSendCoverUrl, + String? roomOpenedUrl, + String? roomNotOpenedUrl, + String? imSendCoverUrl, + String? imOpenedUrl, + String? imNotOpenedUrl, + String? imOpenedUrlTwo, + num? createTime, + num? updateTime,}){ + _id = id; + _sysOrigin = sysOrigin; + _type = type; + _code = code; + _name = name; + _cover = cover; + _sourceUrl = sourceUrl; + _expand = expand; + _amount = amount; + _del = del; + _roomSendCoverUrl = roomSendCoverUrl; + _roomOpenedUrl = roomOpenedUrl; + _roomNotOpenedUrl = roomNotOpenedUrl; + _imSendCoverUrl = imSendCoverUrl; + _imOpenedUrl = imOpenedUrl; + _imNotOpenedUrl = imNotOpenedUrl; + _imOpenedUrlTwo = imOpenedUrlTwo; + _createTime = createTime; + _updateTime = updateTime; +} + + DataCard.fromJson(dynamic json) { + _id = json['id']; + _sysOrigin = json['sysOrigin']; + _type = json['type']; + _code = json['code']; + _name = json['name']; + _cover = json['cover']; + _sourceUrl = json['sourceUrl']; + _expand = json['expand']; + _amount = json['amount']; + _del = json['del']; + _roomSendCoverUrl = json['roomSendCoverUrl']; + _roomOpenedUrl = json['roomOpenedUrl']; + _roomNotOpenedUrl = json['roomNotOpenedUrl']; + _imSendCoverUrl = json['imSendCoverUrl']; + _imOpenedUrl = json['imOpenedUrl']; + _imNotOpenedUrl = json['imNotOpenedUrl']; + _imOpenedUrlTwo = json['imOpenedUrlTwo']; + _createTime = json['createTime']; + _updateTime = json['updateTime']; + } + String? _id; + String? _sysOrigin; + String? _type; + String? _code; + String? _name; + String? _cover; + String? _sourceUrl; + String? _expand; + num? _amount; + bool? _del; + String? _roomSendCoverUrl; + String? _roomOpenedUrl; + String? _roomNotOpenedUrl; + String? _imSendCoverUrl; + String? _imOpenedUrl; + String? _imNotOpenedUrl; + String? _imOpenedUrlTwo; + num? _createTime; + num? _updateTime; + String? get id => _id; + String? get sysOrigin => _sysOrigin; + String? get type => _type; + String? get code => _code; + String? get name => _name; + String? get cover => _cover; + String? get sourceUrl => _sourceUrl; + String? get expand => _expand; + num? get amount => _amount; + bool? get del => _del; + String? get roomSendCoverUrl => _roomSendCoverUrl; + String? get roomOpenedUrl => _roomOpenedUrl; + String? get roomNotOpenedUrl => _roomNotOpenedUrl; + String? get imSendCoverUrl => _imSendCoverUrl; + String? get imOpenedUrl => _imOpenedUrl; + String? get imNotOpenedUrl => _imNotOpenedUrl; + String? get imOpenedUrlTwo => _imOpenedUrlTwo; + num? get createTime => _createTime; + num? get updateTime => _updateTime; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['sysOrigin'] = _sysOrigin; + map['type'] = _type; + map['code'] = _code; + map['name'] = _name; + map['cover'] = _cover; + map['sourceUrl'] = _sourceUrl; + map['expand'] = _expand; + map['amount'] = _amount; + map['del'] = _del; + map['roomSendCoverUrl'] = _roomSendCoverUrl; + map['roomOpenedUrl'] = _roomOpenedUrl; + map['roomNotOpenedUrl'] = _roomNotOpenedUrl; + map['imSendCoverUrl'] = _imSendCoverUrl; + map['imOpenedUrl'] = _imOpenedUrl; + map['imNotOpenedUrl'] = _imNotOpenedUrl; + map['imOpenedUrlTwo'] = _imOpenedUrlTwo; + map['createTime'] = _createTime; + map['updateTime'] = _updateTime; + return map; + } + +} + +/// id : 0 +/// sysOrigin : "" +/// type : "" +/// code : "" +/// name : "" +/// cover : "" +/// sourceUrl : "" +/// expand : "" +/// amount : 0.0 +/// del : false +/// roomSendCoverUrl : "" +/// roomOpenedUrl : "" +/// roomNotOpenedUrl : "" +/// imSendCoverUrl : "" +/// imOpenedUrl : "" +/// imNotOpenedUrl : "" +/// imOpenedUrlTwo : "" +/// createTime : 0 +/// updateTime : 0 + +class ChatBubble { + ChatBubble({ + String? id, + String? sysOrigin, + String? type, + String? code, + String? name, + String? cover, + String? sourceUrl, + String? expand, + num? amount, + bool? del, + String? roomSendCoverUrl, + String? roomOpenedUrl, + String? roomNotOpenedUrl, + String? imSendCoverUrl, + String? imOpenedUrl, + String? imNotOpenedUrl, + String? imOpenedUrlTwo, + num? createTime, + num? updateTime,}){ + _id = id; + _sysOrigin = sysOrigin; + _type = type; + _code = code; + _name = name; + _cover = cover; + _sourceUrl = sourceUrl; + _expand = expand; + _amount = amount; + _del = del; + _roomSendCoverUrl = roomSendCoverUrl; + _roomOpenedUrl = roomOpenedUrl; + _roomNotOpenedUrl = roomNotOpenedUrl; + _imSendCoverUrl = imSendCoverUrl; + _imOpenedUrl = imOpenedUrl; + _imNotOpenedUrl = imNotOpenedUrl; + _imOpenedUrlTwo = imOpenedUrlTwo; + _createTime = createTime; + _updateTime = updateTime; +} + + ChatBubble.fromJson(dynamic json) { + _id = json['id']; + _sysOrigin = json['sysOrigin']; + _type = json['type']; + _code = json['code']; + _name = json['name']; + _cover = json['cover']; + _sourceUrl = json['sourceUrl']; + _expand = json['expand']; + _amount = json['amount']; + _del = json['del']; + _roomSendCoverUrl = json['roomSendCoverUrl']; + _roomOpenedUrl = json['roomOpenedUrl']; + _roomNotOpenedUrl = json['roomNotOpenedUrl']; + _imSendCoverUrl = json['imSendCoverUrl']; + _imOpenedUrl = json['imOpenedUrl']; + _imNotOpenedUrl = json['imNotOpenedUrl']; + _imOpenedUrlTwo = json['imOpenedUrlTwo']; + _createTime = json['createTime']; + _updateTime = json['updateTime']; + } + String? _id; + String? _sysOrigin; + String? _type; + String? _code; + String? _name; + String? _cover; + String? _sourceUrl; + String? _expand; + num? _amount; + bool? _del; + String? _roomSendCoverUrl; + String? _roomOpenedUrl; + String? _roomNotOpenedUrl; + String? _imSendCoverUrl; + String? _imOpenedUrl; + String? _imNotOpenedUrl; + String? _imOpenedUrlTwo; + num? _createTime; + num? _updateTime; + String? get id => _id; + String? get sysOrigin => _sysOrigin; + String? get type => _type; + String? get code => _code; + String? get name => _name; + String? get cover => _cover; + String? get sourceUrl => _sourceUrl; + String? get expand => _expand; + num? get amount => _amount; + bool? get del => _del; + String? get roomSendCoverUrl => _roomSendCoverUrl; + String? get roomOpenedUrl => _roomOpenedUrl; + String? get roomNotOpenedUrl => _roomNotOpenedUrl; + String? get imSendCoverUrl => _imSendCoverUrl; + String? get imOpenedUrl => _imOpenedUrl; + String? get imNotOpenedUrl => _imNotOpenedUrl; + String? get imOpenedUrlTwo => _imOpenedUrlTwo; + num? get createTime => _createTime; + num? get updateTime => _updateTime; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['sysOrigin'] = _sysOrigin; + map['type'] = _type; + map['code'] = _code; + map['name'] = _name; + map['cover'] = _cover; + map['sourceUrl'] = _sourceUrl; + map['expand'] = _expand; + map['amount'] = _amount; + map['del'] = _del; + map['roomSendCoverUrl'] = _roomSendCoverUrl; + map['roomOpenedUrl'] = _roomOpenedUrl; + map['roomNotOpenedUrl'] = _roomNotOpenedUrl; + map['imSendCoverUrl'] = _imSendCoverUrl; + map['imOpenedUrl'] = _imOpenedUrl; + map['imNotOpenedUrl'] = _imNotOpenedUrl; + map['imOpenedUrlTwo'] = _imOpenedUrlTwo; + map['createTime'] = _createTime; + map['updateTime'] = _updateTime; + return map; + } + +} + +/// id : 0 +/// sysOrigin : "" +/// type : "" +/// code : "" +/// name : "" +/// cover : "" +/// sourceUrl : "" +/// expand : "" +/// amount : 0.0 +/// del : false +/// roomSendCoverUrl : "" +/// roomOpenedUrl : "" +/// roomNotOpenedUrl : "" +/// imSendCoverUrl : "" +/// imOpenedUrl : "" +/// imNotOpenedUrl : "" +/// imOpenedUrlTwo : "" +/// createTime : 0 +/// updateTime : 0 + +class Car { + Car({ + String? id, + String? sysOrigin, + String? type, + String? code, + String? name, + String? cover, + String? sourceUrl, + String? expand, + num? amount, + bool? del, + String? roomSendCoverUrl, + String? roomOpenedUrl, + String? roomNotOpenedUrl, + String? imSendCoverUrl, + String? imOpenedUrl, + String? imNotOpenedUrl, + String? imOpenedUrlTwo, + num? createTime, + num? updateTime,}){ + _id = id; + _sysOrigin = sysOrigin; + _type = type; + _code = code; + _name = name; + _cover = cover; + _sourceUrl = sourceUrl; + _expand = expand; + _amount = amount; + _del = del; + _roomSendCoverUrl = roomSendCoverUrl; + _roomOpenedUrl = roomOpenedUrl; + _roomNotOpenedUrl = roomNotOpenedUrl; + _imSendCoverUrl = imSendCoverUrl; + _imOpenedUrl = imOpenedUrl; + _imNotOpenedUrl = imNotOpenedUrl; + _imOpenedUrlTwo = imOpenedUrlTwo; + _createTime = createTime; + _updateTime = updateTime; +} + + Car.fromJson(dynamic json) { + _id = json['id']; + _sysOrigin = json['sysOrigin']; + _type = json['type']; + _code = json['code']; + _name = json['name']; + _cover = json['cover']; + _sourceUrl = json['sourceUrl']; + _expand = json['expand']; + _amount = json['amount']; + _del = json['del']; + _roomSendCoverUrl = json['roomSendCoverUrl']; + _roomOpenedUrl = json['roomOpenedUrl']; + _roomNotOpenedUrl = json['roomNotOpenedUrl']; + _imSendCoverUrl = json['imSendCoverUrl']; + _imOpenedUrl = json['imOpenedUrl']; + _imNotOpenedUrl = json['imNotOpenedUrl']; + _imOpenedUrlTwo = json['imOpenedUrlTwo']; + _createTime = json['createTime']; + _updateTime = json['updateTime']; + } + String? _id; + String? _sysOrigin; + String? _type; + String? _code; + String? _name; + String? _cover; + String? _sourceUrl; + String? _expand; + num? _amount; + bool? _del; + String? _roomSendCoverUrl; + String? _roomOpenedUrl; + String? _roomNotOpenedUrl; + String? _imSendCoverUrl; + String? _imOpenedUrl; + String? _imNotOpenedUrl; + String? _imOpenedUrlTwo; + num? _createTime; + num? _updateTime; + String? get id => _id; + String? get sysOrigin => _sysOrigin; + String? get type => _type; + String? get code => _code; + String? get name => _name; + String? get cover => _cover; + String? get sourceUrl => _sourceUrl; + String? get expand => _expand; + num? get amount => _amount; + bool? get del => _del; + String? get roomSendCoverUrl => _roomSendCoverUrl; + String? get roomOpenedUrl => _roomOpenedUrl; + String? get roomNotOpenedUrl => _roomNotOpenedUrl; + String? get imSendCoverUrl => _imSendCoverUrl; + String? get imOpenedUrl => _imOpenedUrl; + String? get imNotOpenedUrl => _imNotOpenedUrl; + String? get imOpenedUrlTwo => _imOpenedUrlTwo; + num? get createTime => _createTime; + num? get updateTime => _updateTime; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['sysOrigin'] = _sysOrigin; + map['type'] = _type; + map['code'] = _code; + map['name'] = _name; + map['cover'] = _cover; + map['sourceUrl'] = _sourceUrl; + map['expand'] = _expand; + map['amount'] = _amount; + map['del'] = _del; + map['roomSendCoverUrl'] = _roomSendCoverUrl; + map['roomOpenedUrl'] = _roomOpenedUrl; + map['roomNotOpenedUrl'] = _roomNotOpenedUrl; + map['imSendCoverUrl'] = _imSendCoverUrl; + map['imOpenedUrl'] = _imOpenedUrl; + map['imNotOpenedUrl'] = _imNotOpenedUrl; + map['imOpenedUrlTwo'] = _imOpenedUrlTwo; + map['createTime'] = _createTime; + map['updateTime'] = _updateTime; + return map; + } + +} + +/// id : 0 +/// sysOrigin : "" +/// type : "" +/// code : "" +/// name : "" +/// cover : "" +/// sourceUrl : "" +/// expand : "" +/// amount : 0.0 +/// del : false +/// roomSendCoverUrl : "" +/// roomOpenedUrl : "" +/// roomNotOpenedUrl : "" +/// imSendCoverUrl : "" +/// imOpenedUrl : "" +/// imNotOpenedUrl : "" +/// imOpenedUrlTwo : "" +/// createTime : 0 +/// updateTime : 0 + +class AvatarFrame { + AvatarFrame({ + String? id, + String? sysOrigin, + String? type, + String? code, + String? name, + String? cover, + String? sourceUrl, + String? expand, + num? amount, + bool? del, + String? roomSendCoverUrl, + String? roomOpenedUrl, + String? roomNotOpenedUrl, + String? imSendCoverUrl, + String? imOpenedUrl, + String? imNotOpenedUrl, + String? imOpenedUrlTwo, + num? createTime, + num? updateTime,}){ + _id = id; + _sysOrigin = sysOrigin; + _type = type; + _code = code; + _name = name; + _cover = cover; + _sourceUrl = sourceUrl; + _expand = expand; + _amount = amount; + _del = del; + _roomSendCoverUrl = roomSendCoverUrl; + _roomOpenedUrl = roomOpenedUrl; + _roomNotOpenedUrl = roomNotOpenedUrl; + _imSendCoverUrl = imSendCoverUrl; + _imOpenedUrl = imOpenedUrl; + _imNotOpenedUrl = imNotOpenedUrl; + _imOpenedUrlTwo = imOpenedUrlTwo; + _createTime = createTime; + _updateTime = updateTime; +} + + AvatarFrame.fromJson(dynamic json) { + _id = json['id']; + _sysOrigin = json['sysOrigin']; + _type = json['type']; + _code = json['code']; + _name = json['name']; + _cover = json['cover']; + _sourceUrl = json['sourceUrl']; + _expand = json['expand']; + _amount = json['amount']; + _del = json['del']; + _roomSendCoverUrl = json['roomSendCoverUrl']; + _roomOpenedUrl = json['roomOpenedUrl']; + _roomNotOpenedUrl = json['roomNotOpenedUrl']; + _imSendCoverUrl = json['imSendCoverUrl']; + _imOpenedUrl = json['imOpenedUrl']; + _imNotOpenedUrl = json['imNotOpenedUrl']; + _imOpenedUrlTwo = json['imOpenedUrlTwo']; + _createTime = json['createTime']; + _updateTime = json['updateTime']; + } + String? _id; + String? _sysOrigin; + String? _type; + String? _code; + String? _name; + String? _cover; + String? _sourceUrl; + String? _expand; + num? _amount; + bool? _del; + String? _roomSendCoverUrl; + String? _roomOpenedUrl; + String? _roomNotOpenedUrl; + String? _imSendCoverUrl; + String? _imOpenedUrl; + String? _imNotOpenedUrl; + String? _imOpenedUrlTwo; + num? _createTime; + num? _updateTime; + String? get id => _id; + String? get sysOrigin => _sysOrigin; + String? get type => _type; + String? get code => _code; + String? get name => _name; + String? get cover => _cover; + String? get sourceUrl => _sourceUrl; + String? get expand => _expand; + num? get amount => _amount; + bool? get del => _del; + String? get roomSendCoverUrl => _roomSendCoverUrl; + String? get roomOpenedUrl => _roomOpenedUrl; + String? get roomNotOpenedUrl => _roomNotOpenedUrl; + String? get imSendCoverUrl => _imSendCoverUrl; + String? get imOpenedUrl => _imOpenedUrl; + String? get imNotOpenedUrl => _imNotOpenedUrl; + String? get imOpenedUrlTwo => _imOpenedUrlTwo; + num? get createTime => _createTime; + num? get updateTime => _updateTime; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['sysOrigin'] = _sysOrigin; + map['type'] = _type; + map['code'] = _code; + map['name'] = _name; + map['cover'] = _cover; + map['sourceUrl'] = _sourceUrl; + map['expand'] = _expand; + map['amount'] = _amount; + map['del'] = _del; + map['roomSendCoverUrl'] = _roomSendCoverUrl; + map['roomOpenedUrl'] = _roomOpenedUrl; + map['roomNotOpenedUrl'] = _roomNotOpenedUrl; + map['imSendCoverUrl'] = _imSendCoverUrl; + map['imOpenedUrl'] = _imOpenedUrl; + map['imNotOpenedUrl'] = _imNotOpenedUrl; + map['imOpenedUrlTwo'] = _imOpenedUrlTwo; + map['createTime'] = _createTime; + map['updateTime'] = _updateTime; + return map; + } + +} + +/// id : 0 +/// sysOrigin : "" +/// type : "" +/// code : "" +/// name : "" +/// cover : "" +/// sourceUrl : "" +/// expand : "" +/// amount : 0.0 +/// del : false +/// roomSendCoverUrl : "" +/// roomOpenedUrl : "" +/// roomNotOpenedUrl : "" +/// imSendCoverUrl : "" +/// imOpenedUrl : "" +/// imNotOpenedUrl : "" +/// imOpenedUrlTwo : "" +/// createTime : 0 +/// updateTime : 0 + +class PropsResources { + PropsResources({ + String? id, + String? sysOrigin, + String? type, + String? code, + String? name, + String? cover, + String? sourceUrl, + String? expand, + num? amount, + bool? del, + String? roomSendCoverUrl, + String? roomOpenedUrl, + String? roomNotOpenedUrl, + String? imSendCoverUrl, + String? imOpenedUrl, + String? imNotOpenedUrl, + String? imOpenedUrlTwo, + num? createTime, + num? updateTime,}){ + _id = id; + _sysOrigin = sysOrigin; + _type = type; + _code = code; + _name = name; + _cover = cover; + _sourceUrl = sourceUrl; + _expand = expand; + _amount = amount; + _del = del; + _roomSendCoverUrl = roomSendCoverUrl; + _roomOpenedUrl = roomOpenedUrl; + _roomNotOpenedUrl = roomNotOpenedUrl; + _imSendCoverUrl = imSendCoverUrl; + _imOpenedUrl = imOpenedUrl; + _imNotOpenedUrl = imNotOpenedUrl; + _imOpenedUrlTwo = imOpenedUrlTwo; + _createTime = createTime; + _updateTime = updateTime; +} + + PropsResources.fromJson(dynamic json) { + _id = json['id']; + _sysOrigin = json['sysOrigin']; + _type = json['type']; + _code = json['code']; + _name = json['name']; + _cover = json['cover']; + _sourceUrl = json['sourceUrl']; + _expand = json['expand']; + _amount = json['amount']; + _del = json['del']; + _roomSendCoverUrl = json['roomSendCoverUrl']; + _roomOpenedUrl = json['roomOpenedUrl']; + _roomNotOpenedUrl = json['roomNotOpenedUrl']; + _imSendCoverUrl = json['imSendCoverUrl']; + _imOpenedUrl = json['imOpenedUrl']; + _imNotOpenedUrl = json['imNotOpenedUrl']; + _imOpenedUrlTwo = json['imOpenedUrlTwo']; + _createTime = json['createTime']; + _updateTime = json['updateTime']; + } + String? _id; + String? _sysOrigin; + String? _type; + String? _code; + String? _name; + String? _cover; + String? _sourceUrl; + String? _expand; + num? _amount; + bool? _del; + String? _roomSendCoverUrl; + String? _roomOpenedUrl; + String? _roomNotOpenedUrl; + String? _imSendCoverUrl; + String? _imOpenedUrl; + String? _imNotOpenedUrl; + String? _imOpenedUrlTwo; + num? _createTime; + num? _updateTime; + String? get id => _id; + String? get sysOrigin => _sysOrigin; + String? get type => _type; + String? get code => _code; + String? get name => _name; + String? get cover => _cover; + String? get sourceUrl => _sourceUrl; + String? get expand => _expand; + num? get amount => _amount; + bool? get del => _del; + String? get roomSendCoverUrl => _roomSendCoverUrl; + String? get roomOpenedUrl => _roomOpenedUrl; + String? get roomNotOpenedUrl => _roomNotOpenedUrl; + String? get imSendCoverUrl => _imSendCoverUrl; + String? get imOpenedUrl => _imOpenedUrl; + String? get imNotOpenedUrl => _imNotOpenedUrl; + String? get imOpenedUrlTwo => _imOpenedUrlTwo; + num? get createTime => _createTime; + num? get updateTime => _updateTime; + + Map toJson() { + final map = {}; + map['id'] = _id; + map['sysOrigin'] = _sysOrigin; + map['type'] = _type; + map['code'] = _code; + map['name'] = _name; + map['cover'] = _cover; + map['sourceUrl'] = _sourceUrl; + map['expand'] = _expand; + map['amount'] = _amount; + map['del'] = _del; + map['roomSendCoverUrl'] = _roomSendCoverUrl; + map['roomOpenedUrl'] = _roomOpenedUrl; + map['roomNotOpenedUrl'] = _roomNotOpenedUrl; + map['imSendCoverUrl'] = _imSendCoverUrl; + map['imOpenedUrl'] = _imOpenedUrl; + map['imNotOpenedUrl'] = _imNotOpenedUrl; + map['imOpenedUrlTwo'] = _imOpenedUrlTwo; + map['createTime'] = _createTime; + map['updateTime'] = _updateTime; + return map; + } + +} + +/// ATVIPType : "" +/// vipLevel : 0 +/// adminNumber : 0 +/// loginRewardsAmount : 0.0 +/// roomMaxMember : 0 +/// avatarFrameId : 0 +/// carId : 0 + +class NobleVipAbility { + NobleVipAbility({ + String? ATVIPType, + num? vipLevel, + num? adminNumber, + num? loginRewardsAmount, + num? roomMaxMember, + num? avatarFrameId, + num? carId,}){ + _ATVIPType = ATVIPType; + _vipLevel = vipLevel; + _adminNumber = adminNumber; + _loginRewardsAmount = loginRewardsAmount; + _roomMaxMember = roomMaxMember; + _avatarFrameId = avatarFrameId; + _carId = carId; +} + + NobleVipAbility.fromJson(dynamic json) { + _ATVIPType = json['ATVIPType']; + _vipLevel = json['vipLevel']; + _adminNumber = json['adminNumber']; + _loginRewardsAmount = json['loginRewardsAmount']; + _roomMaxMember = json['roomMaxMember']; + _avatarFrameId = json['avatarFrameId']; + _carId = json['carId']; + } + String? _ATVIPType; + num? _vipLevel; + num? _adminNumber; + num? _loginRewardsAmount; + num? _roomMaxMember; + num? _avatarFrameId; + num? _carId; + String? get ATVIPType => _ATVIPType; + num? get vipLevel => _vipLevel; + num? get adminNumber => _adminNumber; + num? get loginRewardsAmount => _loginRewardsAmount; + num? get roomMaxMember => _roomMaxMember; + num? get avatarFrameId => _avatarFrameId; + num? get carId => _carId; + + Map toJson() { + final map = {}; + map['ATVIPType'] = _ATVIPType; + map['vipLevel'] = _vipLevel; + map['adminNumber'] = _adminNumber; + map['loginRewardsAmount'] = _loginRewardsAmount; + map['roomMaxMember'] = _roomMaxMember; + map['avatarFrameId'] = _avatarFrameId; + map['carId'] = _carId; + return map; + } + +} \ No newline at end of file diff --git a/lib/chatvibe_domain/repositories/config_repository.dart b/lib/chatvibe_domain/repositories/config_repository.dart new file mode 100644 index 0000000..ad55274 --- /dev/null +++ b/lib/chatvibe_domain/repositories/config_repository.dart @@ -0,0 +1,91 @@ +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +import 'package:aslan/chatvibe_domain/models/res/country_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_game_ranking_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_get_list_game_config_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_google_pay_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_index_banner_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_level_config_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_product_config_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_start_page_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_top_four_with_reward_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/version_manage_lates_review_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_version_manage_latest_res.dart'; + +abstract class ChatVibeConfigRepository { + ///国家列表 + Future loadCountry(); + + ///推荐国家列表 + Future loadTopSix(); + + ///app启动页. + Future> getStartPage(); + + ///系统参数配置列表. + Future getConfig(); + + ///最近活动通知列表. + Future getNoticeMessage(); + + ///房间内-快捷入口-游戏. + Future getShortcutGame(); + + ///房间内-游戏列表配置. + Future> getListGameConfig({ + String? roomId, + String? category, + }); + + ///房间内-sud游戏. + Future getSudCode(); + + ///匹配-游戏列表配置. + Future getGameConfig(); + + ///获得客服. + Future getCustomerService(); + + ///获取平台banner. + Future> getBanner({List?types}); + + ///获取商品配置 + Future> productConfig(); + + ///谷歌内购 + Future googlePay( + String product, + String signature, + String purchaseData, { + String? friendId, + }); + + ///苹果内购 + Future applePay( + String product, + String receipt, + String transaction, { + String? friendId, + }); + + ///等级资源 + Future configLevel(); + + ///热门游戏排行榜 + Future gameRanking(int current,String periodType, {int? size = 20}); + + ///查询游戏排行榜前四名 + Future> topFourWithReward(); + + ///获取APP最新版本 + Future versionManageLatest(); + + ///获取最新及正在审核版本 + Future versionManageLatestReview(); + + ///获得客服 + Future customerService(); + + ///查询KING_GAMES_DAILY日榜前三名 + Future> kingGamesDailyTopThree(); +} diff --git a/lib/chatvibe_domain/repositories/dynamic_repository.dart b/lib/chatvibe_domain/repositories/dynamic_repository.dart new file mode 100644 index 0000000..6334ef2 --- /dev/null +++ b/lib/chatvibe_domain/repositories/dynamic_repository.dart @@ -0,0 +1,106 @@ +import 'package:aslan/chatvibe_domain/models/req/at_dynamic_create_pic_cmd.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_comment_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/create_dynamic_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/dynamic_list_res.dart'; + +abstract class ChatVibeDynamicRepository { + ///创建动态 + Future create({ + String? content, + List? pics, + }); + + ///最新动态列表 + Future dynamicListLatest({int? current, int? size}); + + ///我的动态列表 + Future dynamicMyPage( + String userId, { + int? current, + int? size, + }); + + ///点赞动态 + Future dynamicLike(String dynamicId, bool like); + + ///点赞评论 + Future likeComment( + String dynamicId, + String commentId, + num rootCommentId, + String toUserId, + bool like, + ); + + ///动态详情 + Future dynamicDetails(String id); + + ///主动态评论列表 + Future dynamicListComment( + String dynamicId, + int pageNumber, + int limit, + ); + + ///动态子评论列表 + Future dynamicListCommentChildren( + num rootCommentId, + int pageNumber, + ); + + ///评论动态或回复用户 + Future dynamicComment( + String content, + String id, { + String? toUserId, + num? rootCommentId, + String? commentId, + }); + + ///删除动态 + Future dynamicDelete(String id); + + ///删除评论 + Future deleteComment(String id); + + ///动态关注列表 + Future dynamicListFollow({int? current, int? size}); + + ///动态点赞列表 + Future dynamicListLike( + String dynamicId, { + int? current, + int? size, + }); + + ///动态礼物列表 + Future dynamicListGift( + String dynamicId, { + int? current, + int? size, + }); + + ///举报动态 + Future dynamicReport( + String dynamicContentId, + num reportType, { + String? reportedContent, + String? imageUrls, + }); + + ///校验发送动态权限 + Future dynamicCheckSendPermission({String? userId}); + + ///添加黑名单 + Future dynamicAdminBlacklistAdd(String userId); + + ///删除黑名单 + Future dynamicAdminBlacklistDelete(String userId); + + ///我的消息列表 + Future dynamicListMessage( + List dynamicMessageEnums, { + int? current, + int? size, + }); +} diff --git a/lib/chatvibe_domain/repositories/family_repository.dart b/lib/chatvibe_domain/repositories/family_repository.dart new file mode 100644 index 0000000..76b3d8a --- /dev/null +++ b/lib/chatvibe_domain/repositories/family_repository.dart @@ -0,0 +1,102 @@ +import 'package:aslan/chatvibe_domain/models/res/at_famaily_leader_board_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/family_base_info_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_family_home_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_family_level_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_family_list_member_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_family_list_message_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/family_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_family_my_apply_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_family_news_list_res.dart'; + +abstract class ChatVibeFamilyRepository { + ///创建家族 + Future createFamily( + String familyAvatar, + String familyName, + String familyIntro, + ); + + ///获取加载列表 + Future familyList({ + String? familyAccount, + int? current, + int? size, + }); + + ///根据家族id获取家族详情 + Future familyBaseInfo({String? familyId}); + + ///根据用户id获取家族详情 + Future familyUserBaseInfo(String userId); + + ///我的家族申请列表. + Future> familyMyApplyList({num? lastId}); + + ///取消家族申请. + Future familyCancelApply({num? msgId}); + + ///族长审核列表 + Future> familyListMessage({String? lastId}); + + ///家族成员列表 + Future familyListMember({ + String? familyId, + String? account, + int? pageNo, + int? pageSize, + }); + + ///家族成员Top10排行榜 + Future familyMemberTop10(String familyId); + + ///家族成员Top100总排行榜 + Future familyMemberTop100(String familyId); + + ///编辑家族信息. + Future familyEdit( + String familyAvatar, + String familyName, + String familyIntro, + String familyNotice, + ); + + ///投入宝箱金币. + Future familyBoxContribute(String familyId); + + ///家族消息处理. + Future familyMessageHandle(String familyMessageId, String event); + + ///申请加入家族. + Future familyApplyJoin(String familyId); + + ///用户主动退出家族. + Future familyUserExit(); + + ///授权用户家族权限. + Future familyGrantUserRole(String authorizedMemberId, String roleKey); + + ///家族踢人 + Future familyRemoveUser(String removeMemberId); + + ///领取宝箱奖励. + Future familyBoxClaim(num level); + + ///重新申请 + Future familyReapply(String msgId); + + ///解散家族 + Future familyDisband(); + + ///家族等级配置 + Future> familyLevelConfig(); + + ///首页家族列表. + Future familyHomeList({int? pageNo, int? pageSize}); + + ///家族动态列表 + Future familyNewsList( + String familyId, { + int? pageNo, + int? pageSize, + }); +} diff --git a/lib/chatvibe_domain/repositories/general_repository.dart b/lib/chatvibe_domain/repositories/general_repository.dart new file mode 100644 index 0000000..2db5890 --- /dev/null +++ b/lib/chatvibe_domain/repositories/general_repository.dart @@ -0,0 +1,7 @@ + +import 'dart:io'; + +abstract class ChatVibeGeneralRepository { + ///上传文件 + Future upload(File image); +} \ No newline at end of file diff --git a/lib/chatvibe_domain/repositories/gift_repository.dart b/lib/chatvibe_domain/repositories/gift_repository.dart new file mode 100644 index 0000000..5be6e09 --- /dev/null +++ b/lib/chatvibe_domain/repositories/gift_repository.dart @@ -0,0 +1,6 @@ +import 'package:aslan/chatvibe_domain/models/res/gift_res.dart'; + +abstract class ChatVibeGiftRepository { + ///礼物墙 + Future> giftWall(String userId); +} diff --git a/lib/chatvibe_domain/repositories/room_repository.dart b/lib/chatvibe_domain/repositories/room_repository.dart new file mode 100644 index 0000000..11b616d --- /dev/null +++ b/lib/chatvibe_domain/repositories/room_repository.dart @@ -0,0 +1,283 @@ +import 'package:flutter/cupertino.dart'; + +import 'package:aslan/chatvibe_domain/models/req/at_give_away_gift_room_acceptscmd.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_banner_leaderboard_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/game_ludo_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_get_list_game_config_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/gift_backpack_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/gift_by_group_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/gift_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_is_follow_room_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_mic_go_up_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/mic_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/my_room_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_contribute_level_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_emoji_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_gift_rank_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_join_black_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_member_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_red_packet_detail_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_red_packet_grab_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_red_packet_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_reward_info_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_rocket_config_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_rocket_status_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_task_claimable_count_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_task_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_user_card_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_top_four_with_reward_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/user_count_guard_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_violation_handle_res.dart'; + +import '../models/res/country_res.dart'; + +abstract class ChatVibeRoomRepository { + Future> discovery({bool? allRegion}); + + ///房间麦位列表 + Future> micList(String roomId); + + ///获取房间信息 + Future specific(String roomId); + + ///是否关注了房间 + Future isFollowRoom(String roomId); + + ///房间贡献等级 + Future contributeLevel(String roomId); + + ///房间本周贡献值 + Future roomContributionActivity(String roomId); + + ///上麦 + Future micGoUp( + String roomId, + num mickIndex, { + String? eventType, + }); + + ///下麦 + Future micGoDown(String roomId, num mickIndex); + + ///这个用户是否可以被踢下麦 + Future kickOffMicrophone(String roomId, String kickUserId); + + ///锁麦 + Future micLock(String roomId, num mickIndex, bool lock); + + ///静音麦克风 + Future micMute(String roomId, num mickIndex, bool mute); + + ///T麦克风(房主&管理员) + Future micKill(String roomId, num mickIndex); + + ///房间在线用户 + Future> roomOnlineUsers(String roomId); + + ///房间用户资料卡 + Future roomUserCard(String roomId, String userId); + + ///礼物列表 + Future> giftList(); + + Future> giftListByGroup(); + + ///赠送礼物 + Future giveGift( + List acceptUserIds, + String giftId, + num quantity, + bool checkCombo, { + String? roomId, + ATGiveAwayGiftRoomAcceptsCmd? accepts, + String? dynamicContentId, + }); + + ///举报 + Future reported( + String reportUserId, + String reportedUserId, + num reportType, { + String? reportedContent, + num? relatedId, + String? imageUrls, + String? videoUrls, + }); + + ///房间搜索 + Future> searchRoom(String roomAccount); + + ///房间用户加入黑名单<踢出房间> + Future joinBlacklist( + String roomId, + String userId, + num outTime, + ); + + ///解除黑名单 + Future removeBlacklist(String roomId, String userId); + + ///守护列表 + Future> userCountGuard(String userId); + + ///麦位表情 + Future> emojiAll(); + + ///设置房间用户身份 + Future changeRoomRole( + String roomId, + String userType, + String changeUserId, + String operatorUserId, + String reason, + ); + + ///房间上锁 + Future roomLocked(String roomId, String password); + + ///房间解锁 + Future roomUnlocked(String roomId); + + ///更改房间设置信息 + Future updateRoomSetting( + String roomId, + String roomAcount, + BuildContext context, { + bool? touristMike, + String? takeMicRole, + bool? touristMsg, + String? joinGolds, + int? mikeSize, + bool? allowMusic, + bool? adminLockSeat, + bool? showHeartbeat, + bool? openKtvMode, + String? roomSpecialMikeType, + }); + + ///房间成员列表 + Future> roomMember( + String roomId, { + String? lastId, + }); + + ///房间贡献榜单 + Future> roomContributionRank( + String roomId, + String dataType, { + num size, + }); + + ///赠送幸运礼物 + Future giveLuckyGift( + List acceptUserIds, + String giftId, + num quantity, + bool checkCombo, { + String? roomId, + ATGiveAwayGiftRoomAcceptsCmd? accepts, + }); + + ///查询榜单前三名 + Future appLeaderboard(); + + ///礼物背包 + Future> giftBackpack(); + + ///查询房间火箭状态 + Future rocketStatus(String roomId); + + ///查询火箭配置 + Future> rocketConfigEnabled(); + + ///领取奖励 + Future rocketClaim(String roomId); + + ///处理房间违规 + Future roomViolationHandle( + String roomId, + int violationType, + int operationType, + String description, { + List? imageUrls, + }); + + ///查询房间红包列表 + Future> roomRedPacketList( + String roomId, + int current, { + int size = 20, + }); + + ///发送红包 + Future roomSendRedPacket( + String roomId, + String totalAmount, + String totalCount, + String expireMinutes, + ); + + ///抢红包 + Future roomRedPacketGrab(String packetId); + + ///查询红包详情 + Future roomRedPacketDetail(String packetId); + + ///查询房间奖励信息 + Future roomRewardInfo(String roomId); + + ///领取房间奖励房主收入 + Future roomRewardReceive(String roomId); + + ///发送平台红包 + Future sendPlatformRedPack( + String roomId, + String totalAmount, + String totalCount, + String expireMinutes, + ); + + ///获取活动礼物列表 + Future> giftActivityList(); + + ///获取任务列表 + Future roomTaskList(int taskCategory); + + ///领取任务奖励 + Future roomTaskClaim(String taskCode); + + ///获取当前可领取奖励数量 + Future roomTaskClaimableCount(); + + ///获取房间运行ludo游戏 + Future gameLudo(String roomId); + + ///创建游戏 + Future gameLudoCreate( + String roomId, + num amount, + String localSeat, + String homeownerUserId, + String gameId, + ); + + ///开始游戏 + Future gameLudoStart(String id, String homeownerUserId); + + ///主动解散游戏 + Future gameLudoDisband(String id); + + ///记录游玩历史 + Future ludoHistoryRecord(String gameListConfigId, String scene); + + ///查询最近游玩历史(最多5条) + Future> gameLudoHistoryRecent(String scene); + + ///explore 列表. + Future> roomLiveVoiceExplore({String? countryCode}); + + ///房间国家列表. + Future> roomLiveVoiceCountry(); +} diff --git a/lib/chatvibe_domain/repositories/store_repository.dart b/lib/chatvibe_domain/repositories/store_repository.dart new file mode 100644 index 0000000..ad83b2d --- /dev/null +++ b/lib/chatvibe_domain/repositories/store_repository.dart @@ -0,0 +1,39 @@ +import 'package:aslan/chatvibe_domain/models/res/bags_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_theme_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/store_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/vip_list_res.dart'; + +abstract class ChatVibeStoreRepository { + ///获取商店道具 + Future> storeList(String ATCurrencyType, String ATPropsType); + + ///道具商店购买 + Future storePurchasing( + String propsId, + String type, + String ATCurrencyType, + String days, { + String? acceptUserId, + }); + + ///用户道具背包 + Future> storeBackpack(String userId, String type); + + ///佩戴道具 + Future switchPropsUse(String type, String propsId, bool unload); + + ///切换主题 + Future themeSwitchUse(String themeId, bool unload); + + ///获取最新的自定义主题 + Future themeCustomizeLatest(); + + ///我的主题背包 + Future> roomThemeBackpack(); + + ///上传自定义主题 + Future roomThemeCustomize(String themeUrl); + + ///贵族列表 + Future> nobleVipList(String ATCurrencyType); +} diff --git a/lib/chatvibe_domain/repositories/user_repository.dart b/lib/chatvibe_domain/repositories/user_repository.dart new file mode 100644 index 0000000..cb31ad7 --- /dev/null +++ b/lib/chatvibe_domain/repositories/user_repository.dart @@ -0,0 +1,346 @@ +import 'package:aslan/chatvibe_domain/models/res/join_room_res.dart' hide WearBadge; + +import 'package:aslan/chatvibe_domain/models/req/at_mobile_auth_cmd.dart'; +import 'package:aslan/chatvibe_domain/models/res/badge_backpack_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_edit_room_info_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/follow_room_res.dart' hide WearBadge; +import 'package:aslan/chatvibe_domain/models/res/follow_user_res.dart' hide WearBadge; +import 'package:aslan/chatvibe_domain/models/res/get_cp_relationship_pair_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_gold_record_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_material_badge_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/message_friend_user_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/my_room_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_prop_coupon_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_prop_coupon_record_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_public_message_page_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_black_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_res.dart' hide WearBadge; +import 'package:aslan/chatvibe_domain/models/req/at_user_profile_cmd.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_rtc_token_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_sign_in_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/task_checkIn_latest_vip_record_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_task_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_user_counter_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_user_identity_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_user_level_exp_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_user_red_packet_grab_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_user_red_packet_send_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_user_vip_ability_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_violation_handle_res.dart'; +import '../models/res/at_user_freight_balance_page_res.dart'; +import '../models/res/at_web_pay_commodity_res.dart'; +import '../models/res/at_web_pay_recharge_res.dart'; +import '../models/res/at_web_pay_user_profile_res.dart'; + +abstract class ChatVibeUserRepository { + Future getUser(String id); + + ///账号登录 + Future loginForAccount(String account, String pwd); + + ///渠道登录谷歌 苹果 + Future loginForChannel(String authType, String openId); + + ///注册 + Future regist( + String type, + String openId, + ATUserProfileCmd userProfileCmd, { + ATMobileAuthCmd? mobileAuthCmd, + String invitePeople, + }); + + ///关注的房间 + Future> followRoomList({String? lastId}); + + ///历史记录 + Future> trace({String? lastId}); + + ///加入的房间 + Future> joined(); + + ///我的房间 + Future myProfile(); + + ///创建房间 + Future createRoom(); + + ///加入房间 + Future entryRoom(String roomId, {String? pwd}); + + ///发送心跳 + Future heartbeat(String status, bool upMick, {String? roomId}); + + ///上麦心跳 + Future anchorHeartbeat(String roomId); + + ///声网token + Future getRtcToken(String channel, String userId); + + ///退出房间 + Future quitRoom(String roomId); + + ///修改房间信息 + Future editRoomInfo( + String roomId, + String roomCover, + String roomName, + String roomDesc, + String event, + ); + + ///获取指定用户信息 + Future loadUserInfo(String userId); + + ///关注房间 + Future followRoom(String roomId); + + ///关注用户 + Future followUser(String userId); + + ///获取我的金币余额 + Future balance(); + + ///搜索用户 + Future searchUser(String account); + + ///设置密码 + Future pwdReset(String pwd); + + ///账号是否绑定 + Future accountIsBind(); + + ///绑定账号 + Future bind(String pwd); + + ///修改密码 + Future updatePwd(String pwd, String oldPwd); + + ///房间用户黑名单 + Future> roomBlacklist(String roomId, String lastId); + + ///修改用户信息 + Future updateUserInfo({ + String? userAvatar, + String? userNickname, + num? userSex, + num? age, + num? bornYear, + num? bornMonth, + num? bornDay, + String? countryId, + String? hobby, + String? autograph, + List? backgroundPhotos, + List? personalPhotos, + }); + + ///关注列表 + Future> followMyList({String? account, String? lastId}); + + ///粉丝列表 + Future> fansMyList({String? account, String? lastId}); + + ///金币记录 + Future> goldRecord({num? type, String? lastId}); + + ///获取身份 + Future userIdentity({String? userId}); + + ///获取用户关系计数 SUBSCRIPTION:订阅、FANS:粉丝、FRIEND:朋友,访客. + Future> userCounter(String userId); + + ///徽章列表 + Future> materialBadge({String? type}); + + ///用户徽章背包 + Future> badgeBackpack(String userId); + + ///用户等级 + Future userLevelConsumptionExp(String userId, String type); + + ///是否已经签到 + Future checkInToday(); + + ///签到列表 + Future sginListAward(); + + ///签到领奖 + Future checkInReceive(String id, String resourceGroupId); + + ///添加代理 + Future teamCreate(String ownUserId); + + ///分页查询公共消息列表. + Future publicMessagePage( + String type, + num pageNo, + num pageSize, + ); + + ///好友列表 + Future> friendList({String? lastId}); + + ///好友搜索 + Future> friendSearch(String account); + + ///代理邀请成为主播-处理 + Future inviteHost(String id, String status); + + ///代理邀请成为Agent-处理 + Future inviteAgent(String id, String status); + + ///代理邀请成为BD-处理 + Future inviteBD(String id, String status); + + ///代理邀请成为BDLeader-处理 + Future inviteBDLeader(String id, String status); + + ///处理邀请-邀请充值代理. + Future inviteRechargeAgent(String id, String status); + + ///检测我是否有关注指定用户. + Future followCheck(String userId); + + ///系统消息专用关注接口 + Future followNew(String userId); + + ///任务列表 + Future> tasks(); + + ///领取奖励 + Future taskReward(num taskId); + + ///我的访客列表 + Future> visitorList(num pageNumber); + + ///查询我的道具券列表 + Future propCouponList( + num size, + num current, { + int? couponType, + }); + + ///查询道具券使用记录 + Future couponRecordList( + num size, + num current, { + int? couponType, + int? status, + }); + + ///使用道具券 + Future couponUse(String couponNo); + + ///赠送道具券 + Future couponSend(String couponNo, String receiverId); + + ///拥有的徽章徽章. + Future> badgeOwnList(String type); + + ///切换单个徽章佩戴状态. + Future badgeToggle(String badgeId); + + ///处理用户违规 + Future userViolationHandle( + String userId, + int violationType, + int operationType, + String description, { + List? imageUrls, + }); + + ///获取我的-cp对象. + Future> getCpRelationshipPairList(); + + ///CP申请. + Future cpRelationshipSendApply(String acceptApplyUserId); + + ///处理CP申请记录. + Future cpRelationshipProcessApply(String applyId, bool agree); + + ///解除cp关系. + Future cpRelationshipDismissApply(String cpUserId); + + ///验证是否是好友 + Future friendRelationCheck(String userId); + + ///发送用户红包 + Future userRedPacketSend( + String receiverUserId, + String totalAmount, + ); + + ///查询红包详情 + Future userRedPacketDetail(String packetId); + + ///领取用户红包 + Future userRedPacketGrab(String packetId); + + ///查询黑名单列表. + Future> userBlacklistList({ + String? account, + String? lastId, + }); + + ///检测是否在黑名单. + Future blacklistCheck(String shieldUserId); + + ///添加黑名单 + Future addUserBlacklist(String shieldUserId); + + ///删除黑名单 + Future deleteUserBlacklist(String shieldUserId); + + ///注销账号 + Future logoutAccount(); + + ///最新的vip金币赠送记录 + Future> taskCheckInLatestVipRecord(); + + ///获取用户 VIP 设置 + Future userVipAbility(); + + ///更新用户 VIP 设置 + Future userVipAbilityUpdate({ + String? contactMode, + bool? kickPrevention, + bool? mysteriousInvisibility, + bool? antiBlock, + }); + + ///代理邀请成为主播 + Future teamInviteHost(String inviteUserId); + + ///支付 用户基本资料信息. + Future webPayUserProfile({ + String? account, + String? userId, + String? type, + }); + + ///支付 开放支付国家. + Future webPayCommodity({ + String? payCountryId, + String? type, + String? regionId, + }); + + ///支付 下单. + Future webPayRecharge( + String goodsId, + String payCountryId, + String userId, + String channelCode, { + bool? newVersion, + String? requestIp, + }); + + ///货运代理分页列表 + Future userFreightBalancePage({ + int? cursor = 1, + int? limit = 20, + }); + +} diff --git a/lib/chatvibe_domain/usecases/at_accurate_length_limiting_textInput_formatter.dart b/lib/chatvibe_domain/usecases/at_accurate_length_limiting_textInput_formatter.dart new file mode 100644 index 0000000..1911177 --- /dev/null +++ b/lib/chatvibe_domain/usecases/at_accurate_length_limiting_textInput_formatter.dart @@ -0,0 +1,34 @@ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; + +class ATAccurateLengthLimitingTextInputFormatter extends TextInputFormatter { + ATAccurateLengthLimitingTextInputFormatter(this.maxLength); + + final int maxLength; + + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, + TextEditingValue newValue, + ) { + final newText = newValue.text; + final newTextCharacters = newText.characters; + + if (newTextCharacters.length > maxLength) { + // 如果新文本超过最大长度,尝试截断 + if (oldValue.text.characters.length == maxLength) { + return oldValue; // 已经达到最大长度,不允许再输入 + } + + // 截取前 maxLength 个字符 + final truncated = newTextCharacters.take(maxLength); + return TextEditingValue( + text: truncated.toString(), + selection: TextSelection.collapsed(offset: truncated.length), + ); + } + + return newValue; + } +} \ No newline at end of file diff --git a/lib/chatvibe_domain/usecases/at_case.dart b/lib/chatvibe_domain/usecases/at_case.dart new file mode 100644 index 0000000..7a3c0ba --- /dev/null +++ b/lib/chatvibe_domain/usecases/at_case.dart @@ -0,0 +1,95 @@ +import 'dart:ui'; + +import 'package:extended_text/extended_text.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/utilities/at_string_utils.dart'; + +class AtTextSpanBuilder extends SpecialTextSpanBuilder { + final Function(String userId)? onTapCall; + + AtTextSpanBuilder({this.onTapCall}); + + @override + SpecialText? createSpecialText( + String flag, { + TextStyle? textStyle, + SpecialTextGestureTapCallback? onTap, + required int index, + }) { + if (isStart(flag, AtText.flag)) { + return AtText( + textStyle: textStyle!, + start: index - (AtText.flag.length - 1), + onTap: (userId) { + onTapCall?.call(userId); + }, + ); + } + return null; + } +} + +class AtText extends SpecialText { + static const String flag = '用户名< + final RegExp exp = RegExp(r'>([^<]+)<'); + final RegExpMatch? match = exp.firstMatch(atContent); + return match?.group(1)?.trim() ?? ''; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is AtText && + other.start == start && + other.toString() == toString(); + } + + @override + int get hashCode => start.hashCode ^ toString().hashCode; +} diff --git a/lib/chatvibe_domain/usecases/at_custom_filtering_textinput_formatter.dart b/lib/chatvibe_domain/usecases/at_custom_filtering_textinput_formatter.dart new file mode 100644 index 0000000..9e9a791 --- /dev/null +++ b/lib/chatvibe_domain/usecases/at_custom_filtering_textinput_formatter.dart @@ -0,0 +1,23 @@ +import 'package:flutter/services.dart'; + +class ATCustomFilteringTextInputFormatter extends TextInputFormatter { + // 定义您想要拒绝的字符正则表达式 + // 这个例子拒绝了空格、@、#、¥、* 以及您添加的其他特殊字符 + static final RegExp _deniedRegExp = RegExp(r'[\s@#¥*$%^&+=<>]'); + + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, + TextEditingValue newValue, + ) { + // 如果新值处于输入法组合状态,则直接返回,不进行过滤:cite[8] + if (newValue.isComposingRangeValid) { + return newValue; + } + // 如果新值包含被拒绝的字符,则返回旧值 + if (_deniedRegExp.hasMatch(newValue.text)) { + return oldValue; + } + return newValue; + } +} \ No newline at end of file diff --git a/lib/chatvibe_domain/usecases/at_custom_filtering_textinput_formatter2.dart b/lib/chatvibe_domain/usecases/at_custom_filtering_textinput_formatter2.dart new file mode 100644 index 0000000..e5a1d1f --- /dev/null +++ b/lib/chatvibe_domain/usecases/at_custom_filtering_textinput_formatter2.dart @@ -0,0 +1,23 @@ +import 'package:flutter/services.dart'; + +class ATCustomFilteringTextInputFormatter2 extends TextInputFormatter { + // 定义您想要拒绝的字符正则表达式 + // 这个例子拒绝了空格、@、#、¥、* 以及您添加的其他特殊字符 + static final RegExp _deniedRegExp = RegExp(r'[@#¥*$%^&+=<>]'); + + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, + TextEditingValue newValue, + ) { + // 如果新值处于输入法组合状态,则直接返回,不进行过滤:cite[8] + if (newValue.isComposingRangeValid) { + return newValue; + } + // 如果新值包含被拒绝的字符,则返回旧值 + if (_deniedRegExp.hasMatch(newValue.text)) { + return oldValue; + } + return newValue; + } +} \ No newline at end of file diff --git a/lib/chatvibe_domain/usecases/at_family_rank_tab_selector.dart b/lib/chatvibe_domain/usecases/at_family_rank_tab_selector.dart new file mode 100644 index 0000000..6b145e6 --- /dev/null +++ b/lib/chatvibe_domain/usecases/at_family_rank_tab_selector.dart @@ -0,0 +1,88 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class ATFamilyRankTabSelector extends StatefulWidget { + final List options; + final int initialIndex; + final ValueChanged onChanged; + + const ATFamilyRankTabSelector({ + Key? key, + required this.options, + required this.initialIndex, + required this.onChanged, + }) : super(key: key); + + @override + _FamilyRankTabSelectorState createState() => _FamilyRankTabSelectorState(); +} + +class _FamilyRankTabSelectorState extends State { + late int _selectedIndex; + + @override + void initState() { + super.initState(); + _selectedIndex = widget.initialIndex; + } + + @override + Widget build(BuildContext context) { + return Container( + height: 45.w, + padding: EdgeInsets.symmetric(horizontal: 12.w), + alignment: Alignment.center, + decoration: BoxDecoration( + color: Colors.white24, + borderRadius: BorderRadius.circular(30.w), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: + widget.options.asMap().entries.map((entry) { + final index = entry.key; + final title = entry.value; + final isSelected = index == _selectedIndex; + + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + setState(() { + _selectedIndex = index; + }); + widget.onChanged(index); + }, + child: Container( + height: 32.w, + padding: EdgeInsets.symmetric(horizontal: 10.w), + decoration: + isSelected + ? BoxDecoration( + color: Colors.black87, + borderRadius: BorderRadius.circular(30.w), + ) + : null, + child: Container( + alignment: Alignment.center, + child: Text( + title, + textAlign: TextAlign.center, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: Colors.white, + fontSize: isSelected ? 14.sp : 12.sp, + fontFamily: 'MyCustomFont', + fontWeight: + isSelected ? FontWeight.w600 : FontWeight.w500, + ), + ), + ), + ), + ); + }).toList(), + ), + ); + } +} diff --git a/lib/chatvibe_domain/usecases/at_fixed_width_tabIndicator.dart b/lib/chatvibe_domain/usecases/at_fixed_width_tabIndicator.dart new file mode 100644 index 0000000..2593a15 --- /dev/null +++ b/lib/chatvibe_domain/usecases/at_fixed_width_tabIndicator.dart @@ -0,0 +1,84 @@ +// 自定义固定宽度指示器(带圆角),支持渐变色 +import 'package:flutter/cupertino.dart'; + +class ATFixedWidthTabIndicator extends Decoration { + final double width; + final Color? color; // 改为可选参数 + final Gradient? gradient; // 新增渐变参数 + final double height; + final double borderRadius; + + const ATFixedWidthTabIndicator({ + required this.width, + this.color, // 颜色和渐变至少提供一个 + this.gradient, + this.height = 3.0, + this.borderRadius = 2.0, + }) : assert(color != null || gradient != null, + '必须提供color或gradient参数'); + + @override + BoxPainter createBoxPainter([VoidCallback? onChanged]) { + return _FixedWidthTabIndicatorPainter( + width: width, + color: color, + gradient: gradient, + height: height, + borderRadius: borderRadius, + onChange: onChanged, + ); + } +} + +class _FixedWidthTabIndicatorPainter extends BoxPainter { + final double width; + final Color? color; + final Gradient? gradient; + final double height; + final double borderRadius; + + _FixedWidthTabIndicatorPainter({ + required this.width, + required this.color, + required this.gradient, + required this.height, + required this.borderRadius, + VoidCallback? onChange, + }) : assert(color != null || gradient != null), + super(onChange); + + @override + void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { + final Paint paint = Paint()..style = PaintingStyle.fill; + + // 设置颜色或渐变 + if (gradient != null) { + final Rect rect = offset & configuration.size!; + final double left = rect.left + (rect.width - width) / 2; + final double top = rect.bottom - height; + final Rect indicatorRect = Rect.fromLTWH(left, top, width, height); + + // 创建渐变着色器 + paint.shader = gradient!.createShader(indicatorRect); + } else { + paint.color = color!; + } + + final Rect rect = offset & configuration.size!; + + // 计算固定宽度的指示器位置(居中) + final double left = rect.left + (rect.width - width) / 2; + final double top = rect.bottom - height; + + final Rect indicatorRect = Rect.fromLTWH(left, top, width, height); + + // 创建圆角矩形 + final RRect roundedRect = RRect.fromRectAndRadius( + indicatorRect, + Radius.circular(borderRadius), + ); + + // 绘制圆角矩形 + canvas.drawRRect(roundedRect, paint); + } +} \ No newline at end of file diff --git a/lib/chatvibe_domain/usecases/at_hollow_circle.dart b/lib/chatvibe_domain/usecases/at_hollow_circle.dart new file mode 100644 index 0000000..1b3e9d8 --- /dev/null +++ b/lib/chatvibe_domain/usecases/at_hollow_circle.dart @@ -0,0 +1,57 @@ +import 'dart:ui'; + +import 'package:flutter/cupertino.dart'; + +class ATHollowCircle extends StatelessWidget { + final double size; + final double strokeWidth; + final Gradient gradient; + + const ATHollowCircle({ + super.key, + required this.size, + this.strokeWidth = 3.0, + required this.gradient, + }); + + @override + Widget build(BuildContext context) { + return CustomPaint( + size: Size(size, size), + painter: _HollowCirclePainter( + strokeWidth: strokeWidth, + gradient: gradient, + ), + ); + } +} + +class _HollowCirclePainter extends CustomPainter { + final double strokeWidth; + final Gradient gradient; + + _HollowCirclePainter({ + required this.strokeWidth, + required this.gradient, + }); + + @override + void paint(Canvas canvas, Size size) { + final center = Offset(size.width / 2, size.height / 2); + final radius = size.width / 2 - strokeWidth / 2; + + final paint = Paint() + ..shader = gradient.createShader(Rect.fromCircle( + center: center, + radius: radius, + )) + ..strokeWidth = strokeWidth + ..style = PaintingStyle.stroke + ..strokeCap = StrokeCap.round; + + canvas.drawCircle(center, radius, paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} \ No newline at end of file diff --git a/lib/chatvibe_domain/usecases/at_partially_bold_marquee.dart b/lib/chatvibe_domain/usecases/at_partially_bold_marquee.dart new file mode 100644 index 0000000..1e85121 --- /dev/null +++ b/lib/chatvibe_domain/usecases/at_partially_bold_marquee.dart @@ -0,0 +1,102 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class ATPartiallyBoldMarquee extends StatefulWidget { + String boldText1 = ""; + String boldText2 = ""; + String normalText1 = ""; + String normalText2 = ""; + + @override + _PartiallyBoldMarqueeState createState() => _PartiallyBoldMarqueeState(); + + ATPartiallyBoldMarquee( + this.boldText1, + this.boldText2, + this.normalText1, + this.normalText2, + ); +} + +class _PartiallyBoldMarqueeState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _animation; + bool goMarquee = false; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: Duration(seconds: 20), // 总滚动时间,需调试 + vsync: this, + )..repeat(); // 无限循环 + _animation = Tween(begin: 0.2, end: -1.0).animate(_controller); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + physics: const NeverScrollableScrollPhysics(), // 禁用用户手势 + child: AnimatedBuilder( + animation: _animation, + builder: (context, child) { + return Transform.translate( + offset: Offset(_animation.value * 650, 0), + // 500为估算的滚动距离,需根据文本宽度精确计算 + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: widget.boldText1, + style: TextStyle( + fontSize: 13.sp, + color: Colors.black, + fontFamily: 'MyCustomFont', + fontWeight: FontWeight.w900, + ), + ), + TextSpan( + text: widget.normalText1, + style: TextStyle( + fontSize: 12.sp, + color: Colors.black, + fontFamily: 'MyCustomFont', + fontWeight: FontWeight.w600, // 加粗 + ), + ), + TextSpan( + text: widget.boldText2, + style: TextStyle( + fontSize: 13.sp, + color: Colors.black, + fontFamily: 'MyCustomFont', + fontWeight: FontWeight.w900, + ), + ), + TextSpan( + text: widget.normalText2, + style: TextStyle( + fontSize: 12.sp, + color: Colors.black, + fontFamily: 'MyCustomFont', + fontWeight: FontWeight.w600, // 加粗 + ), + ), + ], + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/chatvibe_domain/usecases/at_shining_text.dart b/lib/chatvibe_domain/usecases/at_shining_text.dart new file mode 100644 index 0000000..269e87a --- /dev/null +++ b/lib/chatvibe_domain/usecases/at_shining_text.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; + +class ATShiningText extends StatefulWidget { + final String text; + final double fontSize; + final FontWeight fontWeight; + final Color baseColor; + final Color shineColor; + + const ATShiningText({ + super.key, + required this.text, + this.fontSize = 24, + this.fontWeight = FontWeight.bold, + this.baseColor = Colors.grey, + this.shineColor = Colors.white, + }); + + @override + State createState() => _ShiningText1State(); +} + + +class _ShiningText1State extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _animation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: const Duration(milliseconds: 3000), + vsync: this, + )..repeat(reverse: false); + + _animation = Tween(begin: -1.0, end: 2.0).animate(_controller); + } + + @override + Widget build(BuildContext context) { + return Center( + child: AnimatedBuilder( + animation: _animation, + builder: (context, child) { + return ShaderMask( + blendMode: BlendMode.srcIn, + shaderCallback: (Rect bounds) { + return LinearGradient( + colors: [ + widget.baseColor, + widget.shineColor, + widget.shineColor, + widget.baseColor, + ], + stops: const [0.0, 0.1, 0.9, 1.0], + transform: _GradientTransform(_animation.value), + ).createShader(bounds); + }, + child: Text( + widget.text, + style: TextStyle( + fontSize: widget.fontSize, + fontWeight: widget.fontWeight, + ), + ), + ); + }, + ), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } +} + +class _GradientTransform extends GradientTransform { + final double value; + + const _GradientTransform(this.value); + + @override + Matrix4 transform(Rect bounds, {TextDirection? textDirection}) { + return Matrix4.translationValues(bounds.width * value, 0.0, 0.0); + } +} diff --git a/lib/chatvibe_domain/usecases/custom_tab_selector.dart b/lib/chatvibe_domain/usecases/custom_tab_selector.dart new file mode 100644 index 0000000..6e3f796 --- /dev/null +++ b/lib/chatvibe_domain/usecases/custom_tab_selector.dart @@ -0,0 +1,92 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class CustomTabSelector extends StatefulWidget { + final List options; + final int initialIndex; + final ValueChanged onChanged; + + const CustomTabSelector({ + Key? key, + required this.options, + required this.initialIndex, + required this.onChanged, + }) : super(key: key); + + @override + _CustomTabSelectorState createState() => _CustomTabSelectorState(); +} + +class _CustomTabSelectorState extends State { + late int _selectedIndex; + + @override + void initState() { + super.initState(); + _selectedIndex = widget.initialIndex; + } + + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/room/at_icon_red_envelope_config_tab_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: + widget.options.asMap().entries.map((entry) { + final index = entry.key; + final title = entry.value; + final isSelected = index == _selectedIndex; + + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + setState(() { + _selectedIndex = index; + }); + widget.onChanged(index); + }, + child: Container( + width: 72.w, + height: 32.w, + decoration: + isSelected + ? BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_red_envelope_config_tab_item_bg.png", + ), + fit: BoxFit.fill, + ), + ) + : null, + child: Container( + alignment: Alignment.center, + child: Text( + title, + textAlign: TextAlign.center, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: Colors.white, + fontSize: isSelected ? 15.sp : 13.sp, + fontFamily: 'MyCustomFont', + fontWeight: + isSelected ? FontWeight.w600 : FontWeight.w500, + ), + ), + ), + ), + ); + }).toList(), + ), + ); + } +} diff --git a/lib/chatvibe_features/admin/editing/editing_user_room_page.dart b/lib/chatvibe_features/admin/editing/editing_user_room_page.dart new file mode 100644 index 0000000..bfebd40 --- /dev/null +++ b/lib/chatvibe_features/admin/editing/editing_user_room_page.dart @@ -0,0 +1,701 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_debouncer/flutter_debouncer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_pick_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_res.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +class EditingUserRoomPage extends StatefulWidget { + final String type; + + ///被举报的id(用户id或者房间id) + final ChatVibeUserProfile? userProfile; + final ChatVibeRoomRes? roomProfile; + + const EditingUserRoomPage({ + Key? key, + required this.type, + this.userProfile, + this.roomProfile, + }) : super(key: key); + + @override + _EditingUserRoomPageState createState() => _EditingUserRoomPageState(); +} + +class _EditingUserRoomPageState extends State { + // 业务逻辑策略访问器 + BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; + + num selectedIndex = 0; + String reportReason = ""; + List imageUrls = []; // 动态图片URL列表 + + int violationType = 1; + final TextEditingController _descriptionController = TextEditingController(); + Debouncer debouncer = Debouncer(); + + @override + void initState() { + super.initState(); + final violationTypeMapping = _strategy.getAdminEditingViolationTypeMapping(widget.type); + if (violationTypeMapping.isNotEmpty) { + // 获取第一个值作为默认违规类型 + violationType = violationTypeMapping.values.first; + } + // 初始化图片URL列表,长度为最大上传数量 + final maxImageCount = _strategy.getAdminEditingMaxImageUploadCount(); + imageUrls = List.generate(maxImageCount, (index) => ''); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + _strategy.getAdminEditingBackgroundImage(), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + actions: [], + title: + widget.type == "User" + ? ATAppLocalizations.of(context)!.userEditing + : ATAppLocalizations.of(context)!.roomEditing, + backgroundColor: Colors.transparent, + ), + body: SafeArea( + top: false, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + widget.type == "User" + ? Container( + padding: EdgeInsets.symmetric( + vertical: 8.w, + horizontal: 5.w, + ), + margin: EdgeInsets.symmetric(horizontal: 15.w), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.black12, width: 1.w), + borderRadius: BorderRadius.all(Radius.circular(12.w)), + ), + child: Row( + children: [ + GestureDetector( + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == widget.userProfile?.id}&tageId=${widget.userProfile?.id}", + ); + }, + behavior: HitTestBehavior.opaque, + child: head( + url: widget.userProfile?.userAvatar ?? "", + width: 55.w, + ), + ), + SizedBox(width: 2.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + xb( + widget.userProfile?.userSex, + color: + widget.userProfile?.userSex == 1 + ? Colors.blue + : Colors.purpleAccent, + ), + SizedBox(width: 5.w), + // richText( + // txt: data.userNickname ?? "", + // key: (widget as SearchUserList).text, + // highlight: ChatVibeTheme.primaryColor, + // defaultColor: Colors.black, + // ), + Expanded( + child: text( + widget.userProfile?.userNickname ?? + "", + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.black, + ), + ), + SizedBox(width: 5.w), + // dj(data.level.toString()), + ], + ), + Row( + children: [ + GestureDetector( + child: Container( + padding: EdgeInsets.symmetric( + vertical: 8.w, + ), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + (widget.userProfile + ?.hasSpecialId() ?? + false) + ? _strategy.getAdminEditingIcon('specialIdBg') + : _strategy.getAdminEditingIcon('normalIdBg'), + ), + fit: BoxFit.fitWidth, + ), + ), + child: Row( + textDirection: TextDirection.ltr, + children: [ + SizedBox(width: 38.w), + text( + widget.userProfile?.getID() ?? + "", + fontSize: 12.sp, + textColor: Colors.white, + fontWeight: FontWeight.bold, + ), + SizedBox(width: 5.w), + Image.asset( + _strategy.getAdminEditingIcon('copyId'), + width: 12.w, + height: 12.w, + ), + SizedBox(width: 8.w), + ], + ), + ), + onTap: () { + Clipboard.setData( + ClipboardData( + text: + widget.userProfile?.getID() ?? + "", + ), + ); + ATTts.show( + ATAppLocalizations.of( + context, + )!.copiedToClipboard, + ); + }, + ), + ], + ), + ], + ), + ), + SizedBox(width: 5.w), + ], + ), + ) + : Container( + padding: EdgeInsets.symmetric( + vertical: 8.w, + horizontal: 5.w, + ), + margin: EdgeInsets.symmetric(horizontal: 15.w), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.black12, width: 1.w), + borderRadius: BorderRadius.all(Radius.circular(12.w)), + ), + child: Row( + children: [ + GestureDetector( + onTap: () { + ATRoomUtils.goRoom( + widget.roomProfile?.id ?? "", + context, + ); + }, + behavior: HitTestBehavior.opaque, + child: head( + url: widget.roomProfile?.roomCover ?? "", + width: 55.w, + ), + ), + SizedBox(width: 2.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: text( + widget.roomProfile?.roomName ?? "", + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.black, + ), + ), + SizedBox(width: 5.w), + // dj(data.level.toString()), + ], + ), + Row( + children: [ + GestureDetector( + child: Container( + padding: EdgeInsets.symmetric( + vertical: 8.w, + ), + child: Row( + textDirection: TextDirection.ltr, + children: [ + SizedBox(width: 5.w), + text( + "ID:${widget.roomProfile?.roomAccount}", + fontSize: 12.sp, + textColor: Colors.black26, + fontWeight: FontWeight.bold, + ), + SizedBox(width: 5.w), + Image.asset( + _strategy.getAdminEditingIcon('copyId'), + width: 12.w, + height: 12.w, + color: Colors.black26, + ), + SizedBox(width: 8.w), + ], + ), + ), + onTap: () { + Clipboard.setData( + ClipboardData( + text: + widget + .roomProfile + ?.roomAccount ?? + "", + ), + ); + ATTts.show( + ATAppLocalizations.of( + context, + )!.copiedToClipboard, + ); + }, + ), + ], + ), + ], + ), + ), + SizedBox(width: 5.w), + ], + ), + ), + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 15.w), + text( + ATAppLocalizations.of( + context, + )!.pleaseSelectTheTypeToProcess, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.black, + ), + SizedBox(width: 15.w), + ], + ), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + border: Border.all(color: Color(0xffE6E6E6), width: 1.w), + ), + margin: EdgeInsets.only(top: 10.w, left: 15.w, right: 15.w), + padding: EdgeInsets.symmetric(horizontal: 15.w), + child: Column( + children: [ + // 动态生成违规类型选项 + ..._buildViolationTypeOptions(context), + SizedBox(height: 20.w,), + Row( + children: [ + text( + ATAppLocalizations.of(context)!.description, + fontSize: 14.sp, + fontWeight: FontWeight.w500, + textColor: Colors.black, + ), + Spacer(), + ], + ), + SizedBox(height: 8.w), + Container( + padding: EdgeInsets.all(5.w), + decoration: _strategy.getAdminEditingInputDecoration(), + child: TextField( + controller: _descriptionController, + onChanged: (text) {}, + maxLength: _strategy.getAdminEditingDescriptionMaxLength(), + maxLines: 5, + decoration: InputDecoration( + hintText: + ATAppLocalizations.of(context)!.inputDesHint, + hintStyle: TextStyle( + color: Colors.black45, + fontSize: 14.sp, + ), + contentPadding: EdgeInsets.only(top: 0.w), + + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + // enabledBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // focusedBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // prefixIcon: Padding(padding: EdgeInsets.all(8.w), child: Image.asset("images/login/at_icon_phone.png",width: 20.w, height: 20.w,fit: BoxFit.fill,),), + fillColor: Colors.black45, + ), + style: TextStyle( + fontSize: sp(15), + color: Colors.black, + textBaseline: TextBaseline.alphabetic, + ), + ), + ), + SizedBox(height: 5.w), + Row( + children: [ + text( + ATAppLocalizations.of(context)!.screenshotTips, + fontSize: 14.sp, + textColor: Colors.black, + fontWeight: FontWeight.w500, + ), + Spacer(), + ], + ), + SizedBox(height: 8.w), + Row( + children: [ + // 动态生成图片上传组件 + ...List.generate(_strategy.getAdminEditingMaxImageUploadCount(), (index) { + return Expanded( + child: GestureDetector( + child: Stack( + children: [ + imageUrls[index].isNotEmpty + ? netImage(url: imageUrls[index], height: 100.w) + : Image.asset( + _strategy.getAdminEditingIcon('addPic'), + height: 100.w, + ), + imageUrls[index].isNotEmpty + ? Positioned( + top: 5.w, + right: 5.w, + child: GestureDetector( + child: Image.asset( + _strategy.getAdminEditingIcon('closePic'), + width: 14.w, + height: 14.w, + ), + onTap: () { + setState(() { + imageUrls[index] = ""; + }); + }, + ), + ) + : Container(), + ], + ), + onTap: () { + ATPickUtils.pickImageProcess(context, ( + bool success, + String url, + ) { + if (success) { + setState(() { + imageUrls[index] = url; + }); + } + }); + }, + ), + ); + }).expand((widget) => [widget, if (_strategy.getAdminEditingMaxImageUploadCount() > 1 && widget != Expanded) SizedBox(width: 5.w)]).toList(), + ], + ), + SizedBox(height: 15.w), + ], + ), + ), + SizedBox(height: 45.w), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + debouncer.debounce( + duration: Duration(milliseconds: 350), + onDebounce: () { + List uploadedImages = imageUrls.where((url) => url.isNotEmpty).toList(); + if (widget.type == "User") { + AccountRepository().userViolationHandle( + widget.userProfile?.id ?? "", + violationType, + 1, + _descriptionController.text, + imageUrls: imageUrls, + ).then((b){ + ATTts.show(ATAppLocalizations.of(context)!.operationSuccessful); + ATNavigatorUtils.goBack(context); + }).catchError((e){ + + }); + } else { + ChatRoomRepository().roomViolationHandle( + widget.roomProfile?.id ?? "", + violationType, + 1, + _descriptionController.text, + imageUrls: imageUrls, + ).then((b){ + ATTts.show(ATAppLocalizations.of(context)!.operationSuccessful); + ATNavigatorUtils.goBack(context); + }).catchError((e){ + + }); + } + }, + ); + }, + child: Container( + height: 40.w, + width: double.infinity, + alignment: Alignment.center, + margin: EdgeInsets.symmetric(horizontal: 45.w), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: AlignmentDirectional.topCenter, + end: AlignmentDirectional.bottomCenter, + colors: _strategy.getAdminEditingButtonGradient('warning'), + ), + borderRadius: BorderRadius.circular(25.w), + ), + child: Text( + ATAppLocalizations.of(context)!.warning, + style: TextStyle( + color: Colors.white, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + SizedBox(height: 20.w), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + debouncer.debounce( + duration: Duration(milliseconds: 350), + onDebounce: () { + List uploadedImages = imageUrls.where((url) => url.isNotEmpty).toList(); + if (widget.type == "User") { + AccountRepository().userViolationHandle( + widget.userProfile?.id ?? "", + violationType, + 2, + _descriptionController.text, + imageUrls: imageUrls, + ).then((b){ + ATTts.show(ATAppLocalizations.of(context)!.operationSuccessful); + ATNavigatorUtils.goBack(context); + }).catchError((e){ + + }); + } else { + ChatRoomRepository().roomViolationHandle( + widget.roomProfile?.id ?? "", + violationType, + 2, + _descriptionController.text, + imageUrls: imageUrls, + ).then((b){ + ATTts.show(ATAppLocalizations.of(context)!.operationSuccessful); + ATNavigatorUtils.goBack(context); + }).catchError((e){ + + }); + } + }, + ); + }, + child: Container( + height: 40.w, + width: double.infinity, + alignment: Alignment.center, + margin: EdgeInsets.symmetric(horizontal: 45.w), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: AlignmentDirectional.topCenter, + end: AlignmentDirectional.bottomCenter, + colors: _strategy.getAdminEditingButtonGradient('adjust'), + ), + borderRadius: BorderRadius.circular(25.w), + ), + child: Text( + ATAppLocalizations.of(context)!.adjust, + style: TextStyle( + color: Colors.white, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + SizedBox(height: 35.w), + ], + ), + ), + ), + ), + ], + ); + } + + List _buildViolationTypeOptions(BuildContext context) { + final violationTypeMapping = _strategy.getAdminEditingViolationTypeMapping(widget.type); + final List options = []; + + if (violationTypeMapping.isEmpty) { + // 如果没有映射,返回默认选项 + if (widget.type == "User") { + options.add(_item(ATAppLocalizations.of(context)!.userName, 0, 1)); + options.add(_item(ATAppLocalizations.of(context)!.userProfilePicture, 1, 2)); + } else { + options.add(_item(ATAppLocalizations.of(context)!.roomName, 0, 3)); + options.add(_item(ATAppLocalizations.of(context)!.roomProfilePicture, 1, 4)); + options.add(_item(ATAppLocalizations.of(context)!.roomNotice, 2, 5)); + options.add(_item(ATAppLocalizations.of(context)!.roomTheme, 3, 6)); + } + return options; + } + + // 根据映射动态生成选项 + int index = 0; + violationTypeMapping.forEach((displayName, typeId) { + // 需要将英文显示名称转换为本地化文本 + String localizedName; + switch (displayName) { + case 'Pornography': + localizedName = ATAppLocalizations.of(context)!.pornography; + break; + case 'Illegal information': + localizedName = ATAppLocalizations.of(context)!.illegalInformation; + break; + case 'Fraud': + localizedName = ATAppLocalizations.of(context)!.fraud; + break; + case 'Other': + localizedName = ATAppLocalizations.of(context)!.others; + break; + case 'User Name': + localizedName = ATAppLocalizations.of(context)!.userName; + break; + case 'User Profile Picture': + localizedName = ATAppLocalizations.of(context)!.userProfilePicture; + break; + case 'Room Name': + localizedName = ATAppLocalizations.of(context)!.roomName; + break; + case 'Room Profile Picture': + localizedName = ATAppLocalizations.of(context)!.roomProfilePicture; + break; + case 'Room Notice': + localizedName = ATAppLocalizations.of(context)!.roomNotice; + break; + case 'Room Theme': + localizedName = ATAppLocalizations.of(context)!.roomTheme; + break; + default: + localizedName = displayName; + } + options.add(_item(localizedName, index, typeId)); + index++; + }); + + return options; + } + + Widget _item(String str, int index, int vtype) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + setState(() { + reportReason = str; + selectedIndex = index; + violationType = vtype; + }); + }, + child: Container( + width: double.infinity, + height: 35.w, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + text(str, fontSize: 13.sp, textColor: Colors.black,fontWeight: FontWeight.w500), + Spacer(), + selectedIndex == index + ? Image.asset( + _strategy.getAdminEditingIcon('checked'), + width: 20.w, + fit: BoxFit.fitWidth, + ) + : Container( + width: 20.w, + height: 20.w, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.grey, width: 2.w), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/chatvibe_features/admin/search/edit_room_search_admin_page.dart b/lib/chatvibe_features/admin/search/edit_room_search_admin_page.dart new file mode 100644 index 0000000..027a8cf --- /dev/null +++ b/lib/chatvibe_features/admin/search/edit_room_search_admin_page.dart @@ -0,0 +1,232 @@ +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/chatvibe_gradient_button.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_res.dart'; + +class EditRoomSearchAdminPage extends StatefulWidget { + @override + _EditRoomSearchAdminPageState createState() => + _EditRoomSearchAdminPageState(); +} + +class _EditRoomSearchAdminPageState extends State + with SingleTickerProviderStateMixin { + final TextEditingController _textEditingController = TextEditingController(); + List rooms = []; + bool canEdit = false; + + BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + _strategy.getAdminSearchBackgroundImage('roomSearch'), + width: ScreenUtil().screenWidth, + height: 150.w, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.roomEditing, + actions: [], + ), + body: SafeArea( + top: false, + child: Container( + margin: EdgeInsetsDirectional.only(top: 15.w), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadiusDirectional.only( + topStart: Radius.circular(35.w), + ), + ), + child: Column( + children: [ + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: searchWidget( + hint: ATAppLocalizations.of(context)!.enterTheRoomId, + controller: _textEditingController, + borderColor: _strategy.getAdminSearchInputBorderColor('roomSearch'), + textColor: _strategy.getAdminSearchInputTextColor('roomSearch'), + ), + ), + chatvibeGradientButton( + text: ATAppLocalizations.of(context)!.search, + radius: 25, + textSize: 14.sp, + textColor: _strategy.getAdminSearchButtonTextColor('roomSearch'), + gradient: LinearGradient( + colors: _strategy.getAdminSearchButtonGradient('roomSearch'), + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + onPress: () { + if (_textEditingController.text.isNotEmpty) { + _searchUser(); + } + }, + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 10.w), + rooms.isNotEmpty + ? Expanded( + child: ListView.separated( + itemBuilder: (context, i) => buildItem(rooms[i]), + itemCount: rooms.length, + padding: EdgeInsets.zero, + separatorBuilder: + (context, i) => Divider( + height: height(1), + color: ChatVibeTheme.dividerColor, + indent: width(28), //25+50+10 + endIndent: width(15), + ), + ), + ) + : mainEmpty(), + ], + ), + ), + ), + ), + ], + ); + } + + Widget buildItem(ChatVibeRoomRes data) { + return Container( + padding: EdgeInsets.symmetric(vertical: 8.w, horizontal: 5.w), + margin: EdgeInsets.symmetric(horizontal: 15.w), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.black12, width: 1.w), + borderRadius: BorderRadius.all(Radius.circular(12.w)), + ), + child: Row( + children: [ + GestureDetector( + onTap: () { + ATRoomUtils.goRoom(data.id ?? "", context); + }, + behavior: HitTestBehavior.opaque, + child: head(url: data.roomCover ?? "", width: 55.w), + ), + SizedBox(width: 2.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + // richText( + // txt: data.userNickname ?? "", + // key: (widget as SearchUserList).text, + // highlight: ChatVibeTheme.primaryColor, + // defaultColor: Colors.black, + // ), + Expanded( + child: text( + data.roomName ?? "", + fontSize: 15.sp, + textColor: Colors.black, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(width: 5.w), + // dj(data.level.toString()), + ], + ), + Row( + children: [ + text( + "ID:${data.roomAccount}", + fontSize: 12.sp, + textColor: Colors.black26, + fontWeight: FontWeight.w600, + ), + ], + ), + ], + ), + ), + canEdit + ? GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + width: 20.w, + height: 20.w, + child: Image.asset( + _strategy.getAdminSearchEditIcon('roomSearch'), + color: Colors.black, + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.editingUserRoomAdmin}?type=Room&profile=${Uri.encodeComponent(jsonEncode(data.toJson()))}", + ); + }, + ) + : Container(), + SizedBox(width: 5.w), + ], + ), + ); + } + + void _searchUser() async { + try { + ATLoadingManager.exhibitOperation(); + rooms.clear(); + rooms = await ChatRoomRepository().searchRoom( + _textEditingController.text, + ); + var userIdentity = await AccountRepository().userIdentity( + userId: rooms.first.userId ?? "", + ); + if (!(userIdentity.admin ?? false) && + !(userIdentity.superAdmin ?? false)) { + canEdit = true; + } + ATLoadingManager.veilRoutine(); + setState(() {}); + } catch (e) { + ATLoadingManager.veilRoutine(); + } + } +} diff --git a/lib/chatvibe_features/admin/search/edit_user_search_admin_page.dart b/lib/chatvibe_features/admin/search/edit_user_search_admin_page.dart new file mode 100644 index 0000000..3fd3f3a --- /dev/null +++ b/lib/chatvibe_features/admin/search/edit_user_search_admin_page.dart @@ -0,0 +1,277 @@ +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/chatvibe_gradient_button.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; + +class EditUserSearchAdminPage extends StatefulWidget { + @override + _EditUserSearchAdminPageState createState() => + _EditUserSearchAdminPageState(); +} + +class _EditUserSearchAdminPageState extends State + with SingleTickerProviderStateMixin { + final TextEditingController _textEditingController = TextEditingController(); + List users = []; + + bool canEdit = false; + + BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + _strategy.getAdminSearchBackgroundImage('userSearch'), + width: ScreenUtil().screenWidth, + height: 150.w, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.userEditing, + actions: [], + ), + body: SafeArea( + top: false, + child: Container( + margin: EdgeInsetsDirectional.only(top: 15.w), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadiusDirectional.only( + topStart: Radius.circular(35.w), + ), + ), + child: Column( + children: [ + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: searchWidget( + hint: ATAppLocalizations.of(context)!.enterTheUserId, + controller: _textEditingController, + borderColor: _strategy.getAdminSearchInputBorderColor('userSearch'), + textColor: _strategy.getAdminSearchInputTextColor('userSearch'), + ), + ), + chatvibeGradientButton( + text: ATAppLocalizations.of(context)!.search, + radius: 25, + textSize: 14.sp, + textColor: _strategy.getAdminSearchButtonTextColor('userSearch'), + gradient: LinearGradient( + colors: _strategy.getAdminSearchButtonGradient('userSearch'), + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + onPress: () { + if (_textEditingController.text.isNotEmpty) { + _searchUser(); + } + }, + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 10.w), + users.isNotEmpty + ? Expanded( + child: ListView.separated( + itemBuilder: (context, i) => buildItem(users[i]), + itemCount: users.length, + padding: EdgeInsets.zero, + separatorBuilder: + (context, i) => Divider( + height: height(1), + color: ChatVibeTheme.dividerColor, + indent: width(28), //25+50+10 + endIndent: width(15), + ), + ), + ) + : mainEmpty(), + ], + ), + ), + ), + ), + ], + ); + } + + Widget buildItem(ChatVibeUserProfile data) { + return Container( + padding: EdgeInsets.symmetric(vertical: 8.w, horizontal: 5.w), + margin: EdgeInsets.symmetric(horizontal: 15.w), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.black12, width: 1.w), + borderRadius: BorderRadius.all(Radius.circular(12.w)), + ), + child: Row( + children: [ + GestureDetector( + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == data.id}&tageId=${data.id}", + ); + }, + behavior: HitTestBehavior.opaque, + child: head(url: data.userAvatar ?? "", width: 55.w), + ), + SizedBox(width: 2.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + xb( + data.userSex, + color: + data.userSex == 1 ? Colors.blue : Colors.purpleAccent, + ), + SizedBox(width: 5.w), + // richText( + // txt: data.userNickname ?? "", + // key: (widget as SearchUserList).text, + // highlight: ChatVibeTheme.primaryColor, + // defaultColor: Colors.black, + // ), + Expanded( + child: text( + data.userNickname ?? "", + fontSize: 15.sp, + textColor: Colors.black, + ), + ), + SizedBox(width: 5.w), + // dj(data.level.toString()), + ], + ), + Row( + children: [ + GestureDetector( + child: Container( + padding: EdgeInsets.symmetric(vertical: 8.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + (data.hasSpecialId() ?? false) + ? _strategy.getAdminSearchUserInfoIcon('specialIdBg') + : _strategy.getAdminSearchUserInfoIcon('normalIdBg'), + ), + fit: BoxFit.fitWidth, + ), + ), + child: Row( + textDirection: TextDirection.ltr, + children: [ + SizedBox(width: 38.w), + text( + data.getID(), + fontSize: 12.sp, + textColor: Colors.white, + fontWeight: FontWeight.bold, + ), + SizedBox(width: 5.w), + Image.asset( + _strategy.getAdminSearchUserInfoIcon('copyId'), + width: 12.w, + height: 12.w, + ), + SizedBox(width: 8.w), + ], + ), + ), + onTap: () { + Clipboard.setData(ClipboardData(text: data.getID())); + ATTts.show( + ATAppLocalizations.of(context)!.copiedToClipboard, + ); + }, + ), + ], + ), + ], + ), + ), + canEdit + ? GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + width: 20.w, + height: 20.w, + child: Image.asset( + _strategy.getAdminSearchEditIcon('userSearch'), + color: Colors.black, + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.editingUserRoomAdmin}?type=User&profile=${Uri.encodeComponent(jsonEncode(data.toJson()))}", + ); + }, + ) + : Container(), + SizedBox(width: 5.w), + ], + ), + ); + } + + void _searchUser() async { + try { + ATLoadingManager.exhibitOperation(); + users.clear(); + var result = await AccountRepository().searchUser( + _textEditingController.text, + ); + var userIdentity = await AccountRepository().userIdentity( + userId: result.id, + ); + if (!(userIdentity.admin ?? false) && + !(userIdentity.superAdmin ?? false)) { + canEdit = true; + } + users.add(result); + ATLoadingManager.veilRoutine(); + setState(() {}); + } catch (e) { + ATLoadingManager.veilRoutine(); + } + } +} diff --git a/lib/chatvibe_features/auth/account/login_with_account_page.dart b/lib/chatvibe_features/auth/account/login_with_account_page.dart new file mode 100644 index 0000000..291c7e1 --- /dev/null +++ b/lib/chatvibe_features/auth/account/login_with_account_page.dart @@ -0,0 +1,368 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; +import 'package:aslan/chatvibe_core/routes/at_routes.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_core/utilities/at_version_utils.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +///密码账户登录 +class LoginWithAccountPage extends StatefulWidget { + const LoginWithAccountPage({super.key}); + + @override + LoginWithAccountPageState createState() => LoginWithAccountPageState(); +} + +class LoginWithAccountPageState extends State + with WidgetsBindingObserver { + ///显示密码 + bool showPass = false; + String account = ""; + + ///账户控制器 + TextEditingController accountController = TextEditingController(); + + ///密码控制器 + TextEditingController passController = TextEditingController(); + + @override + void initState() { + super.initState(); + String account = DataPersistence.getString( + "Login_Account", + defaultValue: "", + ); + String pwd = DataPersistence.getString("Login_Pwd", defaultValue: ""); + accountController.text = account; + passController.text = pwd; + } + + @override + Widget build(BuildContext context) { + final businessLogicStrategy = ATGlobalConfig.businessLogicStrategy; + final pagePadding = businessLogicStrategy.getLoginPagePadding(); + + return Scaffold( + resizeToAvoidBottomInset: false, + body: GestureDetector( + onTap: (){ + FocusScope.of(context).unfocus(); + }, + child: Stack( + alignment: Alignment.topCenter, + children: [ + Image.asset( + businessLogicStrategy.getLoginBackgroundImage(), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.cover, + ), + + SingleChildScrollView( + // 添加滚动视图 + padding: EdgeInsets.only( + bottom: + MediaQuery.of(context).viewInsets.bottom + 20, // 添加底部内边距 + ), + child: Column( + children: [ + SizedBox(height: pagePadding.top), + Image.asset( + businessLogicStrategy.getLoginAppIcon(), + width: 107.w, + height: 159.w, + ), + SizedBox(height: 35.w), + Container( + margin: EdgeInsetsDirectional.only(start: 65.w), + alignment: AlignmentDirectional.centerStart, + child: text( + ATAppLocalizations.of(context)!.account, + fontSize: 16.sp, + textColor: businessLogicStrategy.getLoginLabelTextColor(), + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 10.w), + _buildAccountInput(businessLogicStrategy), + SizedBox(height: 10.w), + Container( + margin: EdgeInsetsDirectional.only(start: 65.w), + alignment: AlignmentDirectional.centerStart, + child: text( + ATAppLocalizations.of(context)!.password, + fontSize: 16.sp, + textColor: businessLogicStrategy.getLoginLabelTextColor(), + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 10.w), + _buildPassInput(businessLogicStrategy), + SizedBox(height: 80.w), + _buildLoginBtn(businessLogicStrategy), + ], + ), + ), + ], + ), + ), + ); + } + + ///登录按钮 + Widget _buildLoginBtn(BusinessLogicStrategy strategy) { + final buttonColor = strategy.getLoginButtonColor(); + final buttonGradient = strategy.getLoginButtonGradient(); + + BoxDecoration buttonDecoration; + if (buttonGradient != null && buttonGradient.isNotEmpty) { + // 使用渐变 + buttonDecoration = BoxDecoration( + gradient: LinearGradient( + colors: buttonGradient, + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + borderRadius: BorderRadius.circular(70), // 马甲包设计使用70px圆角 + ); + } else { + // 使用纯色 + buttonDecoration = BoxDecoration( + color: buttonColor, + borderRadius: BorderRadius.all(Radius.circular(height(8))), // 原始应用8px圆角 + ); + } + + return Row( + children: [ + Expanded( + child: ATDebounceWidget( + child: Container( + alignment: Alignment.center, + margin: EdgeInsets.symmetric(horizontal: 60.w), + height: 42.w, + decoration: buttonDecoration, + child: text( + ATAppLocalizations.of(context)!.logIn, + textColor: strategy.getLoginButtonTextColor(), + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + _login(); + }, + ), + ), + ], + ); + } + + ///账户输入框 + Widget _buildAccountInput(BusinessLogicStrategy strategy) { + return Row( + children: [ + Expanded( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 60.w), + padding: EdgeInsetsDirectional.only( + start: width(12), + end: width(12), + ), + alignment: AlignmentDirectional.centerStart, + height: 45.w, + width: ScreenUtil().screenWidth, + decoration: strategy.getLoginInputDecoration(), + child: TextField( + controller: accountController, + onChanged: (text) { + setState(() { + account = text; + }); + }, + maxLength: 30, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp("[0-9]")), + ], + decoration: InputDecoration( + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + hintText: ATAppLocalizations.of(context)!.enterAccount, + hintStyle: TextStyle( + color: strategy.getLoginHintTextColor(), + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + contentPadding: EdgeInsets.only(top: 0.w), + counterText: '', + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + // enabledBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // focusedBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // prefixIcon: Padding(padding: EdgeInsets.all(8.w), child: Image.asset("images/login/at_icon_phone.png",width: 20.w, height: 20.w,fit: BoxFit.fill,),), + suffix: _buildPhoneCancel(), + + ), + style: TextStyle( + fontSize: 14.sp, + color: strategy.getLoginInputTextColor(), + fontWeight: FontWeight.w600, + textBaseline: TextBaseline.alphabetic, + ), + ), + ), + ), + ], + ); + } + + _buildPhoneCancel() { + return Offstage( + offstage: accountController.text == "", + child: GestureDetector( + onTap: () { + setState(() { + accountController.text = ''; + }); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Image.asset("atu_images/login/at_icon_sc.png", width: 15.w), + ), + ), + ); + } + + ///密码输入框 + Widget _buildPassInput(BusinessLogicStrategy strategy) { + return Row( + children: [ + Expanded( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 60.w), + padding: REdgeInsetsDirectional.only( + start: width(12), + end: width(12), + ), + alignment: AlignmentDirectional.centerStart, + height: 45.w, + width: ScreenUtil().screenWidth, + decoration: strategy.getLoginInputDecoration(), + child: TextField( + controller: passController, + maxLength: 30, + maxLines: 1, + inputFormatters: [ + FilteringTextInputFormatter.deny("[\u4e00-\u9fa5]"), + ], + + obscureText: !showPass, + textInputAction: TextInputAction.done, + onChanged: (s) { + setState(() {}); + }, + decoration: InputDecoration( + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + hintText: ATAppLocalizations.of(context)!.enterPassword, + hintStyle: TextStyle( + color: strategy.getLoginHintTextColor(), + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + counterText: '', + contentPadding: EdgeInsets.only(top: 0.w), + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + // enabledBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // focusedBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // prefixIcon: Padding(padding: EdgeInsets.all(8.w), child: Image.asset("images/login/at_icon_pwd.png",width: 20.w, height: 20.w,fit: BoxFit.fill,),), + suffix: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + setState(() { + showPass = !showPass; + }); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Image.asset( + !showPass + ? "atu_images/login/at_icon_pass.png" + : "atu_images/login/at_icon_pass1.png", + gaplessPlayback: true, + width: 15.w, + ), + ), + ), + ), + style: TextStyle( + fontSize: 14.sp, + color: strategy.getLoginInputTextColor(), + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ); + } + + ///登录 + _login() async { + String account = accountController.text; + String pass = passController.text; + if (account.isEmpty || pass.isEmpty) { + ATTts.show(ATAppLocalizations.of(context)!.theAccountPasswordCannotBeEmpty); + return; + } + ATLoadingManager.exhibitOperation(context: context); + try { + final results = await Future.wait([ + AccountRepository().loginForAccount(account, pass), + ATVersionUtils.checkReview(), + ]); + var user = (results[0] as ChatVibeLoginRes); + AccountStorage().setCurrentUser(user); + ATLoadingManager.veilRoutine(); + DataPersistence.setString("Login_Account", account); + DataPersistence.setString("Login_Pwd", pass); + if (mounted) { + ATNavigatorUtils.push(context, ATRoutes.home, clearStack: true); + } + } catch (e) { + ATLoadingManager.veilRoutine(); + // 可以添加错误处理逻辑 + rethrow; + } + } +} diff --git a/lib/chatvibe_features/auth/edit/edit_profile_page.dart b/lib/chatvibe_features/auth/edit/edit_profile_page.dart new file mode 100644 index 0000000..5ff4350 --- /dev/null +++ b/lib/chatvibe_features/auth/edit/edit_profile_page.dart @@ -0,0 +1,524 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_core/utilities/at_pick_utils.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_managers/authentication_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/custom_cached_image.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/routes/at_routes.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; + +import '../../../chatvibe_domain/usecases/at_custom_filtering_textinput_formatter.dart'; +import '../../country/at_country_page.dart'; + +///编辑个人信息 +class EditProfilePage extends StatefulWidget { + const EditProfilePage({super.key}); + + @override + _EditProfilePageState createState() => _EditProfilePageState(); +} + +class _EditProfilePageState extends State { + ///默认女 + int type = 0; + DateTime birthdayDate = DateTime(2006); + TextEditingController nicknameController = TextEditingController(); + ChatVibeUserProfileManager? userProvider; + + @override + void initState() { + super.initState(); + userProvider = Provider.of( + context, + listen: false, + ); + Provider.of(context, listen: false).selectCountryInfo = + null; + } + + @override + void dispose() { + userProvider?.clearEditUserUser(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final businessLogicStrategy = ATGlobalConfig.businessLogicStrategy; + return Scaffold( + resizeToAvoidBottomInset: false, + body: Stack( + alignment: Alignment.topCenter, + children: [ + Image.asset( + businessLogicStrategy.getEditProfileBackgroundImage(), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.cover, + ), + Column( + children: [ + SizedBox(height: 120.w), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + child: Consumer( + builder: (_, provider, __) { + return provider.editUser != null && + provider.editUser!.userAvatar != null + ? head( + url: provider.editUser!.userAvatar ?? "", + width: 75.w, + ) + : Image.asset( + businessLogicStrategy + .getEditProfileDefaultAvatarImage(), + width: 75.w, + height: 75.w, + ); + }, + ), + onTap: () { + ATPickUtils.pickImageProcess(context, ( + bool success, + String url, + ) { + if (success) { + userProvider?.setUserAvatar(url); + } + }); + }, + ), + SizedBox(width: 10.w), + Container( + padding: EdgeInsets.only(left: width(12), right: width(12)), + alignment: Alignment.center, + height: 46.w, + width: 185.w, + decoration: BoxDecoration( + color: + businessLogicStrategy + .getEditProfileInputBackgroundColor(), + borderRadius: BorderRadius.all( + Radius.circular(height(8)), + ), + ), + child: TextField( + textAlign: TextAlign.center, + controller: nicknameController, + onChanged: (text) { + setState(() {}); + }, + inputFormatters: [ATCustomFilteringTextInputFormatter()], + maxLength: 38, + decoration: InputDecoration( + hintText: ATAppLocalizations.of(context)!.enterNickname, + hintStyle: TextStyle( + color: Colors.white54, + fontSize: 14.sp, + ), + contentPadding: EdgeInsets.only(top: 0.w), + counterText: '', + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + fillColor: Colors.black45, + ), + style: TextStyle( + fontSize: sp(15), + color: Colors.black, + textBaseline: TextBaseline.alphabetic, + ), + ), + ), + ], + ), + SizedBox(height: 25.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + child: Container( + alignment: Alignment.center, + height: 46.w, + width: 128.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + gradient: + type == 1 + ? LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: businessLogicStrategy + .getEditProfileGenderButtonGradient( + true, + type == 1, + ), + ) + : LinearGradient( + colors: [Colors.white30, Colors.white30], + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + businessLogicStrategy.getEditProfileGenderIcon( + true, + ), + width: 25.w, + height: 25.w, + color: type == 1 ? Colors.white : Colors.white, + ), + SizedBox(width: 8.w), + text( + ATAppLocalizations.of(context)!.male, + textColor: type == 1 ? Colors.white : Colors.white, + fontSize: 13.sp, + ), + ], + ), + ), + onTap: () { + if (type == 0) { + setState(() { + type = 1; + userProvider?.setUserSex(type); + }); + } + }, + ), + SizedBox(width: 15.w), + GestureDetector( + child: Container( + alignment: Alignment.center, + height: 46.w, + width: 128.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: type == 1 ? Colors.white : Colors.white, + gradient: + type == 1 + ? LinearGradient( + colors: [Colors.white30, Colors.white30], + ) + : LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: businessLogicStrategy + .getEditProfileGenderButtonGradient( + false, + type != 1, + ), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + businessLogicStrategy.getEditProfileGenderIcon( + false, + ), + width: 25.w, + height: 25.w, + color: type == 1 ? Colors.white : Colors.white, + ), + SizedBox(width: 8.w), + text( + ATAppLocalizations.of(context)!.female, + textColor: type == 1 ? Colors.white : Colors.white, + fontSize: 13.sp, + ), + ], + ), + ), + onTap: () { + if (type == 1) { + setState(() { + type = 0; + userProvider?.setUserSex(type); + }); + } + }, + ), + ], + ), + SizedBox(height: 12.w), + GestureDetector( + child: Container( + padding: EdgeInsets.only(left: width(12), right: width(12)), + alignment: Alignment.center, + height: 46.w, + width: 270.w, + decoration: BoxDecoration( + color: + businessLogicStrategy + .getEditProfileInputBackgroundColor(), + borderRadius: BorderRadius.all(Radius.circular(height(12))), + ), + child: text( + ATMDateUtils.formatDateTime(birthdayDate), + textColor: Colors.white54, + fontSize: 15.sp, + ), + ), + onTap: () { + _selectDate(); + }, + ), + SizedBox(height: 12.w), + GestureDetector( + child: Consumer( + builder: (_, provider, __) { + return Container( + padding: EdgeInsets.only( + left: width(12), + right: width(12), + ), + alignment: Alignment.center, + height: 46.w, + width: 270.w, + decoration: BoxDecoration( + color: + businessLogicStrategy + .getEditProfileInputBackgroundColor(), + borderRadius: BorderRadius.all( + Radius.circular(height(12)), + ), + ), + child: + provider.selectCountryInfo != null + ? Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomCachedImage( + imageUrl: + provider + .selectCountryInfo! + .nationalFlag ?? + "", + width: 26.w, + height: 16.w, + ), + SizedBox(width: 5.w), + text( + provider.selectCountryInfo!.aliasName ?? "", + textColor: Colors.white54, + fontSize: 14.sp, + ), + ], + ) + : text( + ATAppLocalizations.of( + context, + )!.selectYourCountry, + textColor: Colors.white54, + fontSize: 15.sp, + ), + ); + }, + ), + onTap: () { + SmartDialog.show( + tag: "showCountryPage", + alignment: Alignment.bottomCenter, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return SizedBox( + height: ScreenUtil().screenHeight * 0.5, + child: ATCountryPage(isDialog: true), + ); + }, + ); + }, + ), + SizedBox(height: 25.w), + GestureDetector( + child: Container( + padding: EdgeInsets.only(left: width(12), right: width(12)), + alignment: Alignment.center, + height: 46.w, + width: 270.w, + decoration: BoxDecoration( + color: + businessLogicStrategy + .getEditProfileContinueButtonColor(), + borderRadius: BorderRadius.all(Radius.circular(height(12))), + ), + child: text( + ATAppLocalizations.of(context)!.conntinue, + textColor: Colors.white, + fontSize: 15.sp, + ), + ), + onTap: () { + _submitUserProfile(); + }, + ), + ], + ), + ], + ), + ); + } + + Future _selectDate() async { + showCupertinoModalPopup( + context: context, + builder: + (context) => SafeArea( + top: false, + child: Container( + height: 240.w, + padding: EdgeInsets.only(top: 6), + color: CupertinoColors.systemBackground, + child: Column( + children: [ + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 10.w), + GestureDetector( + child: Container( + alignment: Alignment.topCenter, + child: text( + ATAppLocalizations.of(context)!.cancel, + textColor: Colors.black87, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + Navigator.of(context).pop(); + }, + ), + Expanded( + child: Container( + alignment: Alignment.topCenter, + child: text( + ATAppLocalizations.of(context)!.birthday, + textColor: Colors.black87, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + GestureDetector( + child: Container( + alignment: Alignment.topCenter, + child: text( + ATAppLocalizations.of(context)!.confirm, + textColor: + ATGlobalConfig.businessLogicStrategy + .getEditProfileDatePickerConfirmColor(), + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + setState(() {}); + Navigator.of(context).pop(); + }, + ), + SizedBox(width: 10.w), + ], + ), + Expanded( + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.date, + initialDateTime: _getBefor18(), + maximumDate: _getBefor18(), + onDateTimeChanged: (date) => birthdayDate = date, + ), + ), + ], + ), + ), + ), + ); + } + + DateTime _getBefor18() { + DateTime currentDate = DateTime.now(); + DateTime eighteenYearsAgo = DateTime( + currentDate.year - 18, + currentDate.month, + currentDate.day, + currentDate.hour, + currentDate.minute, + currentDate.second, + currentDate.millisecond, + currentDate.microsecond, + ); + return eighteenYearsAgo; + } + + ///提交数据 + void _submitUserProfile() async { + if (nicknameController.text.isEmpty) { + ATTts.show(ATAppLocalizations.of(context)!.pleaseEnterNickname); + return; + } + if (userProvider?.editUser?.userAvatar == null) { + ATTts.show(ATAppLocalizations.of(context)!.pleaseUploadUserAvatar); + return; + } + if (Provider.of( + context, + listen: false, + ).selectCountryInfo == + null) { + ATTts.show(ATAppLocalizations.of(context)!.pleaseSelectYourCountry); + return; + } + + ATLoadingManager.exhibitOperation(context: context); + userProvider?.setUserNickname(nicknameController.text); + userProvider?.setBornYear(birthdayDate.year); + userProvider?.setBornMonth(birthdayDate.month); + userProvider?.setBornDay(birthdayDate.day); + num age = DateTime.now().year - birthdayDate.year; + userProvider?.setAge(age.abs()); + String authType = + Provider.of( + context, + listen: false, + ).authType; + String idToken = + Provider.of(context, listen: false).uid; + ChatVibeLoginRes user = await AccountRepository().regist( + authType, + idToken, + userProvider!.editUser!, + ); + AccountStorage().setCurrentUser(user); + ATLoadingManager.veilRoutine(); + ATNavigatorUtils.push(context, ATRoutes.home, clearStack: true); + } +} diff --git a/lib/chatvibe_features/auth/login_page.dart b/lib/chatvibe_features/auth/login_page.dart new file mode 100644 index 0000000..81a5910 --- /dev/null +++ b/lib/chatvibe_features/auth/login_page.dart @@ -0,0 +1,415 @@ +import 'dart:io'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_managers/authentication_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/pop/pop_route.dart'; +import 'package:aslan/chatvibe_features/webview/webview_page.dart'; +import 'package:aslan/chatvibe_features/auth/login_route.dart'; + +import '../../chatvibe_data/models/enum/at_auth_type.dart'; + +/// 登录 +class LoginPage extends StatefulWidget { + const LoginPage({super.key}); + + @override + LoginPageState createState() => LoginPageState(); +} + +class LoginPageState extends State with WidgetsBindingObserver { + bool isAgreement = false; + ChatVibeAuthenticationManager? authProvider; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + authProvider = Provider.of(context, listen: false); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final businessLogicStrategy = ATGlobalConfig.businessLogicStrategy; + + return Scaffold( + resizeToAvoidBottomInset: false, + body: Stack( + alignment: Alignment.topCenter, + children: [ + Image.asset( + businessLogicStrategy.getLoginMainBackgroundImage(), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.cover, + ), + Positioned( + top: 120, + child: Image.asset( + businessLogicStrategy.getLoginMainAppIcon(), + width: 107.w, + height: 159.w, + ), + ), + Positioned( + top: 390.w, + left: 27.w, + right: 27.w, + child: SizedBox( + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Platform.isAndroid + ? ATDebounceWidget( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(9999), + color: businessLogicStrategy.getLoginMainButtonBackgroundColor(), + border: Border.all( + color: businessLogicStrategy.getLoginMainButtonBorderColor(), + width: 1.w, + ), + ), + padding: EdgeInsets.symmetric( + vertical: 12.w, + horizontal: 24.w, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Image.asset( + businessLogicStrategy.getLoginMainGoogleIcon(), + width: 30.w, + height: 30.w, + ), + SizedBox(width: 20.w), + Expanded( + child: text( + ATAppLocalizations.of( + context, + )!.signInWithGoogle, + textColor: businessLogicStrategy.getLoginMainButtonTextColor(), + fontSize: 16.sp, + fontWeight: FontWeight.bold, + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + onTap: () { + _loginByGoogle(); + }, + ) + : Container(), + Platform.isIOS ? SizedBox(height: 20.w) : Container(), + Platform.isIOS + ? ATDebounceWidget( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(9999), + color: businessLogicStrategy.getLoginMainButtonBackgroundColor(), + border: Border.all( + color: businessLogicStrategy.getLoginMainButtonBorderColor(), + width: 1.w, + ), + ), + padding: EdgeInsets.symmetric( + vertical: 12.w, + horizontal: 24.w, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Image.asset( + businessLogicStrategy.getLoginMainAppleIcon(), + width: 30.w, + height: 30.w, + ), + SizedBox(width: 20.w), + Expanded( + child: text( + ATAppLocalizations.of( + context, + )!.signInWithApple, + textColor: businessLogicStrategy.getLoginMainButtonTextColor(), + fontSize: 16.sp, + fontWeight: FontWeight.bold, + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + onTap: () { + _loginByApple(); + }, + ) + : Container(), + SizedBox(height: 8.w), // 从5.w改为8.w + Container( + alignment: Alignment.center, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + color: businessLogicStrategy.getLoginMainDividerColor(), + height: 0.5.w, + width: 82.w, + ), + SizedBox(width: 20.w), + text( + ATAppLocalizations.of(context)!.or, + textColor: businessLogicStrategy.getLoginMainDividerColor(), + fontSize: 18.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 20.w), + Container( + color: businessLogicStrategy.getLoginMainDividerColor(), + height: 0.5.w, + width: 82.w, + ), + ], + ), + ), + SizedBox(height: 12.w), // 从8.w改为12.w + ATDebounceWidget( + onTap: () { + _goLoginWithAccount(); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(9999), + color: businessLogicStrategy.getLoginMainButtonBackgroundColor(), + border: Border.all( + color: businessLogicStrategy.getLoginMainButtonBorderColor(), + width: 1.w, + ), + ), + padding: EdgeInsets.symmetric( + vertical: 12.w, + horizontal: 24.w, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Image.asset( + businessLogicStrategy.getLoginMainAccountIcon(), + width: 30.w, + height: 30.w, + ), + SizedBox(width: 20.w), + Expanded( + child: text( + ATAppLocalizations.of( + context, + )!.signInWithYourAccount, + textColor: businessLogicStrategy.getLoginMainButtonTextColor(), + fontSize: 16.sp, + fontWeight: FontWeight.bold, + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + ), + ], + ), + SizedBox(height: 8.w), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(width: 8.w), + Column( + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + setState(() { + isAgreement = !isAgreement; + }); + }, + child: Container( + padding: EdgeInsets.all(3.w), + child: Image.asset( + businessLogicStrategy.getLoginMainAgreementIcon(isAgreement), + width: 16.w, + height: 16.w, + ), + ), + ), + ], + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 3.w), + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: + ATAppLocalizations.of( + context, + )!.loginRepresentsAgreementTo, + style: TextStyle( + color: Color.fromRGBO(0, 0, 0, 0.8), + fontSize: sp(12), + fontWeight: FontWeight.w400, + ), + ), + TextSpan( + text: + ATAppLocalizations.of( + context, + )!.termsofService, + style: TextStyle( + color: businessLogicStrategy.getLoginMainLinkColor(), + fontSize: sp(12), + fontWeight: FontWeight.w500, + ), + recognizer: + TapGestureRecognizer() + ..onTap = () async { + Navigator.push( + context, + PopRoute( + child: WebViewPage( + title: + ATAppLocalizations.of( + context, + )!.termsofService, + url: + ATGlobalConfig.userAgreementUrl, + ), + ), + ); + }, + ), + TextSpan( + text: ATAppLocalizations.of(context)!.and, + style: TextStyle( + color: Color.fromRGBO(0, 0, 0, 0.8), + fontSize: sp(12), + fontWeight: FontWeight.w400, + ), + ), + TextSpan( + text: + ATAppLocalizations.of( + context, + )!.privaceyPolicy, + style: TextStyle( + color: businessLogicStrategy.getLoginMainLinkColor(), + fontSize: sp(12), + fontWeight: FontWeight.w500, + ), + recognizer: + TapGestureRecognizer() + ..onTap = () async { + Navigator.push( + context, + PopRoute( + child: WebViewPage( + title: + ATAppLocalizations.of( + context, + )!.privaceyPolicy, + url: + ATGlobalConfig.privacyAgreementUrl, + ), + ), + ); + }, + ), + ], + ), + textAlign: TextAlign.center, + ), + ), + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 45.w), + ], + ), + ), + ),], + ), + ); + } + + void _loginByGoogle() async { + if (!isAgreement) { + //弹出勾选确认 + showPolicyConfirm(); + return; + } + authProvider?.signIn(context, ATAuthType.GOOGLE.name); + } + + void showPolicyConfirm() { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.termsOfServicePrivacyPolicyTips, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + isAgreement = true; + setState(() {}); + }, + ); + }, + ); + } + + void _goLoginWithAccount() async { + if (!isAgreement) { + //弹出勾选确认 + showPolicyConfirm(); + return; + } + ATNavigatorUtils.push( + context, + LoginRouter.loginWithAccount, + replace: false, + ); + } + + void _loginByApple() { + if (!isAgreement) { + //弹出勾选确认 + showPolicyConfirm(); + return; + } + authProvider?.signIn(context, ATAuthType.APPLE.name); + } +} diff --git a/lib/chatvibe_features/auth/login_route.dart b/lib/chatvibe_features/auth/login_route.dart new file mode 100644 index 0000000..a4fe3fa --- /dev/null +++ b/lib/chatvibe_features/auth/login_route.dart @@ -0,0 +1,23 @@ +import 'package:fluro/fluro.dart'; +import 'package:aslan/chatvibe_features/auth/account/login_with_account_page.dart'; + +import 'package:aslan/chatvibe_core/routes/at_router_init.dart'; +import 'package:aslan/chatvibe_features/auth/edit/edit_profile_page.dart'; +import 'package:aslan/chatvibe_features/auth/login_page.dart'; + +class LoginRouter implements ATIRouterProvider { + static String login = '/login'; + static String editProfile = '/login/edit_profile'; + static String loginWithAccount = '/login/login_account'; + + @override + void initRouter(FluroRouter router) { + router.define(login, + handler: Handler(handlerFunc: (_, params) => LoginPage())); + router.define(editProfile, + handler: Handler(handlerFunc: (_, params) => EditProfilePage())); + router.define(loginWithAccount, + handler: Handler( + handlerFunc: (_, params) => LoginWithAccountPage())); + } +} diff --git a/lib/chatvibe_features/chat/activity/at_message_activity_page.dart b/lib/chatvibe_features/chat/activity/at_message_activity_page.dart new file mode 100644 index 0000000..f1bf047 --- /dev/null +++ b/lib/chatvibe_features/chat/activity/at_message_activity_page.dart @@ -0,0 +1,283 @@ +import 'package:extended_text/extended_text.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:provider/provider.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_public_message_page_res.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; + +class ATMessageActivityPage extends StatefulWidget { + @override + _MessageActivityPageState createState() => _MessageActivityPageState(); +} + +class _MessageActivityPageState extends State { + List records = []; + num index = 1; + final RefreshController _refreshController = RefreshController(); + final ScrollController _scrollController = ScrollController(); + RtmProvider? rtmProvider; + + BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; + + @override + void initState() { + super.initState(); + rtmProvider = Provider.of(context, listen: false); + rtmProvider?.onNewActivityMessageCurrentConversationListener = + _onNewMessage; + loadData(); + } + + _onNewMessage(Records? message) async { + if (message == null) { + return; + } + + int? index; + for (var element in records) { + if (element.id == message.id) { + index = records.indexOf(element); + continue; + } + } + if (index != null) { + records.removeAt(index); + records.insert(index, message); + } else { + records.insert(0, message); + } + _scrollController.jumpTo(0.0); + setState(() {}); + } + + @override + void dispose() { + rtmProvider?.onNewActivityMessageCurrentConversationListener = null; + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + _strategy.getATMessageChatPageRoomSettingBackground(), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.activity, + actions: [], + ), + body: SafeArea( + top: false, + child: SmartRefresher( + enablePullDown: false, + enablePullUp: true, + onLoading: () async { + index = index + 1; + loadData(); + }, + footer: CustomFooter( + height: 1, + builder: (context, mode) { + return SizedBox( + height: 1, + width: 1, + child: SizedBox(height: 1, width: 1), + ); + }, + ), + controller: _refreshController, + child: + records.isNotEmpty + ? Container( + child: CustomScrollView( + shrinkWrap: records.length > 3 ? false : true, + controller: _scrollController, + reverse: true, + physics: const ClampingScrollPhysics(), + // 禁用回弹效果 + slivers: [ + SliverList( + delegate: SliverChildBuilderDelegate( + (c, i) => _MessageItem(message: records[i]), + childCount: records.length, + ), + ), + ], + ), + ) + : mainEmpty(), + ), + ), + ), + ], + ); + } + + void loadData() { + AccountRepository().publicMessagePage("activity", index, 30).then(( + result, + ) { + var list = result.records ?? []; + if (list.isNotEmpty) { + records.addAll(list); + if (list.length == 30) { + _refreshController.loadComplete(); + } else { + _refreshController.loadNoData(); + } + } else { + _refreshController.loadNoData(); + } + setState(() {}); + }); + } +} + +class _MessageItem extends StatelessWidget { + final Records message; + late BuildContext context; + + _MessageItem({required this.message}); + + @override + Widget build(BuildContext context) { + this.context = context; + print('status:${message.status}'); + String time = ATMDateUtils.formatMessageTime( + context, + DateTime.fromMillisecondsSinceEpoch(int.parse(message.createdAt ?? "0")), + ); + return Container( + margin: EdgeInsets.symmetric(horizontal: 15.w, vertical: 8.w), + child: Column( + children: [ + Container( + margin: EdgeInsets.only(bottom: 12.w), + child: Row( + children: [ + Spacer(), + Container( + decoration: BoxDecoration( + color: Colors.black12, + borderRadius: BorderRadius.all(Radius.circular(height(13))), + ), + alignment: Alignment.center, + padding: EdgeInsets.symmetric( + horizontal: width(15), + vertical: 8.w, + ), + child: Text( + time, + style: TextStyle( + fontSize: sp(12), + color: Colors.white, + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + height: 1, + ), + ), + ), + Spacer(), + ], + ), + ), + _msg(message), + ], + ), + ); + } + + _msg(Records message) { + return GestureDetector( + child: Container( + alignment: AlignmentDirectional.centerStart, + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [BoxShadow(blurRadius: 2.w, color: Colors.black26)], + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + padding: EdgeInsets.all(8.0.w), + child: Column( + children: [ + Row( + children: [ + SizedBox(width: 5.w), + Expanded( + child: ExtendedText( + message.title ?? "", + overflow: TextOverflow.ellipsis, + maxLines:10, + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle( + fontSize: sp(16), + color: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + ), + SizedBox(width: 5.w), + ], + ), + message.imageUrl != null && message.imageUrl!.isNotEmpty + ? Container( + margin: EdgeInsets.all(5.w), + child: netImage( + url: message.imageUrl ?? "", + height: 110.w, + fit: BoxFit.contain, + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + ) + : Container(), + Row( + children: [ + SizedBox(width: 5.w), + Expanded( + child: ExtendedText( + message.content ?? "", + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle(fontSize: sp(14), color: Colors.black38), + ), + ), + SizedBox(width: 5.w), + ], + ), + + ], + ), + ), + onTap: () { + if (message.extraData != null && message.extraData?.link != null) { + if (message.extraData!.link!.startsWith("http") || + message.extraData!.link!.startsWith("https")) { + ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(message.extraData?.link ?? "")}&showTitle=false", + replace: false, + ); + } + } + }, + ); + } +} diff --git a/lib/chatvibe_features/chat/at_chat_route.dart b/lib/chatvibe_features/chat/at_chat_route.dart new file mode 100644 index 0000000..aa405c6 --- /dev/null +++ b/lib/chatvibe_features/chat/at_chat_route.dart @@ -0,0 +1,59 @@ +import 'dart:convert'; + +import 'package:fluro/fluro.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:aslan/chatvibe_features/chat/noti/message_notifcation_page.dart'; +import 'package:aslan/chatvibe_features/chat/system/message_system_page.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'; +import 'package:aslan/chatvibe_core/routes/at_router_init.dart'; +import 'package:aslan/chatvibe_features/chat/message_chat_page.dart'; + +import 'activity/at_message_activity_page.dart'; + +class ATChatRouter implements ATIRouterProvider { + static String chat = '/at/chat'; + static String systemChat = '/at/systemChat'; + static String dynamicMsg = '/at/dynamicMsg'; + static String notifcation = '/at/chat/notifcation'; + static String activity = '/at/chat/activity'; + + @override + void initRouter(FluroRouter router) { + router.define( + chat, + handler: Handler( + handlerFunc: + (_, params) => ATMessageChatPage( + conversation: V2TimConversation.fromJson( + jsonDecode(params['conversation']!.first), + ), + ), + ), + ); + + router.define( + systemChat, + handler: Handler( + handlerFunc: + (_, params) => MessageSystemPage( + conversation: V2TimConversation.fromJson( + jsonDecode(params['conversation']!.first), + ), + ), + ), + ); + router.define( + dynamicMsg, + handler: Handler(handlerFunc: (_, params) => Container()), + ); + + router.define( + notifcation, + handler: Handler(handlerFunc: (_, params) => MessageNotifcationPage()), + ); + router.define( + activity, + handler: Handler(handlerFunc: (_, params) => ATMessageActivityPage()), + ); + } +} diff --git a/lib/chatvibe_features/chat/message/at_message_friends_page.dart b/lib/chatvibe_features/chat/message/at_message_friends_page.dart new file mode 100644 index 0000000..25d2d7d --- /dev/null +++ b/lib/chatvibe_features/chat/message/at_message_friends_page.dart @@ -0,0 +1,320 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:provider/provider.dart'; +import 'package:tencent_cloud_chat_sdk/enum/conversation_type.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/message_friend_user_res.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_features/chat/at_chat_route.dart'; + +import '../../../chatvibe_ui/components/at_compontent.dart'; +import '../../../chatvibe_ui/components/at_page_list.dart'; +import '../../../chatvibe_ui/components/text/at_text.dart'; + +class MessageFriendsPage extends ATPageList { + @override + _MessageFriendsPageState createState() => _MessageFriendsPageState(); +} + +class _MessageFriendsPageState + extends ATPageListState { + BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; + + String? lastId; + late TextEditingController _textEditingController; + String search = ''; + Timer? _searchTimer; + + @override + void initState() { + super.initState(); + _textEditingController = TextEditingController(); + _textEditingController.addListener(() { + search = _textEditingController.text; + if (search.trim().isNotEmpty) { + _searchTimer?.cancel(); + _searchTimer = Timer(Duration(milliseconds: 550), () { + if (search.trim().isNotEmpty) { + _search(); + } + }); + } + }); + enablePullUp = true; + backgroundColor = Colors.transparent; + isShowDivider = true; + loadData(1); + } + + _search() { + AccountRepository() + .friendSearch(search) + .then((result) { + items = result; + loadComplete(); + setState(() {}); + }) + .catchError((e) { + loadComplete(); + setState(() {}); + }); + } + + @override + void dispose() { + _searchTimer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: Column( + children: [ + Container( + height: kToolbarHeight + ScreenUtil().statusBarHeight, + alignment: Alignment.centerLeft, + child: Row( + children: [ + Expanded( + child: searchWidget( + hint: ATAppLocalizations.of(context)!.inputUserId, + borderColor: Colors.white, + controller: _textEditingController, + ), + ), + SizedBox(width: 10.w), + GestureDetector( + child: text( + ATAppLocalizations.of(context)!.cancel, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.black45, + ), + onTap: () { + _textEditingController.text = ""; + search = ""; + loadData(1); + }, + ), + SizedBox(width: 10.w), + ], + ), + ), + Expanded(child: buildList(context)), + ], + ), + ); + } + + @override + Widget buildItem(MessageFriendUserRes res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration(), + child: Row( + children: [ + SizedBox(width: 10.w), + head(url: res?.userProfile?.userAvatar ?? "", width: 62.w), + SizedBox(width: 10.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + netImage( + url: + Provider.of(context, listen: false) + .getCountryByName( + res?.userProfile?.countryName ?? "", + ) + ?.nationalFlag ?? + "", + borderRadius: BorderRadius.all(Radius.circular(3.w)), + width: 19.w, + height: 14.w, + ), + SizedBox(width: 3.w), + chatvibeNickNameText( + maxWidth: 160.w, + textColor: Colors.black, + res.userProfile + ?.userNickname ?? + "", + fontSize: 14.sp, + type: + res.userProfile + ?.getVIP() + ?.name ?? + "", + needScroll: + (res.userProfile + ?.userNickname + ?.characters.length ?? + 0) > + 14, + ), + SizedBox(width: 3.w), + Container( + width: 47.w, + height: 24.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + _strategy.getGenderBackgroundImage(res?.userProfile?.userSex == 0), + ), + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + xb(res?.userProfile?.userSex), + text( + "${res?.userProfile?.age}", + textColor: Colors.white, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + ], + ), + ), + ], + ), + SizedBox(height: 4.w), + Row( + children: [ + GestureDetector( + child: Container( + padding: EdgeInsets.symmetric(vertical: 8.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + _strategy.getIdBackgroundImage(res?.userProfile?.hasSpecialId() ?? false), + ), + fit: BoxFit.fitWidth, + ), + ), + child: Row( + textDirection: TextDirection.ltr, + children: [ + SizedBox(width: 38.w), + text( + "${res?.userProfile?.getID()}", + fontSize: 12.sp, + textColor: Colors.white, + fontWeight: FontWeight.bold, + ), + SizedBox(width: 5.w), + Image.asset( + _strategy.getCopyIdIcon(), + width: 12.w, + height: 12.w, + ), + SizedBox(width: 8.w), + ], + ), + ), + onTap: () { + Clipboard.setData( + ClipboardData( + text: res?.userProfile?.getID() ?? "", + ), + ); + ATTts.show( + ATAppLocalizations.of(context)!.copiedToClipboard, + ); + }, + ), + SizedBox(width: 8.w), + getWealthLevel( + res?.userProfile?.wealthLevel ?? 0, + width: 58.w, + height: 30.w, + ), + SizedBox(width: 8.w), + getUserLevel( + res?.userProfile?.charmLevel ?? 0, + width: 58.w, + height: 30.w, + ), + ], + ), + ], + ), + ), + SizedBox(width: 15.w), + ], + ), + ), + onTap: () async { + var conversation = V2TimConversation( + type: ConversationType.V2TIM_C2C, + userID: res.userProfile?.id, + conversationID: '', + ); + var bool = await Provider.of( + context, + listen: false, + ).startConversation(conversation); + if (!bool) return; + var json = jsonEncode(conversation.toJson()); + ATNavigatorUtils.push( + context, + "${ATChatRouter.chat}?conversation=${Uri.encodeComponent(json)}", + ); + }, + ); + } + + @override + builderDivider() { + return Divider( + height: 22.w, + color: Color(0xffF2F2F2), + indent: 15.w, + endIndent: 15.w, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + if (_textEditingController.text.isNotEmpty) { + _search(); + } else { + if (page == 1) { + lastId = null; + } + try { + var followList = await AccountRepository().friendList(lastId: lastId); + if (followList.isNotEmpty) { + lastId = followList.last.id; + } + onSuccess(followList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + } +} diff --git a/lib/chatvibe_features/chat/message/at_message_page.dart b/lib/chatvibe_features/chat/message/at_message_page.dart new file mode 100644 index 0000000..f2b09c3 --- /dev/null +++ b/lib/chatvibe_features/chat/message/at_message_page.dart @@ -0,0 +1,413 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:tencent_cloud_chat_sdk/enum/conversation_type.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'; +import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/msg/message_conversation_list_page.dart'; +import 'package:aslan/chatvibe_features/chat/at_chat_route.dart'; +import 'package:aslan/chatvibe_features/chat/message/at_message_friends_page.dart'; + +import '../../index/main_route.dart'; + +///消息 +class ATMessagePage extends StatefulWidget { + bool isFromRoom = false; + + ATMessagePage({this.isFromRoom = false}); + + @override + _MessagePageState createState() => _MessagePageState(isFromRoom); +} + +class _MessagePageState extends State { + BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; + bool isFromRoom = false; + bool showSystemAnnouncementTips = true; + + _MessagePageState(this.isFromRoom); + + @override + void initState() { + super.initState(); + showSystemAnnouncementTips = DataPersistence.getBool( + "${AccountStorage().getCurrentUser()?.userProfile?.account}-ShowSystemAnnouncementTips", + defaultValue: true, + ); + _checkLogin(); + } + + void _checkLogin() async { + V2TimValueCallback getLoginStatusRes = + await TencentImSDKPlugin.v2TIMManager.getLoginStatus(); + if (getLoginStatusRes.code == 0) { + var status = getLoginStatusRes.data; // getLoginStatusRes.data为用户登录状态值 + if (status == 1) { + // 已登录 + ///处理有时候拉不到最后一条消息 + Provider.of( + context, + listen: false, + ).initConversation().then((value) { + Future.delayed(Duration(milliseconds: 550), () { + Provider.of( + context, + listen: false, + ).initConversation().then((value) { + setState(() {}); + }); + }); + }); + } else if (status == 2) { + // 登录中 + } else if (status == 3) { + // 未登录 + Provider.of( + context, + listen: false, + ).loginTencetRtm(context).then((value) { + _checkLogin(); + }); + } + setState(() {}); + } + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + widget.isFromRoom + ? Image.asset( + _strategy.getMessagePageIndexMaskIcon(), + width: ScreenUtil().screenWidth, + fit: BoxFit.cover, + ) + : Container(), + DefaultTabController( + length: 2, + child: Scaffold( + backgroundColor: Colors.transparent, + appBar: AppBar( + backgroundColor: Colors.transparent, + title: TabBar( + tabs: + [ + ATAppLocalizations.of(context)!.message, + ] + .map( + (e) => Padding( + padding: EdgeInsets.only(bottom: 3.w, top: 4.w), + child: Text(e), + ), + ) + .toList(), + dividerColor: Colors.transparent, + labelStyle: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.bold, + ), + unselectedLabelStyle: TextStyle( + fontSize: 15.sp, + color: Color.fromRGBO(66, 12, 40, 0.40), + fontWeight: FontWeight.normal, + ), + indicator: BoxDecoration(), + //TabBarIndicator(), + isScrollable: false, + labelColor: Colors.black, + unselectedLabelColor: Color.fromRGBO(66, 12, 40, 0.40), + tabAlignment: TabAlignment.center, + ), + actions: [ + ATDebounceWidget( + child: Container( + padding: EdgeInsetsDirectional.only(end: 10.w), + child: Image.asset( + "atu_images/index/at_icon_serach.png", + width: 20.w, + height: 20.w, + ), + ), + onTap: () { + ATNavigatorUtils.push(context, MainRoute.mainSearch); + }, + ), + ], + leading: Container(), + ), + body: TabBarView( + children: [ + Consumer( + builder: (_, provider, __) { + return Column( + children: [ + _headMenu(provider), + Expanded( + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(8.w), + topRight: Radius.circular(8.w), + ), + ), + margin: EdgeInsets.only( + top: 10.w, + left: 8.w, + right: 8.w, + ), + child: MessageConversationListPage(false), + ), + ), + ], + ); + }, + ), + ], + ), + ), + ), + ], + ); + } + + Widget _headMenu(RtmProvider provider) { + return Container( + height: 92.w, + margin: EdgeInsets.only(top: 15.w, bottom: 0.w, left: 8.w, right: 8.w), + padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 6.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: ATDebounceWidget( + child: + provider.activityUnReadCount > 0 + ? Badge( + backgroundColor: Colors.red, + label: text( + "${provider.activityUnReadCount > 99 ? "99+" : provider.activityUnReadCount}", + fontSize: 9.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + alignment: AlignmentDirectional.topEnd, + offset: Offset( + ATGlobalConfig.lang == "ar" ? 5 : -15, + 0, + ), + child: Container( + alignment: Alignment.center, + child: Column( + children: [ + SizedBox(height: 3.w), + Image.asset( + _strategy.getMessagePageActivityMessageIcon(), + width: 56.w, + height: 56.w, + ), + text( + ATAppLocalizations.of(context)!.activity, + textColor: Colors.black, + fontSize: 12.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + ), + ) + : Container( + alignment: Alignment.center, + child: Column( + children: [ + SizedBox(height: 3.w), + Image.asset( + _strategy.getMessagePageActivityMessageIcon(), + width: 56.w, + height: 56.w, + ), + text( + ATAppLocalizations.of(context)!.activity, + textColor: Colors.black, + fontSize: 12.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + ), + onTap: () { + provider.updateActivityCount(0); + ATNavigatorUtils.push( + context, + ATChatRouter.activity, + replace: false, + ); + }, + ), + ), + Expanded( + child: ATDebounceWidget( + child: + provider.systemUnReadCount > 0 + ? Badge( + backgroundColor: Colors.red, + label: text( + "${provider.systemUnReadCount > 99 ? "99+" : provider.systemUnReadCount}", + fontSize: 9.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + alignment: AlignmentDirectional.topEnd, + offset: Offset( + ATGlobalConfig.lang == "ar" ? 5 : -15, + 0, + ), + child: Container( + alignment: Alignment.center, + child: Column( + children: [ + SizedBox(height: 3.w), + Image.asset( + _strategy.getMessagePageSystemMessageIcon(), + width: 56.w, + height: 56.w, + ), + text( + ATAppLocalizations.of(context)!.system, + textColor: Colors.black, + fontSize: 12.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + ), + ) + : Container( + alignment: Alignment.center, + child: Column( + children: [ + SizedBox(height: 3.w), + Image.asset( + _strategy.getMessagePageSystemMessageIcon(), + width: 56.w, + height: 56.w, + ), + text( + ATAppLocalizations.of(context)!.system, + textColor: Colors.black, + fontSize: 12.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + ), + onTap: () async { + var conversation = V2TimConversation( + type: ConversationType.V2TIM_C2C, + userID: "atyou-admin", + conversationID: ATGlobalConfig.imAdmin, + ); + provider.updateSystemCount(0); + var bool = await provider.startConversation(conversation); + if (!bool) return; + var json = jsonEncode(conversation.toJson()); + ATNavigatorUtils.push( + context, + "${ATChatRouter.systemChat}?conversation=${Uri.encodeComponent(json)}", + ); + }, + ), + ), + Expanded( + child: ATDebounceWidget( + child: + provider.notifcationUnReadCount > 0 + ? Badge( + backgroundColor: Colors.red, + label: text( + "${provider.notifcationUnReadCount > 99 ? "99+" : provider.notifcationUnReadCount}", + fontSize: 9.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + offset: Offset( + ATGlobalConfig.lang == "ar" ? -2 : -8, + 0, + ), + alignment: AlignmentDirectional.topEnd, + child: Container( + alignment: Alignment.center, + child: Column( + children: [ + SizedBox(height: 3.w), + Image.asset( + _strategy + .getMessagePageNotificationMessageIcon(), + width: 56.w, + height: 56.w, + ), + text( + ATAppLocalizations.of(context)!.notifcation, + textColor: Colors.black, + fontSize: 12.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + ), + ) + : Container( + alignment: Alignment.center, + child: Column( + children: [ + SizedBox(height: 3.w), + Image.asset( + _strategy.getMessagePageNotificationMessageIcon(), + width: 56.w, + height: 56.w, + ), + text( + ATAppLocalizations.of(context)!.notifcation, + textColor: Colors.black, + fontSize: 12.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + ), + onTap: () { + provider.updateNotificationCount(0); + ATNavigatorUtils.push( + context, + ATChatRouter.notifcation, + replace: false, + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/chatvibe_features/chat/message_chat_page.dart b/lib/chatvibe_features/chat/message_chat_page.dart new file mode 100644 index 0000000..2b26de4 --- /dev/null +++ b/lib/chatvibe_features/chat/message_chat_page.dart @@ -0,0 +1,2470 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:extended_image/extended_image.dart' + show ExtendedImage, ExtendedRawImage, ExtendedImageState, LoadState; +import 'package:extended_text/extended_text.dart'; +import 'package:extended_text_field/extended_text_field.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/constants/at_emoji_datas.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_core/utilities/at_message_utils.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; +import 'package:tencent_cloud_chat_sdk/enum/V2TimAdvancedMsgListener.dart'; +import 'package:tencent_cloud_chat_sdk/enum/message_elem_type.dart'; +import 'package:tencent_cloud_chat_sdk/enum/message_status.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_custom_elem.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_elem.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_image_elem.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_extension.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_online_url.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_text_elem.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_video_elem.dart'; +import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart'; + +import 'package:aslan/config/pickImage.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_keybord_util.dart'; +import 'package:aslan/chatvibe_core/utilities/at_message_notifier.dart'; +import 'package:aslan/chatvibe_core/utilities/at_path_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_domain/usecases/custom_tab_selector.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +import '../../chatvibe_domain/models/res/at_user_red_packet_send_res.dart'; + +class ATMessageChatPage extends StatefulWidget { + final V2TimConversation? conversation; + final bool shrinkWrap; + + //是否是房间内聊天界面。 + final bool inRoom; + + const ATMessageChatPage({ + Key? key, + this.conversation, + this.shrinkWrap = false, + this.inRoom = false, + }) : super(key: key); + + @override + _ATMessageChatPageState createState() => _ATMessageChatPageState(); +} + +class _ATMessageChatPageState extends State { + BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; + + final TextEditingController _textController = TextEditingController(); + final ScrollController _scrollController = ScrollController(); + final RefreshController _refreshController = RefreshController(); + + // FlutterSoundPlayer? flutterSound; + RtmProvider? rtmProvider; + V2TimConversation? currentConversation; + List currentConversationMessageList = []; + final FocusNode _focusNode = FocusNode(); + + ///是否显示工具栏 + bool showTools = false; + bool showEmoji = false; + + bool showDoNotClickUnfamiliarTips = true; + + ///互相关注才能互相发送消息 + bool canSendMsg = false; + ChatVibeUserProfile? friend; + + ///是否显示发送按钮 + bool showSend = false; + + List coinsTitles = ["100", "1000", "10000", "50000"]; + String selecteCoins = "100"; + + @override + void initState() { + super.initState(); + ATMessageNotifier.canPlay = false; + ATMessageUtils.redPacketFutureCache.clear(); + + // flutterSound = FlutterSoundPlayer(); + + rtmProvider = Provider.of(context, listen: false); + currentConversation = widget.conversation; + rtmProvider?.onMessageRecvC2CReadListener = _onMessageRecvC2CRead; + rtmProvider?.onRevokeMessageListener = _onRevokeMessage; + rtmProvider?.onNewMessageCurrentConversationListener = _onNewMessage; + loadMsg(); + + if (friend == null) { + loadFriend(); + } + + _focusNode.addListener(() { + if (_focusNode.hasFocus) { + setState(() { + showTools = false; + showEmoji = false; + }); + } + }); + } + + @override + void dispose() { + rtmProvider?.onMessageRecvC2CReadListener = null; + rtmProvider?.onRevokeMessageListener = null; + rtmProvider?.onNewMessageCurrentConversationListener = null; + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (_, provider, __) { + return WillPopScope( + onWillPop: () { + if (showEmoji || showTools) { + setState(() { + showEmoji = false; + showTools = false; + }); + return Future.value(false); + } + ATNavigatorUtils.goBack(context); + return Future.value(true); + }, + child: Stack( + children: [ + if (!widget.inRoom) + // 渐变背景 + Positioned( + left: 0, + right: 0, + top: 0, + child: Column( + children: [ + SizedBox( + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + child: Image.asset( + _strategy.getATMessageChatPageRoomSettingBackground(), + fit: BoxFit.fill, + ), + ), + ], + ), + ), + Scaffold( + //backgroundColor: widget.inRoom ? Colors.transparent: Color(0xfff8f8f8), + backgroundColor: Colors.transparent, + // appBar: _appBar(), + appBar: null, + body: SafeArea( + top: widget.shrinkWrap ? false : true, + bottom: false, + child: Column( + children: [ + Row( + children: [ + !widget.inRoom + ? GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + Navigator.pop(context); + }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 10.w, + vertical: 10.w, + ), + child: Icon( + ATGlobalConfig.lang == "ar" + ? Icons.keyboard_arrow_right + : Icons.keyboard_arrow_left, + size: 28.w, + color: Colors.black, + ), + ), + ) + : Container(), + SizedBox(width: 5.w), + Expanded( + child: Container( + alignment: AlignmentDirectional.center, + child: chatvibeNickNameText( + friend?.userNickname ?? "", + fontSize: 14.sp, + fontWeight: FontWeight.w500, + textColor: Colors.black, + type: friend?.getVIP()?.name ?? "", + needScroll: + (friend?.userNickname?.characters.length ?? + 0) > + 22, + ), + ), + ), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + ATNavigatorUtils.push( + context, + replace: true, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == friend?.id}&tageId=${friend?.id}", + ); + }, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10.w), + child: Icon( + Icons.more_vert, + size: 22.w, + color: Colors.black, + ), + ), + ), + ], + ), + Expanded(child: _msgList()), + Container( + decoration: BoxDecoration( + //color: Colors.white, + //border: Border(top: BorderSide(color: Color(0xfff1f1f1))) + ), + child: SafeArea( + top: false, + child: Column( + children: [ + if (currentConversation?.conversationID != + "administrator") + _input(), + _tools(provider), + _emoji(), + // _fahongbao(), + ], + ), + ), + ), + ], + ), + ), + ), + ], + ), + ); + }, + ); + } + + void loadFriend() { + ATLoadingManager.exhibitOperation(); + Future.wait([ + AccountRepository().loadUserInfo("${currentConversation?.userID}"), + AccountRepository().friendRelationCheck( + "${currentConversation?.userID}", + ), + ]) + .then((result) { + ATLoadingManager.veilRoutine(); + friend = result[0] as ChatVibeUserProfile; + canSendMsg = result[1] as bool; + setState(() {}); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } + + ///消息列表 + Widget _msgList() { + print('xiaoxiliebiao:${currentConversationMessageList.length}'); + return SmartRefresher( + enablePullDown: false, + enablePullUp: true, + onLoading: () async { + print('onLoading'); + if (currentConversationMessageList.isNotEmpty) { + // 拉取单聊历史消息 + // 首次拉取,lastMsgID 设置为 null + // 再次拉取时,lastMsgID 可以使用返回的消息列表中的最后一条消息的id + V2TimValueCallback> v2timValueCallback = + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .getC2CHistoryMessageList( + userID: currentConversation!.userID ?? "", + count: 30, + lastMsgID: currentConversationMessageList.last.msgID, + ); + List messages = + v2timValueCallback.data as List; + print('加载前:${currentConversationMessageList.length}'); + currentConversationMessageList.addAll(messages); + print('加载后:${currentConversationMessageList.length}'); + if (messages.length == 30) { + _refreshController.loadComplete(); + } else { + _refreshController.loadNoData(); + } + } else { + _refreshController.loadNoData(); + } + setState(() {}); + }, + footer: CustomFooter( + height: 1, + builder: (context, mode) { + return SizedBox( + height: 1, + width: 1, + child: SizedBox(height: 1, width: 1), + ); + }, + ), + controller: _refreshController, + child: CustomScrollView( + shrinkWrap: true, + controller: _scrollController, + reverse: true, + physics: const ClampingScrollPhysics(), + // 禁用回弹效果 + slivers: [ + SliverList( + delegate: SliverChildBuilderDelegate( + (c, i) => _MessageItem( + message: currentConversationMessageList[i], + preMessage: + i < currentConversationMessageList.length - 1 + ? currentConversationMessageList[i + 1] + : null, + isSystem: + currentConversationMessageList[i].sender == "administrator", + friend: friend, + currentConversationMessageList: currentConversationMessageList, + updateCall: () { + setState(() {}); + }, + ), + childCount: currentConversationMessageList.length, + ), + ), + ], + ), + ); + } + + Future loadMsg() async { + // 1. 在初始化时,添加高级消息监听器 + TencentImSDKPlugin.v2TIMManager.getMessageManager().addAdvancedMsgListener( + listener: V2TimAdvancedMsgListener( + // 监听消息扩展更新 + onRecvMessageExtensionsChanged: (msgID, extensions) { + for (var ext in extensions) { + if (ext.extensionKey == "packetID") { + String packetID = ext.extensionValue; + ATMessageUtils.redPacketFutureCache[packetID] = + AccountRepository().userRedPacketDetail(packetID); + setState(() {}); + break; + } + } + }, + ), + ); + V2TimValueCallback> v2timValueCallback = + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .getC2CHistoryMessageList( + userID: currentConversation!.userID!, + count: 100, + lastMsgID: null, + ); + List messages = v2timValueCallback.data!; + // List messages = await FTIM.getMessageManager().getMessages(conversation: currentConversation); + print("messages : ${messages?.length ?? 0}"); + currentConversationMessageList ??= []; + currentConversationMessageList?.clear(); + + for (var msg in messages) { + if (!msg.isSelf! && msg.isPeerRead!) { + TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .sendMessageReadReceipts(messageIDList: [msg.msgID!]); + } + } + currentConversationMessageList.insertAll(0, messages ?? []); + setState(() {}); + } + + ///输入栏 + Widget _input() { + return Consumer( + builder: + (_, provider, __) => Container( + color: Colors.white, + height: 56.w, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(width: 15.w), + Expanded( + child: Container( + height: 34.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(52.w), + color: Color(0xffEDEDED), + ), + child: Row( + children: [ + Expanded( + child: ExtendedTextField( + controller: _textController, + focusNode: _focusNode, + onChanged: (s) { + setState(() { + showSend = _textController.text.isNotEmpty; + }); + }, + onSubmitted: (s) { + if (s.isNotEmpty) { + sendMsg(s); + showSend = false; + setState(() {}); + _scrollController.jumpTo(0.0); + _textController.clear(); + } + }, + decoration: InputDecoration( + hintText: + ATAppLocalizations.of( + context, + )!.clickHereToStartChatting, + hintStyle: TextStyle( + color: Color(0xff999999), + fontSize: 14.sp, + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 15, + ), + counterText: '', + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + ), + style: TextStyle( + fontSize: ScreenUtil().setSp(14), + color: Colors.black.withOpacity(1), + ), + ), + ), + //原来的emoji按钮 + //SizedBox(width: 10.w,), + ], + ), + ), + ), + SizedBox(width: 10.w), + !showEmoji + ? GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + ATKeybordUtil.conceal(context); + setState(() { + if (showTools) { + showTools = false; + } + showEmoji = !showEmoji; + }); + }, + child: Image.asset( + _strategy.getATMessageChatPageEmojiIcon(), + width: 24.w, + //color: Colors.black, + fit: BoxFit.fill, + ), + ) + : Container(), + showEmoji + ? GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + ATKeybordUtil.conceal(context); + setState(() { + if (showTools) { + showTools = false; + } + showEmoji = !showEmoji; + }); + }, + child: Image.asset( + _strategy.getATMessageChatPageChatKeyboardIcon(), + width: 24.w, + //color: Colors.black, + fit: BoxFit.fill, + ), + ) + : Container(), + if (!showSend) SizedBox(width: 12.w), + if (showSend) SizedBox(width: 5.w), + Visibility( + visible: !showSend, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + ATKeybordUtil.conceal(context); + setState(() { + if (showEmoji) { + showEmoji = false; + } + showTools = !showTools; + }); + }, + child: Image.asset( + _strategy.getATMessageChatPageAddIcon(), + width: 24.w, + //color: Colors.black, + fit: BoxFit.fill, + ), + ), + ), + Visibility( + visible: showSend, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () async { + _scrollController.jumpTo(0.0); + if (_textController.text.isNotEmpty) { + ATKeybordUtil.conceal(context); + sendMsg(_textController.text); + showSend = false; + setState(() {}); + _textController.clear(); + } + }, + child: Image.asset( + _strategy.getATMessageChatPageSendMessageIcon(), + height: 22.w, + width: 32.w, + ), + ), + ), + SizedBox(width: 15.w), + ], + ), + ), + ); + } + + ///发消息 + sendMsg(String msg) async { + if (!canSendMsg) { + ATTts.show(ATAppLocalizations.of(context)!.canSendMsgTips); + return; + } + Provider.of( + context, + listen: false, + ).sendC2CTextMsg(msg, currentConversation!); + } + + ///发消息 + sendCustomMsg(String msg, String extension) async { + if (!canSendMsg) { + ATTts.show(ATAppLocalizations.of(context)!.canSendMsgTips); + return; + } + Provider.of( + context, + listen: false, + ).sendC2CCustomMsg(msg, currentConversation!, extension); + } + + ///工具栏 + Widget _tools(RtmProvider provider) { + return Visibility( + visible: showTools, + child: Container( + color: Colors.white, + padding: EdgeInsets.only(bottom: 18.w, top: 5.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + SizedBox(width: 20.w), + Expanded( + child: _toolsItem( + _strategy.getATMessageChatPageCameraIcon(), + ATAppLocalizations.of(context)!.camera, + () async { + if (!canSendMsg) { + ATTts.show(ATAppLocalizations.of(context)!.canSendMsgTips); + return; + } + var pick = await ImagePick.pickFromCamera(context); + if (pick != null) { + provider.sendImageMsg( + file: pick, + conversation: currentConversation!, + ); + } + }, + ), + ), + Expanded( + child: _toolsItem( + _strategy.getATMessageChatPagePictureIcon(), + ATAppLocalizations.of(context)!.album, + () async { + if (!canSendMsg) { + ATTts.show(ATAppLocalizations.of(context)!.canSendMsgTips); + return; + } + final List? result = + await ImagePick.pickPicAndVideoFromGallery(context); + + if (result == null) return; // 用户取消选择 + if (result.isNotEmpty) { + provider.sendImageMsg( + selectedList: result, + conversation: currentConversation!, + ); + } + }, + ), + ), + Expanded(flex: 1, child: SizedBox()), + Expanded(flex: 1, child: SizedBox()), + ], + ), + ), + ); + } + + _emoji() { + return Visibility( + visible: showEmoji, + child: Container( + color: Colors.white, + height: 150.w, + child: GridView.builder( + padding: EdgeInsets.symmetric(horizontal: 15.w), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 8, + crossAxisSpacing: 8.w, + mainAxisSpacing: 8.w, + ), + itemBuilder: (c, index) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + var text = ATEmojiDatas.smileys[index]; + final TextEditingValue value = _textController.value; + _textController.value = value.copyWith( + text: value.text + text, + selection: TextSelection.fromPosition( + TextPosition(offset: text.length), + ), + ); + showSend = true; + setState(() {}); + }, + child: Padding( + padding: EdgeInsets.all(4.0.w), + child: ExtendedText( + ATEmojiDatas.smileys[index], + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle(fontSize: sp(15)), + ), + ), + ); + }, + itemCount: ATEmojiDatas.smileys.length, + ), + ), + ); + } + + ///工具项 + _toolsItem(String image, String title, Function() onclick) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: onclick, + child: Column( + children: [ + Image.asset(image, width: 50.w, fit: BoxFit.fitWidth), + SizedBox(height: 9.w), + Text(title, style: TextStyle(fontSize: 11.sp, color: Colors.black)), + ], + ), + ); + } + + _onRevokeMessage(String msgId) { + int index = 0; + for (var msg in currentConversationMessageList) { + if (msgId == msg.msgID) { + break; + } + index = index + 1; + } + currentConversationMessageList.removeAt(index); + setState(() {}); + } + + _onMessageRecvC2CRead(List messageIDList) { + for (var msg in currentConversationMessageList) { + for (var id in messageIDList) { + if (id == msg.msgID) { + msg.isPeerRead = true; + } + } + } + setState(() {}); + } + + _onNewMessage(V2TimMessage? message, {String? msgId}) async { + if (message == null) { + return; + } + if (message.userID != currentConversation?.userID) return; + + int? index; + for (var element in currentConversationMessageList) { + if (msgId != null) { + if (msgId == element.msgID) { + element.status = MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL; + setState(() {}); + continue; + } + } else { + if (element.msgID == message.msgID) { + index = currentConversationMessageList.indexOf(element); + continue; + } + } + } + if (msgId != null) { + return; + } + if (index != null) { + currentConversationMessageList.removeAt(index!); + currentConversationMessageList.insert(index!, message); + } else { + currentConversationMessageList.insert(0, message); + } + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .markC2CMessageAsRead(userID: currentConversation!.userID!); + //发送已读回执 + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .sendMessageReadReceipts(messageIDList: [message.msgID!]); + setState(() {}); + // await FTIM.getContactManager().setReadMessage(currentConversation); + } + + void _showSendHongbaoDialog() { + SmartDialog.show( + tag: "showSendHongbaoDialog", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return SafeArea( + top: false, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 15.w), + height: 350.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + _strategy.getATMessageChatPageRedEnvelopeMessageBackground(), + ), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + SizedBox(height: 120.w), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text( + ATAppLocalizations.of(context)!.redEnvelopeTips1, + maxLines: 2, + textColor: Colors.white, + fontSize: 15.sp, + ), + ], + ), + SizedBox(height: 10.w), + CustomTabSelector( + options: coinsTitles, + initialIndex: 0, + onChanged: (index) { + selecteCoins = coinsTitles[index]; + }, + ), + SizedBox(height: 10.w), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: text( + ATAppLocalizations.of(context)!.msgSendRedEnvelopeTips, + maxLines: 4, + textColor: Colors.white, + fontSize: 13.sp, + ), + ), + ], + ), + Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + child: Container( + alignment: Alignment.center, + width: 135.w, + height: 38.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + _strategy + .getATMessageChatPageRedEnvelopeConfigTabButtonIcon(), + ), + fit: BoxFit.fill, + ), + ), + child: text( + ATAppLocalizations.of(context)!.send, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showConfirmDialog"); + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.sendRedPackConfirmTips, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + SmartDialog.dismiss( + tag: "showSendHongbaoDialog", + ); + ATLoadingManager.exhibitOperation(); + AccountRepository() + .userRedPacketSend( + friend?.id ?? "", + selecteCoins, + ) + .then((res) { + ATLoadingManager.veilRoutine(); + sendCustomMsg( + jsonEncode(res.toJson()), + "Red_Envelopes", + ); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + }, + ); + }, + ); + }, + ), + ], + ), + SizedBox(height: 20.w), + ], + ), + ), + ); + }, + ); + } +} + +class _MessageItem extends StatelessWidget { + final V2TimMessage message; + final V2TimMessage? preMessage; + + BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; + + // final FTIMUserProfile userProfile; + final ChatVibeUserProfile? friend; + bool isSystem = false; + List currentConversationMessageList = []; + Function updateCall; + + ///上一条 + late BuildContext context; + + _MessageItem({ + required this.message, + this.preMessage, + // this.userProfile, + this.isSystem = false, + required this.currentConversationMessageList, + this.friend, + required this.updateCall, + }); + + @override + Widget build(BuildContext context) { + this.context = context; + ChatVibeUserProfile? me = AccountStorage().getCurrentUser()?.userProfile; + bool showTime = true; + int timestamp = (message.timestamp ?? 0) * 1000; + int preTimestamp = (preMessage?.timestamp ?? 0) * 1000; + if (preMessage != null) { + ///5分钟以内 不显示 + // print('timestamp:$timestamp===preTimestamp:${preTimestamp}'); + if (DateTime.fromMillisecondsSinceEpoch(timestamp) + .difference(DateTime.fromMillisecondsSinceEpoch(preTimestamp)) + .inMilliseconds < + 1000 * 60 * 5) { + showTime = false; + } + } + String time = ATMDateUtils.formatMessageTime( + context, + DateTime.fromMillisecondsSinceEpoch(timestamp ?? 0), + ); + if (message.status == MessageStatus.V2TIM_MSG_STATUS_LOCAL_REVOKED) { + return Container( + alignment: AlignmentDirectional.center, + padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w), + child: text( + ATAppLocalizations.of(context)!.messageHasBeenRecalled, + fontSize: 12.sp, + textColor: Colors.black38, + fontWeight: FontWeight.w400, + ), + ); + } + return Container( + margin: EdgeInsets.symmetric(horizontal: 15.w, vertical: 8.w), + child: Column( + crossAxisAlignment: + !message.isSelf! + ? CrossAxisAlignment.start + : CrossAxisAlignment.end, + // textDirection: message.isSelf ? TextDirection.rtl : TextDirection.ltr, + children: [ + Visibility( + visible: showTime, + child: Container( + margin: EdgeInsets.only(bottom: 12.w), + child: Row( + children: [ + Spacer(), + Container( + alignment: Alignment.center, + padding: EdgeInsets.symmetric( + horizontal: width(15), + vertical: 8.w, + ), + decoration: BoxDecoration( + //color: Color(0xFFEAEFF7), + borderRadius: BorderRadius.all( + Radius.circular(height(13)), + ), + ), + child: Text( + time, + style: TextStyle( + fontSize: sp(12), + color: Colors.black38, + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + height: 1, + ), + ), + ), + Spacer(), + ], + ), + ), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + textDirection: + message.isSelf! ? TextDirection.rtl : TextDirection.ltr, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (isSystem) { + return; + } + ATNavigatorUtils.push( + context, + replace: + AccountStorage().getCurrentUser()?.userProfile?.id != + message.sender, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == message.sender}&tageId=${message.sender}", + ); + }, + child: + message.sender == "administrator" || + message.sender == "customer" + ? ExtendedImage.asset( + _strategy.getATMessageChatPageSystemHeadImage(), + width: 45.w, + shape: BoxShape.circle, + fit: BoxFit.cover, + ) + : netImage( + url: + message.isSelf! + ? (me?.userAvatar ?? "") + : (friend?.userAvatar ?? ""), + shape: BoxShape.circle, + width: width(45), + ), + ), + // 他人消息的阴影间隔是10dp + Container(width: 8.w), + Row( + textDirection: + message.isSelf! ? TextDirection.rtl : TextDirection.ltr, + children: [ + Container( + alignment: + message.isSelf! + ? AlignmentDirectional.topEnd + : AlignmentDirectional.topStart, + margin: EdgeInsets.only(top: 4.w), + child: _msg(), + ), + SizedBox(width: 6.w), + Visibility( + visible: + message.status == + MessageStatus.V2TIM_MSG_STATUS_SENDING, + child: CupertinoActivityIndicator(), + ), + message.customElem?.extension == "Red_Envelopes" + ? Container() + : (message.isSelf! + ? (message.status == + MessageStatus.V2TIM_MSG_STATUS_SEND_SUCC + ? (message.isPeerRead! + ? Text( + ATAppLocalizations.of(context)!.read, + style: TextStyle( + fontSize: sp(12), + color: Colors.grey, + decoration: TextDecoration.none, + height: 1, + ), + ) + : Text( + ATAppLocalizations.of(context)!.unread, + style: TextStyle( + fontSize: sp(12), + color: Colors.grey, + decoration: TextDecoration.none, + height: 1, + ), + )) + : Container()) + : Container()), + + Visibility( + visible: + message.status == + MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL, + child: Icon(Icons.error, color: Colors.red), + ), + ], + ), + Spacer(), + ], + ), + ], + ), + ); + } + + _msg() { + V2TIMElem? elem = message.textElem; + if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_TEXT) { + V2TimTextElem textElem = elem as V2TimTextElem; + String content = textElem.text ?? ""; + } else if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_IMAGE) { + V2TimImageElem imageElem = message.imageElem as V2TimImageElem; + var image; + if (imageElem.path!.length > 2 && + File("${imageElem.path}").existsSync()) { + image = Image.file(File(imageElem.path!), fit: BoxFit.cover); + } else { + image = ExtendedImage.network( + '${imageElem.imageList![1]?.url}', + fit: BoxFit.cover, + width: double.infinity, + cache: true, + shape: BoxShape.rectangle, + borderRadius: BorderRadius.all(Radius.circular(width(6))), + loadStateChanged: (ExtendedImageState state) { + if (state.extendedImageLoadState == LoadState.completed) { + return ExtendedRawImage( + image: state.extendedImageInfo?.image, + fit: BoxFit.cover, + ); + } else { + return Image.asset( + _strategy.getATMessageChatPageLoadingIcon(), + fit: BoxFit.cover, + ); + } + }, + ); + } + return Builder( + builder: (ct) { + return GestureDetector( + onTap: () { + List ims = []; + int initialIndex = 0; + int index = 0; + for (var imageMsg in currentConversationMessageList) { + if (imageMsg.elemType == + MessageElemType.V2TIM_ELEM_TYPE_IMAGE) { + String path = imageMsg.imageElem?.imageList![1]?.url ?? ""; + if (path.isNotEmpty) { + ims.add(imageMsg.imageElem?.imageList![1]?.url ?? ""); + if (imageMsg.msgID == message.msgID) { + initialIndex = index; + } + index = index + 1; + } + } + } + String encodedUrls = Uri.encodeComponent(jsonEncode(ims)); + ATNavigatorUtils.push( + context, + "${MainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=$initialIndex", + ); + }, + onLongPress: () { + _showMsgItemMenu(ct, ""); + }, + child: Container( + constraints: BoxConstraints(maxWidth: 200.w), + child: ClipRRect( + borderRadius: BorderRadius.circular(6.w), + child: image, + ), + ), + ); + }, + ); + } else if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_VIDEO) { + V2TimVideoElem videoElem = message.videoElem as V2TimVideoElem; + if (videoElem.snapshotPath != null && + videoElem.snapshotPath!.isNotEmpty) { + return Builder( + builder: (ct) { + return GestureDetector( + child: Container( + constraints: BoxConstraints(maxWidth: 200.w), + child: ClipRRect( + borderRadius: BorderRadius.circular(6.w), + child: Stack( + alignment: Alignment.center, + children: [ + Image.file( + File(videoElem.snapshotPath!), + fit: BoxFit.cover, + ), + Icon(Icons.play_circle, color: Colors.white, size: 55.w), + ], + ), + ), + ), + onTap: () { + String encodedVideoUrl = Uri.encodeComponent( + videoElem.videoPath ?? "", + ); + ATNavigatorUtils.push( + context, + "${MainRoute.videoPlayer}?videoUrl=$encodedVideoUrl", + ); + }, + onLongPress: () { + _showMsgItemMenu(ct, ""); + }, + ); + }, + ); + } else { + return FutureBuilder>( + // 假设 getMessageOnlineUrlForMessage 是对 TencentImSDKPlugin.v2TIMManager.getMessageManager().getMessageOnlineUrl 的封装,返回 Future + future: ATMessageUtils().getMessageOnlineUrl( + msgID: message.msgID ?? "", + ), + builder: (context, snapshot) { + // 根据异步任务状态构建UI + if (snapshot.connectionState == ConnectionState.waiting) { + // 数据加载中,显示占位符 + return Container(); + } else if (snapshot.hasError) { + // 数据加载失败,显示错误UI + return Container(); + } else if (snapshot.hasData) { + // 数据加载成功,更新消息的mediaUrl并构建正常消息UI + message.videoElem = snapshot.data?.data?.videoElem!; + return Builder( + builder: (ct) { + return GestureDetector( + child: Container( + constraints: BoxConstraints(maxWidth: 200.w), + child: ClipRRect( + borderRadius: BorderRadius.circular(6.w), + child: Stack( + alignment: Alignment.center, + children: [ + ATPathUtils.collectPathType( + message.videoElem?.snapshotPath ?? "", + ) == + PathType.file + ? Image.file( + File(message.videoElem?.snapshotPath ?? ""), + fit: BoxFit.cover, + ) + : netImage( + url: message.videoElem?.snapshotUrl ?? "", + ), + Icon( + Icons.play_circle, + color: Colors.white, + size: 55.w, + ), + ], + ), + ), + ), + onTap: () { + String encodedVideoUrl = Uri.encodeComponent( + message.videoElem?.videoUrl ?? "", + ); + ATNavigatorUtils.push( + context, + "${MainRoute.videoPlayer}?videoUrl=$encodedVideoUrl", + ); + }, + onLongPress: () { + _showMsgItemMenu(ct, ""); + }, + ); + }, + ); + } else { + // 其他情况 + return Container(); + } + }, + ); + } + } + + return _text(); + } + + ///文本消息 + _text() { + String content = ""; + if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_TEXT) { + V2TimTextElem textElem = message.textElem!; + content = textElem.text ?? ""; + if (isSystem) {} + } else if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_IMAGE) { + content = ATAppLocalizations.of(context)!.image; + } else if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_VIDEO) { + content = ATAppLocalizations.of(context)!.video; + } else if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_SOUND) { + content = ATAppLocalizations.of(context)!.sound; + } else if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_CUSTOM) { + V2TimCustomElem customElem = message.customElem!; + content = customElem.data ?? ""; + if (customElem.extension == 'Red_Envelopes') { + ATUserRedPacketSendRes redPacketSendRes = ATUserRedPacketSendRes.fromJson( + jsonDecode(content), + ); + String packetId = redPacketSendRes.packetId ?? ""; + // 获取或创建缓存 Future + Future getRedPacketFuture() { + if (!ATMessageUtils.redPacketFutureCache.containsKey(packetId)) { + ATMessageUtils.redPacketFutureCache[packetId] = AccountRepository() + .userRedPacketDetail(packetId); + } + return ATMessageUtils.redPacketFutureCache[packetId]!; + } + + return Builder( + builder: (ct) { + return FutureBuilder( + future: getRedPacketFuture(), + builder: (ct, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.hasData) { + ATUserRedPacketSendRes? resData = snapshot.data; + return ATDebounceWidget( + child: Column( + children: [ + Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + _strategy + .getATMessageChatPageRedEnvelopeMessageItemBackground(), + ), + fit: BoxFit.fill, + ), + ), + child: Container( + alignment: Alignment.center, + width: 280.w, + height: 80.w, + child: Column( + children: [ + SizedBox(height: 5.w), + Row( + children: [ + SizedBox(width: 5.w), + Image.asset( + _strategy + .getATMessageChatPageRedEnvelopeReceivePackageIcon(), + width: 43.w, + ), + SizedBox(width: 5.w), + ExtendedText( + ATAppLocalizations.of( + context, + )!.wishingYouHappinessEveryDay, + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle( + fontSize: sp(13), + color: Colors.white, + ), + ), + ], + ), + Divider( + color: Colors.white, + thickness: 0.4, + height: 5.w, + endIndent: 10.w, + indent: 10.w, + ), + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: text( + resData?.senderInfo?.nickName ?? "", + fontSize: sp(13), + textColor: Colors.white, + ), + ), + SizedBox(width: 10.w), + ], + ), + ], + ), + ), + ), + SizedBox(height: 5.w), + resData?.status == 1 + ? Row( + children: [ + SizedBox(width: 5.w), + Image.asset( + _strategy + .getATMessageChatPageRedEnvelopeReceivePackageIcon(), + width: 23.w, + ), + SizedBox(width: 5.w), + Container( + width: ScreenUtil().screenWidth * 0.6, + child: text( + maxLines: 2, + textColor: Colors.black26, + ATAppLocalizations.of( + context, + )!.unclaimedRedEnvelopes, + ), + ), + ], + ) + : Container(), + resData?.status == 1 + ? Container() + : Row( + children: [ + SizedBox(width: 5.w), + Image.asset( + _strategy + .getATMessageChatPageRedEnvelopeReceivePackageIcon(), + width: 23.w, + ), + SizedBox(width: 5.w), + resData?.status == 3 + ? text( + maxLines: 2, + ATAppLocalizations.of( + context, + )!.theRedEnvelopeHasExpired, + textColor: Colors.black26, + ) + : _buildPackTips(resData), + ], + ), + ], + ), + onTap: () { + if (resData != null) { + if (resData.status == 3) { + //过期了 + ATTts.show( + ATAppLocalizations.of( + context, + )!.theRedEnvelopeHasExpired, + ); + return; + } + if ("${resData.senderInfo?.userId}" != + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id) { + if (resData.status == 1) { + _showOpenDialog(resData); + } else { + _showOpenedDialog(resData); + } + } else { + _showOpenedDialog(resData); + } + } + }, + ); + } + } + return Column( + children: [ + Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + _strategy + .getATMessageChatPageRedEnvelopeMessageItemBackground(), + ), + fit: BoxFit.fill, + ), + ), + child: Container( + alignment: Alignment.center, + width: 280.w, + height: 80.w, + child: Column( + children: [ + SizedBox(height: 5.w), + Row( + children: [ + SizedBox(width: 5.w), + Image.asset( + _strategy + .getATMessageChatPageRedEnvelopeReceivePackageIcon(), + width: 43.w, + ), + SizedBox(width: 5.w), + ], + ), + Divider( + color: Colors.white, + thickness: 0.4, + height: 5.w, + endIndent: 10.w, + indent: 10.w, + ), + ], + ), + ), + ), + SizedBox(height: 5.w), + ], + ); + }, + ); + }, + ); + } else { + content = ATAppLocalizations.of(context)!.receivedAMessage; + } + } + + return Builder( + builder: (ct) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [BoxShadow(blurRadius: 2.w, color: Colors.black26)], + borderRadius: BorderRadius.only( + topLeft: + message.isSelf! ? Radius.circular(8) : Radius.circular(0), + topRight: Radius.circular(8.w), + bottomLeft: Radius.circular(8.w), + bottomRight: + message.isSelf! ? Radius.circular(0) : Radius.circular(8), + ), + ), + padding: EdgeInsets.all(8.0.w), + child: Container( + constraints: BoxConstraints(maxWidth: width(220)), + child: ExtendedText( + content, + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle( + fontSize: sp(14), + color: message.isSelf! ? Colors.black87 : Colors.black87, + ), + ), + ), + ), + onLongPress: () { + _showMsgItemMenu(ct, content); + }, + ); + }, + ); + } + + void _showMsgItemMenu(BuildContext ct, String content) { + SmartDialog.showAttach( + tag: "showMsgItemMenu", + targetContext: ct, + alignment: Alignment.topCenter, + maskColor: Colors.transparent, + animationType: SmartAnimationType.fade, + scalePointBuilder: (selfSize) => Offset(selfSize.width, 10), + builder: (_) { + return Container( + margin: EdgeInsets.only(bottom: 10.w), + decoration: BoxDecoration( + color: Color(0xffdcdddf), + borderRadius: BorderRadius.circular(10), + ), + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.w), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + SmartDialog.dismiss(tag: "showMsgItemMenu"); + _delete(); + }, + child: Container( + margin: EdgeInsets.symmetric(horizontal: 8.w), + child: Column( + children: [ + Image.asset( + _strategy.getATMessageChatPageMessageMenuDeleteIcon(), + width: 20.w, + ), + SizedBox(height: 3.w), + text( + ATAppLocalizations.of(context)!.delete, + fontSize: 12.sp, + textColor: Color(0xff777777), + ), + ], + ), + ), + ), + content.isNotEmpty + ? GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + SmartDialog.dismiss(tag: "showMsgItemMenu"); + Clipboard.setData(ClipboardData(text: content)); + ATTts.show( + ATAppLocalizations.of(context)!.copiedToClipboard, + ); + }, + child: Container( + margin: EdgeInsets.symmetric(horizontal: 8.w), + child: Column( + children: [ + Image.asset( + _strategy.getATMessageChatPageMessageMenuCopyIcon(), + width: 20.w, + ), + SizedBox(height: 3.w), + text( + ATAppLocalizations.of(context)!.copy, + fontSize: 12.sp, + textColor: Color(0xff777777), + ), + ], + ), + ), + ) + : Container(), + (message.isSelf ?? false) + ? GestureDetector( + onTap: () { + SmartDialog.dismiss(tag: "showMsgItemMenu"); + _revokeMessage(); + }, + behavior: HitTestBehavior.opaque, + child: Container( + margin: EdgeInsets.symmetric(horizontal: 8.w), + child: Column( + children: [ + Image.asset( + _strategy + .getATMessageChatPageMessageMenuRecallIcon(), + width: 20.w, + ), + SizedBox(height: 3.w), + text( + ATAppLocalizations.of(context)!.recall, + fontSize: 12.sp, + textColor: Color(0xff777777), + ), + ], + ), + ), + ) + : Container(), + ], + ), + ); + }, + ); + } + + ///撤回消息 + void _revokeMessage() async { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.recallThisMessage, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () async { + // 撤回指定msgID的消息 + V2TimCallback revokeMessage = await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .revokeMessage(msgID: message.msgID ?? ""); + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .deleteMessages(msgIDs: [message.msgID ?? ""]); + if (revokeMessage.code == 0) { + // 撤回成功 + int index = 0; + for (var value in currentConversationMessageList) { + if (value.msgID == message.msgID) { + break; + } + index = index + 1; + } + currentConversationMessageList.removeAt(index); + updateCall(); + } else { + // 撤回失败,可通过 revokeMessage.desc 获取错误描述 + ATTts.show("fail:${revokeMessage.desc}"); + } + }, + ); + }, + ); + } + + ///删除消息 + void _delete() { + SmartDialog.show( + tag: "showDeleteDialog", + alignment: Alignment.bottomCenter, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return SafeArea( + child: Container( + height: 168.w, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(8.0), + topRight: Radius.circular(8.0), + ), + ), + child: Column( + children: [ + SizedBox(height: 10.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.tips, + fontSize: 16.sp, + textColor: Colors.black, + fontWeight: FontWeight.bold, + ), + ], + ), + SizedBox(height: 5.w), + Divider(height: height(1), color: ChatVibeTheme.dividerColor), + GestureDetector( + child: Container( + padding: EdgeInsets.symmetric(vertical: 10.w), + color: Colors.transparent, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.deleteFromMyDevice, + fontSize: 14.sp, + textColor: Colors.black, + ), + ], + ), + ), + onTap: () async { + V2TimCallback deleteMessageFromLocalStorage = + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .deleteMessageFromLocalStorage( + msgID: message.msgID ?? "", + ); + if (deleteMessageFromLocalStorage.code == 0) { + //删除成功 + int index = 0; + for (var value in currentConversationMessageList) { + if (value.msgID == message.msgID) { + break; + } + index = index + 1; + } + currentConversationMessageList.removeAt(index); + updateCall(); + SmartDialog.dismiss(tag: "showDeleteDialog"); + } + }, + ), + Divider(height: height(1), color: ChatVibeTheme.dividerColor), + GestureDetector( + onTap: () async { + V2TimCallback deleteMessageFromLocalStorage = + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .deleteMessages(msgIDs: [message.msgID ?? ""]); + if (deleteMessageFromLocalStorage.code == 0) { + //删除成功 + int index = 0; + for (var value in currentConversationMessageList) { + if (value.msgID == message.msgID) { + break; + } + index = index + 1; + } + currentConversationMessageList.removeAt(index); + updateCall(); + SmartDialog.dismiss(tag: "showDeleteDialog"); + } + }, + child: Container( + padding: EdgeInsets.symmetric(vertical: 10.w), + color: Colors.transparent, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.deleteOnAllDevices, + fontSize: 14.sp, + textColor: Colors.black, + ), + ], + ), + ), + ), + Divider(height: height(1), color: ChatVibeTheme.dividerColor), + GestureDetector( + onTap: () async { + SmartDialog.dismiss(tag: "showDeleteDialog"); + }, + child: Container( + padding: EdgeInsets.symmetric(vertical: 10.w), + color: Colors.transparent, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.cancel, + fontSize: 14.sp, + textColor: Colors.black, + ), + ], + ), + ), + ), + ], + ), + ), + ); + }, + ); + } + + Future updateRedPacketStatus(String messageID, String packetID) async { + // 构建扩展信息,例如标记为已领取,并包含领取者ID + Map extensionMap = {"packetID": packetID}; + + List extensions = + extensionMap.entries + .map( + (e) => V2TimMessageExtension( + extensionKey: e.key, + extensionValue: e.value, + ), + ) + .toList(); + + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .setMessageExtensions( + msgID: messageID, // 原始红包消息的ID + extensions: extensions, + ); + } + + void _showOpenDialog(ATUserRedPacketSendRes resData) { + SmartDialog.show( + tag: "showOpenDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return Container( + height: 400.w, + alignment: Alignment.topCenter, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + _strategy.getATMessageChatPageRedEnvelopeOpenBackground(), + ), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + SizedBox(height: 100.w), + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 60.w, + height: 60.w, + color: Colors.transparent, + child: text( + ATAppLocalizations.of(context)!.open, + textColor: Colors.white, + fontSize: 16.sp, + fontWeight: FontWeight.w900, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showOpenDialog"); + ATLoadingManager.exhibitOperation(); + AccountRepository() + .userRedPacketGrab(resData.packetId ?? "") + .then((res) { + var future = AccountRepository().userRedPacketDetail( + resData.packetId ?? "", + ); + updateRedPacketStatus( + message.msgID ?? "", + resData.packetId ?? "", + ); + ATMessageUtils.redPacketFutureCache[resData.packetId ?? + ""] = + future; + future + .then((res2) { + ATLoadingManager.veilRoutine(); + updateCall(); + _showOpenedDialog(res2); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + }, + ), + SizedBox(height: 35.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.openRedPackDialogTip, + fontWeight: FontWeight.w600, + textColor: Colors.white, + fontSize: 14.sp, + ), + SizedBox(width: 3.w), + Container( + margin: EdgeInsets.only(bottom: 1.w), + height: 18.w, + constraints: BoxConstraints(maxWidth: 65.w), + child: + (resData.senderInfo?.nickName?.length ?? 0) > 6 + ? Marquee( + text: resData.senderInfo?.nickName ?? "", + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration(milliseconds: 550), + decelerationCurve: Curves.easeOut, + ) + : Text( + resData.senderInfo?.nickName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + SizedBox(width: 3.w), + Container( + child: text( + ATAppLocalizations.of(context)!.sent, + fontWeight: FontWeight.w600, + textColor: Colors.white, + fontSize: 14.sp, + ), + height: 18.w, + ), + ], + ), + SizedBox(height: 15.w), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.wishingYouHappinessEveryDay, + fontWeight: FontWeight.w600, + textColor: Colors.white70, + fontSize: 13.sp, + ), + ], + ), + ], + ), + ); + }, + ); + } + + void _showOpenedDialog(ATUserRedPacketSendRes resData) { + SmartDialog.show( + tag: "showOpenedDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return Container( + margin: EdgeInsets.symmetric(horizontal: 20.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + _strategy.getATMessageChatPageRedEnvelopeOpenedBackground(), + ), + fit: BoxFit.fill, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 70.w), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + GestureDetector( + child: Icon(Icons.close, size: 18.w, color: Colors.white), + onTap: () { + SmartDialog.dismiss(tag: "showOpenedDialog"); + }, + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 25.w), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(width: 20.w), + netImage( + url: resData.senderInfo?.avatar ?? "", + shape: BoxShape.circle, + width: 45.w, + height: 45.w, + ), + SizedBox(width: 10.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + height: 18.w, + constraints: BoxConstraints(maxWidth: 85.w), + child: + (resData.senderInfo?.nickName?.length ?? 0) > 8 + ? Marquee( + text: resData.senderInfo?.nickName ?? "", + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration( + seconds: 1, + ), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + resData.senderInfo?.nickName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + SizedBox(width: 5.w), + Container( + child: text( + ATAppLocalizations.of(context)!.sentARedEnvelope, + fontWeight: FontWeight.w600, + textColor: Colors.white, + fontSize: 14.sp, + ), + height: 18.w, + ), + ], + ), + SizedBox(height: 5.w), + Row( + children: [ + text( + ATAppLocalizations.of( + context, + )!.wishingYouHappinessEveryDay, + fontWeight: FontWeight.w600, + textColor: Colors.white70, + fontSize: 13.sp, + ), + ], + ), + ], + ), + ], + ), + SizedBox(height: 10.w), + AccountStorage().getCurrentUser()?.userProfile?.id == + "${resData.receiverInfo?.userId}" + ? Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + _strategy.getATMessageChatPageGoldCoinIcon(), + height: 35.w, + ), + text( + "${resData.totalAmount ?? 0}", + fontWeight: FontWeight.w900, + textColor: Color(0xffFFF836), + fontSize: 22.sp, + ), + ], + ) + : Container(), + Divider( + color: Color(0xffF3F3F3), + endIndent: 15.w, + indent: 15.w, + thickness: 0.4, + ), + SizedBox(height: 5.w), + Row( + children: [ + SizedBox(width: 15.w), + text( + ATAppLocalizations.of( + context, + )!.redEnvelopeAmount("${resData.totalAmount}"), + fontWeight: FontWeight.w600, + textColor: Colors.white70, + fontSize: 13.sp, + ), + Spacer(), + resData.status == 2 + ? Row( + children: [ + Image.asset( + _strategy.getATMessageChatPageGoldCoinIcon(), + height: 15.w, + ), + text( + "${resData.totalAmount ?? 0}", + fontWeight: FontWeight.w400, + textColor: Color(0xffFFF836), + fontSize: 14.sp, + ), + ], + ) + : Container(), + SizedBox(width: 15.w), + ], + ), + SizedBox(height: 8.w), + resData.status == 2 + ? Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(width: 20.w), + netImage( + url: resData.receiverInfo?.avatar ?? "", + shape: BoxShape.circle, + width: 45.w, + height: 45.w, + ), + SizedBox(width: 10.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + height: 18.w, + constraints: BoxConstraints(maxWidth: 120.w), + child: + (resData.receiverInfo?.nickName?.length ?? + 0) > + 12 + ? Marquee( + text: + resData.receiverInfo?.nickName ?? + "", + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration( + seconds: 1, + ), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + resData.receiverInfo?.nickName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + SizedBox(width: 5.w), + ], + ), + SizedBox(height: 5.w), + Row( + children: [ + text( + ATMDateUtils.formatDateTime3( + DateTime.fromMillisecondsSinceEpoch( + resData.grabbedAt ?? 0, + ), + ), + fontWeight: FontWeight.w600, + textColor: Colors.white70, + fontSize: 13.sp, + ), + ], + ), + ], + ), + ], + ) + : (resData.status == 1 + ? Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of( + context, + )!.redEnvelopeNotYetClaimed, + fontWeight: FontWeight.w600, + textColor: Colors.white, + fontSize: 15.sp, + ), + ], + ) + : Container()), + SizedBox(height: 18.w), + ], + ), + ); + }, + ); + } + + _buildPackTips(ATUserRedPacketSendRes? resData) { + //状态:1-待领取 2-已领取 3-已过期 + if (resData?.status == 2) { + if ("${resData?.receiverInfo?.userId}" == + AccountStorage().getCurrentUser()?.userProfile?.id) { + return Container( + width: ScreenUtil().screenWidth * 0.6, + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: ATAppLocalizations.of( + context, + )!.youAccepted(resData?.senderInfo?.nickName ?? ""), + style: TextStyle( + fontSize: 12.sp, + color: Colors.black26, + fontWeight: FontWeight.bold, + ), + ), + TextSpan( + text: ATAppLocalizations.of(context)!.redEnvelope, + style: TextStyle( + fontSize: 12.sp, + color: Color(0xffFF7759), + fontWeight: FontWeight.w600, + ), + ), + TextSpan( + text: ".", + style: TextStyle( + fontSize: 12.sp, + color: Color(0xffFF7759), + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ); + } else if ("${resData?.senderInfo?.userId}" == + AccountStorage().getCurrentUser()?.userProfile?.id) { + return Container( + width: ScreenUtil().screenWidth * 0.6, + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: ATAppLocalizations.of( + context, + )!.acceptedYour(resData?.receiverInfo?.nickName ?? ""), + style: TextStyle( + fontSize: 12.sp, + color: Colors.black26, + fontWeight: FontWeight.bold, + ), + ), + TextSpan( + text: ATAppLocalizations.of(context)!.redEnvelope, + style: TextStyle( + fontSize: 12.sp, + color: Color(0xffFF7759), + fontWeight: FontWeight.w600, + ), + ), + TextSpan( + text: ".", + style: TextStyle( + fontSize: 12.sp, + color: Color(0xffFF7759), + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ); + } + } + return Container(); + } +} + +// class MySpecialTextSpanBuilder extends SpecialTextSpanBuilder { +// /// whether show background for @somebody +// MySpecialTextSpanBuilder(); +// +// @override +// TextSpan build(String data, {TextStyle? textStyle, onTap}) { +// var textSpan = super.build(data, textStyle: textStyle, onTap: onTap); +// return textSpan; +// } +// +// @override +// SpecialText? createSpecialText( +// String flag, { +// TextStyle? textStyle, +// SpecialTextGestureTapCallback? onTap, +// required int index, +// }) { +// if (flag == null || flag == "") return null; +// +// ///index is end index of start flag, so text start index should be index-(flag.length-1) +// if (textStyle != null) { +// if (isStart(flag, EmojiText.flag)) { +// return EmojiText(textStyle, start: index - (EmojiText.flag.length - 1)); +// } +// } +// return null; +// } +// } + +// class EmojiText extends SpecialText { +// EmojiText(TextStyle textStyle, {required this.start}) +// : super(EmojiText.flag, ']', textStyle); +// static const String flag = '['; +// final int start; +// +// @override +// InlineSpan finishText() { +// String key = toString(); +// key = key.replaceAll("[", ""); +// key = key.replaceAll("]", ""); +// int index; +// try { +// index = int.parse(key); +// } catch (e) { +// return TextSpan(text: toString(), style: textStyle); +// } +// +// ///https://github.com/flutter/flutter/issues/42086 +// /// widget span is not working on web +// if (index <= 113) { +// //fontsize id define image height +// //size = 30.0/26.0 * fontSize +// double size = 16.w; +// +// ///fontSize 26 and text height =30.0 +// //final double fontSize = 26.0; +// return ImageSpan( +// AssetImage("atu_images/msg/emoji/$key.png"), +// actualText: key, +// imageWidth: textStyle!.fontSize!, +// imageHeight: textStyle!.fontSize!, +// start: start, +// fit: BoxFit.fill, +// margin: const EdgeInsets.only(left: 2.0, top: 2.0, right: 2.0), +// ); +// } +// +// return TextSpan(text: toString(), style: textStyle); +// } +// } diff --git a/lib/chatvibe_features/chat/noti/message_notifcation_page.dart b/lib/chatvibe_features/chat/noti/message_notifcation_page.dart new file mode 100644 index 0000000..bc6e59f --- /dev/null +++ b/lib/chatvibe_features/chat/noti/message_notifcation_page.dart @@ -0,0 +1,295 @@ +import 'dart:convert'; + +import 'package:extended_text/extended_text.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:provider/provider.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_public_message_page_res.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; + +class MessageNotifcationPage extends StatefulWidget { + @override + _MessageNotifcationPageState createState() => _MessageNotifcationPageState(); +} + +class _MessageNotifcationPageState extends State { + List records = []; + num index = 1; + final RefreshController _refreshController = RefreshController(); + final ScrollController _scrollController = ScrollController(); + RtmProvider? rtmProvider; + + BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; + + @override + void initState() { + super.initState(); + rtmProvider = Provider.of(context, listen: false); + rtmProvider?.onNewNotifcationMessageCurrentConversationListener = + _onNewMessage; + loadData(); + } + + @override + void dispose() { + rtmProvider?.onNewNotifcationMessageCurrentConversationListener = null; + super.dispose(); + } + + _onNewMessage(Records? message) async { + if (message == null) { + return; + } + + int? index; + for (var element in records) { + if (element.id == message.id) { + index = records.indexOf(element); + continue; + } + } + if (index != null) { + records.removeAt(index); + records.insert(index, message); + } else { + records.insert(0, message); + } + _scrollController.jumpTo(0.0); + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + _strategy.getATMessageChatPageRoomSettingBackground(), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.notifcation, + actions: [], + ), + body: SafeArea( + top: false, + child: SmartRefresher( + enablePullDown: false, + enablePullUp: true, + onLoading: () async { + index = index + 1; + loadData(); + }, + footer: CustomFooter( + height: 1, + builder: (context, mode) { + return SizedBox( + height: 1, + width: 1, + child: SizedBox(height: 1, width: 1), + ); + }, + ), + controller: _refreshController, + child: + records.isNotEmpty + ? Container( + child: CustomScrollView( + shrinkWrap: records.length > 3 ? false : true, + controller: _scrollController, + reverse: true, + physics: const ClampingScrollPhysics(), + // 禁用回弹效果 + slivers: [ + SliverList( + delegate: SliverChildBuilderDelegate( + (c, i) => _MessageItem(message: records[i]), + childCount: records.length, + ), + ), + ], + ), + ) + : mainEmpty(), + ), + ), + ), + ], + ); + } + + void loadData() { + AccountRepository().publicMessagePage("notification", index, 30).then(( + result, + ) { + var list = result.records ?? []; + if (list.isNotEmpty) { + records.addAll(list); + if (list.length == 30) { + _refreshController.loadComplete(); + } else { + _refreshController.loadNoData(); + } + } else { + _refreshController.loadNoData(); + } + setState(() {}); + _scrollController.jumpTo(0.0); + }); + } +} + +class _MessageItem extends StatelessWidget { + final Records message; + late BuildContext context; + + _MessageItem({required this.message}); + + @override + Widget build(BuildContext context) { + this.context = context; + print('status:${message.status}'); + String time = ATMDateUtils.formatMessageTime( + context, + DateTime.fromMillisecondsSinceEpoch(int.parse(message.createdAt ?? "0")), + ); + return Container( + margin: EdgeInsets.symmetric(horizontal: 15.w, vertical: 8.w), + child: Column( + children: [ + Container( + margin: EdgeInsets.only(bottom: 12.w), + child: Row( + children: [ + Spacer(), + Container( + decoration: BoxDecoration( + color: Colors.black12, + borderRadius: BorderRadius.all(Radius.circular(height(13))), + ), + alignment: Alignment.center, + padding: EdgeInsets.symmetric( + horizontal: width(15), + vertical: 8.w, + ), + child: Text( + time, + style: TextStyle( + fontSize: sp(12), + color: Colors.white, + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + height: 1, + ), + ), + ), + Spacer(), + ], + ), + ), + _msg(message), + ], + ), + ); + } + + _msg(Records message) { + return GestureDetector( + child: Container( + alignment: AlignmentDirectional.centerStart, + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [BoxShadow(blurRadius: 2.w, color: Colors.black26)], + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + padding: EdgeInsets.all(8.0.w), + child: Column( + children: [ + Row( + children: [ + SizedBox(width: 5.w), + Expanded( + child: ExtendedText( + message.title ?? "", + overflow: TextOverflow.ellipsis, + maxLines: 10, + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle( + fontSize: sp(16), + color: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + ), + SizedBox(width: 5.w), + ], + ), + message.imageUrl != null && message.imageUrl!.isNotEmpty + ? GestureDetector( + child: Container( + margin: EdgeInsets.all(5.w), + child: netImage( + url: message.imageUrl ?? "", + fit: BoxFit.contain, + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + ), + onTap: () { + String encodedUrls = Uri.encodeComponent( + jsonEncode([message.imageUrl]), + ); + ATNavigatorUtils.push( + context, + "${MainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=0", + ); + }, + ) + : Container(), + Row( + children: [ + SizedBox(width: 5.w), + Expanded( + child: ExtendedText( + message.content ?? "", + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle(fontSize: sp(14), color: Colors.black38), + ), + ), + SizedBox(width: 5.w), + ], + ), + ], + ), + ), + onTap: () { + if (message.extraData != null && message.extraData?.link != null) { + if (message.extraData!.link!.startsWith("http") || + message.extraData!.link!.startsWith("https")) { + ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(message.extraData?.link ?? "")}&showTitle=false", + replace: false, + ); + } + } + }, + ); + } +} diff --git a/lib/chatvibe_features/chat/system/message_system_page.dart b/lib/chatvibe_features/chat/system/message_system_page.dart new file mode 100644 index 0000000..83b0467 --- /dev/null +++ b/lib/chatvibe_features/chat/system/message_system_page.dart @@ -0,0 +1,1292 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:extended_image/extended_image.dart' + show ExtendedImage, ExtendedRawImage, ExtendedImageState, LoadState; +import 'package:extended_text/extended_text.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_system_message_utils.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:provider/provider.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; +import 'package:tencent_cloud_chat_sdk/enum/message_elem_type.dart'; +import 'package:tencent_cloud_chat_sdk/enum/message_status.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_custom_elem.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_text_elem.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'; +import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_features/home/coupon/coupon_route.dart'; +import 'package:aslan/chatvibe_features/store/store_route.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; + +import '../../../chatvibe_data/models/enum/at_sysytem_message_type.dart'; +import '../../../chatvibe_domain/models/res/at_system_invit_message_res.dart'; + +class MessageSystemPage extends StatefulWidget { + final V2TimConversation? conversation; + final bool shrinkWrap; + + //是否是房间内聊天界面。 + final bool inRoom; + + const MessageSystemPage({ + Key? key, + this.conversation, + this.shrinkWrap = false, + this.inRoom = false, + }) : super(key: key); + + @override + _MessageSystemPageState createState() => _MessageSystemPageState(); +} + +class _MessageSystemPageState extends State { + final ScrollController _scrollController = ScrollController( + keepScrollOffset: false, + ); + final RefreshController _refreshController = RefreshController(); + RtmProvider? rtmProvider; + V2TimConversation? currentConversation; + List currentConversationMessageList = []; + + BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; + + @override + void initState() { + super.initState(); + rtmProvider = Provider.of(context, listen: false); + currentConversation = widget.conversation; + rtmProvider?.onNewMessageCurrentConversationListener = _onNewMessage; + loadMsg(); + } + + @override + void dispose() { + rtmProvider?.onNewMessageCurrentConversationListener = null; + _scrollController.dispose(); + _refreshController.dispose(); + super.dispose(); + } + + Future loadMsg() async { + V2TimValueCallback> v2timValueCallback = + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .getC2CHistoryMessageList( + userID: currentConversation!.userID!, + count: 100, + lastMsgID: null, + ); + List messages = v2timValueCallback.data!; + // List messages = await FTIM.getMessageManager().getMessages(conversation: currentConversation); + print("messages : ${messages?.length ?? 0}"); + currentConversationMessageList ??= []; + currentConversationMessageList?.clear(); + + for (var msg in messages) { + if (!msg.isSelf! && msg.isPeerRead!) { + TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .sendMessageReadReceipts(messageIDList: [msg.msgID!]); + } + } + currentConversationMessageList.insertAll(0, messages ?? []); + if (mounted) { + setState(() {}); + } + _scrollController.jumpTo(0.0); + } + + _onNewMessage(V2TimMessage? message, {String? msgId}) async { + if (message == null) { + return; + } + if (message.userID != currentConversation?.userID) return; + + int? index; + for (var element in currentConversationMessageList) { + if (msgId != null) { + if (msgId == element.msgID) { + element.status = MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL; + setState(() {}); + continue; + } + } else { + if (element.msgID == message.msgID) { + index = currentConversationMessageList.indexOf(element); + continue; + } + } + } + if (msgId != null) { + return; + } + if (index != null) { + currentConversationMessageList.removeAt(index!); + currentConversationMessageList.insert(index!, message); + } else { + currentConversationMessageList.insert(0, message); + } + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .markC2CMessageAsRead(userID: currentConversation!.userID!); + //发送已读回执 + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .sendMessageReadReceipts(messageIDList: [message.msgID!]); + setState(() {}); + _scrollController.jumpTo(0.0); + // await FTIM.getContactManager().setReadMessage(currentConversation); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + _strategy.getATMessageChatPageRoomSettingBackground(), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.system, + actions: [], + ), + body: SafeArea(top: false, child: _msgList()), + ), + ], + ); + } + + Widget _msgList() { + print('xiaoxiliebiao:${currentConversationMessageList.length}'); + return SmartRefresher( + enablePullDown: false, + enablePullUp: true, + onLoading: () async { + print('onLoading'); + if (currentConversationMessageList.isNotEmpty) { + // 拉取单聊历史消息 + // 首次拉取,lastMsgID 设置为 null + // 再次拉取时,lastMsgID 可以使用返回的消息列表中的最后一条消息的id + V2TimValueCallback> v2timValueCallback = + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .getC2CHistoryMessageList( + userID: currentConversation!.userID ?? "", + count: 30, + lastMsgID: currentConversationMessageList.last.msgID, + ); + List messages = + v2timValueCallback.data as List; + print('加载前:${currentConversationMessageList.length}'); + currentConversationMessageList.addAll(messages); + print('加载后:${currentConversationMessageList.length}'); + if (messages.length == 30) { + _refreshController.loadComplete(); + } else { + _refreshController.loadNoData(); + } + } else { + _refreshController.loadNoData(); + } + setState(() {}); + }, + footer: CustomFooter( + height: 1, + builder: (context, mode) { + return SizedBox( + height: 1, + width: 1, + child: SizedBox(height: 1, width: 1), + ); + }, + ), + controller: _refreshController, + child: Container( + child: CustomScrollView( + shrinkWrap: widget.shrinkWrap, + controller: _scrollController, + reverse: true, + physics: const ClampingScrollPhysics(), + // 禁用回弹效果 + slivers: [ + SliverList( + delegate: SliverChildBuilderDelegate( + (c, i) => _MessageItem( + message: currentConversationMessageList[i], + preMessage: + i < currentConversationMessageList.length - 1 + ? currentConversationMessageList[i + 1] + : null, + currentConversationMessageList: + currentConversationMessageList, + updateCall: () { + setState(() {}); + }, + ), + childCount: currentConversationMessageList.length, + ), + ), + ], + ), + ), + ); + } +} + +class _MessageItem extends StatelessWidget { + final V2TimMessage message; + final V2TimMessage? preMessage; + List currentConversationMessageList = []; + Function updateCall; + + ///上一条 + late BuildContext context; + + _MessageItem({ + required this.message, + this.preMessage, + required this.currentConversationMessageList, + required this.updateCall, + }); + + @override + Widget build(BuildContext context) { + this.context = context; + bool showTime = true; + int timestamp = (message.timestamp ?? 0) * 1000; + int preTimestamp = (preMessage?.timestamp ?? 0) * 1000; + if (preMessage != null) { + ///5分钟以内 不显示 + // print('timestamp:$timestamp===preTimestamp:${preTimestamp}'); + if (DateTime.fromMillisecondsSinceEpoch(timestamp) + .difference(DateTime.fromMillisecondsSinceEpoch(preTimestamp)) + .inMilliseconds < + 1000 * 60 * 5) { + showTime = false; + } + } + String time = ATMDateUtils.formatMessageTime( + context, + DateTime.fromMillisecondsSinceEpoch(timestamp ?? 0), + ); + String noticeType = ""; + String tagHead = ""; + if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_CUSTOM) { + V2TimCustomElem customElem = message.customElem!; + String content = customElem.data ?? ""; + final data = jsonDecode(content); + noticeType = data["noticeType"]; + if (noticeType == ATSysytemMessageType.CP_LOVE_LETTER.name) { + var bean = ATSystemInvitMessageRes.fromJson( + jsonDecode(data["content"]), + ); + tagHead = bean.userAvatar ?? ""; + } + } + print('status:${message.status}'); + return Container( + margin: EdgeInsets.symmetric(horizontal: 15.w, vertical: 8.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + // textDirection: message.isSelf ? TextDirection.rtl : TextDirection.ltr, + children: [ + Visibility( + visible: showTime, + child: Container( + margin: EdgeInsets.only(bottom: 12.w), + child: Row( + children: [ + Spacer(), + Container( + alignment: Alignment.center, + padding: EdgeInsets.symmetric( + horizontal: width(15), + vertical: 8.w, + ), + decoration: BoxDecoration( + color: Colors.black12, + borderRadius: BorderRadius.all( + Radius.circular(height(13)), + ), + ), + child: Text( + time, + style: TextStyle( + fontSize: sp(12), + color: Colors.white, + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + height: 1, + ), + ), + ), + Spacer(), + ], + ), + ), + ), + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + textDirection: + message.isSelf! ? TextDirection.rtl : TextDirection.ltr, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () {}, + child: + noticeType == ATSysytemMessageType.CP_LOVE_LETTER.name && + tagHead.isNotEmpty + ? netImage( + url: tagHead, + width: 45.w, + shape: BoxShape.circle, + ) + : ExtendedImage.asset( + ATGlobalConfig.businessLogicStrategy + .getMessagePageSystemMessageIcon(), + width: 45.w, + shape: BoxShape.circle, + fit: BoxFit.cover, + ), + ), + // 他人消息的阴影间隔是10dp + Container(width: 8.w), + Expanded( + child: Row( + textDirection: TextDirection.ltr, + children: [ + Expanded( + child: Container( + alignment: AlignmentDirectional.topStart, + margin: EdgeInsets.only(top: 4.w), + child: _msg(), + ), + ), + ], + ), + ), + ], + ), + ], + ), + ); + } + + _msg() { + return _text(); + } + + ///文本消息 + _text() { + String content = ""; + if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_TEXT) { + V2TimTextElem textElem = message.textElem!; + content = textElem.text ?? ""; + } else if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_IMAGE) { + content = ATAppLocalizations.of(context)!.image; + } else if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_VIDEO) { + content = ATAppLocalizations.of(context)!.video; + } else if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_SOUND) { + content = ATAppLocalizations.of(context)!.sound; + } else if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_CUSTOM) { + V2TimCustomElem customElem = message.customElem!; + content = customElem.data ?? ""; + final data = jsonDecode(content); + String type = data["noticeType"]; + if (type == ATSysytemMessageType.GIVE_AWAY_PROPS.name) { + String title = data["title"] ?? ""; + String expand = data["expand"] ?? ""; + return Builder( + builder: (ct) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow(blurRadius: 2.w, color: Colors.black26), + ], + borderRadius: BorderRadius.only( + topLeft: Radius.circular(0), + topRight: Radius.circular(8.w), + bottomLeft: Radius.circular(8.w), + bottomRight: Radius.circular(8), + ), + ), + padding: EdgeInsets.all(8.0.w), + child: Column( + children: [ + text( + ATAppLocalizations.of(context)!.propMessagePrompt, + maxLines: 1, + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + fontSize: sp(16), + textColor: Colors.black, + fontWeight: FontWeight.bold, + ), + ExtendedText( + data["content"], + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle(fontSize: sp(14), color: Colors.black54), + ), + expand.isNotEmpty + ? Row( + children: [ + SizedBox(width: 10.w), + netImage( + url: expand, + width: 45.w, + height: 45.w, + fit: BoxFit.contain, + ), + ], + ) + : Container(), + ], + ), + ), + onTap: () { + if (title.isNotEmpty) { + var dTitle = jsonDecode(title); + String propType = dTitle["propType"] ?? ""; + if ("ACTIVITY" == propType || + "ADMINISTRATOR" == propType || + "ACHIEVEMENT" == propType || + "BADGE" == propType) { + ATNavigatorUtils.push( + context, + MainRoute.medalsList, + replace: false, + ); + } else if ("HONOR_ACTIVITY" == propType || + "HONOR_ADMIN" == propType) { + ATNavigatorUtils.push( + context, + MainRoute.honorList, + replace: false, + ); + } else { + ATNavigatorUtils.push( + context, + StoreRoute.bags, + replace: false, + ); + } + } else { + ATNavigatorUtils.push( + context, + StoreRoute.bags, + replace: false, + ); + } + }, + onLongPress: () { + _showMsgItemMenu(ct, ""); + }, + ); + }, + ); + } else if (type == ATSysytemMessageType.AGENT_SEND_INVITE_HOST.name) { + return ATSystemMessageUtils.buildAgentSendInviteHostMessage( + data, + (ct, content) { + _showMsgItemMenu(ct, content); + }, + (String id) { + rejectOpt(id, type); + }, + (String id) { + acceptOpt(id, type); + }, + context, + ); + } else if (type == ATSysytemMessageType.BD_SEND_INVITE_AGENT.name) { + return ATSystemMessageUtils.buildBdSendInviteAgentMessage( + data, + (ct, content) { + _showMsgItemMenu(ct, content); + }, + (String id) { + rejectOpt(id, type); + }, + (String id) { + acceptOpt(id, type); + }, + context, + ); + } else if (type == ATSysytemMessageType.BD_LEADER_INVITE_BD.name) { + return ATSystemMessageUtils.buildBdLeaderInviteBdMessage( + data, + (ct, content) { + _showMsgItemMenu(ct, content); + }, + (String id) { + rejectOpt(id, type); + }, + (String id) { + acceptOpt(id, type); + }, + context, + ); + } else if (type == ATSysytemMessageType.BD_LEADER_INVITE_BD_LEADER.name) { + return ATSystemMessageUtils.buildBdLeaderInviteBdLeaderMessage( + data, + (ct, content) { + _showMsgItemMenu(ct, content); + }, + (String id) { + rejectOpt(id, type); + }, + (String id) { + acceptOpt(id, type); + }, + context, + ); + } else if (type == + ATSysytemMessageType.ADMIN_INVITE_RECHARGE_AGENT.name) { + return ATSystemMessageUtils.buildAdminInviteRechargeAgentMessage( + data, + (ct, content) { + _showMsgItemMenu(ct, content); + }, + (String id) { + rejectOpt(id, type); + }, + (String id) { + acceptOpt(id, type); + }, + context, + ); + } else if (type == ATSysytemMessageType.CP_BUILD.name) { + return ATSystemMessageUtils.buildCPBuildMessage( + data, + (ct, content) { + _showMsgItemMenu(ct, content); + }, + (String id) { + rejectOpt(id, type); + }, + (String id) { + acceptOpt(id, type); + }, + context, + ); + } else if (type == ATSysytemMessageType.CP_LOVE_LETTER.name) { + return ATSystemMessageUtils.buildCPLoveLetterMessage(data, ( + ct, + content, + ) { + _showMsgItemMenu(ct, content); + }, context); + } else if (type == ATSysytemMessageType.USER_COINS_RECEIVED.name) { + return ATSystemMessageUtils.buildUserCoinsReceivedMessage( + data, + (ct, content) { + _showMsgItemMenu(ct, content); + }, + context, + ATAppLocalizations.of(context)!.rechargeSuccessful, + ); + } else if (type == ATSysytemMessageType.COIN_SELLER_COINS_RECEIVED.name) { + return ATSystemMessageUtils.buildUserCoinsReceivedMessage( + data, + (ct, content) { + _showMsgItemMenu(ct, content); + }, + context, + ATAppLocalizations.of(context)!.transactionReceived, + ); + } else if (type == ATSysytemMessageType.USER_SPECIAL_AUDIT_RESULTS.name) { + if (data["content"] != null) { + return Builder( + builder: (ct) { + return GestureDetector( + onLongPress: () { + _showMsgItemMenu(ct, ""); + }, + onTap: () {}, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow(blurRadius: 2.w, color: Colors.black26), + ], + borderRadius: BorderRadius.only( + topLeft: Radius.circular(0), + topRight: Radius.circular(8.w), + bottomLeft: Radius.circular(8.w), + bottomRight: Radius.circular(8), + ), + ), + padding: EdgeInsets.all(8.0.w), + child: ExtendedText( + data["content"], + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle(fontSize: sp(14), color: Colors.black54), + ), + ), + ); + }, + ); + } + } else if (type == ATSysytemMessageType.FAMILY_APPLY_APPROVAL.name) { + if (data["content"] != null) { + return Builder( + builder: (ct) { + return GestureDetector( + onLongPress: () { + _showMsgItemMenu(ct, ""); + }, + onTap: () {}, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow(blurRadius: 2.w, color: Colors.black26), + ], + borderRadius: BorderRadius.only( + topLeft: Radius.circular(0), + topRight: Radius.circular(8.w), + bottomLeft: Radius.circular(8.w), + bottomRight: Radius.circular(8), + ), + ), + padding: EdgeInsets.all(8.0.w), + child: ExtendedText( + data["content"], + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle(fontSize: sp(14), color: Colors.black54), + ), + ), + ); + }, + ); + } + } else if (type == ATSysytemMessageType.USER_FOLLOW.name) { + String expand = data["expand"] ?? ""; + if (data["content"] != null) { + var bean = ATSystemInvitMessageRes.fromJson( + jsonDecode(data["content"]), + ); + return Builder( + builder: (ct) { + return GestureDetector( + onLongPress: () { + _showMsgItemMenu(ct, ""); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow(blurRadius: 2.w, color: Colors.black26), + ], + borderRadius: BorderRadius.only( + topLeft: Radius.circular(0), + topRight: Radius.circular(8.w), + bottomLeft: Radius.circular(8.w), + bottomRight: Radius.circular(8), + ), + ), + padding: EdgeInsets.all(8.0.w), + child: Column( + children: [ + Row( + children: [ + head(url: bean.userAvatar ?? "", width: 65.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + netImage( + url: + Provider.of( + context, + listen: false, + ) + .getCountryByName( + bean.countryName ?? "", + ) + ?.nationalFlag ?? + "", + borderRadius: BorderRadius.all( + Radius.circular(3.w), + ), + width: 19.w, + height: 14.w, + ), + SizedBox(width: 3.w), + Container( + constraints: BoxConstraints( + maxWidth: 160.w, + ), + child: text( + bean.userNickname ?? "", + fontWeight: FontWeight.bold, + textColor: Colors.black, + fontSize: 16.sp, + ), + ), + ], + ), + GestureDetector( + child: Container( + padding: EdgeInsets.symmetric(vertical: 8.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + ATGlobalConfig.businessLogicStrategy + .getIdBackgroundImage( + bean.account != + bean.actualAccount, + ), + ), + fit: BoxFit.fitWidth, + ), + ), + child: Row( + textDirection: TextDirection.ltr, + children: [ + SizedBox(width: 38.w), + text( + "ID:${bean.actualAccount ?? 0}", + fontSize: 12.sp, + textColor: Colors.black, + fontWeight: FontWeight.bold, + ), + SizedBox(width: 5.w), + Image.asset( + ATGlobalConfig.businessLogicStrategy + .getCopyIdIcon(), + width: 12.w, + height: 12.w, + ), + SizedBox(width: 8.w), + ], + ), + ), + onTap: () { + Clipboard.setData( + ClipboardData( + text: bean.actualAccount ?? "", + ), + ); + ATTts.show( + ATAppLocalizations.of( + context, + )!.copiedToClipboard, + ); + }, + ), + ], + ), + ], + ), + SizedBox(height: 3.w), + GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + height: 25.w, + width: 65.w, + decoration: BoxDecoration( + color: Color(0xffFF9500), + borderRadius: BorderRadius.all( + Radius.circular(15.0), + ), + ), + child: text( + ATAppLocalizations.of(context)!.follow, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () { + AccountRepository().followNew(expand).then((result) { + ATTts.show( + ATAppLocalizations.of(context)!.followSucc, + ); + }); + }, + ), + Divider( + color: Colors.black12, + thickness: 0.5, + height: 28.w, + ), + ExtendedText( + ATAppLocalizations.of(context)!.followedYou, + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle( + fontSize: sp(14), + color: Colors.black54, + ), + ), + SizedBox(height: 5.w), + ], + ), + ), + ); + }, + ); + } + } else if (type == ATSysytemMessageType.RECEIVE_PROP_COUPON.name) { + if (data["content"] != null) { + return Builder( + builder: (ct) { + return GestureDetector( + onLongPress: () { + _showMsgItemMenu(ct, ""); + }, + onTap: () { + ATNavigatorUtils.push( + context, + CouponRoute.couponList, + replace: false, + ); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow(blurRadius: 2.w, color: Colors.black26), + ], + borderRadius: BorderRadius.only( + topLeft: Radius.circular(0), + topRight: Radius.circular(8.w), + bottomLeft: Radius.circular(8.w), + bottomRight: Radius.circular(8), + ), + ), + padding: EdgeInsets.all(8.0.w), + child: ExtendedText( + data["content"], + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle(fontSize: sp(14), color: Colors.black54), + ), + ), + ); + }, + ); + } + } else if (type == ATSysytemMessageType.VIOLATION_WARNING.name) { + if (data["content"] != null) { + return Builder( + builder: (ct) { + return GestureDetector( + onLongPress: () { + _showMsgItemMenu(ct, ""); + }, + onTap: () {}, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow(blurRadius: 2.w, color: Colors.black26), + ], + borderRadius: BorderRadius.only( + topLeft: Radius.circular(0), + topRight: Radius.circular(8.w), + bottomLeft: Radius.circular(8.w), + bottomRight: Radius.circular(8), + ), + ), + padding: EdgeInsets.all(8.0.w), + child: ExtendedText( + data["content"], + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle(fontSize: sp(14), color: Colors.black54), + ), + ), + ); + }, + ); + } + } else if (type == ATSysytemMessageType.VIOLATION_ADJUST.name) { + if (data["content"] != null) { + return Builder( + builder: (ct) { + return GestureDetector( + onLongPress: () { + _showMsgItemMenu(ct, ""); + }, + onTap: () {}, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow(blurRadius: 2.w, color: Colors.black26), + ], + borderRadius: BorderRadius.only( + topLeft: Radius.circular(0), + topRight: Radius.circular(8.w), + bottomLeft: Radius.circular(8.w), + bottomRight: Radius.circular(8), + ), + ), + padding: EdgeInsets.all(8.0.w), + child: ExtendedText( + data["content"], + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle(fontSize: sp(14), color: Colors.black54), + ), + ), + ); + }, + ); + } + } + if (content.isEmpty) { + if (customElem.extension == 'sendGift') { + content = ATAppLocalizations.of(context)!.gift2; + } else { + content = ATAppLocalizations.of(context)!.receivedAMessage; + } + } + } + + return Builder( + builder: (ct) { + return GestureDetector( + onLongPress: () { + _showMsgItemMenu(ct, ""); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [BoxShadow(blurRadius: 2.w, color: Colors.black26)], + borderRadius: BorderRadius.only( + topLeft: Radius.circular(0), + topRight: Radius.circular(8.w), + bottomLeft: Radius.circular(8.w), + bottomRight: Radius.circular(8), + ), + ), + padding: EdgeInsets.all(8.0.w), + child: ExtendedText( + content, + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle(fontSize: sp(14), color: Colors.black54), + ), + ), + ); + }, + ); + } + + void _showMsgItemMenu(BuildContext ct, String content) { + SmartDialog.showAttach( + tag: "showMsgItemMenu", + targetContext: ct, + alignment: Alignment.topCenter, + maskColor: Colors.transparent, + animationType: SmartAnimationType.fade, + scalePointBuilder: (selfSize) => Offset(selfSize.width, 10), + builder: (_) { + return Container( + margin: EdgeInsets.only(bottom: 10.w), + decoration: BoxDecoration( + color: Color(0xffdcdddf), + borderRadius: BorderRadius.circular(10), + ), + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.w), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + SmartDialog.dismiss(tag: "showMsgItemMenu"); + _delete(); + }, + child: Container( + margin: EdgeInsets.symmetric(horizontal: 8.w), + child: Column( + children: [ + Image.asset( + ATGlobalConfig.businessLogicStrategy + .getATMessageChatPageMessageMenuDeleteIcon(), + width: 20.w, + ), + SizedBox(height: 3.w), + text( + ATAppLocalizations.of(context)!.delete, + fontSize: 12.sp, + textColor: Color(0xff777777), + ), + ], + ), + ), + ), + ], + ), + ); + }, + ); + } + + ///删除消息 + void _delete() { + SmartDialog.show( + tag: "showDeleteDialog", + alignment: Alignment.bottomCenter, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return SafeArea( + child: Container( + height: 168.w, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(8.0), + topRight: Radius.circular(8.0), + ), + ), + child: Column( + children: [ + SizedBox(height: 10.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.tips, + fontSize: 16.sp, + textColor: Colors.black, + fontWeight: FontWeight.bold, + ), + ], + ), + SizedBox(height: 5.w), + Divider(height: height(1), color: ChatVibeTheme.dividerColor), + GestureDetector( + child: Container( + padding: EdgeInsets.symmetric(vertical: 10.w), + color: Colors.transparent, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.deleteFromMyDevice, + fontSize: 14.sp, + textColor: Colors.black, + ), + ], + ), + ), + onTap: () async { + V2TimCallback deleteMessageFromLocalStorage = + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .deleteMessageFromLocalStorage( + msgID: message.msgID ?? "", + ); + if (deleteMessageFromLocalStorage.code == 0) { + //删除成功 + int index = 0; + for (var value in currentConversationMessageList) { + if (value.msgID == message.msgID) { + break; + } + index = index + 1; + } + currentConversationMessageList.removeAt(index); + updateCall(); + SmartDialog.dismiss(tag: "showDeleteDialog"); + } + }, + ), + Divider(height: height(1), color: ChatVibeTheme.dividerColor), + GestureDetector( + onTap: () async { + V2TimCallback deleteMessageFromLocalStorage = + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .deleteMessages(msgIDs: [message.msgID ?? ""]); + if (deleteMessageFromLocalStorage.code == 0) { + //删除成功 + int index = 0; + for (var value in currentConversationMessageList) { + if (value.msgID == message.msgID) { + break; + } + index = index + 1; + } + currentConversationMessageList.removeAt(index); + updateCall(); + SmartDialog.dismiss(tag: "showDeleteDialog"); + } + }, + child: Container( + padding: EdgeInsets.symmetric(vertical: 10.w), + color: Colors.transparent, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.deleteOnAllDevices, + fontSize: 14.sp, + textColor: Colors.black, + ), + ], + ), + ), + ), + Divider(height: height(1), color: ChatVibeTheme.dividerColor), + GestureDetector( + onTap: () async { + SmartDialog.dismiss(tag: "showDeleteDialog"); + }, + child: Container( + padding: EdgeInsets.symmetric(vertical: 10.w), + color: Colors.transparent, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.cancel, + fontSize: 14.sp, + textColor: Colors.black, + ), + ], + ), + ), + ), + ], + ), + ), + ); + }, + ); + } + + void acceptOpt(String id, String type) { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.confirmAcceptTheInvitation, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () async { + if (type == ATSysytemMessageType.AGENT_SEND_INVITE_HOST.name) { + Provider.of( + context, + listen: false, + ).inviteHostOpt(context, id, "1"); + } else if (type == ATSysytemMessageType.BD_SEND_INVITE_AGENT.name) { + Provider.of( + context, + listen: false, + ).inviteAgentOpt(context, id, "1"); + } else if (type == ATSysytemMessageType.BD_LEADER_INVITE_BD.name) { + Provider.of( + context, + listen: false, + ).inviteBDOpt(context, id, "1"); + } else if (type == + ATSysytemMessageType.BD_LEADER_INVITE_BD_LEADER.name) { + Provider.of( + context, + listen: false, + ).inviteBDLeader(context, id, "1"); + } else if (type == + ATSysytemMessageType.ADMIN_INVITE_RECHARGE_AGENT.name) { + Provider.of( + context, + listen: false, + ).inviteRechargeAgent(context, id, "1"); + } else if (type == ATSysytemMessageType.CP_BUILD.name) { + Provider.of( + context, + listen: false, + ).cpRlationshipProcessApply(context, id, true); + } + }, + ); + }, + ); + } + + void rejectOpt(String id, String type) { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.confirmDeclineTheInvitation, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () async { + if (type == ATSysytemMessageType.AGENT_SEND_INVITE_HOST.name) { + Provider.of( + context, + listen: false, + ).inviteHostOpt(context, id, "2"); + } else if (type == ATSysytemMessageType.BD_SEND_INVITE_AGENT.name) { + Provider.of( + context, + listen: false, + ).inviteAgentOpt(context, id, "2"); + } else if (type == ATSysytemMessageType.BD_LEADER_INVITE_BD.name) { + Provider.of( + context, + listen: false, + ).inviteBDOpt(context, id, "2"); + } else if (type == + ATSysytemMessageType.BD_LEADER_INVITE_BD_LEADER.name) { + Provider.of( + context, + listen: false, + ).inviteBDLeader(context, id, "2"); + } else if (type == + ATSysytemMessageType.ADMIN_INVITE_RECHARGE_AGENT.name) { + Provider.of( + context, + listen: false, + ).inviteRechargeAgent(context, id, "2"); + } else if (type == ATSysytemMessageType.CP_BUILD.name) { + Provider.of( + context, + listen: false, + ).cpRlationshipProcessApply(context, id, false); + } + }, + ); + }, + ); + } +} diff --git a/lib/chatvibe_features/country/at_country_page.dart b/lib/chatvibe_features/country/at_country_page.dart new file mode 100644 index 0000000..f704fa4 --- /dev/null +++ b/lib/chatvibe_features/country/at_country_page.dart @@ -0,0 +1,265 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_data/models/country_mode.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_ui/components/custom_cached_image.dart'; +import 'package:aslan/chatvibe_domain/models/res/country_res.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; + +import '../../chatvibe_core/constants/at_global_config.dart'; +import '../../chatvibe_core/config/business_logic_strategy.dart'; + +///国家 +class ATCountryPage extends StatefulWidget { + bool isDialog = false; + + ATCountryPage({this.isDialog = false}); + + @override + _CountryPageState createState() => _CountryPageState(isDialog); +} + +class _CountryPageState extends State { + bool isDialog = false; + Country? selectCountryInfo; + + _CountryPageState(this.isDialog); + + @override + void initState() { + super.initState(); + Provider.of(context, listen: false).loadCountry(); + } + @override + void dispose() { + Provider.of(context, listen: false).resetCountryState(); + super.dispose(); + } + + /// 获取业务逻辑策略 + BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: _strategy.getCountryPageScaffoldBackgroundColor(), + resizeToAvoidBottomInset: false, + appBar: + widget.isDialog + ? null + : ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.countryRegion, + onTag: () { + Provider.of(context, listen: false).resetCountryState(); + ATNavigatorUtils.goBack(context); + }, + actions: [ + Consumer( + builder: (_, provider, __) { + return Container( + margin: EdgeInsets.only(right: 20.w), + child: ATDebounceWidget( + onTap: () { + if (selectCountryInfo != null) { + Provider.of( + context, + listen: false, + ).setCountry(selectCountryInfo!); + provider.setCurrentCountry(selectCountryInfo); + ATNavigatorUtils.goBack(context); + } + }, + child: Image.asset( + selectCountryInfo != null + ? _strategy.getCountrySelectOkIcon() + : _strategy.getCountrySelectUnOkIcon(), + width: 38.w, + height: 22.w, + ), + ), + ); + }, + ), + ], + ), + body: SafeArea( + top: false, + child: Column( + children: [ + widget.isDialog ? SizedBox(height: 10.w) : Container(), + widget.isDialog + ? Row( + children: [ + ATDebounceWidget( + child: Container( + width: 50.w, + height: 30.w, + alignment: AlignmentDirectional.centerStart, + margin: EdgeInsetsDirectional.only(start: 10.w), + child: Icon( + ATGlobalConfig.lang == "ar" + ? Icons.keyboard_arrow_right + : Icons.keyboard_arrow_left, + size: 28.w, + color: _strategy.getCountryPageIconColor(), + ), + ), + onTap: () { + Provider.of(context, listen: false).resetCountryState(); + SmartDialog.dismiss(tag: "showCountryPage"); + }, + ), + Spacer(), + Consumer( + builder: (_, provider, __) { + return Container( + margin: EdgeInsets.only(right: 20.w), + child: ATDebounceWidget( + onTap: () { + if (selectCountryInfo != null) { + Provider.of( + context, + listen: false, + ).setCountry(selectCountryInfo!); + provider.setCurrentCountry(selectCountryInfo); + SmartDialog.dismiss(tag: "showCountryPage"); + } + }, + child: Image.asset( + selectCountryInfo != null + ? _strategy.getCountrySelectOkIcon() + : _strategy.getCountrySelectUnOkIcon(), + width: 38.w, + height: 22.w, + ), + ), + ); + }, + ), + ], + ) + : Container(), + widget.isDialog + ? Row( + children: [ + SizedBox(width: 15.w), + text( + ATAppLocalizations.of(context)!.selectYourCountry, + textColor: _strategy.getCountryPagePrimaryTextColor(), + fontSize: 16.sp, + ), + ], + ) + : Container(), + widget.isDialog ? SizedBox(height: 12.w) : Container(), + Expanded( + child: Consumer( + builder: (_, provider, __) { + return ListView.separated( + itemCount: provider.countryModeList.length, // 列表项数量 + padding: EdgeInsets.only(bottom: 15.w), + itemBuilder: (context, index) { + return _buildCountryItem( + index, + provider.countryModeList[index], + provider, + ); + }, + separatorBuilder: (BuildContext context, int index) { + return SizedBox(height: 10.w); + }, + ); + }, + ), + ), + ], + ), + ), + ); + } + + Widget _buildCountryItem( + int subIndex, + CountryMode countryModeList, + AppGeneralManager provider, + ) { + return Column( + children: [ + Container( + margin: EdgeInsets.only(left: 25.w), + alignment: Alignment.centerLeft, + child: text( + countryModeList.prefix, + textColor: _strategy.getCountryPageSecondaryTextColor(), + fontSize: 15.sp, + ), + ), + SizedBox(height: 5.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + padding: EdgeInsets.symmetric(vertical: 8.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: _strategy.getCountryPageContainerBackgroundColor(), + border: Border.all(color: _strategy.getCountryItemBorderColor(), width: 1.w), + ), + child: Column( + spacing: 3.w, + children: List.generate(countryModeList.prefixCountrys.length, ( + cIndex, + ) { + Country country = countryModeList.prefixCountrys[cIndex]; + return Row( + children: [ + SizedBox(width: 15.w), + CustomCachedImage( + imageUrl: country.nationalFlag ?? "", + width: 28.w, + height: 19.w, + ), + SizedBox(width: 5.w), + Expanded( + child: text( + country.aliasName ?? "", + textColor: _strategy.getCountryPagePrimaryTextColor(), + fontSize: 15.sp, + ), + ), + GestureDetector( + child: Container( + color: Colors.transparent, + padding: EdgeInsets.all(8.w), + child: Image.asset( + selectCountryInfo?.id == country.id + ? _strategy.getCountryRadioSelectedIcon() + : _strategy.getCountryRadioUnselectedIcon(), + width: 16.w, + height: 16.w, + ), + ), + onTap: () { + selectCountryInfo = provider.selectCountry( + subIndex, + cIndex, + ); + setState(() {}); + }, + ), + ], + ); + }), + ), + ), + ], + ); + } +} diff --git a/lib/chatvibe_features/country/country_route.dart b/lib/chatvibe_features/country/country_route.dart new file mode 100644 index 0000000..5829151 --- /dev/null +++ b/lib/chatvibe_features/country/country_route.dart @@ -0,0 +1,21 @@ +import 'package:fluro/fluro.dart'; +import 'package:aslan/chatvibe_core/routes/at_router_init.dart'; + +import 'at_country_page.dart'; + +class CountryRoute implements ATIRouterProvider { + static String country = '/at/country'; + + @override + void initRouter(FluroRouter router) { + router.define( + country, + handler: Handler( + handlerFunc: (_, params) { + String? isDialog = params['isDialog']?.first; + return ATCountryPage(isDialog: isDialog == 'true'); + }, + ), + ); + } +} diff --git a/lib/chatvibe_features/dynamic/create/create_dynamic_page.dart b/lib/chatvibe_features/dynamic/create/create_dynamic_page.dart new file mode 100644 index 0000000..5425ac3 --- /dev/null +++ b/lib/chatvibe_features/dynamic/create/create_dynamic_page.dart @@ -0,0 +1,313 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/dynamic_repository_imp.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/general_repository_imp.dart'; +import 'package:aslan/chatvibe_ui/widgets/dynamic/draggable_image_dynamic_photo_grid.dart'; + +import '../../../chatvibe_core/at_lk_event_bus.dart'; +import '../../../chatvibe_domain/models/req/at_dynamic_create_pic_cmd.dart'; +import '../../../chatvibe_domain/usecases/at_accurate_length_limiting_textInput_formatter.dart'; + +class CreateDynamicPage extends StatefulWidget { + @override + _CreateDynamicPageState createState() => _CreateDynamicPageState(); +} + +class _CreateDynamicPageState extends State { + final TextEditingController _editingController = TextEditingController(); + String roomCover = ""; + final int _contentMaxLength = 2000; + int _contentCurrentLength = 0; + Timer? _timer; + List images = []; + + @override + void initState() { + super.initState(); + _editingController.addListener(() { + setState(() { + _contentCurrentLength = _getActualCharacterCount( + _editingController.text, + ); + }); + }); + _editingController.text = DataPersistence.getDraftDynamicText(); + images = DataPersistence.getDraftDynamicPhoto(); + _startTimer(); + } + + void _startTimer() { + _timer = Timer.periodic(Duration(milliseconds: 2000), (timer) { + if (_editingController.text.isNotEmpty) { + DataPersistence.setDraftDynamicText(_editingController.text); + } + if (images.isNotEmpty) { + DataPersistence.setDraftDynamicPhoto(images); + } + }); + } + + dispose() { + _timer?.cancel(); + super.dispose(); + } + + int _getActualCharacterCount(String text) { + return text.characters.length; + } + + @override + Widget build(BuildContext context) { + return ATDebounceWidget(child: Stack( + children: [ + Image.asset( + "atu_images/room/at_icon_room_settig_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: true, + appBar: ChatVibeStandardAppBar( + title: "", + leading: ATDebounceWidget( + child: Container( + margin: EdgeInsetsDirectional.only(start: 5.w), + child: Icon(Icons.clear, color: Colors.black, size: 20.w), + padding: EdgeInsets.all(5.w), + ), + onTap: () { + if(_editingController.text.trim().isNotEmpty||images.isNotEmpty){ + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.doYouWantToKeepTheDraft, + btnText: ATAppLocalizations.of(context)!.save, + cancelText: ATAppLocalizations.of(context)!.discard, + onEnsure: () { + DataPersistence.setDraftDynamicText(_editingController.text); + DataPersistence.setDraftDynamicPhoto(images); + ATNavigatorUtils.goBack(context); + }, + onCancel: (){ + _editingController.text = ""; + DataPersistence.setDraftDynamicText(""); + DataPersistence.setDraftDynamicPhoto([]); + ATNavigatorUtils.goBack(context); + }, + ); + }, + ); + }else{ + DataPersistence.setDraftDynamicText(_editingController.text); + DataPersistence.setDraftDynamicPhoto(images); + ATNavigatorUtils.goBack(context); + } + }, + ), + actions: [ + ATDebounceWidget( + child: Container( + margin: EdgeInsetsDirectional.only(end: 10.w), + padding: EdgeInsets.symmetric(horizontal: 8.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12.w), + ), + child: Row( + children: [ + Image.asset( + "atu_images/room/at_icon_room_message_send.png", + color: Colors.black, + width: 15.w, + height: 15.w, + ), + SizedBox(width: 3.w), + text( + ATAppLocalizations.of(context)!.posting, + textColor: Colors.black, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ], + ), + ), + onTap: () { + submit(); + }, + ), + ], + ), + body: SafeArea( + top: false, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Expanded( + child: Stack( + alignment: AlignmentDirectional.bottomEnd, + children: [ + Container( + margin: EdgeInsets.symmetric(horizontal: 10.w), + padding: EdgeInsets.only( + top: 5.w, + bottom: 10.w, + left: width(12), + right: width(12), + ), + alignment: AlignmentDirectional.centerStart, + height: 150.w, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.all( + Radius.circular(height(8)), + ), + border: Border.all( + color: Colors.grey, + width: 0.2.w, + ), + ), + child: TextField( + controller: _editingController, + inputFormatters: [ + ATAccurateLengthLimitingTextInputFormatter( + _contentMaxLength, + ), + ], + maxLines: 100, + decoration: InputDecoration( + hintText: "", + hintStyle: TextStyle( + color: Colors.black45, + fontSize: 14.sp, + ), + contentPadding: EdgeInsets.only(top: 0.w), + counterText: '', + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + // enabledBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // focusedBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // prefixIcon: Padding(padding: EdgeInsets.all(8.w), child: Image.asset("images/login/at_icon_phone.png",width: 20.w, height: 20.w,fit: BoxFit.fill,),), + fillColor: Colors.black45, + ), + style: TextStyle( + fontSize: sp(15), + color: Colors.black, + textBaseline: TextBaseline.alphabetic, + ), + ), + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 3.w), + text( + '$_contentCurrentLength/$_contentMaxLength', + textColor: Color(0xFF999999), + ), + SizedBox(width: 15.w), + ], + ), + ], + ), + ), + ], + ), + SizedBox(height: 10.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 10.w), + child: DraggableImageDynamicPhotoGrid( + callback: (List images) { + this.images = images; + }, + ), + ), + ], + ), + ), + ), + ), + ], + ), onTap: (){ + FocusScope.of(context).unfocus(); // 彻底取消当前上下文的所有焦点 + }); + } + + void submit() async { + if (_editingController.text.trim().isEmpty && images.isEmpty) { + return; + } + ATLoadingManager.exhibitOperation(); + try { + bool canSend = await DynamicRepositoryImp().dynamicCheckSendPermission(); + if (!canSend) { + ATTts.show(ATAppLocalizations.of(context)!.cantSendDynamicTips); + ATLoadingManager.veilRoutine(); + return; + } + List? pics = []; + if (images.isNotEmpty) { + ///先上传图片 + List futures = []; + images.forEach((element) { + futures.add(GeneralRepositoryImp().upload(File(element))); + }); + var res = await Future.wait(futures); + int sort = 0; + res.forEach((el) { + String url = el as String; + pics.add(ATDynamicCreatePicCmd(resourceUrl: url, sort: sort)); + }); + } + + await DynamicRepositoryImp().create( + content: _editingController.text, + pics: pics, + ); + ATLoadingManager.veilRoutine(); + _timer?.cancel(); + DataPersistence.setDraftDynamicText(""); + DataPersistence.setDraftDynamicPhoto([]); + ATTts.show(ATAppLocalizations.of(context)!.createDynamicSuccess); + eventBus.fire(UpdateDynamicEvent()); + ATNavigatorUtils.goBack(context); + } catch (e) { + ATTts.show("fail:${e.toString()}"); + ATLoadingManager.veilRoutine(); + } + } +} diff --git a/lib/chatvibe_features/dynamic/detail/comment/dynamic_comment_page.dart b/lib/chatvibe_features/dynamic/detail/comment/dynamic_comment_page.dart new file mode 100644 index 0000000..f83e6c9 --- /dev/null +++ b/lib/chatvibe_features/dynamic/detail/comment/dynamic_comment_page.dart @@ -0,0 +1,694 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_core/utilities/at_dialog_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; +import 'package:readmore/readmore.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_comment_list_res.dart'; +import 'package:aslan/chatvibe_managers/dynamic_content_manager.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +class DynamicCommentPage extends StatefulWidget { + String dynamicId = ""; + String canGoUserInfo = ""; + num commentQuantity = 0; + Function(String? cId, String toUerId, num rootCommentId, String userName)? + repayCall; + Function(num count)? deleteCall; + + DynamicCommentPage( + this.dynamicId, + this.commentQuantity, + this.canGoUserInfo, { + this.repayCall, + this.deleteCall, + }); + + @override + _DynamicCommentPageState createState() => _DynamicCommentPageState(); +} + +class _DynamicCommentPageState extends State { + final RefreshController _refreshController = RefreshController( + initialRefresh: false, + ); + + int page = 1; + + @override + void initState() { + super.initState(); + _loadData(); + } + + @override + void dispose() { + Provider.of(context, listen: false).reset(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: Consumer( + builder: (context, ref, child) { + return SmartRefresher( + enablePullDown: false, + enablePullUp: true, + controller: _refreshController, + onLoading: () { + page++; + _loadData(); + }, + footer: CustomFooter( + height: 1, + builder: (context, mode) { + return SizedBox( + height: 1, + width: 1, + child: SizedBox(height: 1, width: 1), + ); + }, + ), + child: + ref.subCommentList.isEmpty + ? (ref.commentIsLoading + ? Center(child: CupertinoActivityIndicator()) + : mainEmpty(textColor: Colors.grey)) + : ListView.separated( + shrinkWrap: true, + itemBuilder: + (context, i) => buildItem(ref.subCommentList[i], ref), + itemCount: ref.subCommentList.length, + padding: EdgeInsets.symmetric(vertical: 15.w), + separatorBuilder: (context, i) => builderDivider(), + ), + ); + }, + ), + ); + } + + Widget buildItem(CommentListRecords res, ChatVibeDynamicContentManager ref) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(width: 10.w), + ATDebounceWidget( + child: netImage( + url: res.userAvatar ?? "", + height: 35.w, + width: 35.w, + shape: BoxShape.circle, + ), + onTap: () { + if (widget.canGoUserInfo == "true") { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == res.userId}&tageId=${res.userId}", + ); + } + }, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 用户昵称行 + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: text( + res.userNickname ?? "", + overflow: TextOverflow.ellipsis, + fontSize: 14.sp, + textColor: Colors.black, + ), + ), + ATDebounceWidget( + child: Container( + padding: EdgeInsets.all(3.w), + child: Icon( + Icons.more_vert, + color: Colors.grey, + size: 15.w, + ), + ), + onTap: () { + ATDialogUtils.revealDynamicCommentOptDialog( + context, + reportCallback: + res.userId != + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id + ? () { + ATNavigatorUtils.push( + context, + "${MainRoute.report}?type=dynamic&tageId=${res.dynamicId}", + replace: false, + ); + } + : null, + deleteCallback: + res.userId == + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id || + (Provider.of( + context, + listen: false, + ).userIdentity?.admin ?? + false) + ? () { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: + ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.deleteCommentTips, + btnText: + ATAppLocalizations.of( + context, + )!.confirm, + leftConfirm: true, + isBtnBgFlip: true, + onEnsure: () { + ref.deleteComment( + context, + widget.dynamicId, + res.id ?? "", + widget.commentQuantity, + true, + () { + setState(() {}); + }, + deleteCall: widget.deleteCall, + ); + }, + ); + }, + ); + } + : null, + ); + }, + ), + ], + ), + // 评论内容行 + Row( + children: [ + Expanded( + child: Container( + margin: EdgeInsetsDirectional.symmetric(horizontal: 10.w), + child: ReadMoreText( + res.content ?? "", + trimLines: 3, + trimMode: TrimMode.Line, + style: TextStyle( + fontFamily: "MyCustomFont", + fontSize: 13.sp, + color: Colors.black, + fontWeight: FontWeight.w400, + ), + trimCollapsedText: + ATAppLocalizations.of(context)!.showMore, + trimExpandedText: + ATAppLocalizations.of(context)!.showLess, + moreStyle: TextStyle( + color: ChatVibeTheme.primaryLight, + fontFamily: "MyCustomFont", + fontWeight: FontWeight.w600, + ), + lessStyle: TextStyle( + color: ChatVibeTheme.primaryLight, + fontFamily: "MyCustomFont", + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ), + SizedBox(height: 10.w), + // 操作栏行 (时间、回复、点赞等) + Row( + children: [ + SizedBox(width: 10.w), + text( + ATMDateUtils.formatMessageTime( + context, + DateTime.fromMillisecondsSinceEpoch(res.createTime ?? 0), + ), + textColor: Colors.grey, + fontSize: 12.sp, + ), + SizedBox(width: 5.w), + ATDebounceWidget( + child: Container( + child: text( + ATAppLocalizations.of(context)!.reply, + textColor: Color(0xff7726FF), + fontSize: 12.sp, + ), + ), + onTap: () { + widget.repayCall?.call( + res.id, + res.userId ?? "", + res.rootCommentId ?? 0, + res.userNickname ?? "", + ); + }, + ), + Spacer(), + ATDebounceWidget( + child: Row( + children: [ + Image.asset( + "atu_images/dynamic/at_icon_reply.png", + height: 14.w, + color: Colors.grey, + ), + text( + "${res.childCommentCount ?? 0}", + textColor: Colors.grey, + fontSize: 12.sp, + fontWeight: FontWeight.w400, + ), + ], + ), + onTap: () { + ref.withdraw(res.rootCommentId ?? 0, () { + setState(() {}); + }); + }, + ), + SizedBox(width: 10.w), + ATDebounceWidget( + child: Row( + children: [ + Image.asset( + (res.like ?? false) + ? "atu_images/dynamic/at_icon_like_red.png" + : "atu_images/dynamic/at_icon_like.png", + height: 14.w, + color: (res.like ?? false) ? null : Colors.grey, + ), + text( + res.likeStrQuantity ?? "0", + textColor: Colors.grey, + fontSize: 12.sp, + fontWeight: FontWeight.w400, + ), + ], + ), + onTap: () { + ref.likeComment( + context, + res.dynamicId ?? "", + res.id ?? "", + res.rootCommentId ?? 0, + res.userId ?? "", + !(res.like ?? false), + true, + () { + setState(() {}); + }, + ); + }, + ), + ], + ), + ref.childCommentListMap[res.rootCommentId ?? 0] != null && + (ref + .childCommentListMap[res.rootCommentId ?? 0] + ?.length ?? + 0) > + 0 + ? Container( + margin: EdgeInsetsDirectional.only(top: 3.w, start: 10.w), + // 添加顶部间距和左缩进 + child: ListView.builder( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: + ref + .childCommentListMap[res.rootCommentId ?? 0] + ?.length ?? + 0, + itemBuilder: + (context, index) => buildChildItem( + ref.childCommentListMap[res.rootCommentId ?? + 0]![index], + ), + ), + ) + : Container(), + if ((ref.childCommentListMap[res.rootCommentId ?? 0]?.length ?? + 0) < + (res.childCommentCount ?? 0)) + ATDebounceWidget( + child: Row( + children: [ + SizedBox(width: 10.w), + text( + "${(res.childCommentCount ?? 0) - (ref.childCommentListMap[res.rootCommentId ?? 0]?.length ?? 0)} ${ATAppLocalizations.of(context)!.itemsLeft}", + textColor: Colors.grey, + fontSize: 12.sp, + ), + Icon( + Icons.keyboard_arrow_right_outlined, + size: 14.w, + color: Colors.grey, + ), + ], + ), + onTap: () { + ref.dynamicListCommentChildren(res.rootCommentId ?? 0, () { + setState(() {}); + }); + }, + ), + ], + ), + ), + SizedBox(width: 10.w), + ], + ); + } + + builderDivider() { + return Divider(height: 22.w, thickness: 0.2.w, color: Colors.grey); + } + + Widget buildChildItem(CommentListRecords res) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ATDebounceWidget( + child: netImage( + url: res.userAvatar ?? "", + height: 25.w, + width: 25.w, + shape: BoxShape.circle, + ), + onTap: () { + if (widget.canGoUserInfo == "true") { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == res.userId}&tageId=${res.userId}", + ); + } + }, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + SizedBox(width: 10.w), + Container( + constraints: BoxConstraints(maxWidth: 120.w), + child: text( + res.userNickname ?? "", + overflow: TextOverflow.ellipsis, + fontSize: 11.sp, + textColor: Colors.black, + ), + ), + Spacer(), + ATDebounceWidget( + child: Container( + padding: EdgeInsets.all(3.w), + child: Icon( + Icons.more_vert, + color: Colors.grey, + size: 13.w, + ), + ), + onTap: () { + ATDialogUtils.revealDynamicCommentOptDialog( + context, + reportCallback: + res.userId != + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id + ? () { + ATNavigatorUtils.push( + context, + "${MainRoute.report}?type=dynamic&tageId=${res.dynamicId}", + replace: false, + ); + } + : null, + deleteCallback: + res.userId == + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id || + (Provider.of( + context, + listen: false, + ).userIdentity?.admin ?? + false) + ? () { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: + ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.deleteCommentTips, + leftConfirm: true, + isBtnBgFlip: true, + btnText: + ATAppLocalizations.of( + context, + )!.confirm, + onEnsure: () { + Provider.of( + context, + listen: false, + ).deleteComment( + context, + widget.dynamicId, + res.id ?? "", + widget.commentQuantity, + false, + () { + page = 1; + _loadData(); + }, + deleteCall: widget.deleteCall, + ); + }, + ); + }, + ); + } + : null, + ); + }, + ), + ], + ), + Row( + children: [ + Expanded( + child: Container( + margin: EdgeInsetsDirectional.symmetric(horizontal: 10.w), + child: + res.replyCommentId == res.rootCommentId + ? ReadMoreText( + res.content ?? "", + trimLines: 3, + trimMode: TrimMode.Line, + style: TextStyle( + fontFamily: "MyCustomFont", + fontSize: 11.sp, + color: Colors.black, + fontWeight: FontWeight.w400, + ), + trimCollapsedText: + ATAppLocalizations.of(context)!.showMore, + trimExpandedText: + ATAppLocalizations.of(context)!.showLess, + moreStyle: TextStyle( + color: ChatVibeTheme.primaryLight, + fontFamily: "MyCustomFont", + fontWeight: FontWeight.w600, + ), + lessStyle: TextStyle( + color: ChatVibeTheme.primaryLight, + fontFamily: "MyCustomFont", + fontWeight: FontWeight.w600, + ), + ) + : Text.rich( + strutStyle: StrutStyle( + height: 1.0, // 行高倍数 + fontWeight: FontWeight.w500, + forceStrutHeight: true, // 强制应用行高 + ), + TextSpan( + children: [ + TextSpan( + text: ATAppLocalizations.of(context)!.reply, + style: TextStyle( + fontSize: 11.sp, + color: Colors.black, + ), + ), + TextSpan( + text: " ${res.toUserNickname} ", + style: TextStyle( + fontSize: 11.sp, + color: Colors.black38, + ), + ), + TextSpan( + text: res.content, + style: TextStyle( + fontSize: 11.sp, + color: Colors.black, + ), + ), + ], + ), + ), + ), + ), + ], + ), + SizedBox(height: 5.w), + Row( + children: [ + SizedBox(width: 10.w), + text( + ATMDateUtils.formatMessageTime( + context, + DateTime.fromMillisecondsSinceEpoch(res.createTime ?? 0), + ), + textColor: Colors.grey, + fontSize: 11.sp, + ), + SizedBox(width: 5.w), + ATDebounceWidget( + child: Container( + child: text( + ATAppLocalizations.of(context)!.reply, + textColor: Color(0xff7726FF), + fontSize: 11.sp, + ), + ), + onTap: () { + widget.repayCall?.call( + res.id, + res.userId ?? "", + res.rootCommentId ?? 0, + res.userNickname ?? "", + ); + }, + ), + Spacer(), + SizedBox(width: 10.w), + ATDebounceWidget( + child: Row( + children: [ + Image.asset( + (res.like ?? false) + ? "atu_images/dynamic/at_icon_like_red.png" + : "atu_images/dynamic/at_icon_like.png", + height: 14.w, + color: (res.like ?? false) ? null : Colors.grey, + ), + text( + res.likeStrQuantity ?? "0", + textColor: Colors.grey, + fontSize: 11.sp, + fontWeight: FontWeight.w400, + ), + ], + ), + onTap: () { + Provider.of( + context, + listen: false, + ).likeComment( + context, + res.dynamicId ?? "", + res.id ?? "", + res.rootCommentId ?? 0, + res.toUserId ?? "", + !(res.like ?? false), + false, + () { + setState(() {}); + }, + ); + }, + ), + ], + ), + ], + ), + ), + ], + ); + } + + void _loadData() { + Provider.of(context, listen: false).dynamicListComment( + widget.dynamicId, + page, + (hasMore) { + setState(() {}); + _refreshController.refreshCompleted(); + _refreshController.loadComplete(); + if (hasMore) { + _refreshController.loadNoData(); + } + }, + () { + setState(() {}); + _refreshController.loadComplete(); + _refreshController.loadFailed(); + }, + ); + } +} diff --git a/lib/chatvibe_features/dynamic/detail/dynamic_detail_for_pic_page.dart b/lib/chatvibe_features/dynamic/detail/dynamic_detail_for_pic_page.dart new file mode 100644 index 0000000..c64ba58 --- /dev/null +++ b/lib/chatvibe_features/dynamic/detail/dynamic_detail_for_pic_page.dart @@ -0,0 +1,576 @@ +import 'package:extended_image/extended_image.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_core/utilities/at_dialog_utils.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/dynamic_repository_imp.dart'; +import 'package:photo_view/photo_view.dart'; +import 'package:photo_view/photo_view_gallery.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/dynamic_list_res.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_features/dynamic/dynamic_route.dart'; + +import '../../../chatvibe_core/at_lk_event_bus.dart'; + +class DynamicDetailForPicPage extends StatefulWidget { + String id = ""; + String canGoUserInfo = ""; + + DynamicDetailForPicPage(this.id, this.canGoUserInfo); + + @override + _DynamicDetailForPicPageState createState() => + _DynamicDetailForPicPageState(); +} + +class _DynamicDetailForPicPageState extends State + with SingleTickerProviderStateMixin { + Records? detail; + int _currentIndex = 0; + PageController? _pageController; + + @override + void initState() { + super.initState(); + _loadData(); + _pageController = PageController(); + } + + void _onPageChanged(int index) { + setState(() { + _currentIndex = index; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Color(0xff17151A), + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + backButtonColor: Colors.white, + title: + (detail?.pictures?.isNotEmpty ?? false) + ? '${_currentIndex + 1} / ${detail?.pictures?.length}' + : "", + leading: ATDebounceWidget( + child: Container( + margin: EdgeInsetsDirectional.only(start: 5.w), + child: Icon(Icons.clear, color: Colors.white, size: 20.w), + padding: EdgeInsets.all(5.w), + ), + onTap: () { + ATNavigatorUtils.goBack(context); + }, + ), + actions: [ + ATDebounceWidget( + child: Container( + padding: EdgeInsets.all(3.w), + child: Icon(Icons.more_vert, size: 20.w, color: Colors.white), + ), + onTap: () { + ATDialogUtils.revealDynamicCommentOptDialog( + context, + reportCallback: + detail?.userId != + AccountStorage().getCurrentUser()?.userProfile?.id + ? () { + ATNavigatorUtils.push( + context, + "${MainRoute.report}?type=dynamic&tageId=${detail?.dynamicId}", + replace: false, + ); + } + : null, + deleteCallback: + detail?.userId == + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id || + (Provider.of( + context, + listen: false, + ).userIdentity?.admin ?? + false) + ? () { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.deleteDynamicTips, + leftConfirm: true, + isBtnBgFlip: true, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + _deleteDynamic(detail?.dynamicId ?? ""); + }, + ); + }, + ); + } + : null, + ); + }, + ), + ], + ), + body: SafeArea( + top: false, + child: Column( + children: [ + (detail?.pictures?.isNotEmpty ?? false) + ? Container( + constraints: BoxConstraints( + maxHeight: ScreenUtil().screenHeight / 2, + ), + child: Stack( + alignment: AlignmentDirectional.bottomCenter, + children: [ + PhotoViewGallery.builder( + scrollPhysics: const BouncingScrollPhysics(), + builder: _buildImage, + itemCount: detail?.pictures?.length, + loadingBuilder: + (context, event) => Center( + child: Container( + width: 20.0, + height: 20.0, + child: CircularProgressIndicator( + value: + event == null + ? 0 + : event.cumulativeBytesLoaded / + event.expectedTotalBytes!, + ), + ), + ), + backgroundDecoration: const BoxDecoration( + color: Colors.black, + ), + pageController: _pageController, + onPageChanged: _onPageChanged, + enableRotation: true, + customSize: MediaQuery.of(context).size, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: + detail!.pictures!.asMap().entries.map((entry) { + return Container( + width: 6.0, + height: 6.0, + margin: EdgeInsets.symmetric( + vertical: 8.0, + horizontal: 4.0, + ), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: + _currentIndex == entry.key + ? Colors.blue + : Colors.white, + ), + ); + }).toList(), + ), + ], + ), + ) + : Container(), + SizedBox(height: 15.w), + Row( + children: [ + SizedBox(width: 10.w), + ATDebounceWidget( + child: netImage( + url: detail?.userProfile?.userAvatar ?? "", + shape: BoxShape.circle, + height: 45.w, + width: 45.w, + ), + onTap: () { + if (widget.canGoUserInfo == "true") { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == detail?.userProfile?.id}&tageId=${detail?.userProfile?.id}", + ); + } + }, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 3.w, + children: [ + Container( + margin: EdgeInsetsDirectional.only(start: 5.w), + constraints: BoxConstraints(maxWidth: 95.w), + child: chatvibeNickNameText( + maxWidth: 95.w, + detail?.userProfile?.userNickname ?? "", + fontSize: 15.sp, + textColor: Colors.white, + fontWeight: FontWeight.w400, + type: + detail?.userProfile?.getVIP()?.name ?? "", + needScroll: + (detail + ?.userProfile + ?.userNickname + ?.characters + .length ?? + 0) > + 10, + ), + ), + Container( + width: + (detail?.userProfile?.age ?? 0) > 999 + ? 58.w + : 48.w, + height: 24.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + detail?.userProfile?.userSex == 0 + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png", + ), + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + xb(detail?.userProfile?.userSex), + text( + "${detail?.userProfile?.age}", + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 3.w), + ], + ), + ), + getVIPBadge( + detail?.userProfile?.getVIP()?.name, + width: 45.w, + height: 25.w, + ), + ], + ), + Container( + alignment: AlignmentDirectional.centerStart, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 5.w), + detail?.userProfile?.wearBadge?.isNotEmpty ?? + false + ? SizedBox( + height: 28.w, + child: ListView.separated( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: + detail + ?.userProfile + ?.wearBadge + ?.length ?? + 0, + itemBuilder: (context, index) { + return netImage( + width: 28.w, + height: 28.w, + url: + detail + ?.userProfile + ?.wearBadge?[index] + .selectUrl ?? + "", + ); + }, + separatorBuilder: ( + BuildContext context, + int index, + ) { + return SizedBox(width: 5.w); + }, + ), + ) + : Container(), + SizedBox(width: 5.w), + ], + ), + ), + ), + ], + ), + ), + SizedBox(width: 5.w), + detail?.userId != + AccountStorage().getCurrentUser()?.userProfile?.id + ? ((detail?.subscription ?? false) + ? Container() + : ATDebounceWidget( + child: Container( + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + gradient: LinearGradient( + colors: [Color(0xff7726FF), Color(0xffC670FF)], + ), + ), + width: 68.w, + height: 23.w, + child: text( + ATAppLocalizations.of(context)!.follow, + textColor: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 13.sp, + ), + ), + onTap: () { + AccountRepository() + .followUser(detail?.userId ?? "") + .then((result) { + detail?.setSubscription(true); + setState(() {}); + }); + }, + )) + : Container(), + SizedBox(width: 3.w), + ], + ), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: Container( + margin: EdgeInsetsDirectional.symmetric(horizontal: 10.w), + child: text( + detail?.content ?? "", + fontWeight: FontWeight.w600, + fontSize: 13.sp, + maxLines: 3, + textColor: Colors.white, + ), + ), + ), + ATDebounceWidget( + child: Row( + children: [ + text( + ATAppLocalizations.of(context)!.more, + fontSize: 13.sp, + fontWeight: FontWeight.w600, + textColor: ChatVibeTheme.primaryLight, + ), + Icon(Icons.expand_more, color: ChatVibeTheme.primaryLight, size: 15.w), + SizedBox(width: 5.w), + ], + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${DynamicRoute.detail}?id=${detail?.dynamicId}&canGoUserInfo=${widget.canGoUserInfo}", + replace: true, + ); + }, + ), + ], + ), + Spacer(), + Container( + height: 55.w, + child: Row( + children: [ + SizedBox(width: 15.w), + Expanded( + child: ATDebounceWidget( + child: Container( + padding: EdgeInsetsDirectional.only(start: 10.w), + alignment: AlignmentDirectional.centerStart, + height: 28.w, + decoration: BoxDecoration( + color: Color(0xffF7F3FB).withOpacity(0.2), + borderRadius: BorderRadius.circular(16.w), + ), + child: text( + ATAppLocalizations.of(context)!.saySomething, + textColor: Colors.grey, + fontSize: 13.sp, + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${DynamicRoute.detail}?id=${detail?.dynamicId}&canGoUserInfo=${widget.canGoUserInfo}", + replace: true, + ); + }, + ), + ), + SizedBox(width: 50.w), + Row( + children: [ + ATDebounceWidget( + child: Row( + children: [ + text( + "${detail?.commentStrQuantity ?? 0}", + textColor: Colors.grey, + fontSize: 12.sp, + ), + SizedBox(width: 3.w), + Image.asset( + "atu_images/dynamic/at_icon_reply.png", + height: 15.w, + color: Colors.white, + ), + ], + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${DynamicRoute.detail}?id=${detail?.dynamicId}&canGoUserInfo=${widget.canGoUserInfo}", + replace: true, + ); + }, + ), + SizedBox(width: 15.w), + ATDebounceWidget( + child: Row( + children: [ + text( + "${detail?.likeStrQuantity ?? 0}", + textColor: Colors.grey, + fontSize: 12.sp, + ), + SizedBox(width: 3.w), + Image.asset( + (detail?.like ?? false) + ? "atu_images/dynamic/at_icon_like_red.png" + : "atu_images/dynamic/at_icon_like.png", + height: 15.w, + color: + (detail?.like ?? false) ? null : Colors.white, + ), + ], + ), + onTap: () { + _like(); + }, + ), + SizedBox(width: 10.w), + ], + ), + ], + ), + ), + ], + ), + ), + ); + } + + void _like() { + DynamicRepositoryImp() + .dynamicLike(detail?.dynamicId ?? "", !(detail?.like ?? false)) + .then((result) { + detail?.setLike(!(detail?.like ?? false)); + if (detail?.like ?? false) { + detail?.setLikeStrQuantity( + "${int.parse(detail?.likeStrQuantity ?? "0") + 1}", + ); + } else { + detail?.setLikeStrQuantity( + "${int.parse(detail?.likeStrQuantity ?? "0") - 1}", + ); + } + setState(() {}); + }) + .catchError((_) {}); + } + + PhotoViewGalleryPageOptions _buildImage(BuildContext context, int index) { + String imageUrl = detail?.pictures?[index].resourceUrl ?? ""; + return PhotoViewGalleryPageOptions( + imageProvider: _getImageProvider(imageUrl), + initialScale: PhotoViewComputedScale.contained, + minScale: PhotoViewComputedScale.contained * 0.8, + maxScale: PhotoViewComputedScale.covered * 2.0, + heroAttributes: PhotoViewHeroAttributes(tag: imageUrl), + onTapUp: (_, __, ___) => () {}, + ); + } + + ImageProvider _getImageProvider(String imageUrl) { + return ExtendedNetworkImageProvider( + imageUrl, + cache: true, // 启用缓存(默认即为true) + cacheMaxAge: Duration(days: 7), // 设置缓存有效期 + ); + } + + void _deleteDynamic(String dynamicId) { + ATLoadingManager.exhibitOperation(); + DynamicRepositoryImp() + .dynamicDelete(dynamicId) + .then((result) { + ATLoadingManager.veilRoutine(); + eventBus.fire(UpdateDynamicEvent()); + ATNavigatorUtils.goBack(context); + ATTts.show(ATAppLocalizations.of(context)!.deleteSuccessful); + }) + .catchError((e) { + eventBus.fire(UpdateDynamicEvent()); + ATLoadingManager.veilRoutine(); + ATNavigatorUtils.goBack(context); + }); + } + + void _loadData() { + ATLoadingManager.exhibitOperation(); + DynamicRepositoryImp() + .dynamicDetails(widget.id) + .then((result) { + ATLoadingManager.veilRoutine(); + detail = result; + setState(() {}); + }) + .catchError((_) { + ATLoadingManager.veilRoutine(); + }); + } +} diff --git a/lib/chatvibe_features/dynamic/detail/dynamic_detail_page.dart b/lib/chatvibe_features/dynamic/detail/dynamic_detail_page.dart new file mode 100644 index 0000000..066e639 --- /dev/null +++ b/lib/chatvibe_features/dynamic/detail/dynamic_detail_page.dart @@ -0,0 +1,1068 @@ +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_core/utilities/at_dialog_utils.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/dynamic_repository_imp.dart'; +import 'package:provider/provider.dart'; +import 'package:readmore/readmore.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/dynamic_list_res.dart'; +import 'package:aslan/chatvibe_managers/dynamic_content_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/dynamic/comment/comment_input.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_features/dynamic/detail/comment/dynamic_comment_page.dart'; +import 'package:aslan/chatvibe_features/dynamic/detail/gift/dynamic_comment_gift_page.dart'; +import 'package:aslan/chatvibe_features/dynamic/detail/like/dynamic_comment_like_page.dart'; + +import '../../../chatvibe_core/at_lk_event_bus.dart'; + +class DynamicDetailPage extends StatefulWidget { + String id = ""; + String canGoUserInfo = ""; + + DynamicDetailPage(this.id, this.canGoUserInfo); + + @override + _DynamicDetailPageState createState() => _DynamicDetailPageState(); +} + +class _DynamicDetailPageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + Records? detail; + String? toUserId; + num? rootCommentId; + String? commentId; + String cName = ""; + + // 添加滚动控制器 + final ScrollController _scrollController = ScrollController(); + double _opacity = 0.0; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 3, vsync: this); + _tabController.addListener(_handleTabSelection); + // 监听滚动 + _scrollController.addListener(() { + if (_scrollController.hasClients) { + final offset = _scrollController.offset; + // 当滚动到一定位置时,头像和昵称应该完全显示在AppBar上 + // 这里计算透明度,可以根据需要调整 + final newOpacity = (offset / 150).clamp(0.0, 1.0); + if (newOpacity != _opacity) { + setState(() { + _opacity = newOpacity; + }); + } + } + }); + _loadData(); + } + + _handleTabSelection() { + if (!_tabController.indexIsChanging) { + Provider.of( + context, + listen: false, + ).updateShowEmojiState(false, () {}); + Provider.of(context, listen: false).updateShowGiftState( + false, + () { + setState(() {}); + }, + ); + } + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () async { + if (Provider.of(context, listen: false).showEmoji) { + Provider.of( + context, + listen: false, + ).updateShowEmojiState(false, () { + setState(() {}); + }); + return false; + } + + if (Provider.of(context, listen: false).showGift) { + Provider.of( + context, + listen: false, + ).updateShowGiftState(false, () { + setState(() {}); + }); + return false; + } + return true; + }, + child: GestureDetector( + child: Stack( + children: [ + Image.asset( + "atu_images/room/at_icon_room_settig_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: true, + appBar: ChatVibeStandardAppBar( + title: "", + actions: [ + if (_opacity > 0.5) ...[ + SizedBox(width: 45.w), + head( + url: detail?.userAvatar ?? "", + width: 50.w, + height: 50.w, + ), + SizedBox(width: 8.w), + Container( + constraints: BoxConstraints( + maxWidth: 135.w, + maxHeight: 22.w, + ), + child: chatvibeNickNameText( + maxWidth: 135.w, + detail?.userProfile?.userNickname ?? "", + fontSize: 16.sp, + textColor: Colors.black, + fontWeight: FontWeight.w600, + type: detail?.userProfile?.getVIP()?.name ?? "", + needScroll: + (detail + ?.userProfile + ?.userNickname + ?.characters + .length ?? + 0) > + 13, + ), + ), + SizedBox(width: 8.w), + Spacer(), + detail?.userId != + AccountStorage().getCurrentUser()?.userProfile?.id + ? ((detail?.subscription ?? false) + ? Container() + : ATDebounceWidget( + child: Container( + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + gradient: LinearGradient( + colors: [ + Color(0xff7726FF), + Color(0xffC670FF), + ], + ), + ), + width: 68.w, + height: 23.w, + child: text( + ATAppLocalizations.of(context)!.follow, + textColor: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 13.sp, + ), + ), + onTap: () { + AccountRepository() + .followUser(detail?.userId ?? "") + .then((result) { + detail?.setSubscription(true); + setState(() {}); + }); + }, + )) + : Container(), + ATDebounceWidget( + child: Container( + child: Icon( + Icons.more_vert, + color: Colors.black45, + size: 18.w, + ), + padding: EdgeInsetsDirectional.all(3.w), + ), + onTap: () { + ATDialogUtils.revealDynamicCommentOptDialog( + context, + reportCallback: + detail?.userId != + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id + ? () { + ATNavigatorUtils.push( + context, + "${MainRoute.report}?type=dynamic&tageId=${detail?.dynamicId}", + replace: false, + ); + } + : null, + deleteCallback: + detail?.userId == + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id || + (Provider.of( + context, + listen: false, + ).userIdentity?.admin ?? + false) + ? () { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: + ATAppLocalizations.of( + context, + )!.tips, + msg: + ATAppLocalizations.of( + context, + )!.deleteDynamicTips, + btnText: + ATAppLocalizations.of( + context, + )!.confirm, + leftConfirm: true, + isBtnBgFlip: true, + onEnsure: () { + _deleteDynamic( + detail?.dynamicId ?? "", + ); + }, + ); + }, + ); + } + : null, + ); + }, + ), + ], + ], + ), + body: SafeArea( + top: false, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + NestedScrollView( + controller: _scrollController, + headerSliverBuilder: (context, innerBoxIsScrolled) { + return [ + SliverToBoxAdapter( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + SizedBox(width: 10.w), + ATDebounceWidget( + child: netImage( + url: + detail?.userProfile?.userAvatar ?? + "", + shape: BoxShape.circle, + height: 45.w, + width: 45.w, + ), + onTap: () { + if (widget.canGoUserInfo == "true") { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == detail?.userProfile?.id}&tageId=${detail?.userProfile?.id}", + ); + } + }, + ), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Row( + spacing: 3.w, + children: [ + Container( + margin: + EdgeInsetsDirectional.only( + start: 5.w, + ), + child: + chatvibeNickNameText( + maxWidth: 95.w, + detail?.userProfile?.userNickname ?? "", + fontSize: 15.sp, + textColor: Colors.black, + fontWeight: FontWeight.w400, + type: detail?.userProfile?.getVIP()?.name ?? "", + needScroll: + (detail + ?.userProfile + ?.userNickname + ?.characters + .length ?? + 0) > + 10, + ) + + ), + Container( + width: + (detail?.userProfile?.age ?? + 0) > + 999 + ? 58.w + : 48.w, + height: 24.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + detail + ?.userProfile + ?.userSex == + 0 + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png", + ), + ), + ), + child: Row( + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + xb( + detail + ?.userProfile + ?.userSex, + ), + text( + "${detail?.userProfile?.age ?? 0}", + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: + FontWeight.w600, + ), + SizedBox(width: 3.w), + ], + ), + ), + getVIPBadge( + detail?.userProfile + ?.getVIP() + ?.name, + width: 45.w, + height: 25.w, + ), + ], + ), + Container( + alignment: + AlignmentDirectional + .centerStart, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 5.w), + detail + ?.userProfile + ?.wearBadge + ?.isNotEmpty ?? + false + ? SizedBox( + height: 28.w, + child: ListView.separated( + scrollDirection: + Axis.horizontal, + shrinkWrap: true, + itemCount: + detail + ?.userProfile + ?.wearBadge + ?.length ?? + 0, + itemBuilder: ( + context, + index, + ) { + return netImage( + width: 28.w, + height: 28.w, + url: + detail + ?.userProfile + ?.wearBadge?[index] + .selectUrl ?? + "", + ); + }, + separatorBuilder: ( + BuildContext + context, + int index, + ) { + return SizedBox( + width: 5.w, + ); + }, + ), + ) + : Container(), + SizedBox(width: 5.w), + ], + ), + ), + ), + ], + ), + ), + SizedBox(width: 5.w), + detail?.userId != + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id + ? ((detail?.subscription ?? false) + ? Container() + : ATDebounceWidget( + child: Container( + alignment: + AlignmentDirectional.center, + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular( + 12.w, + ), + gradient: LinearGradient( + colors: [ + Color(0xff7726FF), + Color(0xffC670FF), + ], + ), + ), + width: 68.w, + height: 23.w, + child: text( + ATAppLocalizations.of( + context, + )!.follow, + textColor: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 13.sp, + ), + ), + onTap: () { + AccountRepository() + .followUser( + detail?.userId ?? "", + ) + .then((result) { + detail?.setSubscription( + true, + ); + setState(() {}); + }); + }, + )) + : Container(), + ATDebounceWidget( + child: Container( + child: Icon( + Icons.more_vert, + color: Colors.black45, + size: 18.w, + ), + padding: EdgeInsetsDirectional.all(3.w), + ), + onTap: () { + ATDialogUtils.revealDynamicCommentOptDialog( + context, + reportCallback: + detail?.userId != + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id + ? () { + ATNavigatorUtils.push( + context, + "${MainRoute.report}?type=dynamic&tageId=${detail?.dynamicId}", + replace: false, + ); + } + : null, + deleteCallback: + detail?.userId == + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id || + (Provider.of< + ChatVibeUserProfileManager + >( + context, + listen: false, + ) + .userIdentity + ?.admin ?? + false) + ? () { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: + Alignment.center, + debounce: true, + animationType: + SmartAnimationType + .fade, + builder: (_) { + return MsgDialog( + title: + ATAppLocalizations.of( + context, + )!.tips, + msg: + ATAppLocalizations.of( + context, + )!.deleteDynamicTips, + btnText: + ATAppLocalizations.of( + context, + )!.confirm, + leftConfirm: true, + isBtnBgFlip: true, + onEnsure: () { + _deleteDynamic( + detail?.dynamicId ?? + "", + ); + }, + ); + }, + ); + } + : null, + ); + }, + ), + SizedBox(width: 3.w), + ], + ), + SizedBox(height: 3.w), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: Container( + margin: EdgeInsetsDirectional.symmetric( + horizontal: 10.w, + ), + child: ReadMoreText( + detail?.content ?? "", + trimLines: 3, + // 收缩时显示的行数 + trimMode: TrimMode.Line, + style: TextStyle( + fontFamily: "MyCustomFont", + fontSize: 13.sp, + color: Colors.black, + fontWeight: FontWeight.w600, + ), + + // 按行截断 + trimCollapsedText: + ATAppLocalizations.of( + context, + )!.showMore, + // 收缩时按钮文字 + trimExpandedText: + ATAppLocalizations.of( + context, + )!.showLess, + // 展开时按钮文字 + moreStyle: TextStyle( + color: ChatVibeTheme.primaryLight, + fontFamily: "MyCustomFont", + fontWeight: FontWeight.w600, + ), + lessStyle: TextStyle( + color: ChatVibeTheme.primaryLight, + fontFamily: "MyCustomFont", + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ), + SizedBox(height: 5.w), + (detail?.pictures?.isNotEmpty ?? false) + ? detail?.pictures?.length == 1 + ? Container( + margin: EdgeInsets.symmetric( + horizontal: 10.w, + ), + constraints: BoxConstraints( + maxWidth: + ScreenUtil().screenWidth / 2, + ), + child: ATDebounceWidget( + child: netImage( + url: + detail + ?.pictures + ?.first + .resourceUrl ?? + "", + borderRadius: + BorderRadius.circular(8.w), + ), + onTap: () { + List ims = []; + for (var pc + in detail?.pictures ?? []) { + String path = pc.resourceUrl; + if (path.isNotEmpty) { + ims.add(path); + } + } + String encodedUrls = + Uri.encodeComponent( + jsonEncode(ims), + ); + ATNavigatorUtils.push( + context, + "${MainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=0", + ); + }, + ), + ) + : Container( + child: GridView.builder( + shrinkWrap: true, + physics: + const NeverScrollableScrollPhysics(), + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: + detail + ?.pictures + ?.length == + 1 + ? 1 + : (detail + ?.pictures + ?.length == + 2 + ? 2 + : 3), // 每行2个 + mainAxisSpacing: 10, + crossAxisSpacing: 10, + ), + padding: EdgeInsets.all(10), + itemCount: detail?.pictures?.length, + itemBuilder: (context, index) { + return ATDebounceWidget( + child: netImage( + url: + detail + ?.pictures?[index] + .resourceUrl ?? + "", + borderRadius: + BorderRadius.circular( + 8.w, + ), + ), + onTap: () { + List ims = []; + for (var pc + in detail?.pictures ?? + []) { + String path = + pc.resourceUrl; + if (path.isNotEmpty) { + ims.add(path); + } + } + String encodedUrls = + Uri.encodeComponent( + jsonEncode(ims), + ); + ATNavigatorUtils.push( + context, + "${MainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=$index", + ); + }, + ); + }, + ), + ) + : Container(), + SizedBox(height: 5.w), + Row( + children: [ + Container( + margin: EdgeInsetsDirectional.only( + start: 10.w, + ), + alignment: + AlignmentDirectional.centerStart, + child: text( + ATMDateUtils.formatMessageTime( + context, + DateTime.fromMillisecondsSinceEpoch( + detail?.createTime ?? 0, + ), + ), + textColor: Colors.grey, + fontSize: 12.sp, + ), + ), + Spacer(), + ], + ), + SizedBox(height: 5.w), + ], + ), + ), + SliverPersistentHeader( + pinned: true, // TabBar 是否固定 + delegate: _SliverAppBarDelegate( + Container( + color: Colors.white, // 背景色 + child: TabBar( + controller: _tabController, + isScrollable: false, + indicatorSize: TabBarIndicatorSize.tab, + tabs: [ + _buildTab( + 0, + "atu_images/dynamic/at_icon_reply.png", + "${ATAppLocalizations.of(context)!.comment}(${detail?.commentStrQuantity ?? 0})", + true, + ), + _buildTab( + 1, + (detail?.like ?? false) + ? "atu_images/dynamic/at_icon_like_red.png" + : "atu_images/dynamic/at_icon_like.png", + "${ATAppLocalizations.of(context)!.like}(${detail?.likeStrQuantity ?? 0})", + !(detail?.like ?? false), + ), + _buildTab( + 2, + "atu_images/dynamic/at_icon_dy_gift2.png", + "${ATAppLocalizations.of(context)!.gift}(${detail?.giftStrQuantity ?? 0})", + false, + ), + ], + dividerColor: Colors.transparent, + labelStyle: TextStyle( + fontSize: 16.sp, + fontFamily: 'MyCustomFont', + fontWeight: FontWeight.bold, + ), + unselectedLabelStyle: TextStyle( + fontSize: 15.sp, + fontFamily: 'MyCustomFont', + color: Colors.grey, + fontWeight: FontWeight.normal, + ), + indicator: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xff7726FF).withOpacity(0.6), + Color(0xff7726FF).withOpacity(0.1), + ], + ), + ), + labelPadding: EdgeInsets.zero, + ), + ), + ), + ), + ]; + }, + body: Container( + margin: EdgeInsets.only(bottom: 45.w), + child: TabBarView( + physics: NeverScrollableScrollPhysics(), + controller: _tabController, + children: [ + DynamicCommentPage( + widget.id, + num.parse(detail?.commentStrQuantity ?? "0"), + widget.canGoUserInfo, + repayCall: ( + cId, + toUserId, + rootCommentId, + toUserName, + ) { + this.toUserId = toUserId; + cName = toUserName; + this.rootCommentId = rootCommentId; + this.commentId = cId; + setState(() {}); + }, + deleteCall: (count) { + setState(() { + toUserId = null; + cName = ""; + rootCommentId = null; + commentId = null; + num cont = + int.parse( + detail?.commentStrQuantity ?? "0", + ) - + count; + detail?.setCommentStrQuantity("${cont}"); + setState(() {}); + }); + }, + ), + DynamicCommentLikePage( + widget.id, + widget.canGoUserInfo, + ), + DynamicCommentGiftPage( + widget.id, + widget.canGoUserInfo, + ), + ], + ), + ), + ), + CommentInput( + detail?.userId ?? "", + widget.id, + toUserName: cName, + deleteCallBack: () { + if (rootCommentId == null && + toUserId == null && + cName.isEmpty) { + return; + } + setState(() { + toUserId = null; + cName = ""; + rootCommentId = null; + commentId = null; + }); + }, + callBack: (tx) { + comment(tx); + }, + sendGiftCallBack: (count) { + int cont = + int.parse(detail?.giftStrQuantity ?? "0") + count; + detail?.setGiftStrQuantity("${cont}"); + setState(() {}); + }, + ), + ], + ), + ), + ), + ], + ), + onTap: () { + Provider.of( + context, + listen: false, + ).updateShowEmojiState(false, () { + setState(() {}); + }); + Provider.of( + context, + listen: false, + ).updateShowGiftState(false, () { + setState(() {}); + }); + }, + ), + ); + } + + void _deleteDynamic(String dynamicId) { + DynamicRepositoryImp() + .dynamicDelete(dynamicId) + .then((result) { + eventBus.fire(UpdateDynamicEvent()); + ATNavigatorUtils.goBack(context); + ATTts.show(ATAppLocalizations.of(context)!.deleteSuccessful); + }) + .catchError((e) {}); + } + + void comment(String text) { + DynamicRepositoryImp() + .dynamicComment( + text, + widget.id, + toUserId: toUserId, + commentId: commentId, + rootCommentId: rootCommentId, + ) + .then((res) { + res.setUserNickname( + AccountStorage().getCurrentUser()?.userProfile?.userNickname ?? "", + ); + res.setUserAvatar( + AccountStorage().getCurrentUser()?.userProfile?.userAvatar ?? "", + ); + res.setUserSex( + AccountStorage().getCurrentUser()?.userProfile?.userSex ?? 1, + ); + if (rootCommentId == null) { + Provider.of( + context, + listen: false, + ).updateRootComment(res, () { + setState(() {}); + }); + } else { + Provider.of( + context, + listen: false, + ).updateChildComment(res, rootCommentId ?? 0, toUserId ?? "", () { + setState(() {}); + }); + } + toUserId = null; + cName = ""; + rootCommentId = null; + commentId = null; + FocusScope.of(context).unfocus(); + int cont = int.parse(detail?.commentStrQuantity ?? "0") + 1; + detail?.setCommentStrQuantity("${cont}"); + setState(() {}); + ATTts.show(ATAppLocalizations.of(context)!.replySucc); + }); + } + + Widget _buildTab( + int index, + String imagePath, + String text, + bool needSwitchImage, + ) { + bool isSelected = _tabController.index == index; + return Tab( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (isSelected) + Image.asset( + imagePath, // 原图 + height: 20.w, + ) + else + Image.asset( + imagePath, // 这里可以使用灰色图片路径,或者使用颜色滤镜 + height: 20.w, + color: needSwitchImage ? Colors.grey : null, // 使用颜色滤镜将图片变为灰色 + ), + SizedBox(width: 3.w), + Text( + text, + style: + isSelected + ? TextStyle( + fontSize: 13.sp, + fontWeight: FontWeight.w600, + color: Colors.white, // 选中时文字颜色 + ) + : TextStyle( + fontSize: 13.sp, + fontWeight: FontWeight.normal, + color: index == 2 ? ChatVibeTheme.primaryLight : Colors.grey, // 未选中时文字颜色 + ), + ), + ], + ), + ); + } + + void _loadData() { + ATLoadingManager.exhibitOperation(); + DynamicRepositoryImp() + .dynamicDetails(widget.id) + .then((result) { + ATLoadingManager.veilRoutine(); + detail = result; + setState(() {}); + }) + .catchError((_) { + eventBus.fire(UpdateDynamicEvent()); + ATLoadingManager.veilRoutine(); + ATNavigatorUtils.goBack(context); + }); + } +} + +class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { + _SliverAppBarDelegate(this._tabBar); + + final Widget _tabBar; + + @override + double get minExtent => 35.w; + + @override + double get maxExtent => 35.w; + + @override + Widget build( + BuildContext context, + double shrinkOffset, + bool overlapsContent, + ) { + return _tabBar; + } + + @override + bool shouldRebuild(_SliverAppBarDelegate oldDelegate) { + return true; + } +} diff --git a/lib/chatvibe_features/dynamic/detail/gift/dynamic_comment_gift_page.dart b/lib/chatvibe_features/dynamic/detail/gift/dynamic_comment_gift_page.dart new file mode 100644 index 0000000..a873747 --- /dev/null +++ b/lib/chatvibe_features/dynamic/detail/gift/dynamic_comment_gift_page.dart @@ -0,0 +1,173 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/dynamic_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/dynamic_list_res.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +class DynamicCommentGiftPage extends ATPageList { + String dynamicId; + String canGoUserInfo = ""; + DynamicCommentGiftPage(this.dynamicId,this.canGoUserInfo); + + @override + _DynamicCommentGiftPageState createState() => + _DynamicCommentGiftPageState(dynamicId,canGoUserInfo); +} + +class _DynamicCommentGiftPageState + extends ATPageListState { + String dynamicId; + String canGoUserInfo = ""; + _DynamicCommentGiftPageState(this.dynamicId,this.canGoUserInfo); + + @override + void initState() { + super.initState(); + enablePullUp = true; + enablePullDown = true; + isShowFooter = false; + isCanClickEmpty = false; + backgroundColor = Colors.white; + padding = EdgeInsets.symmetric(vertical:10.w); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: buildList(context), + ); + } + + @override + Widget buildItem(Records res) { + return Row( + children: [ + SizedBox(width: 10.w,), + ATDebounceWidget(child: netImage( + url: res.userAvatar ?? "", + width: 35.w, + height: 35.w, + shape: BoxShape.circle, + ), onTap: (){ + if(canGoUserInfo=="true"){ + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == res.userId}&tageId=${res.userId}", + ); + } + }) + , + SizedBox(width: 10.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + constraints: BoxConstraints(maxWidth: 100.w), + child: text( + res.userNickname ?? "", + fontSize: 14.sp, + textColor: Colors.black, + fontWeight: FontWeight.w500, + ), + ), + SizedBox(width: 3.w), + Container( + width: (res.age ?? 0) > 999 ? 58.w : 48.w, + height: 24.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + res.userSex == 0 + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png", + ), + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + xb(res.userSex), + text( + "${res.age}", + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 3.w), + ], + ), + ), + ], + ), + Container( + alignment: AlignmentDirectional.centerStart, + child: text( + ATMDateUtils.formatMessageTime( + context, + DateTime.fromMillisecondsSinceEpoch(res.updateTime ?? 0), + ), + textColor: Colors.grey, + fontSize: 12.sp, + ), + ), + ], + ), + Spacer(), + text( + "*${res.giftQuantity ?? 0}", + textColor: Colors.black, + fontWeight: FontWeight.w600, + fontSize: 13.sp, + ), + SizedBox(width: 3.w,), + netImage(url: res.giftUrl ?? "", width: 25.w, height: 25.w), + SizedBox(width: 10.w,), + ], + ); + } + + builderDivider() { + return Divider( + height: 22.w, + thickness: 0.2.w, + color: Colors.grey, + ); + } + @override + empty() { + return mainEmpty(textColor: Colors.grey,); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var result = await DynamicRepositoryImp().dynamicListGift( + dynamicId, + current: page, + ); + onSuccess(result.records ?? []); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } +} diff --git a/lib/chatvibe_features/dynamic/detail/like/dynamic_comment_like_page.dart b/lib/chatvibe_features/dynamic/detail/like/dynamic_comment_like_page.dart new file mode 100644 index 0000000..5be35ac --- /dev/null +++ b/lib/chatvibe_features/dynamic/detail/like/dynamic_comment_like_page.dart @@ -0,0 +1,161 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/dynamic_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/dynamic_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/follow_user_res.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +class DynamicCommentLikePage extends ATPageList { + String dynamicId; + String canGoUserInfo = ""; + DynamicCommentLikePage(this.dynamicId,this.canGoUserInfo); + + @override + _DynamicCommentLikePageState createState() => + _DynamicCommentLikePageState(dynamicId,canGoUserInfo); +} + +class _DynamicCommentLikePageState + extends ATPageListState { + String dynamicId; + String canGoUserInfo = ""; + _DynamicCommentLikePageState(this.dynamicId,this.canGoUserInfo); + + @override + void initState() { + super.initState(); + enablePullUp = true; + enablePullDown = true; + isShowFooter = false; + isCanClickEmpty = false; + backgroundColor = Colors.white; + padding = EdgeInsets.symmetric(vertical: 10.w); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: buildList(context), + ); + } + + @override + Widget buildItem(Records res) { + return Row( + children: [ + SizedBox(width: 10.w,), + ATDebounceWidget( + child: netImage( + url: res.userAvatar ?? "", + width: 35.w, + height: 35.w, + shape: BoxShape.circle, + ), + onTap: () { + if(canGoUserInfo=="true"){ + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == res.userId}&tageId=${res.userId}", + ); + } + }, + ), + SizedBox(width: 10.w), + Container( + constraints: BoxConstraints(maxWidth: 100.w), + child: text( + res.userNickname ?? "", + fontSize: 14.sp, + textColor: Colors.black, + fontWeight: FontWeight.w500, + ), + ), + SizedBox(width: 3.w), + Container( + width: (res.age ?? 0) > 999 ? 58.w : 48.w, + height: 24.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + res.userSex == 0 + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png", + ), + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + xb(res.userSex), + text( + "${res.age}", + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 3.w), + ], + ), + ), + Spacer(), + Container( + margin: EdgeInsetsDirectional.only(start: 10.w), + alignment: AlignmentDirectional.centerStart, + child: text( + ATMDateUtils.formatMessageTime( + context, + DateTime.fromMillisecondsSinceEpoch(res.createTime ?? 0), + ), + textColor: Colors.grey, + fontSize: 12.sp, + ), + ), + SizedBox(width: 10.w,), + ], + ); + } + @override + empty() { + return mainEmpty(textColor: Colors.grey,); + } + + + builderDivider() { + return Divider( + height: 22.w, + thickness: 0.2.w, + color: Colors.grey, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var result = await DynamicRepositoryImp().dynamicListLike( + dynamicId, + current: page, + ); + onSuccess(result.records ?? []); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } +} diff --git a/lib/chatvibe_features/dynamic/dynamic_route.dart b/lib/chatvibe_features/dynamic/dynamic_route.dart new file mode 100644 index 0000000..85f532d --- /dev/null +++ b/lib/chatvibe_features/dynamic/dynamic_route.dart @@ -0,0 +1,54 @@ +import 'package:fluro/fluro.dart'; +import 'package:aslan/chatvibe_features/dynamic/trend/gift/dynamic_message_gift_page.dart'; +import 'package:aslan/chatvibe_features/dynamic/trend/like/dynamic_message_like_page.dart'; +import 'package:aslan/chatvibe_features/dynamic/trend/reply/dynamic_message_reply_page.dart'; +import 'package:aslan/chatvibe_core/routes/at_router_init.dart'; +import 'package:aslan/chatvibe_features/dynamic/create/create_dynamic_page.dart'; +import 'package:aslan/chatvibe_features/dynamic/detail/dynamic_detail_for_pic_page.dart'; +import 'package:aslan/chatvibe_features/dynamic/detail/dynamic_detail_page.dart'; + +class DynamicRoute implements ATIRouterProvider { + static String create = '/dynamic/create'; + static String detail = '/dynamic/detail'; + static String detailForPic = '/dynamic/detailForPic'; + static String dynamicMessageReply = '/dynamic/dynamicMessageReply'; + static String dynamicMessageLike = '/dynamic/dynamicMessageLike'; + static String dynamicMessageGift = '/dynamic/DynamicMessageGift'; + + @override + void initRouter(FluroRouter router) { + router.define( + create, + handler: Handler(handlerFunc: (_, params) => CreateDynamicPage()), + ); + + router.define( + dynamicMessageReply, + handler: Handler(handlerFunc: (_, params) => DynamicMessageReplyPage()), + ); + + router.define( + dynamicMessageLike, + handler: Handler(handlerFunc: (_, params) => DynamicMessageLikePage()), + ); + + router.define( + dynamicMessageGift, + handler: Handler(handlerFunc: (_, params) => DynamicMessageGiftPage()), + ); + + router.define( + detail, + handler: Handler( + handlerFunc: (_, params) => DynamicDetailPage(params['id']!.first,params['canGoUserInfo']!.first), + ), + ); + + router.define( + detailForPic, + handler: Handler( + handlerFunc: (_, params) => DynamicDetailForPicPage(params['id']!.first,params['canGoUserInfo']!.first), + ), + ); + } +} diff --git a/lib/chatvibe_features/dynamic/following/dynamic_following_page.dart b/lib/chatvibe_features/dynamic/following/dynamic_following_page.dart new file mode 100644 index 0000000..0d008b3 --- /dev/null +++ b/lib/chatvibe_features/dynamic/following/dynamic_following_page.dart @@ -0,0 +1,497 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:provider/provider.dart'; +import 'package:readmore/readmore.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/at_lk_event_bus.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_dialog_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/dynamic_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/dynamic_list_res.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_features/dynamic/dynamic_route.dart'; + +class DynamicFollowingPage extends ATPageList { + @override + _DynamicFollowingPageState createState() => _DynamicFollowingPageState(); +} + +class _DynamicFollowingPageState + extends ATPageListState { + late StreamSubscription _subscription; + + @override + void initState() { + super.initState(); + enablePullUp = true; + backgroundColor = Colors.transparent; + isShowDivider = true; + isShowFooter = false; + padding = EdgeInsets.only(bottom: 5.w); + loadData(1); + _subscription = eventBus.on().listen((event) { + if (mounted) { + loadData(1); + } + }); + } + + @override + void dispose() { + _subscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: buildList(context), + ); + } + + @override + Widget buildItem(Records res) { + return ATDebounceWidget( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + SizedBox(width: 10.w), + ATDebounceWidget( + child: netImage( + url: res.userProfile?.userAvatar ?? "", + shape: BoxShape.circle, + height: 45.w, + width: 45.w, + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == res.userProfile?.id}&tageId=${res.userProfile?.id}", + ); + }, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 3.w, + children: [ + Container( + margin: EdgeInsetsDirectional.only(start: 5.w), + height: 21.w, + constraints: BoxConstraints(maxWidth: 95.w), + child: chatvibeNickNameText( + maxWidth: 95.w, + res.userProfile?.userNickname ?? "", + fontSize: 15.sp, + textColor: Colors.black, + fontWeight: FontWeight.w400, + type: res.userProfile?.getVIP()?.name ?? "", + needScroll: + (res + .userProfile + ?.userNickname + ?.characters + .length ?? + 0) > + 10, + ), + ), + Container( + width: + (res.userProfile?.age ?? 0) > 999 ? 58.w : 48.w, + height: 24.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + res.userProfile?.userSex == 0 + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png", + ), + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + xb(res.userProfile?.userSex), + text( + "${res.userProfile?.age}", + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 3.w), + ], + ), + ), + getVIPBadge( + res.userProfile?.getVIP()?.name, + width: 45.w, + height: 25.w, + ), + ], + ), + Container( + alignment: AlignmentDirectional.centerStart, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 5.w), + res.userProfile?.wearBadge?.isNotEmpty ?? false + ? SizedBox( + height: 28.w, + child: ListView.separated( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: + res.userProfile?.wearBadge?.length ?? 0, + itemBuilder: (context, index) { + return netImage( + width: 28.w, + height: 28.w, + url: + res + .userProfile + ?.wearBadge?[index] + .selectUrl ?? + "", + ); + }, + separatorBuilder: ( + BuildContext context, + int index, + ) { + return SizedBox(width: 5.w); + }, + ), + ) + : Container(), + SizedBox(width: 5.w), + ], + ), + ), + ), + ], + ), + ), + SizedBox(width: 5.w), + ATDebounceWidget( + child: Container( + padding: EdgeInsetsDirectional.all(3.w), + child: Icon( + Icons.more_vert, + color: Colors.black45, + size: 18.w, + ), + ), + onTap: () { + ATDialogUtils.revealDynamicCommentOptDialog( + context, + reportCallback: + res.userId != + AccountStorage().getCurrentUser()?.userProfile?.id + ? () { + ATNavigatorUtils.push( + context, + "${MainRoute.report}?type=dynamic&tageId=${res.dynamicId}", + replace: false, + ); + } + : null, + deleteCallback: + res.userId == + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id || + (Provider.of( + context, + listen: false, + ).userIdentity?.admin ?? + false) + ? () { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.deleteDynamicTips, + leftConfirm: true, + isBtnBgFlip: true, + btnText: + ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + _deleteDynamic(res.dynamicId ?? ""); + }, + ); + }, + ); + } + : null, + ); + }, + ), + SizedBox(width: 3.w), + ], + ), + SizedBox(height: 3.w), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: Container( + margin: EdgeInsetsDirectional.symmetric(horizontal: 10.w), + child: ReadMoreText( + res.content ?? "", + trimLines: 3, + // 收缩时显示的行数 + trimMode: TrimMode.Line, + style: TextStyle( + fontFamily: "MyCustomFont", + fontSize: 14.sp, + color: Colors.black, + fontWeight: FontWeight.w600, + ), + + // 按行截断 + trimCollapsedText: ATAppLocalizations.of(context)!.showMore, + // 收缩时按钮文字 + trimExpandedText: ATAppLocalizations.of(context)!.showLess, + // 展开时按钮文字 + moreStyle: TextStyle( + color: ChatVibeTheme.primaryLight, + fontFamily: "MyCustomFont", + fontWeight: FontWeight.w600, + ), + lessStyle: TextStyle( + color: ChatVibeTheme.primaryLight, + fontFamily: "MyCustomFont", + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ), + SizedBox(height: 5.w), + (res.pictures?.isNotEmpty ?? false) + ? res.pictures?.length == 1 + ? Container( + margin: EdgeInsets.symmetric(horizontal: 10.w), + constraints: BoxConstraints( + maxWidth: ScreenUtil().screenWidth / 2, + ), + child: ATDebounceWidget( + child: netImage( + url: res.pictures?.first.resourceUrl ?? "", + borderRadius: BorderRadius.circular(8.w), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${DynamicRoute.detailForPic}?id=${res.dynamicId}&canGoUserInfo=true", + replace: false, + ); + }, + ), + ) + : Container( + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: + res.pictures?.length == 2 ? 2 : 3, // 每行2个 + mainAxisSpacing: 10, + crossAxisSpacing: 10, + ), + padding: EdgeInsets.all(10), + itemCount: res.pictures?.length, + itemBuilder: (context, index) { + return ATDebounceWidget( + child: netImage( + url: res.pictures?[index].resourceUrl ?? "", + borderRadius: BorderRadius.circular(8.w), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${DynamicRoute.detailForPic}?id=${res.dynamicId}&canGoUserInfo=true", + replace: false, + ); + }, + ); + }, + ), + ) + : Container(), + SizedBox(height: 10.w), + Row( + children: [ + Row( + children: [ + SizedBox(width: 10.w), + Image.asset( + "atu_images/dynamic/at_icon_reply.png", + height: 22.w, + color: Colors.grey, + ), + SizedBox(width: 3.w), + text( + res.commentStrQuantity != "0" + ? "${ATAppLocalizations.of(context)!.comment}(${res.commentStrQuantity})" + : ATAppLocalizations.of(context)!.catchFirstComment, + textColor: Colors.grey, + fontSize: 13.sp, + ), + ], + ), + SizedBox(width: 25.w), + ATDebounceWidget( + child: Row( + children: [ + Image.asset( + (res.like ?? false) + ? "atu_images/dynamic/at_icon_like_red.png" + : "atu_images/dynamic/at_icon_like.png", + height: 22.w, + color: (res.like ?? false) ? null : Colors.grey, + ), + SizedBox(width: 3.w), + text( + res.likeStrQuantity != "0" + ? "${ATAppLocalizations.of(context)!.like}(${res.likeStrQuantity ?? 0})" + : ATAppLocalizations.of(context)!.like, + textColor: Colors.grey, + fontSize: 13.sp, + ), + ], + ), + onTap: () { + _like(res); + }, + ), + ], + ), + SizedBox(height: 8.w), + Row( + children: [ + SizedBox(width: 10.w), + netImage( + url: + AccountStorage().getCurrentUser()?.userProfile?.userAvatar ?? + "", + shape: BoxShape.circle, + height: 32.w, + width: 32.w, + ), + SizedBox(width: 8.w), + Expanded( + child: Container( + padding: EdgeInsetsDirectional.only(start: 10.w), + alignment: AlignmentDirectional.centerStart, + height: 28.w, + decoration: BoxDecoration( + color: Color(0xffF4EAFF), + borderRadius: BorderRadius.circular(16.w), + ), + child: text( + ATAppLocalizations.of(context)!.saySomething, + textColor: Colors.grey, + fontSize: 13.sp, + ), + ), + ), + SizedBox(width: 10.w), + ], + ), + ], + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${DynamicRoute.detail}?id=${res.dynamicId}&canGoUserInfo=true", + replace: false, + ); + }, + ); + } + + @override + builderDivider() { + return Divider(height: 22.w, color: Color(0xffF2F2F2)); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var result = await DynamicRepositoryImp().dynamicListFollow( + current: page, + ); + onSuccess(result.records ?? []); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + void _like(Records res) { + DynamicRepositoryImp() + .dynamicLike(res.dynamicId ?? "", !(res.like ?? false)) + .then((result) { + res.setLike(!(res.like ?? false)); + if (res.like ?? false) { + res.setLikeStrQuantity( + "${int.parse(res.likeStrQuantity ?? "0") + 1}", + ); + } else { + res.setLikeStrQuantity( + "${int.parse(res.likeStrQuantity ?? "0") - 1}", + ); + } + setState(() {}); + }) + .catchError((_) {}); + } + + void _deleteDynamic(String dynamicId) { + DynamicRepositoryImp() + .dynamicDelete(dynamicId) + .then((result) { + setState(() { + items.removeWhere((element) => element.dynamicId == dynamicId); + }); + }) + .catchError((e) {}); + } +} diff --git a/lib/chatvibe_features/dynamic/index_dynamic_page.dart b/lib/chatvibe_features/dynamic/index_dynamic_page.dart new file mode 100644 index 0000000..9bde447 --- /dev/null +++ b/lib/chatvibe_features/dynamic/index_dynamic_page.dart @@ -0,0 +1,141 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_features/dynamic/dynamic_route.dart'; +import 'package:aslan/chatvibe_features/dynamic/following/dynamic_following_page.dart'; +import 'package:aslan/chatvibe_features/dynamic/trend/dynamic_trend_page.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; + +///动态 +class IndexDynamicPage extends StatefulWidget { + @override + _IndexDynamicPageState createState() => _IndexDynamicPageState(); +} + +class _IndexDynamicPageState extends State { + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + ATGlobalConfig.businessLogicStrategy.getDynamicPageBackgroundImage(), + width: ScreenUtil().screenWidth, + fit: BoxFit.cover, + ), + DefaultTabController( + length: 2, + initialIndex: 1, + child: Scaffold( + backgroundColor: ATGlobalConfig.businessLogicStrategy.getDynamicPageScaffoldBackgroundColor(), + appBar: AppBar( + backgroundColor: ATGlobalConfig.businessLogicStrategy.getDynamicPageAppBarBackgroundColor(), + leading: Transform.translate( + offset: Offset(ATGlobalConfig.lang == "ar" ? -9.w : 9.w, 0), + child: Transform.scale( + scale: 0.782, + child: GestureDetector( + child: Consumer( + builder: (context, ref, child) { + return netImage( + url: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userAvatar ?? + "", + defaultImg: 'atu_images/general/at_icon_avar_defalt.png', + width: 32.w, + height: 32.w, + shape: BoxShape.circle, + ); + }, + ), + onTap: () { + Provider.of( + context, + listen: false, + ).openDrawer(context); + }, + ), + ), + ), + title: TabBar( + tabs: + [ + ATAppLocalizations.of(context)!.following, + ATAppLocalizations.of(context)!.trend, + ] + .map( + (e) => Padding( + padding: EdgeInsets.only(bottom: 3.w, top: 4.w), + child: Text(e), + ), + ) + .toList(), + labelColor: ATGlobalConfig.businessLogicStrategy.getDynamicPageTabLabelColor(), + tabAlignment: TabAlignment.center, + isScrollable: ATGlobalConfig.businessLogicStrategy.shouldDynamicPageTabScrollable(), + splashFactory: NoSplash.splashFactory, + overlayColor: MaterialStateProperty.all(Colors.transparent), + indicator: BoxDecoration(), + unselectedLabelColor: ATGlobalConfig.businessLogicStrategy.getDynamicPageTabUnselectedLabelColor(), + labelStyle: ATGlobalConfig.businessLogicStrategy + .getDynamicPageTabLabelStyle() + .copyWith( + fontSize: ATGlobalConfig.businessLogicStrategy + .getDynamicPageTabLabelStyle() + .fontSize + ?.sp, + ), + unselectedLabelStyle: ATGlobalConfig.businessLogicStrategy + .getDynamicPageTabUnselectedLabelStyle() + .copyWith( + fontSize: ATGlobalConfig.businessLogicStrategy + .getDynamicPageTabUnselectedLabelStyle() + .fontSize + ?.sp, + ), + indicatorColor: ATGlobalConfig.businessLogicStrategy.getDynamicPageTabIndicatorColor(), + dividerColor: ATGlobalConfig.businessLogicStrategy.getDynamicPageTabDividerColor(), + ), + actions: [Container(width: 40.w)], + ), + body: TabBarView( + children: [DynamicFollowingPage(), DynamicTrendPage()], + ), + ), + ), + PositionedDirectional( + bottom: 80.w, + end: 0.w, + child: ATDebounceWidget( + child: Image.asset( + ATGlobalConfig.businessLogicStrategy.getDynamicPageAddButtonIcon(), + height: 75.w, + ), + onTap: () { + ATNavigatorUtils.push(context, DynamicRoute.create, replace: false); + }, + ), + ), + ], + ); + } +} diff --git a/lib/chatvibe_features/dynamic/person_dynamic_list_page.dart b/lib/chatvibe_features/dynamic/person_dynamic_list_page.dart new file mode 100644 index 0000000..aab286f --- /dev/null +++ b/lib/chatvibe_features/dynamic/person_dynamic_list_page.dart @@ -0,0 +1,538 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_core/utilities/at_dialog_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/dynamic_repository_imp.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:provider/provider.dart'; +import 'package:readmore/readmore.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_domain/models/res/dynamic_list_res.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_features/dynamic/dynamic_route.dart'; + +class PersonDynamicListPage extends ATPageList { + String userId; + bool isFromMe = false; + + PersonDynamicListPage(this.isFromMe, this.userId); + + @override + _PersonDynamicListPageState createState() => + _PersonDynamicListPageState(isFromMe, userId); +} + +class _PersonDynamicListPageState + extends ATPageListState { + String userId; + bool isFromMe = false; + + _PersonDynamicListPageState(this.isFromMe, this.userId); + + @override + void initState() { + super.initState(); + enablePullUp = true; + enablePullDown = false; + padding = EdgeInsets.only( + bottom: + AccountStorage().getCurrentUser()?.userProfile?.id == userId + ? 10.w + : 65.w, + top: 8.w, + ); + backgroundColor = Colors.transparent; + isShowDivider = true; + isShowFooter = false; + loadData(1); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: buildList(context), + ); + } + + @override + Widget buildItem(Records res) { + return ATDebounceWidget( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + SizedBox(width: 10.w), + ATDebounceWidget( + child: netImage( + url: res.userProfile?.userAvatar ?? "", + shape: BoxShape.circle, + height: 45.w, + width: 45.w, + ), + onTap: () {}, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 3.w, + children: [ + Container( + margin: EdgeInsetsDirectional.only(start: 5.w), + height: 21.w, + constraints: BoxConstraints(maxWidth: 95.w), + child: chatvibeNickNameText( + maxWidth: 95.w, + res.userProfile?.userNickname ?? "", + fontSize: 15.sp, + textColor: Colors.white, + fontWeight: FontWeight.w400, + type: res.userProfile?.getVIP()?.name ?? "", + needScroll: + (res + .userProfile + ?.userNickname + ?.characters + .length ?? + 0) > + 10, + ), + ), + Container( + width: + (res.userProfile?.age ?? 0) > 999 ? 58.w : 48.w, + height: 24.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + res.userProfile?.userSex == 0 + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png", + ), + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + xb(res.userProfile?.userSex), + text( + "${res.userProfile?.age}", + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 3.w), + ], + ), + ), + getVIPBadge( + res.userProfile?.getVIP()?.name, + width: 45.w, + height: 25.w, + ), + ], + ), + Container( + alignment: AlignmentDirectional.centerStart, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 5.w), + res.userProfile?.wearBadge?.isNotEmpty ?? false + ? SizedBox( + height: 28.w, + child: ListView.separated( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: + res.userProfile?.wearBadge?.length ?? 0, + itemBuilder: (context, index) { + return netImage( + width: 28.w, + height: 28.w, + url: + res + .userProfile + ?.wearBadge?[index] + .selectUrl ?? + "", + ); + }, + separatorBuilder: ( + BuildContext context, + int index, + ) { + return SizedBox(width: 5.w); + }, + ), + ) + : Container(), + SizedBox(width: 5.w), + ], + ), + ), + ), + ], + ), + ), + SizedBox(width: 5.w), + res.userId != AccountStorage().getCurrentUser()?.userProfile?.id + ? ((res.subscription ?? false) + ? Container() + : ATDebounceWidget( + child: Container( + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + gradient: LinearGradient( + colors: [Color(0xff7726FF), Color(0xffC670FF)], + ), + ), + width: 68.w, + height: 23.w, + child: text( + ATAppLocalizations.of(context)!.follow, + textColor: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 13.sp, + ), + ), + onTap: () { + AccountRepository() + .followUser(res.userId ?? "") + .then((result) { + res.setSubscription(true); + setState(() {}); + }); + }, + )) + : Container(), + ATDebounceWidget( + child: Container( + child: Icon(Icons.more_vert, color: Colors.white, size: 18.w), + padding: EdgeInsetsDirectional.all(3.w), + ), + onTap: () { + ATDialogUtils.revealDynamicCommentOptDialog( + context, + reportCallback: + res.userId != + AccountStorage().getCurrentUser()?.userProfile?.id + ? () { + ATNavigatorUtils.push( + context, + "${MainRoute.report}?type=dynamic&tageId=${res.dynamicId}", + replace: false, + ); + } + : null, + deleteCallback: + res.userId == + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id || + (Provider.of( + context, + listen: false, + ).userIdentity?.admin ?? + false) + ? () { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.deleteDynamicTips, + leftConfirm: true, + isBtnBgFlip: true, + btnText: + ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + _deleteDynamic(res.dynamicId ?? ""); + }, + ); + }, + ); + } + : null, + ); + }, + ), + SizedBox(width: 3.w), + ], + ), + SizedBox(height: 3.w), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: Container( + margin: EdgeInsetsDirectional.symmetric(horizontal: 10.w), + child: ReadMoreText( + res.content ?? "", + trimLines: 3, + // 收缩时显示的行数 + trimMode: TrimMode.Line, + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + ), + + // 按行截断 + trimCollapsedText: ATAppLocalizations.of(context)!.showMore, + // 收缩时按钮文字 + trimExpandedText: ATAppLocalizations.of(context)!.showLess, + // 展开时按钮文字 + moreStyle: TextStyle( + color: ChatVibeTheme.primaryLight, + fontWeight: FontWeight.w600, + ), + lessStyle: TextStyle( + color: ChatVibeTheme.primaryLight, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ), + SizedBox(height: 5.w), + (res.pictures?.isNotEmpty ?? false) + ? res.pictures?.length == 1 + ? Container( + margin: EdgeInsets.symmetric(horizontal: 10.w), + constraints: BoxConstraints( + maxWidth: ScreenUtil().screenWidth / 2, + ), + child: ATDebounceWidget( + child: netImage( + url: res.pictures?.first.resourceUrl ?? "", + borderRadius: BorderRadius.circular(8.w), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${DynamicRoute.detailForPic}?id=${res.dynamicId}&canGoUserInfo=false", + replace: false, + ); + }, + ), + ) + : Container( + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: + res.pictures?.length == 1 + ? 1 + : (res.pictures?.length == 2 ? 2 : 3), // 每行2个 + mainAxisSpacing: 10, + crossAxisSpacing: 10, + ), + padding: EdgeInsets.all(10), + itemCount: res.pictures?.length, + itemBuilder: (context, index) { + return ATDebounceWidget( + child: netImage( + url: res.pictures?[index].resourceUrl ?? "", + borderRadius: BorderRadius.circular(8.w), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${DynamicRoute.detailForPic}?id=${res.dynamicId}&canGoUserInfo=false", + replace: false, + ); + }, + ); + }, + ), + ) + : Container(), + SizedBox(height: 10.w), + Row( + children: [ + Row( + children: [ + SizedBox(width: 10.w), + Image.asset( + "atu_images/dynamic/at_icon_reply.png", + height: 22.w, + color: Colors.grey, + ), + SizedBox(width: 3.w), + text( + res.commentStrQuantity != "0" + ? "${ATAppLocalizations.of(context)!.comment}(${res.commentStrQuantity ?? 0})" + : ATAppLocalizations.of(context)!.catchFirstComment, + textColor: Colors.grey, + fontSize: 13.sp, + ), + ], + ), + SizedBox(width: 25.w), + ATDebounceWidget( + child: Row( + children: [ + Image.asset( + (res.like ?? false) + ? "atu_images/dynamic/at_icon_like_red.png" + : "atu_images/dynamic/at_icon_like.png", + height: 22.w, + color: (res.like ?? false) ? null : Colors.grey, + ), + SizedBox(width: 3.w), + text( + res.likeStrQuantity != "0" + ? "${ATAppLocalizations.of(context)!.like}(${res.likeStrQuantity ?? 0})" + : ATAppLocalizations.of(context)!.like, + textColor: Colors.grey, + fontSize: 13.sp, + ), + ], + ), + onTap: () { + _like(res); + }, + ), + ], + ), + SizedBox(height: 8.w), + Row( + children: [ + SizedBox(width: 10.w), + netImage( + url: + AccountStorage().getCurrentUser()?.userProfile?.userAvatar ?? + "", + shape: BoxShape.circle, + height: 32.w, + width: 32.w, + ), + SizedBox(width: 8.w), + Expanded( + child: Container( + padding: EdgeInsetsDirectional.only(start: 10.w), + alignment: AlignmentDirectional.centerStart, + height: 28.w, + decoration: BoxDecoration( + color: Color(0xffF7F3FB).withOpacity(0.2), + borderRadius: BorderRadius.circular(16.w), + ), + child: text( + ATAppLocalizations.of(context)!.saySomething, + textColor: Colors.white54, + fontSize: 13.sp, + ), + ), + ), + SizedBox(width: 10.w), + ], + ), + ], + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${DynamicRoute.detail}?id=${res.dynamicId}&canGoUserInfo=false", + replace: false, + ); + }, + ); + } + + @override + empty() { + return mainEmpty( + msg: ATAppLocalizations.of(context)!.noData, + textColor: Colors.white, + ); + } + + @override + builderDivider() { + return Divider(height: 22.w, thickness: 0.2, color: Colors.white24); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var result = await DynamicRepositoryImp().dynamicMyPage( + userId, + current: page, + ); + onSuccess(result.records ?? []); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + void _like(Records res) { + DynamicRepositoryImp() + .dynamicLike(res.dynamicId ?? "", !(res.like ?? false)) + .then((result) { + res.setLike(!(res.like ?? false)); + if (res.like ?? false) { + res.setLikeStrQuantity( + "${int.parse(res.likeStrQuantity ?? "0") + 1}", + ); + } else { + res.setLikeStrQuantity( + "${int.parse(res.likeStrQuantity ?? "0") - 1}", + ); + } + setState(() {}); + }) + .catchError((_) {}); + } + + void _deleteDynamic(String dynamicId) { + DynamicRepositoryImp() + .dynamicDelete(dynamicId) + .then((result) { + setState(() { + items.removeWhere((element) => element.dynamicId == dynamicId); + ATTts.show(ATAppLocalizations.of(context)!.deleteSuccessful); + }); + }) + .catchError((e) {}); + } +} diff --git a/lib/chatvibe_features/dynamic/trend/dynamic_trend_page.dart b/lib/chatvibe_features/dynamic/trend/dynamic_trend_page.dart new file mode 100644 index 0000000..34605e0 --- /dev/null +++ b/lib/chatvibe_features/dynamic/trend/dynamic_trend_page.dart @@ -0,0 +1,680 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_core/utilities/at_dialog_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/dynamic_repository_imp.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:provider/provider.dart'; +import 'package:readmore/readmore.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_domain/models/res/dynamic_list_res.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_features/dynamic/dynamic_route.dart'; + +import '../../../chatvibe_core/at_lk_event_bus.dart'; + +class DynamicTrendPage extends ATPageList { + @override + _DynamicTrendPageState createState() => _DynamicTrendPageState(); +} + +class _DynamicTrendPageState extends ATPageListState { + late StreamSubscription _subscription; + + @override + void initState() { + super.initState(); + enablePullUp = true; + backgroundColor = Colors.transparent; + isShowDivider = true; + isShowFooter = false; + padding = EdgeInsets.only(bottom: 5.w); + loadData(1); + _subscription = eventBus.on().listen((event) { + if (mounted) { + loadData(1); + } + }); + } + + @override + void dispose() { + _subscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: Column( + children: [ + Container( + height: kToolbarHeight + ScreenUtil().statusBarHeight, + alignment: Alignment.centerLeft, + child: Row( + children: [ + SizedBox(width: 15.w), + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 95.w, + height: 35.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(25.w)), + gradient: LinearGradient( + colors: [Color(0xff2647FF), Color(0xff7081FF)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + boxShadow: [ + BoxShadow( + color: Color(0xffA289EC), + offset: Offset(0, 4), + blurRadius: 4, + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + "atu_images/dynamic/at_icon_reply.png", + height: 20.w, + ), + SizedBox(width: 3.w), + text( + ATAppLocalizations.of(context)!.reply, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + DynamicRoute.dynamicMessageReply, + replace: false, + ); + }, + ), + SizedBox(width: 15.w), + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 95.w, + height: 35.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(25.w)), + gradient: LinearGradient( + colors: [Color(0xffF31216), Color(0xffFF8B8D)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + boxShadow: [ + BoxShadow( + color: Color(0xffA289EC), + offset: Offset(0, 4), + blurRadius: 4, + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + "atu_images/dynamic/at_icon_like.png", + height: 20.w, + ), + SizedBox(width: 3.w), + text( + ATAppLocalizations.of(context)!.like, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + DynamicRoute.dynamicMessageLike, + replace: false, + ); + }, + ), + SizedBox(width: 15.w), + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 95.w, + height: 35.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(25.w)), + gradient: LinearGradient( + colors: [Color(0xff9226FF), Color(0xffB070FF)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + boxShadow: [ + BoxShadow( + color: Color(0xffA289EC), + offset: Offset(0, 4), + blurRadius: 4, + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + "atu_images/dynamic/at_icon_dy_gift.png", + height: 20.w, + ), + SizedBox(width: 3.w), + text( + ATAppLocalizations.of(context)!.gift, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + DynamicRoute.dynamicMessageGift, + replace: false, + ); + }, + ), + ], + ), + ), + Expanded(child: buildList(context)), + ], + ), + ); + } + + @override + Widget buildItem(Records res) { + return ATDebounceWidget( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + SizedBox(width: 10.w), + ATDebounceWidget( + child: netImage( + url: res.userProfile?.userAvatar ?? "", + shape: BoxShape.circle, + height: 45.w, + width: 45.w, + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == res.userProfile?.id}&tageId=${res.userProfile?.id}", + ); + }, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 3.w, + children: [ + Container( + margin: EdgeInsetsDirectional.only(start: 5.w), + height: 21.w, + constraints: BoxConstraints(maxWidth: 95.w), + child: chatvibeNickNameText( + maxWidth: 95.w, + res.userProfile?.userNickname ?? "", + fontSize: 15.sp, + textColor: Colors.black, + fontWeight: FontWeight.w400, + type: res.userProfile?.getVIP()?.name ?? "", + needScroll: + (res + .userProfile + ?.userNickname + ?.characters + .length ?? + 0) > + 10, + ), + ), + Container( + width: + (res.userProfile?.age ?? 0) > 999 ? 58.w : 48.w, + height: 24.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + res.userProfile?.userSex == 0 + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png", + ), + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + xb(res.userProfile?.userSex), + text( + "${res.userProfile?.age}", + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 3.w), + ], + ), + ), + getVIPBadge( + res.userProfile?.getVIP()?.name, + width: 45.w, + height: 25.w, + ), + ], + ), + Container( + alignment: AlignmentDirectional.centerStart, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 5.w), + res.userProfile?.wearBadge?.isNotEmpty ?? false + ? SizedBox( + height: 28.w, + child: ListView.separated( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: + res.userProfile?.wearBadge?.length ?? 0, + itemBuilder: (context, index) { + return netImage( + width: 28.w, + height: 28.w, + url: + res + .userProfile + ?.wearBadge?[index] + .selectUrl ?? + "", + ); + }, + separatorBuilder: ( + BuildContext context, + int index, + ) { + return SizedBox(width: 5.w); + }, + ), + ) + : Container(), + SizedBox(width: 5.w), + ], + ), + ), + ), + ], + ), + ), + SizedBox(width: 5.w), + res.userId != AccountStorage().getCurrentUser()?.userProfile?.id + ? ((res.subscription ?? false) + ? Container() + : ATDebounceWidget( + child: Container( + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + gradient: LinearGradient( + colors: [Color(0xff7726FF), Color(0xffC670FF)], + ), + ), + width: 68.w, + height: 23.w, + child: text( + ATAppLocalizations.of(context)!.follow, + textColor: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 13.sp, + ), + ), + onTap: () { + AccountRepository() + .followUser(res.userId ?? "") + .then((result) { + res.setSubscription(true); + setState(() {}); + }); + }, + )) + : Container(), + ATDebounceWidget( + child: Container( + child: Icon( + Icons.more_vert, + color: Colors.black45, + size: 18.w, + ), + padding: EdgeInsetsDirectional.all(3.w), + ), + onTap: () { + ATDialogUtils.revealDynamicCommentOptDialog( + context, + reportCallback: + res.userId != + AccountStorage().getCurrentUser()?.userProfile?.id + ? () { + ATNavigatorUtils.push( + context, + "${MainRoute.report}?type=dynamic&tageId=${res.dynamicId}", + replace: false, + ); + } + : null, + deleteCallback: + res.userId == + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id || + (Provider.of( + context, + listen: false, + ).userIdentity?.admin ?? + false) + ? () { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.deleteDynamicTips, + leftConfirm: true, + isBtnBgFlip: true, + btnText: + ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + _deleteDynamic(res.dynamicId ?? ""); + }, + ); + }, + ); + } + : null, + ); + }, + ), + SizedBox(width: 3.w), + ], + ), + SizedBox(height: 3.w), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: Container( + margin: EdgeInsetsDirectional.symmetric(horizontal: 10.w), + child: ReadMoreText( + res.content ?? "", + trimLines: 3, + // 收缩时显示的行数 + trimMode: TrimMode.Line, + style: TextStyle( + fontSize: 14.sp, + color: Colors.black, + fontWeight: FontWeight.w600, + ), + + // 按行截断 + trimCollapsedText: ATAppLocalizations.of(context)!.showMore, + // 收缩时按钮文字 + trimExpandedText: ATAppLocalizations.of(context)!.showLess, + // 展开时按钮文字 + moreStyle: TextStyle( + color: ChatVibeTheme.primaryLight, + fontWeight: FontWeight.w600, + ), + lessStyle: TextStyle( + color: ChatVibeTheme.primaryLight, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ), + SizedBox(height: 5.w), + (res.pictures?.isNotEmpty ?? false) + ? res.pictures?.length == 1 + ? Container( + margin: EdgeInsets.symmetric(horizontal: 10.w), + constraints: BoxConstraints( + maxWidth: ScreenUtil().screenWidth / 2, + ), + child: ATDebounceWidget( + child: netImage( + url: res.pictures?.first.resourceUrl ?? "", + borderRadius: BorderRadius.circular(8.w), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${DynamicRoute.detailForPic}?id=${res.dynamicId}&canGoUserInfo=true", + replace: false, + ); + }, + ), + ) + : Container( + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: + res.pictures?.length == 1 + ? 1 + : (res.pictures?.length == 2 ? 2 : 3), // 每行2个 + mainAxisSpacing: 10, + crossAxisSpacing: 10, + ), + padding: EdgeInsets.all(10), + itemCount: res.pictures?.length, + itemBuilder: (context, index) { + return ATDebounceWidget( + child: netImage( + url: res.pictures?[index].resourceUrl ?? "", + borderRadius: BorderRadius.circular(8.w), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${DynamicRoute.detailForPic}?id=${res.dynamicId}&canGoUserInfo=true", + replace: false, + ); + }, + ); + }, + ), + ) + : Container(), + SizedBox(height: 10.w), + Row( + children: [ + Row( + children: [ + SizedBox(width: 10.w), + Image.asset( + "atu_images/dynamic/at_icon_reply.png", + height: 22.w, + color: Colors.grey, + ), + SizedBox(width: 3.w), + text( + res.commentStrQuantity != "0" + ? "${ATAppLocalizations.of(context)!.comment}(${res.commentStrQuantity ?? 0})" + : ATAppLocalizations.of(context)!.catchFirstComment, + textColor: Colors.grey, + fontSize: 13.sp, + ), + ], + ), + SizedBox(width: 25.w), + ATDebounceWidget( + child: Row( + children: [ + Image.asset( + (res.like ?? false) + ? "atu_images/dynamic/at_icon_like_red.png" + : "atu_images/dynamic/at_icon_like.png", + height: 22.w, + color: (res.like ?? false) ? null : Colors.grey, + ), + SizedBox(width: 3.w), + text( + res.likeStrQuantity != "0" + ? "${ATAppLocalizations.of(context)!.like}(${res.likeStrQuantity ?? 0})" + : ATAppLocalizations.of(context)!.like, + textColor: Colors.grey, + fontSize: 13.sp, + ), + ], + ), + onTap: () { + _like(res); + }, + ), + ], + ), + SizedBox(height: 8.w), + Row( + children: [ + SizedBox(width: 10.w), + netImage( + url: + AccountStorage().getCurrentUser()?.userProfile?.userAvatar ?? + "", + shape: BoxShape.circle, + height: 32.w, + width: 32.w, + ), + SizedBox(width: 8.w), + Expanded( + child: Container( + padding: EdgeInsetsDirectional.only(start: 10.w), + alignment: AlignmentDirectional.centerStart, + height: 28.w, + decoration: BoxDecoration( + color: Color(0xffF4EAFF), + borderRadius: BorderRadius.circular(16.w), + ), + child: text( + ATAppLocalizations.of(context)!.saySomething, + textColor: Colors.grey, + fontSize: 13.sp, + ), + ), + ), + SizedBox(width: 10.w), + ], + ), + ], + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${DynamicRoute.detail}?id=${res.dynamicId}&canGoUserInfo=true", + replace: false, + ); + }, + ); + } + + @override + builderDivider() { + return Divider(height: 22.w, color: Color(0xffF2F2F2)); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var result = await DynamicRepositoryImp().dynamicListLatest( + current: page, + ); + onSuccess(result.records ?? []); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + void _like(Records res) { + DynamicRepositoryImp() + .dynamicLike(res.dynamicId ?? "", !(res.like ?? false)) + .then((result) { + res.setLike(!(res.like ?? false)); + if (res.like ?? false) { + res.setLikeStrQuantity( + "${int.parse(res.likeStrQuantity ?? "0") + 1}", + ); + } else { + res.setLikeStrQuantity( + "${int.parse(res.likeStrQuantity ?? "0") - 1}", + ); + } + setState(() {}); + }) + .catchError((_) {}); + } + + void _deleteDynamic(String dynamicId) { + DynamicRepositoryImp() + .dynamicDelete(dynamicId) + .then((result) { + setState(() { + items.removeWhere((element) => element.dynamicId == dynamicId); + ATTts.show(ATAppLocalizations.of(context)!.deleteSuccessful); + }); + }) + .catchError((e) {}); + } +} diff --git a/lib/chatvibe_features/dynamic/trend/gift/dynamic_message_gift_page.dart b/lib/chatvibe_features/dynamic/trend/gift/dynamic_message_gift_page.dart new file mode 100644 index 0000000..14629ba --- /dev/null +++ b/lib/chatvibe_features/dynamic/trend/gift/dynamic_message_gift_page.dart @@ -0,0 +1,175 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/dynamic_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/dynamic_list_res.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +class DynamicMessageGiftPage extends ATPageList { + DynamicMessageGiftPage(); + + @override + _DynamicMessageGiftPageState createState() => _DynamicMessageGiftPageState(); +} + +class _DynamicMessageGiftPageState + extends ATPageListState { + @override + void initState() { + super.initState(); + enablePullUp = true; + enablePullDown = true; + isShowFooter = false; + backgroundColor = Colors.transparent; + padding = EdgeInsets.symmetric(vertical: 10.w); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/room/at_icon_room_settig_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.gift, + actions: [], + ), + body: SafeArea(top: false, child: buildList(context)), + ), + ], + ); + } + + @override + Widget buildItem(Records res) { + return Row( + children: [ + SizedBox(width: 10.w,), + ATDebounceWidget( + child: netImage( + url: res.userAvatar ?? "", + width: 42.w, + height: 42.w, + shape: BoxShape.circle, + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == res.userId}&tageId=${res.userId}", + ); + }, + ), + SizedBox(width: 10.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + constraints: BoxConstraints(maxWidth: 150.w), + child: text( + res.userNickname ?? "", + fontSize: 15.sp, + textColor: Colors.black, + fontWeight: FontWeight.w500, + ), + ), + SizedBox(width: 3.w), + Container( + width: (res.age ?? 0) > 999 ? 58.w : 48.w, + height: 24.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + res.userSex == 0 + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png", + ), + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + xb(res.userSex), + text( + "${res.age}", + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 3.w), + ], + ), + ), + ], + ), + text( + ATMDateUtils.formatMessageTime( + context, + DateTime.fromMillisecondsSinceEpoch(res.createTime ?? 0), + ), + textColor: Colors.grey, + fontSize: 14.sp, + ), + ], + ), + Spacer(), + text( + "*${res.giftCount ?? 0}", + textColor: Colors.black, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + ), + SizedBox(width: 3.w), + (res.messageContent?.isNotEmpty ?? false) + ? netImage(url: res.messageContent ?? "", width: 35.w, height: 35.w) + : Container(), + SizedBox(width: 10.w,), + ], + ); + } + + builderDivider() { + return Divider( + height: 22.w, + thickness: 0.2.w, + color: Colors.grey, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var result = await DynamicRepositoryImp().dynamicListMessage([ + "DYNAMIC_GIFT", + ], current: page); + onSuccess(result.records ?? []); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } +} diff --git a/lib/chatvibe_features/dynamic/trend/like/dynamic_message_like_page.dart b/lib/chatvibe_features/dynamic/trend/like/dynamic_message_like_page.dart new file mode 100644 index 0000000..09d2569 --- /dev/null +++ b/lib/chatvibe_features/dynamic/trend/like/dynamic_message_like_page.dart @@ -0,0 +1,164 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/dynamic_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/dynamic_list_res.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_features/dynamic/dynamic_route.dart'; + +class DynamicMessageLikePage extends ATPageList { + DynamicMessageLikePage(); + + @override + _DynamicMessageLikePageState createState() => _DynamicMessageLikePageState(); +} + +class _DynamicMessageLikePageState + extends ATPageListState { + @override + void initState() { + super.initState(); + enablePullUp = true; + enablePullDown = true; + isShowFooter = false; + backgroundColor = Colors.transparent; + padding = EdgeInsets.symmetric(vertical: 10.w); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/room/at_icon_room_settig_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.like, + actions: [], + ), + body: SafeArea(top: false, child: buildList(context)), + ), + ], + ); + } + + @override + Widget buildItem(Records res) { + return ATDebounceWidget( + child: Row( + children: [ + SizedBox(width: 10.w,), + ATDebounceWidget( + child: netImage( + url: res.userAvatar ?? "", + width: 42.w, + height: 42.w, + shape: BoxShape.circle, + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == res.userId}&tageId=${res.userId}", + ); + }, + ), + SizedBox(width: 10.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + constraints: BoxConstraints(maxWidth: 100.w), + child: text( + res.userNickname ?? "", + fontSize: 15.sp, + textColor: Colors.black, + fontWeight: FontWeight.w500, + ), + ), + SizedBox(width: 5.w), + res.type == "LIKE_DYNAMIC" + ? text( + ATAppLocalizations.of(context)!.likedYourDynamic, + fontSize: 14.sp, + textColor: Colors.grey, + ) + : text( + ATAppLocalizations.of(context)!.likedYourComment, + fontSize: 14.sp, + textColor: Colors.grey, + ), + ], + ), + text( + ATMDateUtils.formatMessageTime( + context, + DateTime.fromMillisecondsSinceEpoch(res.createTime ?? 0), + ), + textColor: Colors.grey, + fontSize: 14.sp, + ), + ], + ), + Spacer(), + (res.messageContent?.isNotEmpty ?? false) + ? netImage( + url: res.messageContent ?? "", + width: 38.w, + height: 38.w, + borderRadius: BorderRadius.circular(4.w), + ) + : Container(), + SizedBox(width: 10.w,), + ], + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${DynamicRoute.detail}?id=${res.dynamicId}&canGoUserInfo=true", + replace: false, + ); + }, + ); + } + + builderDivider() { + return Divider(height: 22.w, thickness: 0.2.w, color: Colors.grey); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var result = await DynamicRepositoryImp().dynamicListMessage([ + "LIKE_DYNAMIC", + "LIKE_COMMENT", + ], current: page); + onSuccess(result.records ?? []); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } +} diff --git a/lib/chatvibe_features/dynamic/trend/reply/dynamic_message_reply_page.dart b/lib/chatvibe_features/dynamic/trend/reply/dynamic_message_reply_page.dart new file mode 100644 index 0000000..f2186b5 --- /dev/null +++ b/lib/chatvibe_features/dynamic/trend/reply/dynamic_message_reply_page.dart @@ -0,0 +1,265 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_features/dynamic/dynamic_route.dart'; +import 'package:readmore/readmore.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/dynamic_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/dynamic_list_res.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +class DynamicMessageReplyPage extends ATPageList { + DynamicMessageReplyPage(); + + @override + _DynamicMessageReplyPageState createState() => + _DynamicMessageReplyPageState(); +} + +class _DynamicMessageReplyPageState + extends ATPageListState { + String userId = ""; + String dynamicContentId = ""; + num? rootCommentId; + String toUserName = ""; + + @override + void initState() { + super.initState(); + enablePullUp = true; + enablePullDown = true; + isShowFooter = false; + backgroundColor = Colors.transparent; + padding = EdgeInsets.symmetric(vertical:10.w); + DataPersistence.setBool( + "DynamicReply_${AccountStorage().getCurrentUser()?.userProfile?.id}", + false, + ); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/room/at_icon_room_settig_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.reply, + actions: [], + ), + body: SafeArea( + top: false, + child: Column( + children: [ + Expanded(child: buildList(context)), + // toUserName.isNotEmpty + // ? CommentInput( + // userId, + // dynamicContentId, + // toUserName: toUserName, + // callBack: (tx) { + // if (rootCommentId != null) { + // comment(tx); + // } else { + // ATTts.show(ATAppLocalizations.of(context)!.operationFail); + // } + // }, + // ) + // : Container(), + ], + ), + ), + ), + ], + ); + } + + @override + Widget buildItem(Records res) { + return ATDebounceWidget( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(width: 10.w,), + ATDebounceWidget( + child: netImage( + url: res.userAvatar ?? "", + width: 42.w, + height: 42.w, + shape: BoxShape.circle, + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == res.userId}&tageId=${res.userId}", + ); + }, + ), + SizedBox(width: 10.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + constraints: BoxConstraints(maxWidth: 150.w), + child: text( + res.userNickname ?? "", + fontSize: 15.sp, + textColor: Colors.black, + fontWeight: FontWeight.w500, + ), + ), + Row( + children: [ + Expanded( + child: Container( + margin: EdgeInsetsDirectional.symmetric( + horizontal: 0.w, + ), + child: ReadMoreText( + res.messageContent ?? "", + trimLines: 3, + // 收缩时显示的行数 + trimMode: TrimMode.Line, + style: TextStyle( + fontFamily: "MyCustomFont", + fontSize: 13.sp, + color: Colors.black, + fontWeight: FontWeight.w600, + ), + + // 按行截断 + trimCollapsedText: + ATAppLocalizations.of(context)!.showMore, + // 收缩时按钮文字 + trimExpandedText: + ATAppLocalizations.of(context)!.showLess, + // 展开时按钮文字 + moreStyle: TextStyle( + color: ChatVibeTheme.primaryLight, + fontFamily: "MyCustomFont", + fontWeight: FontWeight.w600, + ), + lessStyle: TextStyle( + color: ChatVibeTheme.primaryLight, + fontFamily: "MyCustomFont", + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ), + Row( + children: [ + Container( + margin: EdgeInsetsDirectional.only(start: 0.w), + alignment: AlignmentDirectional.centerStart, + child: text( + ATMDateUtils.formatMessageTime( + context, + DateTime.fromMillisecondsSinceEpoch( + res.createTime ?? 0, + ), + ), + textColor: Colors.grey, + fontSize: 14.sp, + ), + ), + SizedBox(width: 5.w), + // ATDebounceWidget( + // child: Container( + // child: text( + // ATAppLocalizations.of(context)!.reply, + // textColor: Color(0xff7726FF), + // fontSize: 13.sp, + // ), + // ), + // onTap: () { + // userId = res.userId ?? ""; + // dynamicContentId = res.dynamicId ?? ""; + // toUserName = res.userNickname ?? ""; + // rootCommentId = res.rootCommentId; + // setState(() {}); + // }, + // ), + ], + ), + ], + ), + ), + SizedBox(width: 10.w,), + ], + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${DynamicRoute.detail}?id=${res.dynamicId}&canGoUserInfo=true", + replace: false, + ); + }, + ); + } + + void comment(String text) { + DynamicRepositoryImp() + .dynamicComment( + text, + dynamicContentId, + toUserId: userId, + rootCommentId: rootCommentId, + ) + .then((res) { + toUserName = ""; + rootCommentId = null; + setState(() {}); + ATTts.show(ATAppLocalizations.of(context)!.replySucc); + }); + } + + builderDivider() { + return Divider( + height: 22.w, + thickness: 0.2.w, + color: Colors.grey, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var result = await DynamicRepositoryImp().dynamicListMessage([ + "COMMENT_DYNAMIC", + "REPLY_COMMENT", + ], current: page); + onSuccess(result.records ?? []); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } +} diff --git a/lib/chatvibe_features/family/create/create_family_page.dart b/lib/chatvibe_features/family/create/create_family_page.dart new file mode 100644 index 0000000..536675a --- /dev/null +++ b/lib/chatvibe_features/family/create/create_family_page.dart @@ -0,0 +1,489 @@ +import 'dart:ui' as ui; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/utilities/at_pick_utils.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/family_repository_impl.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_features/family/family_route.dart'; + +import '../../../chatvibe_data/models/enum/at_vip_type.dart'; +import '../../../chatvibe_domain/usecases/at_accurate_length_limiting_textInput_formatter.dart'; +import '../../../chatvibe_domain/usecases/at_custom_filtering_textinput_formatter.dart'; +import '../../../chatvibe_domain/usecases/at_custom_filtering_textinput_formatter2.dart'; + +class CreateFamilyPage extends StatefulWidget { + @override + _CreateFamilyPageState createState() => _CreateFamilyPageState(); +} + +class _CreateFamilyPageState extends State { + TextEditingController familyNameController = TextEditingController(); + TextEditingController familyInfoController = TextEditingController(); + final int _familyNameMaxLength = 24; + int _familyNameCurrentLength = 0; + String familyCover = ""; + final int _familyInfoMaxLength = 80; + int _familyInfoCurrentLength = 0; + + bool isFreeCreate = false; + + @override + void initState() { + super.initState(); + if (AccountStorage().getVIP()?.name == ATVIPType.DUKE.name || + AccountStorage().getVIP()?.name == ATVIPType.KING.name || + AccountStorage().getVIP()?.name == ATVIPType.EMPEROR.name) { + isFreeCreate = true; + } + familyNameController.addListener(() { + setState(() { + _familyNameCurrentLength = _getActualCharacterCount( + familyNameController.text, + ); + }); + }); + familyInfoController.addListener(() { + setState(() { + _familyInfoCurrentLength = _getActualCharacterCount( + familyInfoController.text, + ); + }); + }); + } + + int _getActualCharacterCount(String text) { + return text.characters.length; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: true, + body: SafeArea( + top: false, + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + child: Stack( + children: [ + Image.asset( + "atu_images/family/at_icon_family_head_bg.png", + fit: BoxFit.fitWidth, + ), + PositionedDirectional( + top: 11.w, + child: ChatVibeStandardAppBar( + actions: [], + title: "", + backButtonColor: Colors.white, + ), + ), + Container( + margin: EdgeInsets.only(top: 212.w), + child: ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 18, sigmaY: 18), + child: Container( + alignment: Alignment.center, + color: Colors.white10, + child: Column( + children: [ + SizedBox(height: 15.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.createFamily, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + SizedBox(height: 20.w), + ATDebounceWidget( + child: Stack( + alignment: Alignment.center, + children: [ + Image.asset( + "atu_images/family/at_icon_create_family_camer.png", + height: 85.w, + ), + familyCover.isNotEmpty + ? netImage( + url: familyCover, + fit: BoxFit.cover, + borderRadius: BorderRadius.all( + Radius.circular(8.w), + ), + width: 85.w, + height: 85.w, + ) + : Container(), + ], + ), + onTap: () { + ATPickUtils.pickImageProcess(aspectRatio: 1, context, ( + bool success, + String url, + ) { + if (success) { + setState(() { + familyCover = url; + }); + } + }); + }, + ), + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 10.w), + text( + "*", + textColor: Colors.red, + fontSize: 13.sp, + fontWeight: FontWeight.w600, + ), + text( + "${ATAppLocalizations.of(context)!.familyName}:", + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ], + ), + SizedBox(height: 8.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + // 外部容器用于实现渐变边框 + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + // 渐变色边框 + gradient: LinearGradient( + colors: [ + Color(0xffcecece), // 渐变色1 + Color(0xff5b5b5b), // 渐变色2 + Color(0xffcecece), // 渐变色3 + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + // 内部留白(边框宽度) + padding: EdgeInsets.all(1.w), // 边框宽度 + child: Container( + padding: EdgeInsets.only( + left: width(12), + right: width(12), + ), + alignment: AlignmentDirectional.centerStart, + height: 45.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(7.5.w), + // 比外圆角小边框宽度 + color: Colors.black87, // 背景色 + ), + child: TextField( + controller: familyNameController, + onChanged: (text) {}, + inputFormatters: [ + ATCustomFilteringTextInputFormatter(), + ATAccurateLengthLimitingTextInputFormatter( + _familyNameMaxLength, + ), + ], + decoration: InputDecoration( + hintText: + ATAppLocalizations.of( + context, + )!.enterFamilyName, + hintStyle: TextStyle( + color: Colors.white, + fontSize: 14.sp, + ), + contentPadding: EdgeInsets.only(top: 0.w), + counterText: '', + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + suffix: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 3.w), + text( + '$_familyNameCurrentLength/$_familyNameMaxLength', + textColor: Colors.white, + ), + ], + ), + ), + style: TextStyle( + fontSize: sp(14), + color: Colors.white, + textBaseline: TextBaseline.alphabetic, + ), + ), + ), + ), + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 10.w), + text( + "${ATAppLocalizations.of(context)!.familyInfo}:", + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ], + ), + SizedBox(height: 8.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + // 外部容器用于实现渐变边框 + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + // 渐变色边框 + gradient: LinearGradient( + colors: [ + Color(0xffcecece), // 渐变色1 + Color(0xff5b5b5b), // 渐变色2 + Color(0xffcecece), // 渐变色3 + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + // 内部留白(边框宽度) + padding: EdgeInsets.all(1.w), // 边框宽度 + child: Stack( + alignment: AlignmentDirectional.bottomEnd, + children: [ + Container( + padding: EdgeInsets.only( + top: width(12), + left: width(12), + right: width(12), + ), + alignment: AlignmentDirectional.topStart, + height: 115.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + 7.5.w, + ), + // 比外圆角小边框宽度 + color: Colors.black87, // 背景色 + ), + child: TextField( + controller: familyInfoController, + onChanged: (text) {}, + inputFormatters: [ + ATCustomFilteringTextInputFormatter2(), + ATAccurateLengthLimitingTextInputFormatter( + _familyInfoMaxLength, + ), + ], + maxLines: 5, + decoration: InputDecoration( + hintText: + ATAppLocalizations.of( + context, + )!.enterFamilyInfo, + hintStyle: TextStyle( + color: Colors.white, + fontSize: 14.sp, + ), + contentPadding: EdgeInsets.only( + top: 0.w, + ), + counterText: '', + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + suffix: Row( + mainAxisSize: MainAxisSize.min, + children: [SizedBox(width: 3.w)], + ), + ), + style: TextStyle( + fontSize: sp(14), + color: Colors.white, + textBaseline: TextBaseline.alphabetic, + ), + ), + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + text( + '$_familyInfoCurrentLength/$_familyInfoMaxLength', + textColor: Colors.white, + ), + SizedBox(width: 3.w), + ], + ), + ], + ), + ), + SizedBox(height: 15.w), + ATDebounceWidget( + debounceTime: Duration(milliseconds: 800), + child: Container( + width: 180.w, + padding: EdgeInsets.symmetric(vertical: 12.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(60.w), + gradient: + _familyNameCurrentLength > 0 + ? LinearGradient( + colors: [ + Color(0xff0D0022), + Color(0xff9F63FF), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ) + : LinearGradient( + colors: [ + Colors.white24, + Colors.white24, + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: Column( + children: [ + text( + ATAppLocalizations.of( + context, + )!.createFamily, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + "atu_images/general/at_icon_jb.png", + width: 18.w, + ), + SizedBox(width: 2.w), + isFreeCreate? text( + "0", + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ):Container(), + isFreeCreate? SizedBox(width: 5.w):Container(), + text( + "100000", + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + decoration: + isFreeCreate + ? TextDecoration.lineThrough + : TextDecoration.none, + ), + ], + ), + ], + ), + ), + onTap: () { + if (familyNameController.text.isEmpty) { + ATTts.show( + ATAppLocalizations.of( + context, + )!.enterFamilyName, + ); + return; + } + _submit(); + }, + ), + SizedBox(height: 130.w), + ], + ), + ), + ), + ), + ), + ], + ), + ); + }, + ), + ), + ); + } + + void _submit() { + if (familyCover.isEmpty) { + familyCover = + AccountStorage().getCurrentUser()?.userProfile?.userAvatar ?? ""; + } + ATLoadingManager.exhibitOperation(); + GroupRepository() + .createFamily( + familyCover, + familyNameController.text, + familyInfoController.text, + ) + .then((result) { + Provider.of( + context, + listen: false, + ).updateFamily("${result}"); + ATTts.show(ATAppLocalizations.of(context)!.createFamilySuccess); + ATLoadingManager.veilRoutine(); + ATNavigatorUtils.push( + context, + "${FamilyRoute.familyInfo}?familyId=$result", + replace: true, + ); + }) + .catchError((_) { + ATLoadingManager.veilRoutine(); + ATNavigatorUtils.goBack(context); + }); + } +} diff --git a/lib/chatvibe_features/family/edit/edit_family_page.dart b/lib/chatvibe_features/family/edit/edit_family_page.dart new file mode 100644 index 0000000..86a1acd --- /dev/null +++ b/lib/chatvibe_features/family/edit/edit_family_page.dart @@ -0,0 +1,646 @@ +import 'dart:ui' as ui; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/routes/at_routes.dart'; +import 'package:aslan/chatvibe_core/utilities/at_pick_utils.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/family_repository_impl.dart'; + +import '../../../chatvibe_domain/usecases/at_accurate_length_limiting_textInput_formatter.dart'; +import '../../../chatvibe_domain/usecases/at_custom_filtering_textinput_formatter.dart'; +import '../../../chatvibe_domain/usecases/at_custom_filtering_textinput_formatter2.dart'; + +class EditFamilyPage extends StatefulWidget { + String familyId = ""; + + EditFamilyPage(this.familyId); + + @override + _EditFamilyPageState createState() => _EditFamilyPageState(); +} + +class _EditFamilyPageState extends State { + TextEditingController familyNameController = TextEditingController(); + TextEditingController familyInfoController = TextEditingController(); + TextEditingController familyAnnouncementController = TextEditingController(); + final int _familyNameMaxLength = 24; + int _familyNameCurrentLength = 0; + String familyCover = ""; + final int _familyInfoMaxLength = 80; + int _familyInfoCurrentLength = 0; + + final int _familyAnnouncementMaxLength = 80; + int _familyAnnouncementCurrentLength = 0; + + @override + void initState() { + super.initState(); + loadFamilyInfo(); + familyNameController.addListener(() { + setState(() { + _familyNameCurrentLength = _getActualCharacterCount( + familyNameController.text, + ); + }); + }); + familyInfoController.addListener(() { + setState(() { + _familyInfoCurrentLength = _getActualCharacterCount( + familyInfoController.text, + ); + }); + }); + + familyAnnouncementController.addListener(() { + setState(() { + _familyAnnouncementCurrentLength = _getActualCharacterCount( + familyAnnouncementController.text, + ); + }); + }); + } + + int _getActualCharacterCount(String text) { + return text.characters.length; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: true, + body: SafeArea( + top: false, + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + child: Stack( + children: [ + Image.asset( + "atu_images/family/at_icon_family_head_bg.png", + fit: BoxFit.fitWidth, + ), + PositionedDirectional( + top: 11.w, + child: ChatVibeStandardAppBar( + actions: [], + title: "", + backButtonColor: Colors.white, + ), + ), + Container( + margin: EdgeInsets.only(top: 212.w), + child: ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 18, sigmaY: 18), + child: Container( + alignment: Alignment.center, + color: Colors.white10, + child: Column( + children: [ + SizedBox(height: 12.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container(width: 48.w), + Spacer(), + text( + ATAppLocalizations.of(context)!.editFamily, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + Spacer(), + ATDebounceWidget( + child: Container( + padding: EdgeInsets.all(3.w), + margin: EdgeInsetsDirectional.only( + end: 10.w, + ), + child: text( + ATAppLocalizations.of(context)!.save, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () { + if (familyNameController.text.isEmpty) { + ATTts.show( + ATAppLocalizations.of( + context, + )!.enterFamilyName, + ); + return; + } + _submit(); + }, + ), + ], + ), + SizedBox(height: 20.w), + ATDebounceWidget( + child: Stack( + alignment: Alignment.center, + children: [ + Image.asset( + "atu_images/family/at_icon_create_family_camer.png", + height: 85.w, + ), + familyCover.isNotEmpty + ? netImage( + url: familyCover, + fit: BoxFit.cover, + borderRadius: BorderRadius.all( + Radius.circular(8.w), + ), + width: 85.w, + height: 85.w, + ) + : Container(), + ], + ), + onTap: () { + ATPickUtils.pickImageProcess(aspectRatio: 1, context, ( + bool success, + String url, + ) { + if (success) { + setState(() { + familyCover = url; + }); + } + }); + }, + ), + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 10.w), + text( + "*", + textColor: Colors.red, + fontSize: 13.sp, + fontWeight: FontWeight.w600, + ), + text( + "${ATAppLocalizations.of(context)!.familyName}:", + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ], + ), + SizedBox(height: 8.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + // 外部容器用于实现渐变边框 + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + // 渐变色边框 + gradient: LinearGradient( + colors: [ + Color(0xffcecece), // 渐变色1 + Color(0xff5b5b5b), // 渐变色2 + Color(0xffcecece), // 渐变色3 + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + // 内部留白(边框宽度) + padding: EdgeInsets.all(1.w), // 边框宽度 + child: Container( + padding: EdgeInsets.only( + left: width(12), + right: width(12), + ), + alignment: AlignmentDirectional.centerStart, + height: 45.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(7.5.w), + // 比外圆角小边框宽度 + color: Colors.black87, // 背景色 + ), + child: TextField( + controller: familyNameController, + onChanged: (text) {}, + inputFormatters: [ + ATCustomFilteringTextInputFormatter(), + ATAccurateLengthLimitingTextInputFormatter( + _familyNameMaxLength, + ), + ], + decoration: InputDecoration( + hintText: + ATAppLocalizations.of( + context, + )!.enterFamilyName, + hintStyle: TextStyle( + color: Colors.white, + fontSize: 14.sp, + ), + contentPadding: EdgeInsets.only(top: 0.w), + counterText: '', + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + suffix: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 3.w), + text( + '$_familyNameCurrentLength/$_familyNameMaxLength', + textColor: Colors.white, + ), + ], + ), + ), + style: TextStyle( + fontSize: sp(14), + color: Colors.white, + textBaseline: TextBaseline.alphabetic, + ), + ), + ), + ), + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 10.w), + text( + "${ATAppLocalizations.of(context)!.familyInfo}:", + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ], + ), + SizedBox(height: 8.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + // 外部容器用于实现渐变边框 + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + // 渐变色边框 + gradient: LinearGradient( + colors: [ + Color(0xffcecece), // 渐变色1 + Color(0xff5b5b5b), // 渐变色2 + Color(0xffcecece), // 渐变色3 + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + // 内部留白(边框宽度) + padding: EdgeInsets.all(1.w), // 边框宽度 + child: Stack( + alignment: AlignmentDirectional.bottomEnd, + children: [ + Container( + padding: EdgeInsets.only( + top: width(12), + left: width(12), + right: width(12), + ), + alignment: AlignmentDirectional.topStart, + height: 115.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + 7.5.w, + ), + // 比外圆角小边框宽度 + color: Colors.black87, // 背景色 + ), + child: TextField( + controller: familyInfoController, + onChanged: (text) {}, + inputFormatters: [ + ATCustomFilteringTextInputFormatter2(), + ATAccurateLengthLimitingTextInputFormatter( + _familyInfoMaxLength, + ), + ], + maxLines: 5, + decoration: InputDecoration( + hintText: + ATAppLocalizations.of( + context, + )!.enterFamilyInfo, + hintStyle: TextStyle( + color: Colors.white, + fontSize: 14.sp, + ), + contentPadding: EdgeInsets.only( + top: 0.w, + ), + counterText: '', + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + suffix: Row( + mainAxisSize: MainAxisSize.min, + children: [SizedBox(width: 3.w)], + ), + ), + style: TextStyle( + fontSize: sp(14), + color: Colors.white, + textBaseline: TextBaseline.alphabetic, + ), + ), + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + text( + '$_familyInfoCurrentLength/$_familyInfoMaxLength', + textColor: Colors.white, + ), + SizedBox(width: 3.w), + ], + ), + ], + ), + ), + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 10.w), + text( + "${ATAppLocalizations.of(context)!.familyAnnouncement}:", + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ], + ), + SizedBox(height: 8.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + // 外部容器用于实现渐变边框 + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + // 渐变色边框 + gradient: LinearGradient( + colors: [ + Color(0xffcecece), // 渐变色1 + Color(0xff5b5b5b), // 渐变色2 + Color(0xffcecece), // 渐变色3 + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + // 内部留白(边框宽度) + padding: EdgeInsets.all(1.w), // 边框宽度 + child: Stack( + alignment: AlignmentDirectional.bottomEnd, + children: [ + Container( + padding: EdgeInsets.only( + top: width(12), + left: width(12), + right: width(12), + ), + alignment: AlignmentDirectional.topStart, + height: 115.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + 7.5.w, + ), + // 比外圆角小边框宽度 + color: Colors.black87, // 背景色 + ), + child: TextField( + controller: + familyAnnouncementController, + onChanged: (text) {}, + inputFormatters: [ + ATCustomFilteringTextInputFormatter2(), + ATAccurateLengthLimitingTextInputFormatter( + _familyAnnouncementMaxLength, + ), + ], + maxLines: 5, + decoration: InputDecoration( + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + hintText: + ATAppLocalizations.of( + context, + )!.enterFamilyAnnouncement, + hintStyle: TextStyle( + color: Colors.white, + fontSize: 14.sp, + ), + contentPadding: EdgeInsets.only( + top: 0.w, + ), + counterText: '', + suffix: Row( + mainAxisSize: MainAxisSize.min, + children: [SizedBox(width: 3.w)], + ), + ), + style: TextStyle( + fontSize: sp(14), + color: Colors.white, + textBaseline: TextBaseline.alphabetic, + ), + ), + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + text( + '$_familyAnnouncementCurrentLength/$_familyAnnouncementMaxLength', + textColor: Colors.white, + ), + SizedBox(width: 3.w), + ], + ), + ], + ), + ), + SizedBox(height: 15.w), + ATDebounceWidget( + debounceTime: Duration(milliseconds: 800), + child: Container( + width: 240.w, + padding: EdgeInsets.symmetric(vertical: 12.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(60.w), + gradient: LinearGradient( + colors: [ + Color(0xff540001), + Color(0xffB70508), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: Column( + children: [ + text( + ATAppLocalizations.of( + context, + )!.disbandTheFamily, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + ), + onTap: () { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: + ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.disbandTheFamilyTips, + btnText: + ATAppLocalizations.of( + context, + )!.confirm, + isDark: true, + onEnsure: () { + ATLoadingManager.exhibitOperation(); + GroupRepository() + .familyDisband() + .then((res) { + ATLoadingManager.veilRoutine(); + ATTts.show( + ATAppLocalizations.of( + context, + )!.operationSuccessful, + ); + Provider.of( + context, + listen: false, + ).updateFamily(null); + ATNavigatorUtils.popUntil( + context, + ModalRoute.withName( + ATRoutes.home, + ), + ); + }) + .catchError((_) { + ATLoadingManager.veilRoutine(); + }); + }, + ); + }, + ); + }, + ), + SizedBox(height: 25.w), + ], + ), + ), + ), + ), + ), + ], + ), + ); + }, + ), + ), + ); + } + + void _submit() { + if (familyCover.isEmpty) { + familyCover = + AccountStorage().getCurrentUser()?.userProfile?.userAvatar ?? ""; + } + ATLoadingManager.exhibitOperation(); + GroupRepository() + .familyEdit( + familyCover, + familyNameController.text, + familyInfoController.text, + familyAnnouncementController.text, + ) + .then((result) { + ATTts.show(ATAppLocalizations.of(context)!.operationSuccessful); + ATLoadingManager.veilRoutine(); + ATNavigatorUtils.goBack(context); + }) + .catchError((_) { + ATLoadingManager.veilRoutine(); + ATNavigatorUtils.goBack(context); + }); + } + + void loadFamilyInfo() { + ATLoadingManager.exhibitOperation(); + GroupRepository() + .familyBaseInfo(familyId: widget.familyId) + .then((result) { + familyCover = result.familyAvatar ?? ""; + familyNameController.text = result.familyName ?? ""; + familyInfoController.text = result.familyIntro ?? ""; + familyAnnouncementController.text = result.familyNotice ?? ""; + setState(() {}); + ATLoadingManager.veilRoutine(); + }) + .catchError((_) { + ATLoadingManager.veilRoutine(); + }); + } +} diff --git a/lib/chatvibe_features/family/family_route.dart b/lib/chatvibe_features/family/family_route.dart new file mode 100644 index 0000000..453f809 --- /dev/null +++ b/lib/chatvibe_features/family/family_route.dart @@ -0,0 +1,109 @@ +import 'package:fluro/fluro.dart'; +import 'package:aslan/chatvibe_features/family/create/create_family_page.dart'; +import 'package:aslan/chatvibe_features/family/rank/family_user_rank_page.dart'; +import 'package:aslan/chatvibe_features/family/record/req_record_family_page.dart'; +import 'package:aslan/chatvibe_core/routes/at_router_init.dart'; +import 'package:aslan/chatvibe_features/family/edit/edit_family_page.dart'; +import 'package:aslan/chatvibe_features/family/info/family_info_page.dart'; +import 'package:aslan/chatvibe_features/family/join_request/family_join_request_page.dart'; +import 'package:aslan/chatvibe_features/family/level/family_level_page.dart'; +import 'package:aslan/chatvibe_features/family/list/family_list_page.dart'; +import 'package:aslan/chatvibe_features/family/member/family_member_list_page.dart'; +import 'package:aslan/chatvibe_features/family/news/family_news_page.dart'; + +class FamilyRoute implements ATIRouterProvider { + static String create = 'family/create'; + static String familyList = 'family/familyList'; + static String reqRecordFamily = 'family/reqRecordFamily'; + static String familyInfo = 'family/familyInfo'; + static String editFamily = 'family/editFamily'; + static String familyJoinRequest = 'family/familyJoinRequest'; + static String familyMemberList = 'family/familyMemberList'; + static String familyLevel = 'family/familyLevel'; + static String familyUserRank = 'family/familyUserRank'; + static String familyNews = 'family/familyNews'; + + @override + void initRouter(FluroRouter router) { + router.define( + create, + handler: Handler(handlerFunc: (_, params) => CreateFamilyPage()), + ); + router.define( + familyList, + handler: Handler(handlerFunc: (_, params) => FamilyListPage()), + ); + router.define( + reqRecordFamily, + handler: Handler(handlerFunc: (_, params) => ReqRecordFamilyPage()), + ); + router.define( + familyLevel, + handler: Handler( + handlerFunc: + (_, params) => FamilyLevelPage( + params['curentLevel']!.first, + params['curentLevelExp']!.first, + ), + ), + ); + + router.define( + familyInfo, + handler: Handler( + handlerFunc: (_, params) => FamilyInfoPage(params['familyId']!.first), + ), + ); + + router.define( + editFamily, + handler: Handler( + handlerFunc: (_, params) => EditFamilyPage(params['familyId']!.first), + ), + ); + + router.define( + familyJoinRequest, + handler: Handler( + handlerFunc: + (_, params) => FamilyJoinRequestPage( + params['currentMember']!.first, + params['maxMember']!.first, + ), + ), + ); + + router.define( + familyMemberList, + handler: Handler( + handlerFunc: + (_, params) => FamilyMemberListPage( + params['familyId']!.first, + params['myFamilyRole']!.first, + ), + ), + ); + + router.define( + familyUserRank, + handler: Handler( + handlerFunc: + (_, params) => FamilyUserRankPage( + params['familyId']!.first, + params['rankType']!.first, + ), + ), + ); + + router.define( + familyNews, + handler: Handler( + handlerFunc: (_, params) { + String familyAnnouncement = + params['familyAnnouncement']!.first; + return FamilyNewsPage(params['familyId']!.first, familyAnnouncement); + }, + ), + ); + } +} diff --git a/lib/chatvibe_features/family/index_family_page.dart b/lib/chatvibe_features/family/index_family_page.dart new file mode 100644 index 0000000..28e1c2c --- /dev/null +++ b/lib/chatvibe_features/family/index_family_page.dart @@ -0,0 +1,530 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_ninepatch_image/flutter_ninepatch_image.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/family_repository_impl.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_family_home_list_res.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_features/family/family_route.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; +import 'package:aslan/chatvibe_ui/widgets/country/at_country_flag_image.dart'; + +class IndexFamilyPage extends StatefulWidget { + const IndexFamilyPage({super.key}); + + @override + IndexFamilyPageState createState() => IndexFamilyPageState(); +} + +class IndexFamilyPageState extends State { + final RefreshController _refreshController = RefreshController( + initialRefresh: false, + ); + Records? familyBaseInfo; + + /// 获取业务逻辑策略 + BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; + + @override + void initState() { + super.initState(); + loadData(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: SafeArea( + top: false, + child: SmartRefresher( + enablePullDown: true, + enablePullUp: false, + controller: _refreshController, + onRefresh: () { + loadData(); + }, + onLoading: () {}, + child: _buildContent(), + ), + ), + ); + } + + Widget _buildContent() { + return SingleChildScrollView( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10.w).copyWith(top: 5.w), + child: Column( + children: [ + _buidFamilyInfo(), + SizedBox(height: 8.w), + _buidFamilyOnlineUser(), + SizedBox(height: 8.w), + _buiRoomList(), + ], + ), + ), + ); + } + + _buidFamilyInfo() { + // 使用业务逻辑策略处理家族通知和等级 + final displayLevel = _strategy.getFamilyDisplayLevel( + familyBaseInfo?.level ?? 0, + ); + final familyNotice = _strategy.getFamilyNoticeDisplayText( + familyBaseInfo?.familyNotice, + context, + ); + return SizedBox( + width: ScreenUtil().screenWidth, + child: NinePatchImage( + alignment: Alignment.center, + imageProvider: AssetImage( + _strategy.getFamilyLevelBackgroundImage(displayLevel), + ), + sliceCachedKey: _strategy.getFamilyLevelBackgroundImage(displayLevel), + child: Column( + children: [ + SizedBox(height: 5.w), + Row( + children: [ + SizedBox(width: 5.w), + ATDebounceWidget( + child: Container( + margin: EdgeInsetsDirectional.only(start: 8.w, top: 5.w), + child: netImage( + height: 80.w, + width: 80.w, + borderRadius: BorderRadius.circular(5.w), + url: familyBaseInfo?.familyAvatar ?? "", + ), + ), + onTap: () { + if ((familyBaseInfo?.familyId ?? "").isNotEmpty) { + ATNavigatorUtils.push( + context, + "${FamilyRoute.familyInfo}?familyId=${familyBaseInfo?.familyId}", + replace: false, + ); + } + }, + ), + SizedBox(width: 10.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + constraints: BoxConstraints( + maxWidth: 135.w, + maxHeight: 24.w, + ), + child: + (familyBaseInfo?.familyName?.length ?? 0) > 12 + ? Marquee( + text: familyBaseInfo?.familyName ?? "", + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 40.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + familyBaseInfo?.familyName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + SizedBox(height: 5.w), + Row( + children: [ + text( + "ID:${familyBaseInfo?.familyAccount ?? ""}", + fontWeight: FontWeight.w600, + fontSize: 13.sp, + textColor: Colors.white, + ), + SizedBox(width: 2.w), + Container( + alignment: AlignmentDirectional.center, + height: 25.w, + width: 66.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/family/at_icon_family_member_btn_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + height: 15.w, + "atu_images/family/at_icon_family_member_tag.png", + ), + SizedBox(width: 3.w), + text( + "${familyBaseInfo?.memberCount ?? 0}", + fontWeight: FontWeight.w600, + fontSize: 13.sp, + textColor: Colors.white, + ), + ], + ), + ), + ], + ), + ], + ), + ), + Image.asset( + "atu_images/family/at_icon_family_level_${familyBaseInfo?.level ?? 0}.png", + height: 80.w, + ), + // Image.asset( + // "images/family/at_icon_family_rank_tag.png", + // height: 80.w, + // ), + SizedBox(width: 5.w), + ], + ), + SizedBox(height: 8.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + padding: EdgeInsets.symmetric(vertical: 5.w, horizontal: 6.w), + // 外部容器用于实现渐变边框 + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + border: Border.all(color: Color(0xFFF5F5F5), width: 0.2.w), + ), + child: Row( + children: [ + Image.asset( + "atu_images/family/at_icon_announcement_tag2.png", + height: 22.w, + ), + SizedBox(width: 3.w), + Expanded( + child: SizedBox( + height: 22.w, + child: + familyNotice.length > 20 + ? Marquee( + text: familyNotice, + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 40.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + familyNotice, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + ), + ], + ), // 背景色, + ), + SizedBox(height: 10.w), + ], + ), + ), + ); + } + + /// 获取显示的成员数量,根据业务逻辑策略限制 + int _getDisplayMemberCount() { + final memberCount = familyBaseInfo?.displayMembers?.length ?? 0; + final maxDisplayCount = _strategy.getFamilyOnlineUserDisplayCount(); + return memberCount > maxDisplayCount ? maxDisplayCount : memberCount; + } + + _buidFamilyOnlineUser() { + // 使用业务逻辑策略处理家族等级 + final displayLevel = _strategy.getFamilyDisplayLevel( + familyBaseInfo?.level ?? 0, + ); + return ATDebounceWidget( + child: SizedBox( + width: ScreenUtil().screenWidth, + child: NinePatchImage( + alignment: Alignment.center, + imageProvider: AssetImage( + _strategy.getFamilyLevelBackgroundImage(displayLevel), + ), + sliceCachedKey: _strategy.getFamilyLevelBackgroundImage(displayLevel), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 8.w), + Row( + children: [ + SizedBox(width: 10.w), + text( + ATAppLocalizations.of(context)!.onlineUsers( + "${familyBaseInfo?.onlineCount ?? 0}", + "${familyBaseInfo?.memberCount ?? 0}", + ), + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + Spacer(), + Icon( + Icons.keyboard_arrow_right, + size: 20.w, + color: Colors.white, + ), + SizedBox(width: 8.w), + ], + ), + SizedBox(height: 3.w), + SizedBox( + height: 45.w, + child: ListView.separated( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: _getDisplayMemberCount(), + // 列表项数量,根据业务逻辑策略限制显示数量 + padding: EdgeInsets.only(left: 15.w, right: 15.w), + itemBuilder: (context, index) { + return Stack( + alignment: AlignmentDirectional.bottomEnd, + children: [ + netImage( + url: + familyBaseInfo?.displayMembers?[index].avatar ?? + "", + width: 45.w, + height: 45.w, + shape: BoxShape.circle, + defaultImg: + "atu_images/general/at_icon_avar_defalt.png", + ), + (familyBaseInfo?.displayMembers?[index].online ?? false) + ? PositionedDirectional( + bottom: 1.w, + child: Image.asset( + "atu_images/family/at_icon_family_user_online_tag.png", + height: 15.w, + ), + ) + : Container(), + ], + ); + }, + separatorBuilder: (BuildContext context, int index) { + return Container(width: 5.w); + }, + ), + ), + SizedBox(height: 8.w), + ], + ), + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${FamilyRoute.familyMemberList}?familyId=${familyBaseInfo?.familyId}&myFamilyRole=${familyBaseInfo?.myFamilyRole}", + replace: false, + ).then((result) { + loadData(); + }); + }, + ); + } + + _buiRoomList() { + return GridView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: _strategy.getFamilyRoomGridCrossAxisCount(), + ), + itemBuilder: (c, i) { + OnlineRooms? roomRes = familyBaseInfo?.onlineRooms?[i]; + return GestureDetector( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: Colors.transparent, + ), + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + netImage( + url: roomRes?.roomCover ?? "", + borderRadius: BorderRadius.circular(12.w), + width: 200.w, + height: 200.w, + ), + Container( + padding: EdgeInsets.symmetric(vertical: 5.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(12.w), + bottomRight: Radius.circular(12.w), + ), + color: Colors.black38, + ), + child: Row( + children: [ + SizedBox(width: 10.w), + ATCountryFlagImage( + countryName: roomRes?.countryName, + width: 20.w, + height: 13.w, + borderRadius: BorderRadius.circular(2.w), + ), + SizedBox(width: 5.w), + Expanded( + child: SizedBox( + height: 21.w, + child: + (roomRes?.roomName?.length ?? 0) > 10 + ? Marquee( + text: roomRes?.roomName ?? "", + style: TextStyle( + fontSize: 15.sp, + color: Color(0xffffffff), + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + roomRes?.roomName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 15.sp, + color: Color(0xffffffff), + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + ), + ), + ), + ), + SizedBox(width: 5.w), + // 根据业务逻辑策略显示房间图标 + if (roomRes?.existsPassword == false) ...[ + Image.asset( + "atu_images/general/at_icon_online_user.gif", + width: 14.w, + height: 14.w, + ), + SizedBox(width: 3.w), + text( + "${roomRes?.onlineQuantity ?? 0}", + fontSize: 12.sp, + ), + ] else if (_strategy.shouldShowPasswordRoomIcon()) ...[ + // 显示密码房间图标 + Image.asset( + "atu_images/index/at_icon_room_suo.png", + width: 20.w, + height: 20.w, + ), + SizedBox(height: 10.w), // 占位符以保持布局一致 + ] else ...[ + // 不显示任何图标,使用占位符保持布局 + SizedBox(width: 20.w, height: 20.w), + SizedBox(height: 10.w), + ], + SizedBox(width: 10.w), + ], + ), + ), + ], + ), + ), + onTap: () { + Provider.of( + context, + listen: false, + ).joinRoom(context, roomRes?.roomId ?? ""); + }, + ); + }, + itemCount: familyBaseInfo?.onlineRooms?.length ?? 0, + ); + } + + void loadData() { + GroupRepository() + .familyHomeList(pageNo: 1) + .then((res) { + familyBaseInfo = res.records?.first; + _refreshController.refreshCompleted(); + _refreshController.loadComplete(); + setState(() {}); + }) + .catchError((e) { + _refreshController.loadNoData(); + _refreshController.refreshCompleted(); + }); + } +} diff --git a/lib/chatvibe_features/family/info/family_info_page.dart b/lib/chatvibe_features/family/info/family_info_page.dart new file mode 100644 index 0000000..e4b482f --- /dev/null +++ b/lib/chatvibe_features/family/info/family_info_page.dart @@ -0,0 +1,2257 @@ +import 'dart:ui' as ui; + +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_ninepatch_image/flutter_ninepatch_image.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/family_repository_impl.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/family_base_info_res.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_features/family/family_route.dart'; + +import '../../../chatvibe_data/models/enum/at_family_roles_type.dart'; +import '../../../chatvibe_domain/models/res/at_famaily_leader_board_res.dart'; +import '../../../chatvibe_domain/usecases/at_family_rank_tab_selector.dart'; + +class FamilyInfoPage extends StatefulWidget { + String familyId = ""; + + @override + _FamilyInfoPageState createState() => _FamilyInfoPageState(); + + FamilyInfoPage(this.familyId); +} + +class _FamilyInfoPageState extends State { + ChatVibeFamilyBaseInfoRes? familyBaseInfo; + List supporterWeekRank = []; + List hostWeekRank = []; + int _currentRankTabIndex = 0; + + @override + void initState() { + super.initState(); + loadFamilyInfo(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + body: SafeArea( + top: false, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + SingleChildScrollView( + child: Stack( + children: [ + Image.asset( + "atu_images/family/at_icon_family_head_bg.png", + fit: BoxFit.fitWidth, + ), + PositionedDirectional( + top: 11.w, + child: ChatVibeStandardAppBar( + actions: [], + title: "", + backButtonColor: Colors.white, + ), + ), + Container( + margin: EdgeInsets.only(top: 212.w), + child: ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 18, sigmaY: 18), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 15.w), + alignment: Alignment.center, + color: Colors.white10, + child: Column( + children: [ + SizedBox(height: 10.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Spacer(), + familyBaseInfo?.myFamilyRole == + ATFamilyRolesType.ADMIN.name + ? Row( + mainAxisSize: MainAxisSize.min, + children: [ + ATDebounceWidget( + child: Container( + padding: EdgeInsets.all(3.w), + child: Image.asset( + height: 22.w, + "atu_images/family/at_icon_family_join_request_tag.png", + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${FamilyRoute.familyJoinRequest}?currentMember=${familyBaseInfo?.currentMember ?? 0}&maxMember=${familyBaseInfo?.maxMember ?? 0}", + replace: false, + ).then((result) { + loadFamilyInfo(); + }); + }, + ), + ATDebounceWidget( + child: Container( + padding: EdgeInsets.all(3.w), + child: Image.asset( + height: 22.w, + "atu_images/family/at_icon_family_edit_tag.png", + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${FamilyRoute.editFamily}?familyId=${familyBaseInfo?.familyId}", + replace: false, + ).then((result) { + loadFamilyInfo(); + }); + }, + ), + SizedBox(width: 8.w), + ], + ) + : Container(), + familyBaseInfo?.myFamilyRole == + ATFamilyRolesType.MANAGE.name || + familyBaseInfo?.myFamilyRole == + ATFamilyRolesType.MEMBER.name + ? Builder( + builder: (ct) { + return ATDebounceWidget( + child: Container( + padding: EdgeInsets.all(3.w), + child: Icon( + Icons.more_horiz, + color: Colors.white, + size: 22.w, + ), + ), + onTap: () { + _showLeavFamilyOpt(ct); + }, + ); + }, + ) + : Container(), + ], + ), + SizedBox(height: 10.w), + _buidFamilyInfo(), + SizedBox(height: 10.w), + _buildFamilyAnnouncement(), + SizedBox(height: 10.w), + Row( + children: [ + Container( + height: 22.w, + width: 4.w, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xffC670FF), + Color(0xff7726FF), + ], + ), + borderRadius: BorderRadius.circular(2.w), + ), + ), + SizedBox(width: 10.w), + text( + ATAppLocalizations.of(context)!.treasureChest, + textColor: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + ), + Spacer(), + ATDebounceWidget( + debounceTime: Duration(milliseconds: 800), + child: Container( + padding: EdgeInsets.all(3.w), + child: Image.asset( + "atu_images/person/at_icon_cp_helpe.png", + height: 18.w, + ), + ), + onTap: () { + if (familyBaseInfo?.myFamilyRole == + ATFamilyRolesType.ADMIN.name || + familyBaseInfo?.myFamilyRole == + ATFamilyRolesType.MANAGE.name || + familyBaseInfo?.myFamilyRole == + ATFamilyRolesType.MEMBER.name) { + SmartDialog.show( + tag: "showFamilyHelp", + alignment: Alignment.bottomCenter, + debounce: true, + animationType: + SmartAnimationType.fade, + clickMaskDismiss: true, + builder: (_) { + return SafeArea( + top: false, + child: Container( + decoration: BoxDecoration( + color: Color(0xff161616), + border: Border.all( + color: Colors.grey, + width: 0.3.w, + ), + borderRadius: + BorderRadius.only( + topLeft: + Radius.circular( + 12.w, + ), + topRight: + Radius.circular( + 12.w, + ), + ), + ), + child: Column( + mainAxisSize: + MainAxisSize.min, + children: [ + SizedBox(height: 8.w), + Row( + mainAxisAlignment: + MainAxisAlignment + .center, + children: [ + text( + ATAppLocalizations.of( + context, + )!.help, + textColor: + Colors.white, + fontSize: 15.sp, + fontWeight: + FontWeight.w600, + ), + ], + ), + Container( + padding: + EdgeInsetsDirectional.only( + top: 8.w, + start: 10.w, + end: 10.w, + bottom: 20.w, + ), + child: text( + ATAppLocalizations.of( + context, + )!.familyHelpTips, + maxLines: 50, + textColor: Colors.white, + fontSize: 15.sp, + fontWeight: + FontWeight.w600, + ), + ), + ], + ), + ), + ); + }, + ); + } + }, + ), + ], + ), + SizedBox(height: 10.w), + _buildBoxLine(), + SizedBox(height: 10.w), + Row( + children: [ + Container( + height: 22.w, + width: 4.w, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xffC670FF), + Color(0xff7726FF), + ], + ), + borderRadius: BorderRadius.circular(2.w), + ), + ), + SizedBox(width: 10.w), + text( + "Lv.${familyBaseInfo?.familyLevel ?? 0}", + textColor: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + ), + Spacer(), + ATDebounceWidget( + debounceTime: Duration(milliseconds: 800), + child: Container( + padding: EdgeInsets.all(3.w), + child: Icon( + Icons.chevron_right, + size: 25.w, + color: Colors.white, + ), + ), + onTap: () { + if (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.familyId == + familyBaseInfo?.familyId) { + ATNavigatorUtils.push( + context, + "${FamilyRoute.familyLevel}?curentLevel=${familyBaseInfo?.familyLevel ?? 0}&curentLevelExp=${familyBaseInfo?.currentExp ?? "0"}", + replace: false, + ); + } + }, + ), + ], + ), + SizedBox(height: 10.w), + _buildLevelLine(), + SizedBox(height: 10.w), + Row( + children: [ + Container( + height: 22.w, + width: 4.w, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xffC670FF), + Color(0xff7726FF), + ], + ), + borderRadius: BorderRadius.circular(2.w), + ), + ), + SizedBox(width: 10.w), + text( + "${ATAppLocalizations.of(context)!.memberList}:", + textColor: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + ), + Spacer(), + ATDebounceWidget( + debounceTime: Duration(milliseconds: 800), + child: Container( + padding: EdgeInsets.all(3.w), + child: Icon( + Icons.chevron_right, + size: 25.w, + color: Colors.white, + ), + ), + onTap: () { + if (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.familyId == + familyBaseInfo?.familyId) { + ATNavigatorUtils.push( + context, + "${FamilyRoute.familyMemberList}?familyId=${familyBaseInfo?.familyId}&myFamilyRole=${familyBaseInfo?.myFamilyRole}", + replace: false, + ).then((result) { + loadFamilyInfo(); + }); + } + }, + ), + ], + ), + SizedBox(height: 10.w), + _buildRankUserLine(), + SizedBox(height: 10.w), + _buildRankTabLine(), + ], + ), + ), + ), + ), + ), + ], + ), + ), + Consumer( + builder: (context, ref, child) { + return AccountStorage().getCurrentUser()?.userProfile?.familyId == + null && + familyBaseInfo?.joinWaiting == false + ? ATDebounceWidget( + debounceTime: Duration(milliseconds: 800), + child: Container( + margin: EdgeInsets.only(bottom: 15.w), + height: 45.w, + alignment: Alignment.center, + width: 180.w, + padding: EdgeInsets.symmetric(vertical: 12.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(60.w), + gradient: LinearGradient( + colors: [Color(0xff0D0022), Color(0xff9F63FF)], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: text( + ATAppLocalizations.of(context)!.applyToJoin, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () { + GroupRepository() + .familyApplyJoin(familyBaseInfo?.familyId ?? "") + .then((res) { + ATTts.show( + ATAppLocalizations.of( + context, + )!.operationSuccessful, + ); + loadFamilyInfo(); + }); + }, + ) + : (familyBaseInfo?.joinWaiting == true + ? Container( + margin: EdgeInsets.only(bottom: 15.w), + height: 45.w, + alignment: Alignment.center, + width: 180.w, + padding: EdgeInsets.symmetric(vertical: 12.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(60.w), + color: Color(0xff5a5851), + ), + child: text( + ATAppLocalizations.of(context)!.pending, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ) + : Container()); + }, + ), + ], + ), + ), + ); + } + + _buidFamilyInfo() { + String familyIntro = (familyBaseInfo?.familyIntro ?? "").replaceAll( + RegExp(r'\s+'), + ' ', + ); + int level = 0; + if ((familyBaseInfo?.familyLevel ?? 0) > 0 && + (familyBaseInfo?.familyLevel ?? 0) < 4) { + level = 1; + } else if ((familyBaseInfo?.familyLevel ?? 0) > 3 && + (familyBaseInfo?.familyLevel ?? 0) < 7) { + level = 2; + } else if ((familyBaseInfo?.familyLevel ?? 0) > 6 && + (familyBaseInfo?.familyLevel ?? 0) < 10) { + level = 3; + } else { + level = familyBaseInfo?.familyLevel ?? 0; + } + return SizedBox( + width: ScreenUtil().screenWidth, + child: NinePatchImage( + alignment: Alignment.center, + imageProvider: AssetImage( + "atu_images/family/at_icon_family_info_item_bg_$level.9.png", + ), + sliceCachedKey: "atu_images/family/at_icon_family_info_item_bg_$level.9.png", + child: Column( + children: [ + SizedBox(height: 5.w), + Row( + children: [ + SizedBox(width: 5.w), + Container( + margin: EdgeInsetsDirectional.only(start: 8.w, top: 5.w), + child: netImage( + height: 80.w, + width: 80.w, + borderRadius: BorderRadius.circular(5.w), + url: familyBaseInfo?.familyAvatar ?? "", + ), + ), + SizedBox(width: 10.w), + Expanded( + child: Column( + children: [ + Container( + constraints: BoxConstraints( + maxWidth: 135.w, + maxHeight: 24.w, + ), + child: + (familyBaseInfo?.familyName?.length ?? 0) > 12 + ? Marquee( + text: familyBaseInfo?.familyName ?? "", + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + blankSpace: 40.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + familyBaseInfo?.familyName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + SizedBox(height: 5.w), + Row( + children: [ + text( + "ID:${familyBaseInfo?.familyAccount ?? ""}", + fontWeight: FontWeight.w600, + fontSize: 13.sp, + textColor: Colors.white, + ), + SizedBox(width: 2.w), + Container( + alignment: AlignmentDirectional.center, + height: 25.w, + width: 68.w, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + height: 15.w, + "atu_images/family/at_icon_family_member_tag.png", + ), + SizedBox(width: 3.w), + text( + "${familyBaseInfo?.currentMember ?? 0}/${familyBaseInfo?.maxMember ?? 0}", + fontWeight: FontWeight.w600, + fontSize: 13.sp, + textColor: Colors.white, + ), + ], + ), + ), + ], + ), + ], + ), + ), + Image.asset( + "atu_images/family/at_icon_family_level_${familyBaseInfo?.familyLevel ?? 0}.png", + height: 80.w, + ), + // Image.asset( + // "images/family/at_icon_family_rank_tag.png", + // height: 80.w, + // ), + SizedBox(width: 5.w), + ], + ), + SizedBox(height: 8.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + padding: EdgeInsets.symmetric(vertical: 5.w, horizontal: 6.w), + // 外部容器用于实现渐变边框 + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + border: Border.all(color: Color(0xFFF5F5F5), width: 0.2.w), + ), + child: Row( + children: [ + Image.asset( + "atu_images/family/at_icon_announcement_tag.png", + height: 22.w, + ), + SizedBox(width: 3.w), + Expanded( + child: SizedBox( + height: 22.w, + child: + (familyIntro?.length ?? 0) > 20 + ? Marquee( + text: familyIntro ?? "", + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + blankSpace: 40.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + (familyIntro ?? "").isEmpty + ? ATAppLocalizations.of( + context, + )!.welcomeToMyFamily + : familyIntro ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + ), + ], + ), // 背景色, + ), + SizedBox(height: 10.w), + ], + ), + ), + ); + } + + Widget _buildFamilyAnnouncement() { + String familyNotice = (familyBaseInfo?.familyNotice ?? "").replaceAll( + RegExp(r'\s+'), + ' ', + ); + return ATDebounceWidget( + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + // 渐变色边框 + gradient: LinearGradient( + colors: [ + Color(0xffcecece), // 渐变色1 + Color(0xff5b5b5b), // 渐变色2 + Color(0xffcecece), // 渐变色3 + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + // 内部留白(边框宽度) + padding: EdgeInsets.all(0.2.w), // 边框宽度 + child: Container( + padding: EdgeInsets.symmetric(vertical: 10.w, horizontal: 6.w), + decoration: BoxDecoration( + color: Colors.black87, + borderRadius: BorderRadius.circular(8.w), + ), + child: Row( + children: [ + Image.asset( + "atu_images/family/at_icon_announcement_tag2.png", + height: 22.w, + ), + SizedBox(width: 3.w), + Expanded( + child: SizedBox( + height: 22.w, + child: + (familyNotice.length ?? 0) > 20 + ? Marquee( + text: familyNotice, + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + blankSpace: 40.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration(milliseconds: 550), + decelerationCurve: Curves.easeOut, + ) + : Text( + (familyNotice ?? "").isEmpty + ? ATAppLocalizations.of( + context, + )!.welcomeToMyFamily + : familyNotice ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + ), + Icon(Icons.chevron_right, size: 25.w, color: Colors.white), + ], + ), // 背景色, + ), + ), + onTap: () { + if (familyBaseInfo?.myFamilyRole == ATFamilyRolesType.ADMIN.name || + familyBaseInfo?.myFamilyRole == ATFamilyRolesType.MANAGE.name || + familyBaseInfo?.myFamilyRole == ATFamilyRolesType.MEMBER.name) { + String familyAnnouncement = Uri.encodeComponent( + familyBaseInfo?.familyNotice ?? "", + ); + ATNavigatorUtils.push( + context, + "${FamilyRoute.familyNews}?familyId=${familyBaseInfo?.familyId}&familyAnnouncement=$familyAnnouncement", + replace: false, + ); + } + }, + ); + } + + Widget _buildBoxLine() { + return Container( + alignment: Alignment.center, + padding: EdgeInsets.all(10.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/family/at_icon_family_box_item_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + Stack( + alignment: Alignment.center, + children: [ + Stack( + children: [ + Container( + decoration: BoxDecoration( + color: Colors.white24, + borderRadius: BorderRadius.circular(8.w), + ), + height: 12.w, + width: ScreenUtil().screenWidth * 0.8, + ), + Container( + height: 12.w, + width: ATRoomUtils.collectCurrenProgressProcess( + familyBaseInfo?.boxInfo?.contributeCount ?? 0, + familyBaseInfo?.boxInfo?.chests?.last.requiredCount ?? 1, + ScreenUtil().screenWidth * 0.8, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [Color(0xffE41A3F), Color(0xffFFFA0F)], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + borderRadius: BorderRadius.circular(8.w), + ), + ), + ], + ), + + PositionedDirectional( + start: 0, + child: Transform.translate( + offset: Offset(-13, 0), + child: _buildBoxIcon(0), + ), + ), + _buildBoxIcon(1), + PositionedDirectional( + end: 0, + child: Transform.translate( + offset: Offset(13, 0), + child: _buildBoxIcon(2), + ), + ), + ], + ), + (familyBaseInfo?.boxInfo?.chests?.length ?? 0) > 2 + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Image.asset( + "atu_images/family/at_icon_family_user_tag.png", + height: 16.w, + ), + Container( + margin: EdgeInsets.only(top: 3.w), + child: text( + "${familyBaseInfo?.boxInfo?.chests![0].requiredCount ?? 0}", + textColor: Colors.white, + fontSize: 16.sp, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + Row( + children: [ + Image.asset( + "atu_images/family/at_icon_family_user_tag.png", + height: 16.w, + ), + Container( + margin: EdgeInsets.only(top: 3.w), + child: text( + "${familyBaseInfo?.boxInfo?.chests![1].requiredCount ?? 0}", + textColor: Colors.white, + fontSize: 16.sp, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + Row( + children: [ + Image.asset( + "atu_images/family/at_icon_family_user_tag.png", + height: 16.w, + ), + Container( + margin: EdgeInsets.only(top: 3.w), + child: text( + "${familyBaseInfo?.boxInfo?.chests![2].requiredCount ?? 0}", + textColor: Colors.white, + fontSize: 16.sp, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ], + ) + : Container(), + Row( + children: [ + Container( + margin: EdgeInsets.only(top: 5.w), + height: 58.w, + width: 165.w, + alignment: Alignment.center, + padding: EdgeInsets.only(bottom: 15.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/family/at_icon_box_btn_bg.png"), + fit: BoxFit.fill, + ), + ), + child: text( + ATAppLocalizations.of(context)!.numberOfSign( + "${familyBaseInfo?.boxInfo?.contributeCount ?? 0}", + ), + fontSize: 13.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + SizedBox(width: 10.w), + ATDebounceWidget( + debounceTime: Duration(milliseconds: 800), + child: Container( + margin: EdgeInsets.only(top: 5.w), + height: 58.w, + width: 135.w, + alignment: Alignment.center, + padding: EdgeInsets.only(bottom: 15.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/family/at_icon_box_btn_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset("atu_images/general/at_icon_jb.png", width: 25.w), + SizedBox(width: 8.w), + text( + "50", + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + ), + onTap: () { + if (familyBaseInfo?.myFamilyRole == + ATFamilyRolesType.ADMIN.name || + familyBaseInfo?.myFamilyRole == + ATFamilyRolesType.MANAGE.name || + familyBaseInfo?.myFamilyRole == + ATFamilyRolesType.MEMBER.name) { + _familyBoxContribute(); + } + }, + ), + ], + ), + ], + ), + ); + } + + Widget _buildBoxIcon(int index) { + if (familyBaseInfo?.myFamilyRole == ATFamilyRolesType.ADMIN.name || + familyBaseInfo?.myFamilyRole == ATFamilyRolesType.MANAGE.name || + familyBaseInfo?.myFamilyRole == ATFamilyRolesType.MEMBER.name) { + Chests? chest = familyBaseInfo?.boxInfo?.chests![index]; + if ((chest?.unlocked ?? false)) { + //宝箱已经解锁 + if ((chest?.claimed ?? false)) { + //宝箱已经领取 + return Image.asset( + "atu_images/family/at_icon_family_box${index + 1}_opened.png", + width: 35.w, + height: 35.w, + ); + } else { + return ATDebounceWidget( + child: Image.asset( + "atu_images/family/at_icon_family_box${index + 1}_open.webp", + width: 35.w, + height: 35.w, + ), + onTap: () { + if (familyBaseInfo?.myFamilyRole == ATFamilyRolesType.ADMIN.name || + familyBaseInfo?.myFamilyRole == ATFamilyRolesType.MANAGE.name || + familyBaseInfo?.myFamilyRole == ATFamilyRolesType.MEMBER.name) { + ATLoadingManager.exhibitOperation(); + GroupRepository() + .familyBoxClaim(chest?.level ?? 0) + .then((res) { + ATTts.show(ATAppLocalizations.of(context)!.receiveSucc); + loadFamilyInfo(); + }) + .catchError((_) {}); + } + }, + ); + } + } + } + return Image.asset( + "atu_images/family/at_icon_family_box${index + 1}.png", + width: 35.w, + height: 35.w, + ); + } + + Widget _buildLevelLine() { + int level = 0; + if ((familyBaseInfo?.familyLevel ?? 0) > 0 && + (familyBaseInfo?.familyLevel ?? 0) < 4) { + level = 1; + } else if ((familyBaseInfo?.familyLevel ?? 0) > 3 && + (familyBaseInfo?.familyLevel ?? 0) < 7) { + level = 2; + } else if ((familyBaseInfo?.familyLevel ?? 0) > 6 && + (familyBaseInfo?.familyLevel ?? 0) < 10) { + level = 3; + } else { + level = familyBaseInfo?.familyLevel ?? 0; + } + + return SizedBox( + width: ScreenUtil().screenWidth, + child: NinePatchImage( + alignment: Alignment.center, + imageProvider: AssetImage( + "atu_images/family/at_icon_family_info_item_bg_$level.9.png", + ), + sliceCachedKey: "atu_images/family/at_icon_family_info_item_bg_$level.9.png", + child: Padding( + padding: EdgeInsetsDirectional.all(10.w), + child: Row( + children: [ + Column( + children: [ + text( + "${familyBaseInfo?.currentExp ?? 0}/${familyBaseInfo?.levelExp ?? 0}", + fontWeight: FontWeight.w600, + fontSize: 14.sp, + textColor: Colors.white, + ), + SizedBox(height: 3.w), + Stack( + children: [ + Container( + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.circular(8.w), + ), + height: 9.w, + width: ScreenUtil().screenWidth * 0.65, + ), + Container( + height: 9.w, + width: ATRoomUtils.collectCurrenProgressProcess( + int.parse(familyBaseInfo?.currentExp ?? "0"), + int.parse(familyBaseInfo?.levelExp ?? "1"), + ScreenUtil().screenWidth * 0.65, + ), + decoration: BoxDecoration( + gradient: buildFamilyProgressLinearGradient(level), + borderRadius: BorderRadius.circular(8.w), + ), + ), + ], + ), + ], + ), + Spacer(), + Image.asset( + "atu_images/family/at_icon_family_level_${familyBaseInfo?.familyLevel ?? 0}.png", + width: 75.w, + ), + ], + ), + ), + ), + ); + } + + Widget _buildRankUserLine() { + return Row( + children: [ + Expanded( + child: ATDebounceWidget( + child: Container( + height: 88.w, + alignment: Alignment.center, + padding: EdgeInsetsDirectional.only( + start: 10.w, + top: 8.w, + end: 3.w, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6.w), + border: Border.all(color: Color(0xffE6E6E6), width: 0.1.w), + gradient: RadialGradient( + colors: [Color(0xff161616), Color(0xff101010)], + ), + ), + child: Column( + children: [ + Row( + children: [ + text( + "${ATAppLocalizations.of(context)!.supporter}:", + fontWeight: FontWeight.w600, + fontSize: 14.sp, + textColor: Colors.white, + ), + Spacer(), + Container( + padding: EdgeInsets.all(3.w), + child: Icon( + Icons.chevron_right, + size: 22.w, + color: Colors.white, + ), + ), + ], + ), + SizedBox(height: 3.w), + Row( + children: [ + Expanded( + child: SizedBox( + height: 35.w, + child: ListView.separated( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: + familyBaseInfo + ?.memberList + ?.supporters + ?.length ?? + 0, + itemBuilder: (context, index) { + return netImage( + width: 35.w, + height: 35.w, + url: + familyBaseInfo + ?.memberList + ?.supporters?[index] + .userAvatar ?? + "", + shape: BoxShape.circle, + defaultImg: + "atu_images/general/at_icon_avar_defalt.png", + ); + }, + separatorBuilder: ( + BuildContext context, + int index, + ) { + return SizedBox(width: 5.w); + }, + ), + ), + ), + ], + ), + ], + ), + ), + onTap: () { + if (AccountStorage().getCurrentUser()?.userProfile?.familyId == + familyBaseInfo?.familyId) { + ATNavigatorUtils.push( + context, + "${FamilyRoute.familyUserRank}?familyId=${familyBaseInfo?.familyId}&rankType=0", + replace: false, + ); + } + }, + ), + ), + SizedBox(width: 10.w), + Expanded( + child: ATDebounceWidget( + child: Container( + height: 88.w, + alignment: Alignment.center, + padding: EdgeInsetsDirectional.only( + start: 10.w, + top: 8.w, + end: 3.w, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6.w), + border: Border.all(color: Color(0xffE6E6E6), width: 0.1.w), + gradient: LinearGradient( + colors: [Color(0xff161616), Color(0xff101010)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: Column( + children: [ + Row( + children: [ + text( + "${ATAppLocalizations.of(context)!.host}:", + fontWeight: FontWeight.w600, + fontSize: 14.sp, + textColor: Colors.white, + ), + Spacer(), + Container( + padding: EdgeInsets.all(3.w), + child: Icon( + Icons.chevron_right, + size: 22.w, + color: Colors.white, + ), + ), + ], + ), + SizedBox(height: 3.w), + + Row( + children: [ + Expanded( + child: SizedBox( + height: 35.w, + child: ListView.separated( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: + familyBaseInfo?.memberList?.hosts?.length ?? 0, + itemBuilder: (context, index) { + return netImage( + width: 35.w, + height: 35.w, + url: + familyBaseInfo + ?.memberList + ?.hosts?[index] + .userAvatar ?? + "", + shape: BoxShape.circle, + defaultImg: + "atu_images/general/at_icon_avar_defalt.png", + ); + }, + separatorBuilder: ( + BuildContext context, + int index, + ) { + return SizedBox(width: 5.w); + }, + ), + ), + ), + ], + ), + ], + ), + ), + onTap: () { + if (AccountStorage().getCurrentUser()?.userProfile?.familyId == + familyBaseInfo?.familyId) { + ATNavigatorUtils.push( + context, + "${FamilyRoute.familyUserRank}?familyId=${familyBaseInfo?.familyId}&rankType=1", + replace: false, + ); + } + }, + ), + ), + ], + ); + } + + Widget _buildRankTabLine() { + return SizedBox( + width: ScreenUtil().screenWidth, + child: NinePatchImage( + imageProvider: AssetImage( + "atu_images/family/at_icon_family_tab_rank_bg.9.png", + ), + sliceCachedKey: "atu_images/family/at_icon_family_tab_rank_bg.9.png", + child: Column( + children: [ + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 10.w), + ATFamilyRankTabSelector( + options: [ + ATAppLocalizations.of(context)!.supporterWeeklyRank, + ATAppLocalizations.of(context)!.hostWeeklyRank, + ], + onChanged: (index) { + setState(() { + _currentRankTabIndex = index; + }); + }, + initialIndex: 0, + ), + SizedBox(width: 10.w), + ], + ), + Container( + margin: EdgeInsets.symmetric(horizontal: 5.w), + child: Column( + children: [ + SizedBox(height: 10.w), + Stack( + alignment: AlignmentDirectional.center, + children: [ + _buildRankUserAvatar(1), + IgnorePointer( + child: Image.asset( + "atu_images/family/at_icon_family_user_rank_1.png", + height: 170.w, + ), + ), + _buildRankUserName(1), + ], + ), + Transform.translate( + offset: Offset(0, -70), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Stack( + alignment: AlignmentDirectional.center, + children: [ + _buildRankUserAvatar(2), + IgnorePointer( + child: Image.asset( + "atu_images/family/at_icon_family_user_rank_2.png", + height: 155.w, + ), + ), + _buildRankUserName(2), + ], + ), + Stack( + alignment: AlignmentDirectional.center, + children: [ + _buildRankUserAvatar(3), + IgnorePointer( + child: Image.asset( + "atu_images/family/at_icon_family_user_rank_3.png", + height: 155.w, + ), + ), + _buildRankUserName(3), + ], + ), + ], + ), + ), + ], + ), + ), + _currentRankTabIndex == 0 + ? (supporterWeekRank.length > 3 + ? Transform.translate( + offset: Offset(0, -35), + child: ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: + supporterWeekRank + .sublist(3, supporterWeekRank.length) + .length, + // 列表项数量 + padding: EdgeInsets.only(left: 5.w, right: 15.w), + itemBuilder: (context, index) { + return _buildRankUserItem(index); + }, + separatorBuilder: (BuildContext context, int index) { + return Divider( + height: 12.w, + thickness: 0.2.w, + color: ChatVibeTheme.dividerColor, + ); + }, + ), + ) + : Container()) + : (hostWeekRank.length > 3 + ? Transform.translate( + offset: Offset(0, -30), + child: ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: + hostWeekRank.sublist(3, hostWeekRank.length).length, + // 列表项数量 + padding: EdgeInsets.only(left: 5.w, right: 15.w), + itemBuilder: (context, index) { + return _buildRankUserItem(index); + }, + separatorBuilder: (BuildContext context, int index) { + return Divider( + height: 12.w, + thickness: 0.2.w, + color: ChatVibeTheme.dividerColor, + ); + }, + ), + ) + : Container()), + ], + ), + ), + ); + } + + Widget _buildRankUserItem(int index) { + if (_currentRankTabIndex == 0) { + SupporterWeekRank swr = supporterWeekRank[index + 3]; + return Row( + children: [ + Container( + alignment: AlignmentDirectional.center, + width: 35.w, + child: text( + "${index + 4}", + fontSize: 15.sp, + textColor: Colors.white, + ), + ), + SizedBox(width: 15.w), + ATDebounceWidget( + child: netImage( + url: swr.avatar ?? "", + width: 45.w, + height: 45.w, + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + shape: BoxShape.circle, + border: Border.all(color: Color(0xffFEE5BB), width: 0.5.w), + ), + onTap: () { + if (AccountStorage().getCurrentUser()?.userProfile?.familyId == + familyBaseInfo?.familyId) { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == swr.userId}&tageId=${swr.userId}", + ); + } + }, + ), + SizedBox(width: 5.w), + Column( + children: [ + Container( + constraints: BoxConstraints(maxWidth: 125.w, maxHeight: 21.w), + child: + (swr.nickname?.length ?? 0) > 12 + ? Marquee( + text: swr.nickname ?? "", + style: TextStyle( + fontSize: 15.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration(milliseconds: 550), + decelerationCurve: Curves.easeOut, + ) + : Text( + swr.nickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 15.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + text( + "ID:${swr.account}", + fontSize: 14.sp, + textColor: Colors.white, + fontWeight: FontWeight.w500, + ), + ], + ), + Spacer(), + Row( + children: [ + Image.asset("atu_images/general/at_icon_jb.png", height: 28.w), + SizedBox(width: 3.w), + text( + supporterWeekRank.first.exp ?? "0", + fontWeight: FontWeight.bold, + fontSize: 15.sp, + textColor: Colors.white, + ), + ], + ), + ], + ); + } else { + HostWeekRank hwr = hostWeekRank[index + 3]; + return Row( + children: [ + Container( + alignment: AlignmentDirectional.center, + width: 35.w, + child: text( + "${index + 4}", + fontSize: 15.sp, + textColor: Colors.white, + ), + ), + ATDebounceWidget( + child: netImage( + url: hwr.avatar ?? "", + width: 45.w, + height: 45.w, + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + shape: BoxShape.circle, + border: Border.all(color: Color(0xffFEE5BB), width: 0.5.w), + ), + onTap: () { + if (AccountStorage().getCurrentUser()?.userProfile?.familyId == + familyBaseInfo?.familyId) { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == hwr.userId}&tageId=${hwr.userId}", + ); + } + }, + ), + SizedBox(width: 5.w), + Column( + children: [ + Container( + constraints: BoxConstraints(maxWidth: 125.w, maxHeight: 21.w), + child: + (hwr.nickname?.length ?? 0) > 12 + ? Marquee( + text: hwr.nickname ?? "", + style: TextStyle( + fontSize: 15.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration(milliseconds: 550), + decelerationCurve: Curves.easeOut, + ) + : Text( + hwr.nickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 15.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + text( + "ID:${hwr.account}", + fontSize: 14.sp, + textColor: Colors.white, + fontWeight: FontWeight.w500, + ), + ], + ), + Spacer(), + Row( + children: [ + Image.asset("atu_images/general/at_icon_jb.png", height: 28.w), + SizedBox(width: 3.w), + text( + hwr.exp ?? "0", + fontWeight: FontWeight.bold, + fontSize: 15.sp, + textColor: Colors.white, + ), + ], + ), + ], + ); + } + } + + void loadFamilyInfo() { + ATLoadingManager.exhibitOperation(); + GroupRepository() + .familyBaseInfo(familyId: widget.familyId) + .then((result) { + familyBaseInfo = result; + loadFamilyLeaderboard(); + setState(() {}); + ATLoadingManager.veilRoutine(); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } + + ///投入宝箱金币 + void _familyBoxContribute() { + GroupRepository() + .familyBoxContribute(familyBaseInfo?.familyId ?? "") + .then((result) { + ATTts.show(ATAppLocalizations.of(context)!.operationSuccessful); + loadFamilyInfo(); + }) + .catchError((_) {}); + } + + ///家族用户排行榜 + void loadFamilyLeaderboard() { + GroupRepository() + .familyMemberTop10(familyBaseInfo?.familyId ?? "") + .then((result) { + supporterWeekRank = result.supporterWeekRank ?? []; + hostWeekRank = result.hostWeekRank ?? []; + setState(() {}); + }) + .catchError((_) {}); + } + Widget _buildRankUserName(int rank) { + if (rank == 1) { + return PositionedDirectional( + bottom: 2.w, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _currentRankTabIndex == 0 + ? (supporterWeekRank.isNotEmpty + ? Container( + alignment: AlignmentDirectional.center, + constraints: BoxConstraints( + maxWidth: 70.w, + maxHeight: 19.w, + ), + child: + (supporterWeekRank.first.nickname?.length ?? 0) > 6 + ? Marquee( + text: supporterWeekRank.first.nickname ?? "", + style: TextStyle( + fontSize: 13.sp, + color: Color(0xffD85458), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + supporterWeekRank.first.nickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13.sp, + color: Color(0xffD85458), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ) + : Container()) + : (hostWeekRank.isNotEmpty + ? Container( + alignment: AlignmentDirectional.center, + constraints: BoxConstraints( + maxWidth: 70.w, + maxHeight: 19.w, + ), + child: + (hostWeekRank.first.nickname?.length ?? 0) > 6 + ? Marquee( + text: hostWeekRank.first.nickname ?? "", + style: TextStyle( + fontSize: 13.sp, + color: Color(0xffD85458), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + hostWeekRank.first.nickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13.sp, + color: Color(0xffD85458), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ) + : Container()), + SizedBox(height: 13.w), + Container( + width: 85.w, + height: 25.w, + decoration: BoxDecoration( + border: Border.all(color: Color(0xffFFB906), width: 0.6.w), + borderRadius: BorderRadius.all(Radius.circular(25.w)), + gradient: LinearGradient( + colors: [ + Color(0xffAD1300), + Color(0xffFE280C), + Color(0xffAD1300), + ], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset("atu_images/general/at_icon_jb.png", height: 20.w), + text( + _currentRankTabIndex == 0 + ? (supporterWeekRank.isNotEmpty + ? supporterWeekRank.first.exp ?? "0" + : "0") + : (hostWeekRank.isNotEmpty + ? hostWeekRank.first.exp ?? "0" + : "0"), + fontWeight: FontWeight.bold, + fontSize: 13.sp, + textColor: Color(0xffFFE601), + ), + ], + ), + ), + ], + ), + ); + } else if (rank == 2) { + return PositionedDirectional( + bottom: 5.w, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _currentRankTabIndex == 0 + ? (supporterWeekRank.length > 1 + ? Container( + constraints: BoxConstraints( + maxWidth: 70.w, + maxHeight: 18.w, + ), + child: + (supporterWeekRank[1].nickname?.length ?? 0) > 6 + ? Marquee( + text: supporterWeekRank[1].nickname ?? "", + style: TextStyle( + fontSize: 12.sp, + color: Color(0xff4073C4), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + supporterWeekRank[1].nickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12.sp, + color: Color(0xff4073C4), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ) + : Container()) + : (hostWeekRank.length > 1 + ? Container( + alignment: Alignment.center, + constraints: BoxConstraints( + maxWidth: 70.w, + maxHeight: 18.w, + ), + child: + (hostWeekRank[1].nickname?.length ?? 0) > 6 + ? Marquee( + text: hostWeekRank[1].nickname ?? "", + style: TextStyle( + fontSize: 12.sp, + color: Color(0xff4073C4), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + hostWeekRank[1].nickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12.sp, + color: Color(0xff4073C4), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ) + : Container()), + SizedBox(height: 10.w), + Container( + width: 75.w, + height: 22.w, + decoration: BoxDecoration( + border: Border.all(color: Color(0xffFFB906), width: 0.6.w), + borderRadius: BorderRadius.all(Radius.circular(25.w)), + gradient: LinearGradient( + colors: [ + Color(0xffA3C7FD), + Color(0xff1D5ABD), + Color(0xffA3C6FD), + ], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset("atu_images/general/at_icon_jb.png", height: 18.w), + text( + _currentRankTabIndex == 0 + ? (supporterWeekRank.length > 1 + ? supporterWeekRank[1].exp ?? "0" + : "0") + : (hostWeekRank.length > 1 + ? hostWeekRank[1].exp ?? "0" + : "0"), + fontWeight: FontWeight.bold, + fontSize: 12.sp, + textColor: Colors.white, + ), + ], + ), + ), + ], + ), + ); + } else if (rank == 3) { + return PositionedDirectional( + bottom: 5.w, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _currentRankTabIndex == 0 + ? (supporterWeekRank.length > 2 + ? Container( + constraints: BoxConstraints( + maxWidth: 70.w, + maxHeight: 18.w, + ), + child: + (supporterWeekRank[2].nickname?.length ?? 0) > 6 + ? Marquee( + text: supporterWeekRank[2].nickname ?? "", + style: TextStyle( + fontSize: 12.sp, + color: Color(0xff95522E), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + supporterWeekRank[2].nickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12.sp, + color: Color(0xff95522E), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ) + : Container()) + : (hostWeekRank.length > 2 + ? Container( + alignment: Alignment.center, + constraints: BoxConstraints( + maxWidth: 70.w, + maxHeight: 18.w, + ), + child: + (hostWeekRank[2].nickname?.length ?? 0) > 6 + ? Marquee( + text: hostWeekRank[2].nickname ?? "", + style: TextStyle( + fontSize: 12.sp, + color: Color(0xff95522E), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + hostWeekRank[2].nickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12.sp, + color: Color(0xff95522E), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ) + : Container()), + SizedBox(height: 10.w), + Container( + width: 75.w, + height: 22.w, + decoration: BoxDecoration( + border: Border.all(color: Color(0xffFFB906), width: 0.6.w), + borderRadius: BorderRadius.all(Radius.circular(25.w)), + gradient: LinearGradient( + colors: [ + Color(0xffF5EAE4), + Color(0xffF9C29E), + Color(0xffF5EAE4), + ], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset("atu_images/general/at_icon_jb.png", height: 18.w), + text( + _currentRankTabIndex == 0 + ? (supporterWeekRank.length > 2 + ? supporterWeekRank[2].exp ?? "0" + : "0") + : (hostWeekRank.length > 2 + ? hostWeekRank[2].exp ?? "0" + : "0"), + fontWeight: FontWeight.bold, + fontSize: 12.sp, + textColor: Colors.white, + ), + ], + ), + ), + ], + ), + ); + } + + return Container(); + } + + Widget _buildRankUserAvatar(int rank) { + if (rank == 1) { + return PositionedDirectional( + top: 21.w, + child: + _currentRankTabIndex == 0 + ? (supporterWeekRank.isNotEmpty + ? ATDebounceWidget( + child: netImage( + url: supporterWeekRank.first.avatar ?? "", + width: 75.w, + height: 75.w, + shape: BoxShape.circle, + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + ), + onTap: () { + if (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.familyId == + familyBaseInfo?.familyId) { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == supporterWeekRank.first.userId}&tageId=${supporterWeekRank.first.userId}", + ); + } + }, + ) + : Container()) + : (hostWeekRank.isNotEmpty + ? ATDebounceWidget( + child: netImage( + url: hostWeekRank.first.avatar ?? "", + width: 75.w, + height: 75.w, + shape: BoxShape.circle, + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + ), + onTap: () { + if (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.familyId == + familyBaseInfo?.familyId) { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == hostWeekRank.first.userId}&tageId=${hostWeekRank.first.userId}", + ); + } + }, + ) + : Container()), + ); + } else if (rank == 2) { + return PositionedDirectional( + top: 10.w, + child: + _currentRankTabIndex == 0 + ? (supporterWeekRank.length > 1 + ? ATDebounceWidget( + child: netImage( + url: supporterWeekRank[1].avatar ?? "", + width: 73.w, + height: 73.w, + shape: BoxShape.circle, + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + ), + onTap: () { + if (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.familyId == + familyBaseInfo?.familyId) { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == supporterWeekRank[1].userId}&tageId=${supporterWeekRank[1].userId}", + ); + } + }, + ) + : Container()) + : (hostWeekRank.length > 1 + ? ATDebounceWidget( + child: netImage( + url: hostWeekRank[1].avatar ?? "", + width: 73.w, + height: 73.w, + shape: BoxShape.circle, + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + ), + onTap: () { + if (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.familyId == + familyBaseInfo?.familyId) { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == hostWeekRank[1].userId}&tageId=${hostWeekRank[1].userId}", + ); + } + }, + ) + : Container()), + ); + } else if (rank == 3) { + return PositionedDirectional( + top: 10.w, + child: + _currentRankTabIndex == 0 + ? (supporterWeekRank.length > 2 + ? ATDebounceWidget( + child: netImage( + url: supporterWeekRank[2].avatar ?? "", + width: 73.w, + height: 73.w, + shape: BoxShape.circle, + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + ), + onTap: () { + if (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.familyId == + familyBaseInfo?.familyId) { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == supporterWeekRank[2].userId}&tageId=${supporterWeekRank[2].userId}", + ); + } + }, + ) + : Container()) + : (hostWeekRank.length > 2 + ? ATDebounceWidget( + child: netImage( + url: hostWeekRank[2].avatar ?? "", + width: 73.w, + height: 73.w, + shape: BoxShape.circle, + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + ), + onTap: () { + if (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.familyId == + familyBaseInfo?.familyId) { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == hostWeekRank[2].userId}&tageId=${hostWeekRank[2].userId}", + ); + } + }, + ) + : Container()), + ); + } + return Container(); + } + + void _showLeavFamilyOpt(ct) { + SmartDialog.showAttach( + tag: "showLeavFamilyOpt", + targetContext: ct, + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + scalePointBuilder: (selfSize) => Offset(selfSize.width, 10), + builder: (_) { + return ATDebounceWidget( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.w), + margin: EdgeInsetsDirectional.only(end: 5.w), + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular(8.w), + border: Border.all(color: Colors.grey, width: 0.5.w), + ), + child: text( + ATAppLocalizations.of(context)!.leavingTheFamily, + fontWeight: FontWeight.w600, + textColor: Colors.white, + fontSize: 15.sp, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showLeavFamilyOpt"); + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.leavFamilyTips, + isDark: true, + leftConfirm: true, + btnText: ATAppLocalizations.of(context)!.keep, + onEnsure: () { + ATLoadingManager.exhibitOperation(); + GroupRepository() + .familyUserExit() + .then((res) { + ATTts.show( + ATAppLocalizations.of(context)!.operationSuccessful, + ); + Provider.of( + context, + listen: false, + ).updateFamily(null); + ATLoadingManager.veilRoutine(); + ATNavigatorUtils.goBack(context); + }) + .catchError((_) { + ATLoadingManager.veilRoutine(); + }); + }, + ); + }, + ); + }, + ); + }, + ); + } +} diff --git a/lib/chatvibe_features/family/join_request/family_join_request_page.dart b/lib/chatvibe_features/family/join_request/family_join_request_page.dart new file mode 100644 index 0000000..e4fa884 --- /dev/null +++ b/lib/chatvibe_features/family/join_request/family_join_request_page.dart @@ -0,0 +1,381 @@ +import 'dart:ui' as ui; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/family_repository_impl.dart'; +import 'package:marquee/marquee.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; + +import '../../../chatvibe_domain/models/res/at_family_list_message_res.dart'; + +class FamilyJoinRequestPage extends ATPageList { + String currentMember = ""; + String maxMember = ""; + + FamilyJoinRequestPage(this.currentMember, this.maxMember); + + @override + _FamilyJoinRequestPageState createState() => + _FamilyJoinRequestPageState(currentMember, maxMember); +} + +class _FamilyJoinRequestPageState + extends ATPageListState { + String? lastId; + + String currentMember = ""; + String maxMember = ""; + + _FamilyJoinRequestPageState(this.currentMember, this.maxMember); + + @override + void initState() { + super.initState(); + backgroundColor = Colors.transparent; + isShowFooter = false; + padding = EdgeInsets.symmetric(horizontal: 10.w); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/family/at_icon_family_head_bg.png", + fit: BoxFit.fitWidth, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: "", + actions: [], + backButtonColor: Colors.white, + ), + body: SafeArea( + top: false, + child: Column( + children: [ + SizedBox(height: 115.w), + Expanded( + child: ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 18, sigmaY: 18), + child: Container( + alignment: Alignment.center, + color: Colors.white10, + child: Column( + children: [ + SizedBox(height: 15.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.joinRequest, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + Container( + alignment: Alignment.center, + margin: EdgeInsets.symmetric(horizontal: 10.w), + padding: EdgeInsets.all(10.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + border: Border.all( + color: Color(0xffE6E6E6), + width: 0.2.w, + ), + gradient: LinearGradient( + colors: [ + Color(0xff161616), + Color(0xff101010), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: Row( + children: [ + Image.asset( + "atu_images/family/at_icon_family_member_tag.png", + height: 25.w, + ), + SizedBox(width: 3.w), + text( + "$currentMember/$maxMember", + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + ), + SizedBox(height: 15.w), + Expanded(child: buildList(context)), + SizedBox(height: 15.w), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ), + ], + ); + } + + @override + Widget buildItem(ATFamilyListMessageRes res) { + return Container( + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + border: Border.all(color: Color(0xffE6E6E6), width: 0.1.w), + gradient: LinearGradient( + colors: [Color(0xff161616), Color(0xff101010)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: Row( + children: [ + SizedBox(height: 5.w), + netImage( + height: 65.w, + width: 65.w, + shape: BoxShape.circle, + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + url: res.familyUser?.userAvatar ?? "", + ), + SizedBox(width: 5.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + constraints: BoxConstraints(maxWidth: 115.w, maxHeight: 24.w), + child: + (res.familyUser?.userNickname?.length ?? 0) > 12 + ? Marquee( + text: res.familyUser?.userNickname ?? "", + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 40.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration(milliseconds: 550), + decelerationCurve: Curves.easeOut, + ) + : Text( + res.familyUser?.userNickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + Row( + children: [ + text( + "ID:${res.familyUser?.account}", + fontWeight: FontWeight.w600, + fontSize: 13.sp, + textColor: Colors.white, + ), + ], + ), + SizedBox(width: 5.w), + ], + ), + Spacer(), + ATDebounceWidget( + child: Container( + alignment: AlignmentDirectional.center, + width: 70.w, + height: 25.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(18.w), + gradient: LinearGradient( + colors: [Color(0xff540000), Color(0xffB70505)], + ), + ), + child: text( + ATAppLocalizations.of(context)!.refuse, + fontWeight: FontWeight.w600, + textColor: Colors.white, + fontSize: 13.sp, + ), + ), + onTap: () { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.refuseJoinFamilyTips, + btnText: ATAppLocalizations.of(context)!.confirm, + isDark: true, + onEnsure: () { + ATLoadingManager.exhibitOperation(); + GroupRepository() + .familyMessageHandle(res.msgId ?? "", "REFUSE") + .then((result) { + SmartDialog.dismiss(tag: "showConfirmDialog"); + loadData(1); + ATTts.show( + ATAppLocalizations.of(context)!.operationSuccessful, + ); + }) + .catchError((_) { + ATLoadingManager.veilRoutine(); + SmartDialog.dismiss(tag: "showConfirmDialog"); + }); + }, + ); + }, + ); + }, + ), + SizedBox(width: 8.w), + ATDebounceWidget( + child: Container( + alignment: AlignmentDirectional.center, + width: 70.w, + height: 25.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(18.w), + gradient: LinearGradient( + colors: [Color(0xff260054), Color(0xff6105B7)], + ), + ), + child: text( + ATAppLocalizations.of(context)!.agree, + fontWeight: FontWeight.w600, + textColor: Colors.white, + fontSize: 13.sp, + ), + ), + onTap: () { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.agreeJoinFamilyTips, + btnText: ATAppLocalizations.of(context)!.confirm, + isDark: true, + onEnsure: () { + ATLoadingManager.exhibitOperation(); + GroupRepository() + .familyMessageHandle(res.msgId ?? "", "AGREE") + .then((result) { + SmartDialog.dismiss(tag: "showConfirmDialog"); + currentMember = "${int.parse(currentMember) + 1}"; + loadData(1); + ATTts.show( + ATAppLocalizations.of(context)!.operationSuccessful, + ); + }) + .catchError((_) { + ATLoadingManager.veilRoutine(); + SmartDialog.dismiss(tag: "showConfirmDialog"); + }); + }, + ); + }, + ); + }, + ), + ], + ), + ); + } + + @override + builderDivider() { + return Divider( + height: 22.w, + color: Colors.transparent, + indent: 15.w, + endIndent: 15.w, + ); + } + + @override + empty() { + return mainEmpty( + textColor: Colors.white, + msg: ATAppLocalizations.of(context)!.noData, + image: Image.asset( + "atu_images/general/at_icon_loading.png", + width: 140.w, + height: 140.w, + ), + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + if (page == 1) { + lastId = null; + } + try { + var result = await GroupRepository().familyListMessage( + lastId: lastId, + ); + if (result.isNotEmpty) { + lastId = result.last.msgId; + } + ATLoadingManager.veilRoutine(); + onSuccess(result); + } catch (e) { + ATLoadingManager.veilRoutine(); + if (onErr != null) { + onErr(); + } + } + } +} diff --git a/lib/chatvibe_features/family/level/family_level_page.dart b/lib/chatvibe_features/family/level/family_level_page.dart new file mode 100644 index 0000000..d6a8113 --- /dev/null +++ b/lib/chatvibe_features/family/level/family_level_page.dart @@ -0,0 +1,362 @@ +import 'dart:ui' as ui; + +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_ninepatch_image/flutter_ninepatch_image.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/family_repository_impl.dart'; + +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_family_level_res.dart'; + +class FamilyLevelPage extends StatefulWidget { + String curentLevel = "0"; + String curentLevelExp = "0"; + + FamilyLevelPage(this.curentLevel, this.curentLevelExp); + + @override + _FamilyLevelPageState createState() => _FamilyLevelPageState(); +} + +class _FamilyLevelPageState extends State { + List levelList = []; + ATFamilyLevelRes? currentFamily; + + @override + void initState() { + super.initState(); + _loadData(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/family/at_icon_family_level_head_bg.png", + fit: BoxFit.fitWidth, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.familyLevel, + actions: [], + backButtonColor: Colors.white, + ), + body: SafeArea( + top: false, + child: Column( + children: [ + _buildLevelHead(), + SizedBox(height: 32.w), + Expanded( + child: ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 18, sigmaY: 18), + child: + currentFamily != null + ? Container( + alignment: Alignment.center, + color: Colors.white10, + child: Column( + children: [ + SizedBox(height: 10.w), + Container( + margin: EdgeInsets.symmetric( + horizontal: 10.w, + ), + width: ScreenUtil().screenWidth, + height: 105.w, + child: NinePatchImage( + imageProvider: AssetImage( + "atu_images/family/at_icon_family_info_item_bg_${getLevel()}.9.png", + ), + sliceCachedKey: + 'atu_images/family/at_icon_family_info_item_bg_${getLevel()}.9.png', + child: Row( + children: [ + SizedBox(width: 10.w), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 3.w), + text( + "Lv. ${currentFamily?.level}", + fontWeight: + FontWeight.w600, + fontSize: 15.sp, + textColor: Colors.white, + ), + ], + ), + Row( + children: [ + Image.asset( + "atu_images/family/at_icon_family_level_exp_tag.png", + height: 24.w, + width: 24.w, + ), + SizedBox(width: 3.w), + text( + "${widget + .curentLevelExp}/${currentFamily + ?.levelExp ?? 0}", + ), + ], + ), + SizedBox(height: 8.w), + Stack( + children: [ + Container( + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: + BorderRadius.circular( + 8.w, + ), + ), + height: 9.w, + width: + ScreenUtil() + .screenWidth * + 0.65, + ), + widget.curentLevel == + currentFamily?.level ? + Container( + height: 9.w, + width: ATRoomUtils + .collectCurrenProgressProcess( + int.parse( + widget.curentLevelExp, + ), + currentFamily + ?.levelExp ?? + 1, + ScreenUtil() + .screenWidth * + 0.65, + ), + decoration: BoxDecoration( + gradient: + buildFamilyProgressLinearGradient( + getLevel(), + ), + borderRadius: + BorderRadius.circular( + 8.w, + ), + ), + ):Container(), + ], + ), + ], + ), + SizedBox(width: 8.w), + Image.asset( + "atu_images/family/at_icon_family_level_${currentFamily + ?.level ?? 0}.png", + height: 75.w, + ), + SizedBox(width: 10.w), + ], + ), + ), + ), + SizedBox(height: 15.w), + Row( + children: [ + SizedBox(width: 13.w), + text( + ATAppLocalizations.of( + context, + )!.levelPrivileges, + fontSize: 15.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + ], + ), + Row( + children: [ + Expanded( + child: Container( + margin: EdgeInsetsDirectional.only( + start: 15.w, + top: 15.w, + bottom: 15.w, + ), + height: 95.w, + child: NinePatchImage( + imageProvider: AssetImage( + "atu_images/family/at_icon_family_info_item_bg_${getLevel()}.9.png", + ), + sliceCachedKey: + 'atu_images/family/at_icon_family_info_item_bg_${getLevel()}.9.png', + child: Padding( + padding: EdgeInsets.all(15.w), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Image.asset( + "atu_images/family/at_icon_family_role_admin.png", + height: 32.w, + ), + SizedBox(height: 6.w), + text( + ATAppLocalizations.of( + context, + )!.upToAdmins( + "${currentFamily?.maxManager ?? + 0}", + ), + fontWeight: + FontWeight.w600, + textColor: Colors.white, + fontSize: 14.sp, + letterSpacing: 0.1, + ), + ], + ), + ), + ), + ), + ), + SizedBox(width: 5.w), + Expanded( + child: Container( + margin: EdgeInsetsDirectional.only( + end: 15.w, + top: 15.w, + bottom: 15.w, + ), + height: 95.w, + child: NinePatchImage( + imageProvider: AssetImage( + "atu_images/family/at_icon_family_info_item_bg_${getLevel()}.9.png", + ), + sliceCachedKey: + 'atu_images/family/at_icon_family_info_item_bg_${getLevel()}.9.png', + child: Padding( + padding: EdgeInsets.all(15.w), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Image.asset( + "atu_images/family/at_icon_family_member_tag.png", + height: 32.w, + ), + SizedBox(height: 6.w), + text( + ATAppLocalizations.of( + context, + )!.upToMembers( + "${currentFamily?.maxMember ?? + 0}", + ), + letterSpacing: 0.1, + fontWeight: + FontWeight.w600, + textColor: Colors.white, + maxLines: 2, + fontSize: 14.sp, + ), + ], + ), + ), + ), + ), + ), + ], + ), + ], + ), + ) + : Container(), + ), + ), + ), + ], + ), + ), + ), + ], + ); + } + + int getLevel() { + int level = 0; + if (int.parse(currentFamily?.level ?? "0") > 0 && + int.parse(currentFamily?.level ?? "0") < 4) { + level = 1; + } else if (int.parse(currentFamily?.level ?? "0") > 3 && + int.parse(currentFamily?.level ?? "0") < 7) { + level = 2; + } else if (int.parse(currentFamily?.level ?? "0") > 6 && + int.parse(currentFamily?.level ?? "0") < 10) { + level = 3; + } else { + level = int.parse(currentFamily?.level ?? "0"); + } + return level; + } + + _buildLevelHead() { + return levelList.isNotEmpty + ? CarouselSlider( + options: CarouselOptions( + height: 120.w, + autoPlay: false, + initialPage: int.parse(widget.curentLevel), + enlargeCenterPage: true, + viewportFraction: 0.40, + enlargeFactor: 0.40, + enableInfiniteScroll: false, + autoPlayAnimationDuration: Duration(milliseconds: 800), + // 视口分数 + onPageChanged: (index, reason) { + currentFamily = levelList[index]; + setState(() {}); + }, + ), + items: + levelList.map((item) { + return GestureDetector( + child: Image.asset( + "atu_images/family/at_icon_family_level_${item.sort ?? 0}.png", + ), + onTap: () {}, + ); + }).toList(), + ) + : Container(height: 120.w); + } + + void _loadData() { + GroupRepository() + .familyLevelConfig() + .then((res) { + levelList = res; + currentFamily = levelList[int.parse(widget.curentLevel)]; + setState(() {}); + }) + .catchError(((e) {})); + } +} diff --git a/lib/chatvibe_features/family/list/family_list_page.dart b/lib/chatvibe_features/family/list/family_list_page.dart new file mode 100644 index 0000000..f8ddb63 --- /dev/null +++ b/lib/chatvibe_features/family/list/family_list_page.dart @@ -0,0 +1,523 @@ +import 'dart:ui' as ui; + +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/family_repository_impl.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_domain/models/res/family_list_res.dart'; +import 'package:aslan/chatvibe_features/family/family_route.dart'; + +class FamilyListPage extends ATPageList { + @override + _FamilyListPageState createState() => _FamilyListPageState(); +} + +class _FamilyListPageState extends ATPageListState { + TextEditingController searchController = TextEditingController(); + + @override + void initState() { + super.initState(); + backgroundColor = Colors.transparent; + isShowFooter = false; + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/family/at_icon_family_head_bg.png", + fit: BoxFit.fitWidth, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: "", + actions: [ + Consumer( + builder: (context, ref, child) { + return AccountStorage() + .getCurrentUser() + ?.userProfile + ?.familyId == + null + ? ATDebounceWidget( + debounceTime: Duration(milliseconds: 800), + child: Container( + padding: EdgeInsets.all(3.w), + margin: EdgeInsetsDirectional.only(end: 5.w), + child: Image.asset( + "atu_images/family/at_icon_family_req_record.png", + width: 18.w, + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + FamilyRoute.reqRecordFamily, + replace: false, + ).then((res) { + loadMyFamily(); + }); + }, + ) + : Container(); + }, + ), + ], + backButtonColor: Colors.white, + ), + body: SafeArea( + top: false, + child: Column( + children: [ + SizedBox(height: 115.w), + Expanded( + child: ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 18, sigmaY: 18), + child: Container( + alignment: Alignment.center, + color: Colors.white10, + child: Column( + children: [ + SizedBox(height: 12.w), + Container( + alignment: AlignmentDirectional.centerStart, + child: Row( + children: [ + SizedBox(width: width(2)), + Expanded( + child: searchWidget( + hint: + ATAppLocalizations.of( + context, + )!.searchFamilyIdHint, + controller: searchController, + borderColor: Colors.white54, + textColor: Colors.white54, + hintSize: 11.sp, + serachIconColor: Colors.white, + ), + ), + SizedBox(width: width(10)), + ATDebounceWidget( + debounceTime: Duration(milliseconds: 800), + onTap: () { + if (searchController.text.isNotEmpty) { + _search(); + } + }, + child: Container( + alignment: Alignment.center, + height: 32.w, + width: 76.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + 25.w, + ), + gradient: LinearGradient( + colors: [ + Color(0xff48149b), + Color(0xff6105B7), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: text( + ATAppLocalizations.of(context)!.search, + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + SizedBox(width: width(10)), + ], + ), + ), + SizedBox(height: 15.w), + Expanded(child: buildList(context)), + SizedBox(height: 15.w), + Consumer( + builder: (context, ref, child) { + return AccountStorage() + .getCurrentUser() + ?.userProfile + ?.familyId == + null + ? ATDebounceWidget( + debounceTime: Duration(milliseconds: 800), + child: Container( + alignment: Alignment.center, + width: 180.w, + padding: EdgeInsets.symmetric( + vertical: 12.w, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + 60.w, + ), + gradient: LinearGradient( + colors: [ + Color(0xff0D0022), + Color(0xff9F63FF), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: text( + ATAppLocalizations.of( + context, + )!.createAFamily, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + FamilyRoute.create, + replace: true, + ); + }, + ) + : Container(); + }, + ), + SizedBox(height: 15.w), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ), + ], + ); + } + + @override + Widget buildItem(Records res) { + String familyIntro = (res.familyIntro ?? "").replaceAll( + RegExp(r'\s+'), + ' ', + ); + return ATDebounceWidget( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + border: Border.all(color: Color(0xffE6E6E6), width: 0.2.w), + gradient: LinearGradient( + colors: [Color(0xff161616), Color(0xff101010)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: Column( + children: [ + SizedBox(height: 5.w), + Row( + children: [ + SizedBox(width: 5.w), + Container( + margin: EdgeInsetsDirectional.only(start: 8.w, top: 5.w), + child: netImage( + height: 80.w, + width: 80.w, + borderRadius: BorderRadius.circular(12.w), + url: res.familyAvatar ?? "", + ), + ), + SizedBox(width: 10.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + constraints: BoxConstraints( + maxWidth: 135.w, + maxHeight: 24.w, + ), + child: + (res.familyName?.length ?? 0) > 12 + ? Marquee( + text: res.familyName ?? "", + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 40.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + res.familyName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + SizedBox(height: 5.w), + Row( + children: [ + text( + "ID:${res.familyAccount}", + fontWeight: FontWeight.w600, + fontSize: 13.sp, + textColor: Colors.white, + ), + Spacer(), + Container( + alignment: AlignmentDirectional.center, + height: 25.w, + width: 68.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/family/at_icon_family_member_btn_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + height: 15.w, + "atu_images/family/at_icon_family_member_tag.png", + ), + SizedBox(width: 3.w), + text( + "${res.currentMember ?? 0}/${res.maxMember ?? 0}", + fontWeight: FontWeight.w600, + fontSize: 13.sp, + textColor: Colors.white, + ), + ], + ), + ), + ], + ), + ], + ), + ), + Image.asset( + "atu_images/family/at_icon_family_level_${res?.familyLevel ?? 0}.png", + height: 80.w, + ), + // Image.asset( + // "images/family/at_icon_family_rank_tag.png", + // height: 80.w, + // ), + SizedBox(width: 5.w), + ], + ), + SizedBox(height: 8.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + // 外部容器用于实现渐变边框 + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + // 渐变色边框 + gradient: LinearGradient( + colors: [ + Color(0xffcecece), // 渐变色1 + Color(0xff5b5b5b), // 渐变色2 + Color(0xffcecece), // 渐变色3 + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + // 内部留白(边框宽度) + padding: EdgeInsets.all(1.w), // 边框宽度 + child: Container( + padding: EdgeInsets.symmetric(vertical: 3.w, horizontal: 6.w), + decoration: BoxDecoration( + color: Colors.black87, + borderRadius: BorderRadius.circular(8.w), + ), + child: Row( + children: [ + Image.asset( + "atu_images/family/at_icon_announcement_tag.png", + height: 22.w, + ), + SizedBox(width: 3.w), + Expanded( + child: SizedBox( + height: 22.w, + child: + (familyIntro?.length ?? 0) > 20 + ? Marquee( + text: familyIntro ?? "", + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 40.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + (familyIntro ?? "").isEmpty + ? ATAppLocalizations.of( + context, + )!.welcomeToMyFamily + : familyIntro ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + ), + ], + ), // 背景色, + ), + ), + SizedBox(height: 10.w), + ], + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${FamilyRoute.familyInfo}?familyId=${res.familyId}", + replace: false, + ).then((res) { + loadMyFamily(); + }); + }, + ); + } + + @override + builderDivider() { + return Divider( + height: 22.w, + color: Colors.transparent, + indent: 15.w, + endIndent: 15.w, + ); + } + + @override + empty() { + return mainEmpty( + textColor: Colors.white, + msg: ATAppLocalizations.of(context)!.noData, + image: Image.asset( + "atu_images/general/at_icon_loading.png", + width: 140.w, + height: 140.w, + ), + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + if (searchController.text.isNotEmpty) { + _search(); + } else { + try { + var result = await GroupRepository().familyList(current: page); + onSuccess(result.records ?? []); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + } + + void _search() { + GroupRepository() + .familyList(familyAccount: searchController.text) + .then((result) { + items = result.records ?? []; + loadComplete(); + setState(() {}); + }) + .catchError((e) { + loadComplete(); + setState(() {}); + }); + } + + void loadMyFamily() { + GroupRepository() + .familyBaseInfo() + .then((result) { + Provider.of( + context, + listen: false, + ).updateFamily(result.familyId); + }) + .catchError((e) { + Provider.of(context, listen: false).updateFamily(null); + }); + } +} diff --git a/lib/chatvibe_features/family/member/family_member_list_page.dart b/lib/chatvibe_features/family/member/family_member_list_page.dart new file mode 100644 index 0000000..1ca1977 --- /dev/null +++ b/lib/chatvibe_features/family/member/family_member_list_page.dart @@ -0,0 +1,912 @@ +import 'dart:ui' as ui; + +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/family_repository_impl.dart'; +import 'package:aslan/main.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +import '../../../chatvibe_data/models/enum/at_family_roles_type.dart'; +import '../../../chatvibe_data/models/enum/at_room_roles_type.dart'; +import '../../../chatvibe_domain/models/res/at_family_list_member_res.dart'; + +class FamilyMemberListPage extends ATPageList { + String familyId = ""; + String myFamilyRole = ""; + + FamilyMemberListPage(this.familyId, this.myFamilyRole); + + @override + _FamilyMemberListPageState createState() => + _FamilyMemberListPageState(familyId, myFamilyRole); +} + +class _FamilyMemberListPageState + extends ATPageListState, FamilyMemberListPage> + with RouteAware { + String familyId = ""; + String myFamilyRole = ""; + num maxManager = 0; + num maxMember = 0; + FocusNode searchFocusNode = FocusNode(); + TextEditingController searchController = TextEditingController(); + + _FamilyMemberListPageState(this.familyId, this.myFamilyRole); + + @override + void initState() { + super.initState(); + backgroundColor = Colors.transparent; + isShowFooter = false; + loadData(1); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + // 注册路由观察 + final route = ModalRoute.of(context); + if (route is PageRoute) { + routeObserver.subscribe(this, route); + } + } + + @override + void didPopNext() { + FocusScope.of(context).unfocus(); // 彻底取消当前上下文的所有焦点 + searchFocusNode.unfocus(); + } + + @override + void dispose() { + routeObserver.unsubscribe(this); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: (){ + FocusScope.of(context).unfocus(); + }, + child: Stack( + children: [ + Image.asset( + "atu_images/family/at_icon_family_head_bg.png", + fit: BoxFit.fitWidth, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: "", + actions: [], + backButtonColor: Colors.white, + ), + body: SafeArea( + top: false, + child: Column( + children: [ + SizedBox(height: 115.w), + Expanded( + child: ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 18, sigmaY: 18), + child: Container( + alignment: Alignment.center, + color: Colors.white10, + child: Column( + children: [ + SizedBox(height: 12.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.memberList, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + SizedBox(height: 12.w), + Container( + alignment: AlignmentDirectional.centerStart, + child: Row( + children: [ + SizedBox(width: width(2)), + Expanded( + child: searchWidget( + hint: + ATAppLocalizations.of( + context, + )!.searchMemberIdHint, + controller: searchController, + borderColor: Colors.white54, + textColor: Colors.white54, + hintSize: 11.sp, + serachIconColor: Colors.white, + focusNode: searchFocusNode, + ), + ), + SizedBox(width: width(10)), + ATDebounceWidget( + debounceTime: Duration(milliseconds: 800), + onTap: () { + if (searchController.text.isNotEmpty) { + _search(); + } + }, + child: Container( + alignment: Alignment.center, + height: 32.w, + width: 76.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + 25.w, + ), + gradient: LinearGradient( + colors: [ + Color(0xff48149b), + Color(0xff6105B7), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: text( + ATAppLocalizations.of(context)!.search, + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + SizedBox(width: width(10)), + ], + ), + ), + SizedBox(height: 15.w), + Expanded(child: buildList(context)), + SizedBox(height: 15.w), + Consumer( + builder: (context, ref, child) { + return AccountStorage() + .getCurrentUser() + ?.userProfile + ?.familyId == + null + ? ATDebounceWidget( + debounceTime: Duration( + milliseconds: 800, + ), + child: Container( + alignment: Alignment.center, + width: 180.w, + padding: EdgeInsets.symmetric( + vertical: 12.w, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + 60.w, + ), + gradient: LinearGradient( + colors: [ + Color(0xff0D0022), + Color(0xff9F63FF), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: text( + ATAppLocalizations.of( + context, + )!.createAFamily, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () {}, + ) + : Container(); + }, + ), + SizedBox(height: 15.w), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ); + } + + @override + Widget buildItem(List list) { + return Column( + children: [ + list.isNotEmpty + ? buildTag(list.first.memberRole, list.length) + : Container(), + list.isNotEmpty ? SizedBox(height: 3.w) : SizedBox.shrink(), + Column( + children: List.generate(list.length, (cIndex) { + Members userInfo = list[cIndex]; + return ATDebounceWidget( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.w), + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 12.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + border: Border.all(color: Color(0xffE6E6E6), width: 0.2.w), + gradient: LinearGradient( + colors: [Color(0xff2c2b2b), Color(0xff101010)], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: Row( + children: [ + ATDebounceWidget( + child: netImage( + url: userInfo.memberAvatar ?? "", + width: 52.w, + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + shape: BoxShape.circle, + ), + onTap: () { + ATNavigatorUtils.push( + context, + replace: false, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == userInfo.memberUserId}&tageId=${userInfo.memberUserId}", + ); + }, + ), + SizedBox(width: 3.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + SizedBox(width: 3.w), + Container( + constraints: BoxConstraints( + maxWidth: 165.w, + maxHeight: 22.w, + ), + child: + (userInfo.memberNickname?.length ?? 0) > 16 + ? Marquee( + text: userInfo.memberNickname ?? "", + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 40.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration( + seconds: 1, + ), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + userInfo.memberNickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + SizedBox(width: 5.w), + ], + ), + SizedBox(height: 3.w), + Row( + children: [ + GestureDetector( + child: Container( + padding: EdgeInsets.symmetric(vertical: 8.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + (userInfo.specialAccount != null) + ? "atu_images/general/at_icon_special_id_bg.png" + : "atu_images/general/at_icon_id_bg.png", + ), + fit: BoxFit.fitWidth, + ), + ), + child: Row( + textDirection: TextDirection.ltr, + children: [ + SizedBox(width: 38.w), + text( + userInfo.specialAccount != null + ? userInfo.specialAccount ?? "" + : userInfo.account ?? "", + fontSize: 12.sp, + textColor: Colors.white, + fontWeight: FontWeight.bold, + ), + SizedBox(width: 5.w), + Image.asset( + "atu_images/room/at_icon_user_card_copy_id.png", + width: 12.w, + height: 12.w, + ), + SizedBox(width: 8.w), + ], + ), + ), + onTap: () { + Clipboard.setData( + ClipboardData( + text: + userInfo.specialAccount != null + ? userInfo.specialAccount ?? "" + : userInfo.account ?? "", + ), + ); + ATTts.show( + ATAppLocalizations.of( + context, + )!.copiedToClipboard, + ); + }, + ), + userInfo.memberRole == ATFamilyRolesType.ADMIN.name + ? Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/family/at_icon_family_member_btn_bg2.png", + ), + fit: BoxFit.fill, + ), + ), + height: 25.w, + width: 76.w, + child: Row( + children: [ + SizedBox(width: 3.w), + Image.asset( + "atu_images/family/at_icon_family_role_owner.png", + width: 18.w, + ), + SizedBox(width: 2.w), + text( + ATAppLocalizations.of(context)!.owner, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + ) + : (userInfo.memberRole == + ATFamilyRolesType.MANAGE.name + ? Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/family/at_icon_family_member_btn_bg2.png", + ), + fit: BoxFit.fill, + ), + ), + height: 25.w, + width: 74.w, + child: Row( + children: [ + SizedBox(width: 3.w), + Image.asset( + "atu_images/family/at_icon_family_role_admin.png", + width: 18.w, + ), + SizedBox(width: 2.w), + text( + ATAppLocalizations.of(context)!.admin, + fontSize: 13.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + ) + : Container()), + SizedBox(width: 3.w), + userInfo.agent ?? false + ? Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/family/at_icon_family_member_btn_bg2.png", + ), + fit: BoxFit.fill, + ), + ), + height: 25.w, + width: 76.w, + child: Row( + children: [ + SizedBox(width: 3.w), + Image.asset( + "atu_images/family/at_icon_family_role_agency.png", + width: 18.w, + ), + SizedBox(width: 2.w), + text( + ATAppLocalizations.of(context)!.agent, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + ) + : (userInfo.anchor ?? false + ? Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/family/at_icon_family_member_btn_bg2.png", + ), + fit: BoxFit.fill, + ), + ), + height: 25.w, + width: 76.w, + child: Row( + children: [ + SizedBox(width: 3.w), + Image.asset( + "atu_images/family/at_icon_family_role_host.png", + width: 18.w, + ), + SizedBox(width: 2.w), + text( + ATAppLocalizations.of(context)!.host, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + ) + : Container()), + ], + ), + ], + ), + Spacer(), + isShowSetting(userInfo) + ? ATDebounceWidget( + child: Image.asset( + "atu_images/family/at_icon_family_role_setting.png", + height: 21.w, + ), + onTap: () { + if (myFamilyRole == ATFamilyRolesType.ADMIN.name) { + if (userInfo.memberRole == + ATFamilyRolesType.MANAGE.name) { + _showOptionDialog(1, false, userInfo); + } else { + _showOptionDialog(1, true, userInfo); + } + } else if (myFamilyRole == + ATFamilyRolesType.MANAGE.name) { + if (userInfo.memberRole == + ATFamilyRolesType.ADMIN.name && + userInfo.memberRole == + ATFamilyRolesType.MANAGE.name) { + } else { + _showOptionDialog(2, false, userInfo); + } + } + }, + ) + : Container(), + ], + ), + ), + onTap: () {}, + ); + }), + ), + ], + ); + } + + bool isShowSetting(Members userInfo) { + if (AccountStorage().getCurrentUser()?.userProfile?.id == + userInfo.memberUserId) { + return false; + } + if (myFamilyRole == ATFamilyRolesType.ADMIN.name) { + if (userInfo.memberRole == ATFamilyRolesType.MANAGE.name || + userInfo.memberRole == ATFamilyRolesType.MEMBER.name) { + return true; + } + } + if (myFamilyRole == ATFamilyRolesType.MANAGE.name) { + if (userInfo.memberRole == ATFamilyRolesType.ADMIN.name) { + return false; + } + if (userInfo.memberRole == ATFamilyRolesType.MANAGE.name) { + return false; + } + if (userInfo.memberRole == ATFamilyRolesType.MEMBER.name) { + return true; + } + } + return false; + } + + Widget buildTag(String? roles, int length) { + if (roles == ATFamilyRolesType.ADMIN.name) { + return Row( + children: [ + SizedBox(width: 10.w), + Container( + height: 18.w, + width: 4.w, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [Color(0xffC670FF), Color(0xff7726FF)], + ), + borderRadius: BorderRadius.circular(2.w), + ), + ), + SizedBox(width: 5.w), + text( + ATAppLocalizations.of(context)!.familyOwner2, + fontSize: 13.sp, + textColor: Colors.white, + ), + Spacer(), + ], + ); + } else if (roles == ATFamilyRolesType.MANAGE.name) { + return Row( + children: [ + SizedBox(width: 10.w), + Container( + height: 18.w, + width: 4.w, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [Color(0xffC670FF), Color(0xff7726FF)], + ), + borderRadius: BorderRadius.circular(2.w), + ), + ), + SizedBox(width: 5.w), + text( + ATAppLocalizations.of( + context, + )!.familyAdmin2("$length", "$maxManager"), + fontSize: 13.sp, + textColor: Colors.white, + ), + Spacer(), + ], + ); + } else if (roles == ATRoomRolesType.MEMBER.name) { + return Row( + children: [ + SizedBox(width: 10.w), + Container( + height: 18.w, + width: 4.w, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [Color(0xffC670FF), Color(0xff7726FF)], + ), + borderRadius: BorderRadius.circular(2.w), + ), + ), + SizedBox(width: 5.w), + text( + ATAppLocalizations.of(context)!.familyMember3("$length"), + fontSize: 13.sp, + textColor: Colors.white, + ), + Spacer(), + ], + ); + } + return SizedBox.shrink(); + } + + @override + builderDivider() { + return Divider( + height: 3.w, + color: Colors.transparent, + indent: 15.w, + endIndent: 15.w, + ); + } + + @override + empty() { + return mainEmpty( + textColor: Colors.white, + msg: ATAppLocalizations.of(context)!.noData, + image: Image.asset( + "atu_images/general/at_icon_loading.png", + width: 140.w, + height: 140.w, + ), + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List>) onSuccess, + Function? onErr, + }) async { + var mList = >[]; + if (page == 1) { + mList.add([]); + mList.add([]); + mList.add([]); + } + if (searchController.text.isNotEmpty) { + _search(); + } else { + try { + var result = await GroupRepository().familyListMember( + familyId: familyId, + pageNo: page, + ); + maxManager = result.maxManager ?? 0; + maxMember = result.maxMember ?? 0; + for (var user in result.members ?? []) { + if (user.memberRole == ATFamilyRolesType.ADMIN.name) { + // mList["Homeowner"]?.add(user); + mList[0].add(user); + } else if (user.memberRole == ATFamilyRolesType.MANAGE.name) { + // mList["Administrator"]?.add(user); + mList[1].add(user); + } else if (user.memberRole == ATRoomRolesType.MEMBER.name) { + // mList["Member"]?.add(user); + mList[2].add(user); + } + } + onSuccess(mList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + } + + void _search() { + GroupRepository() + .familyListMember(familyId: familyId, account: searchController.text) + .then((result) { + maxManager = result.maxManager ?? 0; + maxMember = result.maxMember ?? 0; + if ((result.members ?? []).isNotEmpty) { + var mList = >[]; + mList.add([]); + mList.add([]); + mList.add([]); + for (var user in result.members!) { + if (user.memberRole == ATFamilyRolesType.ADMIN.name) { + // mList["Homeowner"]?.add(user); + mList[0].add(user); + } else if (user.memberRole == ATFamilyRolesType.MANAGE.name) { + // mList["Administrator"]?.add(user); + mList[1].add(user); + } else if (user.memberRole == ATRoomRolesType.MEMBER.name) { + // mList["Member"]?.add(user); + mList[2].add(user); + } + } + items = mList; + } else { + items = []; + } + + loadComplete(); + setState(() {}); + }) + .catchError((_) { + loadComplete(); + setState(() {}); + }); + } + + void _showOptionDialog(int type, bool setAdmin, Members userInfo) { + SmartDialog.show( + tag: "showFamilyMemberOptionDialog", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return SafeArea( + top: false, + child: ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 15, sigmaY: 15), + child: Container( + width: ScreenUtil().screenWidth, + decoration: BoxDecoration(color: Color(0xff404040)), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 5.w), + type != 2 + ? (setAdmin + ? ATDebounceWidget( + child: Container( + padding: EdgeInsets.symmetric(vertical: 12.w), + alignment: Alignment.center, + child: text( + ATAppLocalizations.of( + context, + )!.setAsFamilyAdmin, + fontWeight: FontWeight.w600, + textColor: Colors.white, + fontSize: 15.sp, + ), + ), + onTap: () { + setUserRole( + userInfo, + ATFamilyRolesType.MANAGE.name, + ); + }, + ) + : ATDebounceWidget( + child: Container( + padding: EdgeInsets.symmetric(vertical: 12.w), + alignment: Alignment.center, + child: text( + ATAppLocalizations.of( + context, + )!.cancelFamilyAdmin, + fontWeight: FontWeight.w600, + textColor: Colors.white, + fontSize: 15.sp, + ), + ), + onTap: () { + setUserRole( + userInfo, + ATFamilyRolesType.MEMBER.name, + ); + }, + )) + : Container(), + ATDebounceWidget( + child: Container( + padding: EdgeInsets.symmetric(vertical: 12.w), + alignment: Alignment.center, + child: text( + ATAppLocalizations.of(context)!.kickOutOfFamily, + fontWeight: FontWeight.w600, + textColor: Colors.white, + fontSize: 15.sp, + ), + ), + onTap: () { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.kickFamilyUserTips, + isDark: true, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + SmartDialog.dismiss(tag: "showConfirmDialog"); + ATLoadingManager.exhibitOperation(); + GroupRepository() + .familyRemoveUser(userInfo.memberId ?? "") + .then((res) { + ATTts.show( + ATAppLocalizations.of( + context, + )!.operationSuccessful, + ); + loadData(1); + SmartDialog.dismiss( + tag: "showFamilyMemberOptionDialog", + ); + ATLoadingManager.veilRoutine(); + }) + .catchError((_) { + ATLoadingManager.veilRoutine(); + }); + }, + ); + }, + ); + }, + ), + SizedBox(height: 5.w), + ], + ), + ), + ), + ), + ); + }, + ); + } + + void setUserRole(Members userInfo, String role) { + ATLoadingManager.exhibitOperation(); + GroupRepository() + .familyGrantUserRole(userInfo.memberId ?? "", role) + .then((res) { + ATTts.show(ATAppLocalizations.of(context)!.operationSuccessful); + SmartDialog.dismiss(tag: "showFamilyMemberOptionDialog"); + loadData(1); + ATLoadingManager.veilRoutine(); + }) + .catchError((_) { + ATLoadingManager.veilRoutine(); + }); + } +} diff --git a/lib/chatvibe_features/family/news/family_news_page.dart b/lib/chatvibe_features/family/news/family_news_page.dart new file mode 100644 index 0000000..eacc2e4 --- /dev/null +++ b/lib/chatvibe_features/family/news/family_news_page.dart @@ -0,0 +1,283 @@ +import 'dart:ui' as ui; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; + +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/family_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_family_news_list_res.dart'; + +class FamilyNewsPage extends ATPageList { + String familyId = ""; + String familyAnnouncement = ""; + + @override + _FamilyNewsPageState createState() => + _FamilyNewsPageState(familyId, familyAnnouncement); + + FamilyNewsPage(this.familyId, this.familyAnnouncement); +} + +class _FamilyNewsPageState extends ATPageListState { + String familyId = ""; + String familyAnnouncement = ""; + + _FamilyNewsPageState(this.familyId, this.familyAnnouncement); + + @override + void initState() { + super.initState(); + backgroundColor = Colors.transparent; + isShowFooter = false; + padding = EdgeInsets.symmetric(horizontal: 10.w); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/family/at_icon_family_head_bg.png", + fit: BoxFit.fitWidth, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: "", + actions: [], + backButtonColor: Colors.white, + ), + body: SafeArea( + top: false, + child: Column( + children: [ + SizedBox(height: 115.w), + Expanded( + child: ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 18, sigmaY: 18), + child: Container( + alignment: Alignment.center, + color: Colors.white10, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 15.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.familyNews, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + SizedBox(height: 20.w), + Row( + children: [ + SizedBox(width: 10.w), + Container( + height: 22.w, + width: 4.w, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xffC670FF), + Color(0xff7726FF), + ], + ), + borderRadius: BorderRadius.circular(2.w), + ), + ), + SizedBox(width: 10.w), + text( + "${ATAppLocalizations.of(context)!.familyAnnouncement}:", + textColor: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + ), + ], + ), + SizedBox(height: 10.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + // 外部容器用于实现渐变边框 + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + // 渐变色边框 + gradient: LinearGradient( + colors: [ + Color(0xffcecece), // 渐变色1 + Color(0xff5b5b5b), // 渐变色2 + Color(0xffcecece), // 渐变色3 + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + // 内部留白(边框宽度) + padding: EdgeInsets.all(0.5.w), // 边框宽度 + child: Stack( + alignment: AlignmentDirectional.bottomEnd, + children: [ + Container( + padding: EdgeInsets.only( + top: width(12), + left: width(12), + right: width(12), + bottom: width(12), + ), + alignment: AlignmentDirectional.topStart, + height: 125.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + 7.5.w, + ), + // 比外圆角小边框宽度 + color: Colors.black87, // 背景色 + ), + child: SingleChildScrollView( + child: text( + (familyAnnouncement ?? "").isEmpty + ? ATAppLocalizations.of( + context, + )!.welcomeToMyFamily + : familyAnnouncement ?? "", + fontSize: 14.sp, + maxLines: 5, + textColor: Colors.white, + ), + ), + ) + ], + ), + ), + SizedBox(height: 20.w), + Row( + children: [ + SizedBox(width: 10.w), + Container( + height: 22.w, + width: 4.w, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xffC670FF), + Color(0xff7726FF), + ], + ), + borderRadius: BorderRadius.circular(2.w), + ), + ), + SizedBox(width: 10.w), + text( + "${ATAppLocalizations.of(context)!.familyNotifcations}:", + textColor: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + ), + ], + ), + SizedBox(height: 20.w), + Expanded(child: buildList(context)), + SizedBox(height: 6.w), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ), + ], + ); + } + + @override + Widget buildItem(Records res) { + return Column( + children: [ + Row( + children: [ + Expanded( + child: text( + res.content ?? "", + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + maxLines: 3, + ), + ), + ], + ), + + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + text( + ATMDateUtils.formatDateTime2( + DateTime.fromMillisecondsSinceEpoch(res.createTime ?? 0), + ), + ), + ], + ), + ], + ); + } + + @override + builderDivider() { + return Divider(height: 25.w, thickness: 0.2.w, color: ChatVibeTheme.dividerColor); + } + + @override + empty() { + return mainEmpty( + textColor: Colors.white, + msg: ATAppLocalizations.of(context)!.noData, + image: Image.asset( + "atu_images/general/at_icon_loading.png", + width: 140.w, + height: 140.w, + ), + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var result = await GroupRepository().familyNewsList( + familyId, + pageNo: page, + ); + onSuccess(result.records ?? []); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } +} diff --git a/lib/chatvibe_features/family/rank/family_user_rank_page.dart b/lib/chatvibe_features/family/rank/family_user_rank_page.dart new file mode 100644 index 0000000..99d2b59 --- /dev/null +++ b/lib/chatvibe_features/family/rank/family_user_rank_page.dart @@ -0,0 +1,1149 @@ +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:marquee/marquee.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/family_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_famaily_leader_board_res.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +class FamilyUserRankPage extends StatefulWidget { + final String familyId; + final String rankType; + @override + final Key? key; + + const FamilyUserRankPage(this.familyId, this.rankType, {this.key}) : super(key: key); + + @override + _FamilyUserRankPageState createState() => _FamilyUserRankPageState(); +} + +class _FamilyUserRankPageState extends State { + List supporterWeekRank = []; + List hostWeekRank = []; + HostWeekRank? myRank; + + bool isLoading = true; + + @override + void initState() { + super.initState(); + _loadRankList(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/family/at_icon_family_head_bg.png", + fit: BoxFit.fitWidth, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: "", + actions: [], + backButtonColor: Colors.white, + ), + body: SafeArea( + top: false, + child: Stack( + alignment: AlignmentDirectional.bottomCenter, + children: [ + Column( + children: [ + SizedBox(height: 115.w), + Expanded( + child: ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 18, sigmaY: 18), + child: Container( + alignment: Alignment.center, + color: Colors.white10, + child: Column( + children: [ + SizedBox(height: 15.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + widget.rankType == "0" + ? ATAppLocalizations.of( + context, + )!.supporterList + : ATAppLocalizations.of( + context, + )!.hostList, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + SizedBox(height: 10.w), + Expanded( + child: Container( + margin: EdgeInsetsDirectional.symmetric(horizontal: 10.w), + alignment: Alignment.topCenter, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + // 渐变色边框 + gradient: LinearGradient( + colors: [ + Color(0xffcecece), // 渐变色1 + Color(0xff5b5b5b), // 渐变色2 + Color(0xffcecece), // 渐变色3 + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + // 内部留白(边框宽度) + padding: EdgeInsets.all(0.2.w), // 边框宽度 + child: Container( + height: double.infinity, + padding: EdgeInsets.symmetric( + vertical: 10.w, + horizontal: 6.w, + ), + decoration: BoxDecoration( + color: Colors.black87, + borderRadius: BorderRadius.circular( + 8.w, + ), + ), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + children: [ + SizedBox(height: 10.w), + Stack( + alignment: + AlignmentDirectional + .center, + children: [ + _buildRankUserAvatar(1), + IgnorePointer( + child: Image.asset( + "atu_images/family/at_icon_family_user_rank_1.png", + height: 170.w, + ), + ), + _buildRankUserName(1), + ], + ), + Transform.translate( + offset: Offset(0, -70), + child: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Stack( + alignment: + AlignmentDirectional + .center, + children: [ + _buildRankUserAvatar( + 2, + ), + IgnorePointer( + child: Image.asset( + "atu_images/family/at_icon_family_user_rank_2.png", + height: 155.w, + ), + ), + _buildRankUserName(2), + ], + ), + Stack( + alignment: + AlignmentDirectional + .center, + children: [ + _buildRankUserAvatar( + 3, + ), + IgnorePointer( + child: Image.asset( + "atu_images/family/at_icon_family_user_rank_3.png", + height: 155.w, + ), + ), + _buildRankUserName(3), + ], + ), + ], + ), + ), + ], + ), + widget.rankType == "0" + ? (supporterWeekRank.length > 3 + ? Transform.translate( + offset: Offset(0, -35), + child: ListView.separated( + shrinkWrap: true, + physics: + NeverScrollableScrollPhysics(), + itemCount: + supporterWeekRank + .sublist( + 3, + supporterWeekRank + .length, + ) + .length, + // 列表项数量 + padding: + EdgeInsets.only( + left: 5.w, + right: 15.w, + ), + itemBuilder: ( + context, + index, + ) { + return _buildRankUserItem( + index, + ); + }, + separatorBuilder: ( + BuildContext context, + int index, + ) { + return Divider( + height: 12.w, + thickness: 0.2.w, + color: ChatVibeTheme.dividerColor, + ); + }, + ), + ) + : Container()) + : (hostWeekRank.length > 3 + ? Transform.translate( + offset: Offset(0, -30), + child: ListView.separated( + shrinkWrap: true, + physics: + NeverScrollableScrollPhysics(), + itemCount: + hostWeekRank + .sublist( + 3, + hostWeekRank + .length, + ) + .length, + // 列表项数量 + padding: + EdgeInsets.only( + left: 5.w, + right: 15.w, + ), + itemBuilder: ( + context, + index, + ) { + return _buildRankUserItem( + index, + ); + }, + separatorBuilder: ( + BuildContext context, + int index, + ) { + return Divider( + height: 12.w, + thickness: 0.2.w, + color: ChatVibeTheme.dividerColor, + ); + }, + ), + ) + : Container()), + + isLoading + ? Container() + : ((myRank?.isHost ?? false) && + widget.rankType == "1" + ? SizedBox(height: 40.w) + : (!(myRank?.isHost ?? + false)) && + widget.rankType == "0" + ? SizedBox(height: 40.w) + : Container()), + ], + ), + ), // 背景色, + ), + ), + ), + ], + ), + ), + ), + ), + ), + ], + ), + isLoading + ? Container() + : ((myRank?.isHost ?? false) && widget.rankType == "1" + ? _buildRankUserWithMe() + : (!(myRank?.isHost ?? false)) && widget.rankType == "0" + ? _buildRankUserWithMe() + : Container()), + ], + ), + ), + ), + ], + ); + } + + Widget _buildRankUserAvatar(int rank) { + if (rank == 1) { + return PositionedDirectional( + top: 21.w, + child: + widget.rankType == "0" + ? (supporterWeekRank.isNotEmpty + ? ATDebounceWidget( + child: netImage( + url: supporterWeekRank.first.avatar ?? "", + width: 75.w, + height: 75.w, + shape: BoxShape.circle, + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == supporterWeekRank.first.userId}&tageId=${supporterWeekRank.first.userId}", + ); + }, + ) + : Container()) + : (hostWeekRank.isNotEmpty + ? ATDebounceWidget( + child: netImage( + url: hostWeekRank.first.avatar ?? "", + width: 75.w, + height: 75.w, + shape: BoxShape.circle, + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == hostWeekRank.first.userId}&tageId=${hostWeekRank.first.userId}", + ); + }, + ) + : Container()), + ); + } else if (rank == 2) { + return PositionedDirectional( + top: 10.w, + child: + widget.rankType == "0" + ? (supporterWeekRank.length > 1 + ? ATDebounceWidget( + child: netImage( + url: supporterWeekRank[1].avatar ?? "", + width: 73.w, + height: 73.w, + shape: BoxShape.circle, + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == supporterWeekRank[1].userId}&tageId=${supporterWeekRank[1].userId}", + ); + }, + ) + : Container()) + : (hostWeekRank.length > 1 + ? ATDebounceWidget( + child: netImage( + url: hostWeekRank[1].avatar ?? "", + width: 73.w, + height: 73.w, + shape: BoxShape.circle, + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == hostWeekRank[1].userId}&tageId=${hostWeekRank[1].userId}", + ); + }, + ) + : Container()), + ); + } else if (rank == 3) { + return PositionedDirectional( + top: 10.w, + child: + widget.rankType == "0" + ? (supporterWeekRank.length > 2 + ? ATDebounceWidget( + child: netImage( + url: supporterWeekRank[2].avatar ?? "", + width: 73.w, + height: 73.w, + shape: BoxShape.circle, + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == supporterWeekRank[2].userId}&tageId=${supporterWeekRank[2].userId}", + ); + }, + ) + : Container()) + : (hostWeekRank.length > 2 + ? ATDebounceWidget( + child: netImage( + url: hostWeekRank[2].avatar ?? "", + width: 73.w, + height: 73.w, + shape: BoxShape.circle, + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == hostWeekRank[2].userId}&tageId=${hostWeekRank[2].userId}", + ); + }, + ) + : Container()), + ); + } + return Container(); + } + + Widget _buildRankUserItem(int index) { + if (widget.rankType == "0") { + SupporterWeekRank swr = supporterWeekRank[index + 3]; + return Row( + children: [ + Container( + alignment: AlignmentDirectional.center, + width: 35.w, + child: text( + "${index + 4}", + fontSize: 15.sp, + textColor: Colors.white, + ), + ), + SizedBox(width: 15.w), + ATDebounceWidget( + child: netImage( + url: swr.avatar ?? "", + width: 45.w, + height: 45.w, + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + shape: BoxShape.circle, + border: Border.all(color: Color(0xffFEE5BB), width: 0.5.w), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == swr.userId}&tageId=${swr.userId}", + ); + }, + ), + SizedBox(width: 5.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + constraints: BoxConstraints(maxWidth: 125.w, maxHeight: 21.w), + child: + (swr.nickname?.length ?? 0) > 12 + ? Marquee( + text: swr.nickname ?? "", + style: TextStyle( + fontSize: 15.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration(milliseconds: 550), + decelerationCurve: Curves.easeOut, + ) + : Text( + swr.nickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 15.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + text( + "ID:${swr.account}", + fontSize: 14.sp, + textColor: Colors.white, + fontWeight: FontWeight.w500, + ), + ], + ), + Spacer(), + Row( + children: [ + Image.asset("atu_images/general/at_icon_jb.png", height: 28.w), + SizedBox(width: 3.w), + text( + swr.exp ?? "0", + fontWeight: FontWeight.bold, + fontSize: 15.sp, + textColor: Colors.white, + ), + ], + ), + ], + ); + } else { + HostWeekRank hwr = hostWeekRank[index + 3]; + return Row( + children: [ + Container( + alignment: AlignmentDirectional.center, + width: 35.w, + child: text( + "${index + 4}", + fontSize: 15.sp, + textColor: Colors.white, + ), + ), + SizedBox(width: 15.w), + ATDebounceWidget( + child: netImage( + url: hwr.avatar ?? "", + width: 45.w, + height: 45.w, + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + shape: BoxShape.circle, + border: Border.all(color: Color(0xffFEE5BB), width: 0.5.w), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == hwr.userId}&tageId=${hwr.userId}", + ); + }, + ), + SizedBox(width: 5.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + constraints: BoxConstraints(maxWidth: 125.w, maxHeight: 21.w), + child: + (hwr.nickname?.length ?? 0) > 12 + ? Marquee( + text: hwr.nickname ?? "", + style: TextStyle( + fontSize: 15.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration(milliseconds: 550), + decelerationCurve: Curves.easeOut, + ) + : Text( + hwr.nickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 15.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + text( + "ID:${hwr.account}", + fontSize: 14.sp, + textColor: Colors.white, + fontWeight: FontWeight.w500, + ), + ], + ), + Spacer(), + Row( + children: [ + Image.asset("atu_images/general/at_icon_jb.png", height: 28.w), + SizedBox(width: 3.w), + text( + hwr.exp ?? "0", + fontWeight: FontWeight.bold, + fontSize: 15.sp, + textColor: Colors.white, + ), + ], + ), + ], + ); + } + } + + Widget _buildRankUserName(int rank) { + if (rank == 1) { + return PositionedDirectional( + bottom: 2.w, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + widget.rankType == "0" + ? (supporterWeekRank.isNotEmpty + ? Container( + alignment: Alignment.center, + constraints: BoxConstraints( + maxWidth: 70.w, + maxHeight: 19.w, + ), + child: + (supporterWeekRank.first.nickname?.length ?? 0) > 6 + ? Marquee( + text: supporterWeekRank.first.nickname ?? "", + style: TextStyle( + fontSize: 13.sp, + color: Color(0xffD85458), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + supporterWeekRank.first.nickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13.sp, + color: Color(0xffD85458), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ) + : Container()) + : (hostWeekRank.isNotEmpty + ? Container( + alignment: Alignment.center, + constraints: BoxConstraints( + maxWidth: 70.w, + maxHeight: 19.w, + ), + child: + (hostWeekRank.first.nickname?.length ?? 0) > 6 + ? Marquee( + text: hostWeekRank.first.nickname ?? "", + style: TextStyle( + fontSize: 13.sp, + color: Color(0xffD85458), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + hostWeekRank.first.nickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13.sp, + color: Color(0xffD85458), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ) + : Container()), + SizedBox(height: 13.w), + Container( + width: 85.w, + height: 25.w, + decoration: BoxDecoration( + border: Border.all(color: Color(0xffFFB906), width: 0.6.w), + borderRadius: BorderRadius.all(Radius.circular(25.w)), + gradient: LinearGradient( + colors: [ + Color(0xffAD1300), + Color(0xffFE280C), + Color(0xffAD1300), + ], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset("atu_images/general/at_icon_jb.png", height: 20.w), + text( + widget.rankType == "0" + ? (supporterWeekRank.isNotEmpty + ? supporterWeekRank.first.exp ?? "0" + : "0") + : (hostWeekRank.isNotEmpty + ? hostWeekRank.first.exp ?? "0" + : "0"), + fontWeight: FontWeight.bold, + fontSize: 13.sp, + textColor: Color(0xffFFE601), + ), + ], + ), + ), + ], + ), + ); + } else if (rank == 2) { + return PositionedDirectional( + bottom: 5.w, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + widget.rankType == "0" + ? (supporterWeekRank.length > 1 + ? Container( + alignment: Alignment.center, + constraints: BoxConstraints( + maxWidth: 70.w, + maxHeight: 17.w, + ), + child: + (supporterWeekRank[1].nickname?.length ?? 0) > 6 + ? Marquee( + text: supporterWeekRank[1].nickname ?? "", + style: TextStyle( + fontSize: 11.sp, + color: Color(0xff4073C4), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + supporterWeekRank[1].nickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 11.sp, + color: Color(0xff4073C4), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ) + : Container()) + : (hostWeekRank.length > 1 + ? Container( + alignment: Alignment.center, + constraints: BoxConstraints( + maxWidth: 70.w, + maxHeight: 18.w, + ), + child: + (hostWeekRank[1].nickname?.length ?? 0) > 6 + ? Marquee( + text: hostWeekRank[1].nickname ?? "", + style: TextStyle( + fontSize: 12.sp, + color: Color(0xff4073C4), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + hostWeekRank[1].nickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12.sp, + color: Color(0xff4073C4), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ) + : Container()), + SizedBox(height: 10.w), + Container( + width: 75.w, + height: 22.w, + decoration: BoxDecoration( + border: Border.all(color: Color(0xffFFB906), width: 0.6.w), + borderRadius: BorderRadius.all(Radius.circular(25.w)), + gradient: LinearGradient( + colors: [ + Color(0xffA3C7FD), + Color(0xff1D5ABD), + Color(0xffA3C6FD), + ], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset("atu_images/general/at_icon_jb.png", height: 18.w), + text( + widget.rankType == "0" + ? (supporterWeekRank.length > 1 + ? supporterWeekRank[1].exp ?? "0" + : "0") + : (hostWeekRank.length > 1 + ? hostWeekRank[1].exp ?? "0" + : "0"), + fontWeight: FontWeight.bold, + fontSize: 12.sp, + textColor: Colors.white, + ), + ], + ), + ), + ], + ), + ); + } else if (rank == 3) { + return PositionedDirectional( + bottom: 5.w, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + widget.rankType == "0" + ? (supporterWeekRank.length > 2 + ? Container( + alignment: Alignment.center, + constraints: BoxConstraints( + maxWidth: 70.w, + maxHeight: 18.w, + ), + child: + (supporterWeekRank[2].nickname?.length ?? 0) > 6 + ? Marquee( + text: supporterWeekRank[2].nickname ?? "", + style: TextStyle( + fontSize: 12.sp, + color: Color(0xff95522E), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + supporterWeekRank[2].nickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12.sp, + color: Color(0xff95522E), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ) + : Container()) + : (hostWeekRank.length > 2 + ? Container( + constraints: BoxConstraints( + maxWidth: 70.w, + maxHeight: 18.w, + ), + child: + (hostWeekRank[2].nickname?.length ?? 0) > 6 + ? Marquee( + text: hostWeekRank[2].nickname ?? "", + style: TextStyle( + fontSize: 12.sp, + color: Color(0xff95522E), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + hostWeekRank[2].nickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12.sp, + color: Color(0xff95522E), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ) + : Container()), + SizedBox(height: 10.w), + Container( + width: 75.w, + height: 22.w, + decoration: BoxDecoration( + border: Border.all(color: Color(0xffFFB906), width: 0.6.w), + borderRadius: BorderRadius.all(Radius.circular(25.w)), + gradient: LinearGradient( + colors: [ + Color(0xffF5EAE4), + Color(0xffF9C29E), + Color(0xffF5EAE4), + ], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset("atu_images/general/at_icon_jb.png", height: 18.w), + text( + widget.rankType == "0" + ? (supporterWeekRank.length > 2 + ? supporterWeekRank[2].exp ?? "0" + : "0") + : (hostWeekRank.length > 2 + ? hostWeekRank[2].exp ?? "0" + : "0"), + fontWeight: FontWeight.bold, + fontSize: 12.sp, + textColor: Colors.white, + ), + ], + ), + ), + ], + ), + ); + } + + return Container(); + } + + void _loadRankList() { + GroupRepository() + .familyMemberTop100(widget.familyId) + .then((res) { + supporterWeekRank = res.supporterWeekRank ?? []; + hostWeekRank = res.hostWeekRank ?? []; + myRank = res.myRankInfo; + isLoading = false; + setState(() {}); + }) + .catchError((_) {}); + } + + _buildRankUserWithMe() { + return Container( + height: 68.w, + padding: EdgeInsets.symmetric(vertical: 8.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(15.w), + topRight: Radius.circular(15.w), + ), + color: Colors.black87, + ), + child: Row( + children: [ + SizedBox(width: 10.w), + Container( + alignment: AlignmentDirectional.center, + width: 35.w, + child: text( + "${myRank?.rank ?? 0}", + fontSize: 15.sp, + textColor: Colors.white, + ), + ), + SizedBox(width: 8.w), + ATDebounceWidget( + child: netImage( + url: myRank?.avatar ?? "", + width: 45.w, + height: 45.w, + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + shape: BoxShape.circle, + border: Border.all(color: Color(0xffFEE5BB), width: 0.5.w), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == myRank?.userId}&tageId=${myRank?.userId}", + ); + }, + ), + SizedBox(width: 5.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + constraints: BoxConstraints(maxWidth: 125.w, maxHeight: 21.w), + child: + (myRank?.nickname?.length ?? 0) > 12 + ? Marquee( + text: myRank?.nickname ?? "", + style: TextStyle( + fontSize: 15.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration(milliseconds: 550), + decelerationCurve: Curves.easeOut, + ) + : Text( + myRank?.nickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 15.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + text( + "ID:${myRank?.account}", + fontSize: 14.sp, + textColor: Colors.white, + fontWeight: FontWeight.w500, + ), + ], + ), + Spacer(), + Row( + children: [ + Image.asset("atu_images/general/at_icon_jb.png", height: 28.w), + SizedBox(width: 3.w), + text( + myRank?.exp ?? "0", + fontWeight: FontWeight.bold, + fontSize: 15.sp, + textColor: Colors.white, + ), + ], + ), + SizedBox(width: 20.w), + ], + ), + ); + } +} diff --git a/lib/chatvibe_features/family/record/req_record_family_page.dart b/lib/chatvibe_features/family/record/req_record_family_page.dart new file mode 100644 index 0000000..7561b3c --- /dev/null +++ b/lib/chatvibe_features/family/record/req_record_family_page.dart @@ -0,0 +1,392 @@ +import 'dart:ui' as ui; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:marquee/marquee.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/family_repository_impl.dart'; + +import '../../../chatvibe_domain/models/res/at_family_my_apply_list_res.dart'; + +class ReqRecordFamilyPage extends ATPageList { + @override + _ReqRecordFamilyPageState createState() => _ReqRecordFamilyPageState(); +} + +class _ReqRecordFamilyPageState + extends ATPageListState { + num? lastId; + + @override + void initState() { + super.initState(); + backgroundColor = Colors.transparent; + isShowFooter = false; + padding = EdgeInsets.symmetric(horizontal: 10.w); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/family/at_icon_family_head_bg.png", + fit: BoxFit.fitWidth, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: "", + actions: [], + backButtonColor: Colors.white, + ), + body: SafeArea( + top: false, + child: Column( + children: [ + SizedBox(height: 115.w), + Expanded( + child: ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 18, sigmaY: 18), + child: Container( + alignment: Alignment.center, + color: Colors.white10, + child: Column( + children: [ + SizedBox(height: 10.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of( + context, + )!.applicationRecord, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + SizedBox(height: 10.w), + Expanded(child: buildList(context)), + SizedBox(height: 6.w), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ), + ], + ); + } + + @override + Widget buildItem(ATFamilyMyApplyListRes res) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + border: Border.all(color: Color(0xffE6E6E6), width: 0.2.w), + gradient: LinearGradient( + colors: [Color(0xff161616), Color(0xff101010)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: Stack( + alignment: AlignmentDirectional.topEnd, + children: [ + Column( + children: [ + SizedBox(height: 5.w), + Row( + children: [ + SizedBox(width: 5.w), + Container( + margin: EdgeInsetsDirectional.only(start: 8.w, top: 5.w), + child: netImage( + height: 80.w, + width: 80.w, + borderRadius: BorderRadius.circular(8.w), + url: res.familyAvatar ?? "", + ), + ), + SizedBox(width: 10.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + constraints: BoxConstraints( + maxWidth: 135.w, + maxHeight: 24.w, + ), + child: + (res.familyName?.length ?? 0) > 12 + ? Marquee( + text: res.familyName ?? "", + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 40.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + res.familyName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + SizedBox(height: 5.w), + Row( + children: [ + text( + "ID:${res.familyAccount ?? ""}", + fontWeight: FontWeight.w600, + fontSize: 13.sp, + textColor: Colors.white, + ), + Spacer(), + Container( + alignment: AlignmentDirectional.center, + height: 25.w, + width: 68.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/family/at_icon_family_member_btn_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + height: 15.w, + "atu_images/family/at_icon_family_member_tag.png", + ), + SizedBox(width: 3.w), + text( + "${res.maxMember ?? "0"}", + fontWeight: FontWeight.w600, + fontSize: 13.sp, + textColor: Colors.white, + ), + ], + ), + ), + ], + ), + ], + ), + ), + SizedBox(width: 8.w), + ATDebounceWidget( + debounceTime: Duration(milliseconds: 800), + child: Container( + margin: EdgeInsets.only(top: 12.w), + alignment: Alignment.center, + width: 80.w, + padding: EdgeInsets.symmetric(vertical: 5.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20.w), + gradient: LinearGradient( + colors: [Color(0xff48149b), Color(0xff9F63FF)], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: text( + res.status == 0 + ? ATAppLocalizations.of(context)!.cancel + : ATAppLocalizations.of(context)!.reapply, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () { + if (res.status == 0) { + familyCancelApply(res.msgId, res.familyName); + } else { + familyApplyJoin(res.msgId); + } + }, + ), + SizedBox(width: 8.w), + ], + ), + SizedBox(height: 10.w), + ], + ), + Container( + height: 25.w, + width: 80.w, + alignment: Alignment.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + res.status == 0 + ? (ATGlobalConfig.lang == "ar" + ? "atu_images/family/at_icon_req_family_state_tag_3.png" + : "atu_images/family/at_icon_req_family_state_tag_1.png") + : (res.status == 1 + ? (ATGlobalConfig.lang == "ar" + ? "atu_images/family/at_icon_req_family_state_tag_3.png" + : "atu_images/family/at_icon_req_family_state_tag_1.png") + : (ATGlobalConfig.lang == "ar" + ? "atu_images/family/at_icon_req_family_state_tag_4.png" + : "atu_images/family/at_icon_req_family_state_tag_2.png")), + ), + fit: BoxFit.fill, + ), + ), + child: text( + res.status == 0 + ? ATAppLocalizations.of(context)!.pending + : (res.status == 1 + ? ATAppLocalizations.of(context)!.approved + : ATAppLocalizations.of(context)!.rejected), + textColor: Colors.white, + fontSize: 13.sp, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ); + } + + @override + builderDivider() { + return Divider( + height: 22.w, + color: Colors.transparent, + indent: 15.w, + endIndent: 15.w, + ); + } + + @override + empty() { + return mainEmpty( + textColor: Colors.white, + msg: ATAppLocalizations.of(context)!.noData, + image: Image.asset( + "atu_images/general/at_icon_loading.png", + width: 140.w, + height: 140.w, + ), + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + if (page == 1) { + lastId = null; + } + try { + var result = await GroupRepository().familyMyApplyList( + lastId: lastId, + ); + if (result.isNotEmpty) { + lastId = result.last.msgId; + } + ATLoadingManager.veilRoutine(); + onSuccess(result); + } catch (e) { + ATLoadingManager.veilRoutine(); + if (onErr != null) { + onErr(); + } + } + } + + void familyCancelApply(num? msgId, String? familyName) { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.cancelRequest, + msg: ATAppLocalizations.of( + context, + )!.cancelRequestFamilyMsg("$familyName"), + isDark: true, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + ATLoadingManager.exhibitOperation(); + GroupRepository() + .familyCancelApply(msgId: msgId) + .then((res) { + loadData(1); + }) + .catchError((_) { + loadData(1); + }); + }, + ); + }, + ); + } + + void familyApplyJoin(num? msgId) { + GroupRepository() + .familyReapply("$msgId") + .then((res) { + ATTts.show(ATAppLocalizations.of(context)!.operationSuccessful); + ATLoadingManager.exhibitOperation(); + loadData(1); + }) + .catchError((_) { + loadData(1); + }); + } +} diff --git a/lib/chatvibe_features/gift/activity/gift_tab_activity_item_page.dart b/lib/chatvibe_features/gift/activity/gift_tab_activity_item_page.dart new file mode 100644 index 0000000..c4749c8 --- /dev/null +++ b/lib/chatvibe_features/gift/activity/gift_tab_activity_item_page.dart @@ -0,0 +1,266 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_domain/models/res/gift_res.dart'; +import 'package:aslan/main.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; + +import '../../../chatvibe_data/models/enum/at_gift_type.dart'; + +class GiftTabActivityItemPage extends StatefulWidget { + String type; + bool isDark = true; + Function(int checkedIndex) checkedCall; + + GiftTabActivityItemPage( + this.type, + this.checkedCall, { + super.key, + this.isDark = true, + }); + + @override + _GiftTabActivityItemPageState createState() => + _GiftTabActivityItemPageState(); +} + +class _GiftTabActivityItemPageState extends State { + PageController _giftPageController = PageController(); + int _index = 0; + int checkedIndex = 0; + + BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + widget.checkedCall(0); + }); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + return (ref.activityGiftByTab[widget.type] ?? []).isEmpty + ? mainEmpty( + textColor: Colors.white54, + msg: ATAppLocalizations.of(context)!.noData, + ) + : Column( + children: [ + ATDebounceWidget( + child: Container( + margin: EdgeInsets.symmetric( + horizontal: 12.w, + vertical: 8.w, + ), + child: netImage( + borderRadius: BorderRadius.all(Radius.circular(8.w)), + url: + ref.activityGiftByTab[widget.type]?.first.bannerUrl ?? + "", + fit: BoxFit.fill, + height: 118.w, + width: double.infinity, + ), + ), + onTap: () { + ATRoomUtils.closeAllDialogs(); + ATNavigatorUtils.push( + navigatorKey.currentContext!, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent("${ref.activityGiftByTab[widget.type]?.first.jumpUrl}")}&showTitle=false", + replace: false, + ); + }, + ), + Expanded( + child: PageView.builder( + controller: _giftPageController, + onPageChanged: (i) { + setState(() { + _index = i; + }); + }, + itemBuilder: (c, i) { + var current = + (ref.activityGiftByTab[widget.type]!.length - + (i * 3)); + int size = + current > 3 + ? 3 + : current % 3 == 0 + ? 3 + : current % 3; + return GridView.builder( + shrinkWrap: true, + padding: EdgeInsets.symmetric(horizontal: 12.w), + physics: NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + childAspectRatio: 0.86, + mainAxisSpacing: 8.w, + crossAxisSpacing: 12.w, + ), + itemCount: size, + itemBuilder: (BuildContext context, int index) { + return _bagItem( + ref.activityGiftByTab[widget.type]![(i * 3) + + index], + ref, + ); + }, + ); + }, + itemCount: + (ref.activityGiftByTab[widget.type]!.length / 3).ceil(), + ), + ), + // _indicator(ref), + SizedBox(height: 10.w), + ], + ); + }, + ); + } + + Widget _bagItem(ChatVibeGiftRes gift, AppGeneralManager ref) { + return GestureDetector( + child: Stack( + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: Colors.white10, + border: Border.all( + color: + checkedIndex == + ref.activityGiftByTab[widget.type]!.indexOf(gift) + ? ChatVibeTheme.primaryLight + : Colors.transparent, + width: 1.w, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + netImage( + url: gift.giftPhoto ?? "", + fit: BoxFit.cover, + width: 58.w, + height: 58.w, + ), + SizedBox(height: 5.w), + Container( + height: 23.w, + margin: EdgeInsets.symmetric(horizontal: 3.w), + alignment: Alignment.center, + child: text( + gift.giftName ?? "", + maxLines: 2, + fontSize: 12.sp, + letterSpacing: 0.1, + lineHeight: 1, + textAlign: TextAlign.center, + fontWeight: FontWeight.w600, + textColor: widget.isDark ? Colors.white : Colors.black, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + _strategy.getGiftPageGoldCoinIcon(), + width: 16.w, + height: 16.w, + ), + SizedBox(width: 3.w), + text( + "${gift.giftCandy}", + fontSize: 12.sp, + textColor: widget.isDark ? Colors.white : Colors.black, + ), + ], + ), + ], + ), + ), + Positioned( + right: 3.w, + child: Column( + spacing: 3.w, + children: [ + SizedBox(height: 3.w), + // 只添加需要的 widget + if (gift.special!.contains(ATGiftType.ANIMATION.name)) + Image.asset( + _strategy.getGiftPageGiftEffectIcon(ATGiftType.ANIMATION.name), + width: 18.w, + height: 18.w, + fit: BoxFit.fill, + ), + if (gift.special!.contains(ATGiftType.MUSIC.name)) + Image.asset( + _strategy.getGiftPageGiftMusicIcon(ATGiftType.MUSIC.name), + width: 18.w, + height: 18.w, + fit: BoxFit.fill, + ), + if (gift.giftTab == (ATGiftType.LUCKY_GIFT.name)) + Image.asset( + _strategy.getGiftPageGiftLuckIcon(ATGiftType.LUCKY_GIFT.name), + width: 18.w, + height: 18.w, + fit: BoxFit.fill, + ), + if (gift.giftTab == (ATGiftType.CP.name)) + Image.asset( + _strategy.getGiftPageGiftCpIcon(ATGiftType.CP.name), + width: 18.w, + height: 18.w, + fit: BoxFit.fill, + ), + ], + ), + ), + ], + ), + onTap: () { + setState(() { + checkedIndex = ref.activityGiftByTab[widget.type]!.indexOf(gift); + widget.checkedCall(checkedIndex); + }); + }, + ); + } + + _indicator(AppGeneralManager ref) { + var size = (ref.activityGiftByTab[widget.type]!.length) / 8; + var list = []; + for (int i = 0; i < size; i++) { + list.add( + Container( + width: 6.5.w, + height: 6.5.w, + margin: EdgeInsets.symmetric(horizontal: 4.w), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _index == i ? ChatVibeTheme.primaryColor : Color(0xffDADADA), + ), + ), + ); + } + return Row(mainAxisAlignment: MainAxisAlignment.center, children: list); + } +} diff --git a/lib/chatvibe_features/gift/activity/gift_tab_activity_page.dart b/lib/chatvibe_features/gift/activity/gift_tab_activity_page.dart new file mode 100644 index 0000000..f153383 --- /dev/null +++ b/lib/chatvibe_features/gift/activity/gift_tab_activity_page.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_features/gift/activity/gift_tab_activity_item_page.dart'; + +class GiftTabActivityPage extends StatefulWidget { + String type; + bool isDark = true; + Function(int checkedIndex,String activityId) checkedCall; + + GiftTabActivityPage( + this.type, + this.checkedCall, { + super.key, + this.isDark = true, + }); + + @override + _GiftTabActivityPageState createState() => _GiftTabActivityPageState(); +} + +class _GiftTabActivityPageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + int checkedIndex = 0; + + final List _pages = []; + final List _tabs = []; + + @override + void initState() { + super.initState(); + Provider.of( + context, + listen: false, + ).activityGiftByTab.keys.forEach((km) { + if (km == "2020752123128635394") { + _pages.add( + GiftTabActivityItemPage("2020752123128635394", (int checkedI) { + widget.checkedCall.call(checkedI,"2020752123128635394"); + }), + ); + } else if (km == "1975233764843642882") { + _pages.add( + GiftTabActivityItemPage("1975233764843642882", (int checkedI) { + widget.checkedCall.call(checkedI,"1975233764843642882"); + }), + ); + } else if (km == "2021548434972721154") { + _pages.add( + GiftTabActivityItemPage("2021548434972721154", (int checkedI) { + widget.checkedCall.call(checkedI,"2021548434972721154"); + }), + ); + } + }); + _tabController = TabController(length: _pages.length, vsync: this); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + _tabs.clear(); + ref.activityGiftByTab.keys.forEach((km) { + if (km == "2020752123128635394") { + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.ramadan)); + } else if (km == "1975233764843642882") { + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.kingQuuen)); + } else if (km == "2021548434972721154") { + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.weekStart)); + } + }); + return Column( + children: [ + Container( + height: 22.w, + child: Row( + children: [ + SizedBox(width: 5.w), + Expanded( + child: TabBar( + tabAlignment: TabAlignment.start, + labelPadding: EdgeInsets.symmetric(horizontal: 8.w), + labelColor: Colors.white, + isScrollable: true, + indicator: BoxDecoration(), + indicatorWeight: 0, + unselectedLabelColor: Colors.white54, + labelStyle: TextStyle( + fontSize: 12.sp, + ), + unselectedLabelStyle: TextStyle( + fontSize: 10.sp, + ), + indicatorColor: Colors.transparent, + dividerColor: Colors.transparent, + controller: _tabController, + tabs: _tabs, + ), + ), + SizedBox(width: 5.w), + ], + ),) , + Expanded( + child: TabBarView( + physics: NeverScrollableScrollPhysics(), + controller: _tabController, + children: _pages, + ), + ), + ], + ); + }, + ); + } +} diff --git a/lib/chatvibe_features/gift/bag/gift_bag_page.dart b/lib/chatvibe_features/gift/bag/gift_bag_page.dart new file mode 100644 index 0000000..f46b804 --- /dev/null +++ b/lib/chatvibe_features/gift/bag/gift_bag_page.dart @@ -0,0 +1,153 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/custom_cached_image.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_domain/models/res/gift_res.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; + +class GiftBagPage extends StatefulWidget { + const GiftBagPage({Key? key}) : super(key: key); + + @override + _GiftBagPageState createState() => _GiftBagPageState(); +} + +class _GiftBagPageState extends State with TickerProviderStateMixin { + PageController _giftPageController = PageController(); + int _index = 0; + + int checkedIndex = 0; + + BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + return ref.giftResList.isEmpty + ? mainEmpty(textColor: Colors.white54,msg: ATAppLocalizations.of(context)!.noData) + : Column( + children: [ + Expanded( + child: PageView.builder( + controller: _giftPageController, + onPageChanged: (i) { + setState(() { + _index = i; + }); + }, + itemBuilder: (c, i) { + var current = (ref.giftResList.length - (i * 8)); + int size = + current > 8 + ? 8 + : current % 8 == 0 + ? 8 + : current % 8; + return GridView.builder( + shrinkWrap: true, + padding: EdgeInsets.symmetric(horizontal: 12.w), + physics: NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, + childAspectRatio: 0.80, + mainAxisSpacing: 8.w, + crossAxisSpacing: 8.w, + ), + itemCount: size, + itemBuilder: (BuildContext context, int index) { + return _bagItem( + ref.giftResList[(i * 8) + index], + ref, + ); + }, + ); + }, + itemCount: (ref.giftResList.length / 8).ceil(), + ), + ), + _indicator(ref), + SizedBox(height: 10.w), + ], + ); + }, + ); + } + + _indicator(AppGeneralManager ref) { + var size = (ref.giftResList.length) / 8; + var list = []; + for (int i = 0; i < size; i++) { + list.add( + Container( + width: 6.5.w, + height: 6.5.w, + margin: EdgeInsets.symmetric(horizontal: 4.w), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _index == i ? ChatVibeTheme.primaryColor : Color(0xffDADADA), + ), + ), + ); + } + return Row(mainAxisAlignment: MainAxisAlignment.center, children: list); + } + + Widget _bagItem(ChatVibeGiftRes gift, AppGeneralManager ref) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: Colors.white10, + border: Border.all( + color: + checkedIndex == ref.giftResList.indexOf(gift) + ? ChatVibeTheme.primaryColor + : Colors.transparent, + width: 1.w, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CustomCachedImage( + imageUrl: gift.giftPhoto ?? "", + fit: BoxFit.cover, + width: 48.w, + height: 48.w, + ), + SizedBox(height: 10.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + _strategy.getGiftPageGoldCoinIcon(), + width: 14.w, + height: 14.w, + ), + SizedBox(width: 3.w), + text("${gift.giftCandy}", fontSize: 10.sp), + ], + ), + ], + ), + ), + onTap: () { + setState(() { + checkedIndex = ref.giftResList.indexOf(gift); + // widget.checkedCall(checkedIndex); + }); + }, + ); + } + + +} \ No newline at end of file diff --git a/lib/chatvibe_features/gift/gift_page.dart b/lib/chatvibe_features/gift/gift_page.dart new file mode 100644 index 0000000..ed85530 --- /dev/null +++ b/lib/chatvibe_features/gift/gift_page.dart @@ -0,0 +1,1185 @@ +import 'dart:convert'; +import 'dart:ui' as ui; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_debouncer/flutter_debouncer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; +import 'package:aslan/chatvibe_core/utilities/at_gift_vap_svga_manager.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/main.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_core/constants/at_room_msg_type.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_dialog_utils.dart'; +import 'package:aslan/chatvibe_core/utilities/at_lk_dialog_util.dart'; +import 'package:aslan/chatvibe_data/sources/local/floating_screen_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/gift_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/mic_res.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_managers/gift_system_manager.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/countdown_timer.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/give_room_luck_page.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_msg_item.dart'; +import 'package:aslan/chatvibe_features/wallet/wallet_route.dart'; +import 'package:aslan/chatvibe_features/gift/activity/gift_tab_activity_page.dart'; +import 'package:aslan/chatvibe_features/gift/gift_tab_page.dart'; +import '../../chatvibe_data/models/enum/at_gift_type.dart'; +import '../../chatvibe_data/models/message/at_floating_message.dart'; +import '../../chatvibe_domain/usecases/at_fixed_width_tabIndicator.dart'; + +class GiftPage extends StatefulWidget { + ChatVibeUserProfile? toUser; + final int initialIndex; + + GiftPage({super.key, this.toUser, this.initialIndex = 0}); + + @override + _GiftPageState createState() => _GiftPageState(); +} + +class _GiftPageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + + /// 业务逻辑策略访问器 + BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; + + // int checkedIndex = 0; + ChatVibeGiftRes? checkedGift; + final List _pages = []; + final List _tabs = []; + RtcProvider? rtcProvider; + + bool isAll = false; + List listMai = []; + + bool noShowNumber = false; + + ///选中人员 + Set set = {}; + + ///数量的箭头是否朝上 + bool isNumberUp = true; + + ///数量 + int number = 1; + + int giveType = 1; + + int giftType = 0; + Debouncer debouncer = Debouncer(); + + @override + void initState() { + super.initState(); + rtcProvider = Provider.of(context, listen: false); + Provider.of(context, listen: false).giftList(); + // Provider.of(context, listen: false).giftActivityList(); + // Provider.of(context, listen: false).giftBackpack(); + Provider.of(context, listen: false).balance(); + _pages.add( + GiftTabPage("ALL", (int checkedI) { + checkedGift = null; + var all = + Provider.of( + context, + listen: false, + ).giftByTab["ALL"]; + if (all != null) { + checkedGift = all[checkedI]; + } + number = 1; + noShowNumber = false; + setState(() { + giftType = 0; + }); + }), + ); + // _pages.add( + // GiftTabPage("NATIONAL_FLAG", (int checkedI) { + // checkedGift = null; + // var flag = + // Provider.of( + // context, + // listen: false, + // ).giftByTab["NATIONAL_FLAG"]; + // if (flag != null) { + // checkedGift = flag[checkedI]; + // } + // number = 1; + // noShowNumber = false; + // setState(() { + // giftType = 0; + // }); + // }), + // ); + + // _pages.add( + // GiftTabActivityPage("ACTIVITY", (int checkedI, String activityId) { + // checkedGift = null; + // var exclusive = + // Provider.of( + // context, + // listen: false, + // ).activityGiftByTab[activityId]; + // if (exclusive != null) { + // checkedGift = exclusive[checkedI]; + // } + // number = 1; + // noShowNumber = false; + // setState(() { + // giftType = 1; + // }); + // }), + // ); + + _pages.add( + GiftTabPage("LUCKY_GIFT", (int checkedI) { + checkedGift = null; + var exclusive = + Provider.of( + context, + listen: false, + ).giftByTab["LUCKY_GIFT"]; + if (exclusive != null) { + checkedGift = exclusive[checkedI]; + } + number = 1; + noShowNumber = false; + setState(() { + giftType = 2; + }); + }), + ); + + _pages.add( + GiftTabPage("CP", (int checkedI) { + checkedGift = null; + var exclusive = + Provider.of( + context, + listen: false, + ).giftByTab["CP"]; + if (exclusive != null) { + checkedGift = exclusive[checkedI]; + } + number = 1; + noShowNumber = false; + setState(() { + giftType = 3; + }); + }), + ); + + // _pages.add( + // GiftTabPage("CUSTOMIZED", (int checkedI) { + // checkedGift = null; + // var exclusive = + // Provider.of( + // context, + // listen: false, + // ).giftByTab["CUSTOMIZED"]; + // + // if (exclusive != null && exclusive.length > 1) { + // checkedGift = exclusive[checkedI]; + // } + // number = 1; + // noShowNumber = false; + // setState(() { + // giftType = 4; + // }); + // }), + // ); + + // _pages.add( + // GiftTabPage("MAGIC", (int checkedI) { + // checkedGift = null; + // var exclusive = + // Provider.of( + // context, + // listen: false, + // ).giftByTab["MAGIC"]; + // if (exclusive != null) { + // checkedGift = exclusive[checkedI]; + // } + // number = 1; + // noShowNumber = true; + // setState(() { + // giftType = 5; + // }); + // }), + // ); + _tabController = TabController( + length: _pages.length, + vsync: this, + initialIndex: widget.initialIndex, + ); + _tabController.addListener(() {}); // 监听切换 + rtcProvider?.roomWheatMap.forEach((k, v) { + if (v.user != null) { + if (v.user?.id == AccountStorage().getCurrentUser()?.userProfile?.id) { + listMai.add(HeadSelect(true, v)); + } else { + listMai.add(HeadSelect(false, v)); + } + } + isAll = true; + for (var mai in listMai) { + if (!mai.isSelect) { + isAll = false; + } + } + }); + } + + @override + Widget build(BuildContext context) { + _tabs.clear(); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.gift)); + // _tabs.add(Tab(text: ATAppLocalizations.of(context)!.country)); + // _tabs.add(Tab(text: ATAppLocalizations.of(context)!.activity)); + _tabs.add( + Tab( + child: Directionality(textDirection: TextDirection.ltr, child: Stack( + clipBehavior: Clip.none, + children: [ + Padding( + padding: EdgeInsets.only(right: 10.w), + child: Text(ATAppLocalizations.of(context)!.luck), + ), + Positioned( + top: -6.w, + right: -3.w, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: _showLuckyGiftHelpDialog, + child: Icon(Icons.help_outline_rounded, size: 12.w), + ), + ), + ], + )), + ), + ); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.cp)); + // _tabs.add(Tab(text: ATAppLocalizations.of(context)!.customized)); + // _tabs.add(Tab(text: ATAppLocalizations.of(context)!.magic)); + return Consumer( + builder: (context, ref, child) { + return SafeArea( + top: false, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildGiftHead(), + ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.0), + topRight: Radius.circular(12.0), + ), + child: Container( + color: Color(0xff262533), + constraints: BoxConstraints(maxHeight: 430.w), + child: Column( + children: [ + SizedBox(height: 12.w), + Row( + children: [ + SizedBox(width: 8.w), + widget.toUser == null + ? Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: + listMai + .map((e) => _maiHead(e)) + .toList(), + ), + ), + ) + : Stack( + alignment: AlignmentDirectional.center, + children: [ + Container( + padding: EdgeInsets.all(2.w), + child: netImage( + url: widget.toUser?.userAvatar ?? "", + width: 26.w, + defaultImg: + _strategy + .getMePageDefaultAvatarImage(), + shape: BoxShape.circle, + ), + ), + widget.toUser == null + ? PositionedDirectional( + bottom: 0, + end: 0, + child: Image.asset( + "atu_images/login/at_icon_login_ser_select.png", + width: 10.w, + height: 10.w, + ), + ) + : Container(), + ], + ), + widget.toUser == null + ? Builder( + builder: (ct) { + return GestureDetector( + child: Image.asset( + isAll + ? "atu_images/room/at_icon_gift_all_en.png" + : "atu_images/room/at_icon_gift_all_no.png", + width: 28.w, + height: 28.w, + ), + onTap: () { + isAll = !isAll; + for (var mai in listMai) { + mai.isSelect = isAll; + } + setState(() {}); + // showGiveTypeDialog(ct); + }, + ); + }, + ) + : Container(), + SizedBox(width: 12.w), + ], + ), + Row( + children: [ + SizedBox(width: 5.w), + Expanded( + child: Container( + height: 28.w, + child: TabBar( + tabAlignment: TabAlignment.start, + labelPadding: EdgeInsets.symmetric( + horizontal: 8.w, + ), + labelColor: Color(0xffFFD800), + + indicatorWeight: 0, + isScrollable: true, + indicator: ATFixedWidthTabIndicator( + width: 15.w, + gradient: LinearGradient( + colors: [ + Color(0xffFFA500), + Color(0xffFFD700), + ], + ), + ), + unselectedLabelColor: Colors.white54, + labelStyle: TextStyle(fontSize: 14.sp), + unselectedLabelStyle: TextStyle( + fontSize: 12.sp, + ), + indicatorColor: Colors.transparent, + dividerColor: Colors.transparent, + controller: _tabController, + tabs: _tabs, + ), + ), + ), + SizedBox(width: 5.w), + ], + ), + Expanded( + child: TabBarView( + physics: NeverScrollableScrollPhysics(), + controller: _tabController, + children: _pages, + ), + ), + Row( + children: [ + SizedBox(width: 15.w), + GestureDetector( + onTap: () { + SmartDialog.dismiss(tag: "showGiftControl"); + ATNavigatorUtils.push( + navigatorKey.currentState!.context, + WalletRoute.recharge, + replace: false, + ); + }, + child: Container( + padding: EdgeInsets.symmetric( + vertical: 8.w, + horizontal: 8.w, + ), + width: 120.w, + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.circular(18), + ), + child: Row( + children: [ + Image.asset( + _strategy.getGiftPageGoldCoinIcon(), + width: 14.w, + height: 14.w, + ), + SizedBox(width: 5.w), + Consumer( + builder: (context, ref, child) { + return Expanded( + child: text( + "${ref.myBalance}", + fontSize: 12.sp, + ), + ); + }, + ), + SizedBox(width: 5.w), + Icon( + Icons.arrow_forward_ios, + color: Colors.white, + size: 14.w, + ), + ], + ), + ), + ), + Spacer(), + Builder( + builder: (ct) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (noShowNumber) { + return; + } + isNumberUp = false; + _showNumber(ct); + setState(() {}); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.circular(18), + ), + child: Row( + children: [ + SizedBox(width: 10.w), + text("$number", fontSize: 12.sp), + Icon( + isNumberUp + ? Icons.keyboard_arrow_up + : Icons.keyboard_arrow_down, + color: Colors.white, + size: 20.w, + ), + SizedBox(width: 5.w), + GestureDetector( + onTap: () { + giveGifts(); + }, + child: Container( + padding: EdgeInsets.symmetric( + vertical: 8.w, + horizontal: 20.w, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xffFFD800), + Color(0xffFF9500), + ], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + borderRadius: BorderRadius.circular( + 18, + ), + ), + child: text( + ATAppLocalizations.of( + context, + )!.send, + fontSize: 14.sp, + ), + ), + ), + ], + ), + ), + ); + }, + ), + SizedBox(width: 15.w), + ], + ), + SizedBox(height: 15.w), + ], + ), + ), + ), + ], + ), + ); + }, + ); + } + + void _showLuckyGiftHelpDialog() { + if (giftType != 2) { + return; + } + SmartDialog.show( + tag: "showLuckyGiftHelpDialog", + maskColor: Colors.black26, + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + clickMaskDismiss: true, + builder: (_) { + return Material( + color: Colors.transparent, + child: Container( + width: ScreenUtil().screenWidth * 0.88, + padding: EdgeInsets.fromLTRB(15.w, 15.w, 15.w, 18.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20.w), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Stack( + children: [ + Align( + alignment: Alignment.center, + child: text( + ATAppLocalizations.of(context)!.tips, + fontSize: 18.sp, + fontWeight: FontWeight.w700, + textColor: Color(0xFF303030), + ), + ), + Align( + alignment: Alignment.centerRight, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: + () => SmartDialog.dismiss( + tag: "showLuckyGiftHelpDialog", + ), + child: Container( + width: 28.w, + height: 28.w, + decoration: BoxDecoration( + border: Border.all(color: Colors.black, width: 2.w), + borderRadius: BorderRadius.circular(10.w), + ), + child: Icon( + Icons.close, + size: 16.w, + color: Colors.black, + ), + ), + ), + ), + ], + ), + SizedBox(height: 22.w), + text( + ATAppLocalizations.of(context)!.luckGiftRuleTips, + maxLines: 20, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + lineHeight: 1.45, + textColor: Color(0xFF404040), + ), + ], + ), + ), + ); + }, + ); + } + + void showGiveTypeDialog(BuildContext ct) { + SmartDialog.showAttach( + tag: "showGiveType", + targetContext: ct, + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + scalePointBuilder: (selfSize) => Offset(selfSize.width, 10), + builder: (_) { + return Container( + height: 135.w, + width: 200.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(_strategy.getGiftPageGiveTypeBackground()), + fit: BoxFit.fill, + ), + ), + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.w), + child: Column( + children: [ + SizedBox(height: 5.w), + Expanded( + child: GestureDetector( + child: Row( + children: [ + Image.asset( + _strategy.getGiftPageAllOnMicrophoneIcon(), + height: 18.w, + width: 18.w, + ), + SizedBox(width: 8.w), + Expanded( + child: text( + ATAppLocalizations.of(context)!.allOnMicrophone, + fontSize: 14.sp, + textColor: Colors.white54, + ), + ), + SizedBox(width: 8.w), + Image.asset( + giveType == 0 + ? _strategy.getCommonSelectIcon() + : _strategy.getCommonUnselectIcon(), + width: 15.w, + height: 15.w, + ), + ], + ), + onTap: () { + giveType = 0; + SmartDialog.dismiss(tag: "showGiveType"); + selecteAllMicUsers(); + setState(() {}); + }, + ), + ), + Expanded( + child: GestureDetector( + child: Row( + children: [ + Image.asset( + _strategy.getGiftPageUsersOnMicrophoneIcon(), + height: 18.w, + width: 18.w, + ), + SizedBox(width: 8.w), + Expanded( + child: text( + ATAppLocalizations.of(context)!.usersOnMicrophone, + fontSize: 14.sp, + textColor: Colors.white54, + ), + ), + SizedBox(width: 8.w), + Image.asset( + giveType == 1 + ? _strategy.getCommonSelectIcon() + : _strategy.getCommonUnselectIcon(), + width: 15.w, + height: 15.w, + ), + ], + ), + onTap: () { + giveType = 1; + SmartDialog.dismiss(tag: "showGiveType"); + setState(() {}); + }, + ), + ), + Expanded( + child: GestureDetector( + onTap: () { + giveType = 2; + SmartDialog.dismiss(tag: "showGiveType"); + setState(() {}); + }, + behavior: HitTestBehavior.opaque, + child: Row( + children: [ + Image.asset( + _strategy.getGiftPageAllInTheRoomIcon(), + height: 18.w, + width: 18.w, + ), + SizedBox(width: 8.w), + Expanded( + child: text( + ATAppLocalizations.of(context)!.allInTheRoom, + fontSize: 14.sp, + textColor: Colors.white54, + ), + ), + SizedBox(width: 8.w), + Image.asset( + giveType == 2 + ? _strategy.getCommonSelectIcon() + : _strategy.getCommonUnselectIcon(), + width: 15.w, + height: 15.w, + ), + ], + ), + ), + ), + ], + ), + ); + }, + ); + } + + Widget _maiHead(HeadSelect shead) { + // 是否选中 + return GestureDetector( + onTap: () { + isAll = true; + for (var mai in listMai) { + if (shead.mic?.user?.id == mai.mic?.user?.id) { + mai.isSelect = !mai.isSelect; + } + if (!mai.isSelect) { + isAll = false; + } + } + + setState(() {}); + }, + child: Stack( + alignment: Alignment.bottomCenter, + clipBehavior: Clip.none, + children: [ + Stack( + alignment: AlignmentDirectional.center, + children: [ + Container( + padding: EdgeInsets.all(2.w), + child: netImage( + url: shead.mic?.user?.userAvatar ?? "", + width: 26.w, + defaultImg: _strategy.getMePageDefaultAvatarImage(), + shape: BoxShape.circle, + ), + ), + PositionedDirectional( + bottom: 0, + end: 0, + child: Container( + alignment: AlignmentDirectional.center, + width: 12.w, + height: 12.w, + decoration: BoxDecoration( + color: Colors.black38, + shape: BoxShape.circle, + border: + shead.isSelect + ? Border.all( + color: ChatVibeTheme.primaryLight, + width: 1.w, + ) + : null, + ), + child: text( + "${shead.mic?.micIndex}", + textColor: Colors.white, + fontSize: 8.sp, + ), + ), + ), + shead.isSelect + ? PositionedDirectional( + bottom: 0, + end: 0, + child: Image.asset( + "atu_images/login/at_icon_login_ser_select.png", + width: 12.w, + height: 12.w, + ), + ) + : Container(), + ], + ), + ], + ), + ); + } + + ///数量选项 + void _showNumber(BuildContext ct) { + SmartDialog.showAttach( + tag: "showNumber", + targetContext: ct, + maskColor: Colors.transparent, + alignment: Alignment.topLeft, + animationType: SmartAnimationType.fade, + scalePointBuilder: (selfSize) => Offset(selfSize.width, 10), + onDismiss: () { + isNumberUp = true; + setState(() {}); + }, + builder: (_) { + return Transform.translate( + offset: Offset(ATGlobalConfig.lang == "ar" ? 20 : -20, -5), + child: CheckNumber( + onNumberChanged: (number) { + this.number = number; + isNumberUp = true; + setState(() {}); + }, + ), + ); + }, + ); + } + + ///选中所有在座位上的用户 + void selecteAllMicUsers() { + for (var mai in listMai) { + mai.isSelect = true; + } + } + + ///赠送礼物 + void giveGifts() { + List acceptUserIds = []; + List acceptUsers = []; + + if (widget.toUser != null) { + acceptUserIds.add(widget.toUser?.id ?? ""); + acceptUsers.add(MicRes(user: widget.toUser)); + } else { + ///所有在线 + if (giveType == 2) { + for (var value + in Provider.of(context, listen: false).onlineUsers) { + acceptUsers.add(MicRes(user: value)); + acceptUserIds.add(value.id ?? ""); + } + } else { + for (var mu in listMai) { + if (mu.isSelect) { + acceptUsers.add(mu.mic!); + acceptUserIds.add(mu.mic!.user!.id ?? ""); + } + } + } + } + if (acceptUserIds.isEmpty) { + ATTts.show(ATAppLocalizations.of(context)!.pleaseSelectTheRecipient); + return; + } + if (checkedGift == null) { + return; + } + if (checkedGift?.giftTab == ATGiftType.LUCKY_GIFT.name || + checkedGift?.giftTab == ATGiftType.MAGIC.name) { + num price = checkedGift!.giftCandy! * number * acceptUserIds.length; + num myBalance = + Provider.of( + context, + listen: false, + ).myBalance; + if (price > myBalance) { + ATRoomUtils.goRechargeOperation(navigatorKey.currentContext!); + return; + } + SmartDialog.dismiss(tag: "showGiftControl"); + SmartDialog.show( + tag: "showGiveRoomLuckPage", + alignment: Alignment.center, + maskColor: Colors.transparent, + clickMaskDismiss: false, + animationType: SmartAnimationType.fade, + builder: (_) { + return GiveRoomLuckPage(checkedGift!, acceptUserIds, () { + ///幸运礼物 + ChatRoomRepository() + .giveLuckyGift( + acceptUserIds, + checkedGift!.id ?? "", + number, + false, + roomId: + rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? + "", + ) + .then((result) { + // ATTts.show(ATAppLocalizations.of(context)!.giftGivingSuccessful); + sendGiftMsg(acceptUsers); + sendLuckGiftAnimOtherMsg(acceptUsers); + Provider.of( + navigatorKey.currentState!.context, + listen: false, + ).updateLuckGiftNumber( + number * acceptUserIds.length, + acceptUserIds.length > 1, + acceptUsers.first, + checkedGift, + ); + Provider.of( + navigatorKey.currentState!.context, + listen: false, + ).updateBalance(result); + }) + .catchError((e) {}); + }); + }, + onDismiss: () { + ATGiftVapSvgaManager().resumeAnim(); + }, + ); + } else { + ChatRoomRepository() + .giveGift( + acceptUserIds, + checkedGift!.id ?? "", + number, + false, + roomId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "", + ) + .then((result) { + // ATTts.show(ATAppLocalizations.of(context)!.giftGivingSuccessful); + sendGiftMsg(acceptUsers); + Provider.of( + context, + listen: false, + ).updateBalance(result); + }) + .catchError((e) {}); + } + } + + void sendGiftMsg(List acceptUsers) { + ///发送一条IM消息 + for (var u in acceptUsers) { + Provider.of( + navigatorKey.currentState!.context, + listen: false, + ).sendMsg( + Msg( + groupId: + rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount, + gift: checkedGift, + user: AccountStorage().getCurrentUser()?.userProfile, + toUser: u.user, + number: number, + type: ATRoomMsgType.gift, + role: + Provider.of( + navigatorKey.currentState!.context, + listen: false, + ).currenRoom?.entrants?.roles ?? + "", + msg: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "", + ), + addLocal: true, + ); + if (rtcProvider?.currenRoom?.roomProfile?.roomSetting?.showHeartbeat ?? + false) { + debouncer.debounce( + duration: Duration(milliseconds: 350), + onDebounce: () { + Provider.of( + navigatorKey.currentState!.context, + listen: false, + ).getMicList(); + }, + ); + } + num coins = checkedGift!.giftCandy! * number; + if (coins > 9999) { + var fMsg = ATFloatingMessage( + type: 1, + userAvatarUrl: + AccountStorage().getCurrentUser()?.userProfile?.userAvatar ?? "", + userName: + AccountStorage().getCurrentUser()?.userProfile?.userNickname ?? + "", + toUserName: u.user?.userNickname ?? "", + giftUrl: checkedGift?.giftPhoto ?? "", + roomId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "", + coins: coins, + number: number, + ); + OverlayManager().addMessage(fMsg); + } + if (checkedGift!.giftSourceUrl != null && checkedGift!.special != null) { + if (checkedGift!.special!.contains(ATGiftType.ANIMATION.name) || + checkedGift!.special!.contains(ATGiftType.GLOBAL_GIFT.name)) { + if (ATGlobalConfig.isGiftSpecialEffects) { + ATGiftVapSvgaManager().play(checkedGift!.giftSourceUrl!); + } + } + } + } + } + + /// 将数字giftType转换为字符串类型,用于活动礼物头部背景 + String _giftTypeToString(int giftType) { + switch (giftType) { + case 1: // ACTIVITY + return 'ACTIVITY'; + case 2: // LUCKY_GIFT -> LUCK + return 'LUCK'; + case 3: // CP + return 'CP'; + case 5: // MAGIC + return 'MAGIC'; + default: + return 'ACTIVITY'; + } + } + + _buildGiftHead() { + if (giftType == 1 || giftType == 5) { + // 获取基础路径 + String basePath = _strategy.getGiftPageActivityGiftHeadBackground( + _giftTypeToString(giftType), + ); + + // 添加语言后缀 + String imagePath; + if (ATGlobalConfig.lang == "ar") { + // 移除扩展名,添加 _ar 后缀,然后重新添加扩展名 + if (basePath.endsWith('.png')) { + imagePath = basePath.substring(0, basePath.length - 4) + '_ar.png'; + } else { + imagePath = basePath + '_ar'; + } + } else { + if (basePath.endsWith('.png')) { + imagePath = basePath.substring(0, basePath.length - 4) + '_en.png'; + } else { + imagePath = basePath + '_en'; + } + } + + // 确定高度 + double height = giftType == 5 ? 80.w : 65.w; + + return Container( + margin: EdgeInsets.symmetric(horizontal: 10.w), + child: Image.asset(imagePath, height: height, fit: BoxFit.fill), + ); + } + return Container(); + } + + ///发送一条消息,幸运礼物,房间里所有人都能看到礼物飘向麦位的动画 + void sendLuckGiftAnimOtherMsg(List acceptUsers) { + Provider.of( + navigatorKey.currentState!.context, + listen: false, + ).sendMsg( + Msg( + groupId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount, + gift: checkedGift, + type: ATRoomMsgType.luckGiftAnimOther, + msg: jsonEncode(acceptUsers.map((u) => u.user?.id).toList()), + ), + addLocal: false, + ); + } +} + +class CheckNumber extends StatelessWidget { + final Function(int) onNumberChanged; + late BuildContext context; + + CheckNumber({super.key, required this.onNumberChanged}); + + @override + Widget build(BuildContext context) { + this.context = context; + return Container( + alignment: AlignmentDirectional.topEnd, + margin: EdgeInsets.only(right: width(22)), + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(12.w)), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 25, sigmaY: 25), + child: Container( + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.all(Radius.circular(height(6))), + ), + width: width(75), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: height(10)), + _item(1), + _item(7), + _item(17), + _item(77), + _item(777), + _item(7777), + SizedBox(height: height(10)), + ], + ), + ), + ), + ), + ); + } + + _item(int number) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + SmartDialog.dismiss(tag: "showNumber"); + onNumberChanged(number); + }, + child: SizedBox( + height: height(24), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(width: width(10)), + Text( + "$number", + style: TextStyle( + fontSize: sp(14), + color: Colors.white, + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + ), + ), + SizedBox(width: width(10)), + ], + ), + ), + ); + } +} + +class HeadSelect { + bool isSelect = false; + MicRes? mic; + + HeadSelect(this.isSelect, this.mic); +} diff --git a/lib/chatvibe_features/gift/gift_tab_page.dart b/lib/chatvibe_features/gift/gift_tab_page.dart new file mode 100644 index 0000000..9109b24 --- /dev/null +++ b/lib/chatvibe_features/gift/gift_tab_page.dart @@ -0,0 +1,305 @@ +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_domain/models/res/gift_res.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; + +import '../../chatvibe_data/models/enum/at_gift_type.dart'; + +class GiftTabPage extends StatefulWidget { + String type; + bool isDark = true; + Function(int checkedIndex) checkedCall; + + GiftTabPage(this.type, this.checkedCall, {super.key, this.isDark = true}); + + @override + _GiftTabPageState createState() => _GiftTabPageState(); +} + +class _GiftTabPageState extends State { + PageController _giftPageController = PageController(); + int _index = 0; + int checkedIndex = 0; + + BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (widget.type == "CUSTOMIZED") { + widget.checkedCall(1); + } else { + widget.checkedCall(0); + } + }); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + return (ref.giftByTab[widget.type] ?? []).isEmpty + ? mainEmpty( + textColor: Colors.white54, + msg: ATAppLocalizations.of(context)!.noData, + ) + : Column( + children: [ + SizedBox(height: 23.w,), + Expanded( + child: PageView.builder( + controller: _giftPageController, + onPageChanged: (i) { + setState(() { + _index = i; + }); + }, + itemBuilder: (c, i) { + var current = + (ref.giftByTab[widget.type]!.length - (i * 8)); + int size = + current > 8 + ? 8 + : current % 8 == 0 + ? 8 + : current % 8; + return GridView.builder( + shrinkWrap: true, + padding: EdgeInsets.symmetric(horizontal: 12.w), + physics: NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, + childAspectRatio: 0.75, + mainAxisSpacing: 8.w, + crossAxisSpacing: 8.w, + ), + itemCount: size, + itemBuilder: (BuildContext context, int index) { + return _bagItem( + ref.giftByTab[widget.type]![(i * 8) + index], + ref, + ); + }, + ); + }, + itemCount: (ref.giftByTab[widget.type]!.length / 8).ceil(), + ), + ), + _indicator(ref), + SizedBox(height: 10.w), + ], + ); + }, + ); + } + + Widget _bagItem(ChatVibeGiftRes gift, AppGeneralManager ref) { + return gift.id == "-1000" + ? GestureDetector( + child: Stack( + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: Colors.white10, + border: Border.all(color: Colors.transparent, width: 1.w), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + _strategy.getGiftPageCustomizedRuleIcon(), + fit: BoxFit.cover, + width: 48.w, + height: 48.w, + ), + SizedBox(height: 5.w), + Container( + height: 23.w, + margin: EdgeInsets.symmetric(horizontal: 3.w), + alignment: Alignment.center, + child: text( + ATAppLocalizations.of(context)!.rulesUpload, + maxLines: 2, + fontSize: 10.sp, + letterSpacing: 0.1, + lineHeight: 1, + textAlign: TextAlign.center, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + ], + ), + ), + ], + ), + onTap: () { + SmartDialog.show( + tag: "showCustomizedRule", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return SafeArea( + top: false, + child: ClipRect( + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 15, sigmaY: 15), + child: Stack( + alignment: Alignment.topCenter, + children: [ + Container( + height: ScreenUtil().screenHeight * 0.7, + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + ), + child: Column( + children: [ + SizedBox(height: 10.w), + text( + ATAppLocalizations.of( + context, + )!.customizedGiftRules, + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(height: 7.w), + Expanded( + child: SingleChildScrollView( + child: Container( + margin: EdgeInsets.symmetric( + horizontal: 12.w, + ), + child: Text( + ATAppLocalizations.of( + context, + )!.customizedGiftRulesContent, + style: TextStyle( + color: Colors.white, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ), + SizedBox(height: 10.w), + ], + ), + ), + ], + ), + ), + ), + ); + }, + ); + }, + ) + : GestureDetector( + child: Stack( + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: Colors.white10, + border: Border.all( + color: + checkedIndex == + ref.giftByTab[widget.type]!.indexOf(gift) + ? ChatVibeTheme.primaryLight + : Colors.transparent, + width: 1.w, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + netImage( + url: gift.giftPhoto ?? "", + fit: BoxFit.cover, + width: 48.w, + height: 48.w, + ), + SizedBox(height: 5.w), + Container( + height: 23.w, + margin: EdgeInsets.symmetric(horizontal: 3.w), + alignment: Alignment.center, + child: text( + gift.giftName ?? "", + maxLines: 2, + fontSize: 10.sp, + letterSpacing: 0.1, + lineHeight: 1, + textAlign: TextAlign.center, + fontWeight: FontWeight.w600, + textColor: widget.isDark ? Colors.white : Colors.black, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + _strategy.getGiftPageGoldCoinIcon(), + width: 14.w, + height: 14.w, + ), + SizedBox(width: 3.w), + text( + "${gift.giftCandy}", + fontSize: 10.sp, + textColor: + widget.isDark ? Colors.white : Colors.black, + ), + ], + ), + ], + ), + ), + ], + ), + onTap: () { + setState(() { + checkedIndex = ref.giftByTab[widget.type]!.indexOf(gift); + widget.checkedCall(checkedIndex); + }); + }, + ); + } + + _indicator(AppGeneralManager ref) { + var size = (ref.giftByTab[widget.type]!.length) / 8; + var list = []; + for (int i = 0; i < size; i++) { + list.add( + Container( + width: 6.5.w, + height: 6.5.w, + margin: EdgeInsets.symmetric(horizontal: 4.w), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _index == i ? ChatVibeTheme.primaryColor : Color(0xffDADADA), + ), + ), + ); + } + return Row(mainAxisAlignment: MainAxisAlignment.center, children: list); + } +} diff --git a/lib/chatvibe_features/home/coupon/coupon_list_page.dart b/lib/chatvibe_features/home/coupon/coupon_list_page.dart new file mode 100644 index 0000000..ffe52ab --- /dev/null +++ b/lib/chatvibe_features/home/coupon/coupon_list_page.dart @@ -0,0 +1,152 @@ +import 'dart:ui'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:tancent_vap/utils/constant.dart'; +import 'package:tancent_vap/widgets/vap_view.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_domain/usecases/at_fixed_width_tabIndicator.dart'; +import 'package:aslan/chatvibe_features/home/coupon/coupon_route.dart'; +import 'package:aslan/chatvibe_features/home/coupon/coupon_tab_page.dart'; + +class CouponListPage extends StatefulWidget { + @override + _CouponListPageState createState() => _CouponListPageState(); +} + +class _CouponListPageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + final List _pages = []; + final List _tabs = []; + + @override + void initState() { + super.initState(); + _pages.add(CouponTabPage()); + _pages.add(CouponTabPage(type: 1)); + _pages.add(CouponTabPage(type: 2)); + _pages.add(CouponTabPage(type: 3)); + _tabController = TabController(length: _pages.length, vsync: this); + } + + @override + Widget build(BuildContext context) { + _tabs.clear(); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.all)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.headdress)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.mountains)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.vip)); + return Stack( + children: [ + Image.asset( + "atu_images/index/icon_coupon_head_bg.png", + width: ScreenUtil().screenWidth, + height: 150.w, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.coupon, + actions: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.all(5.w), + margin: EdgeInsetsDirectional.only(end: 10.w), + child: Image.asset( + "atu_images/index/icon_coupon_recod.png", + width: 20.w, + height: 20.w, + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + CouponRoute.couponRecord, + replace: false, + ); + }, + ), + ], + ), + body: SafeArea( + top: false, + child: Container( + margin: EdgeInsetsDirectional.only(top: 15.w), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadiusDirectional.only( + topStart: Radius.circular(35.w), + ), + ), + child: Column( + children: [ + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 5.w), + TabBar( + tabAlignment: TabAlignment.start, + labelPadding: EdgeInsets.symmetric(horizontal: 12.w), + isScrollable: true, + labelStyle: TextStyle( + fontSize: 15.sp, + color: Colors.black87, + fontWeight: FontWeight.w600, + ), + unselectedLabelStyle: TextStyle( + fontSize: 13.sp, + color: Colors.black38, + fontWeight: FontWeight.w500, + ), + // indicatorPadding: EdgeInsets.symmetric( + // vertical: 5.w, + // horizontal: 15.w, + // ), + indicator: ATFixedWidthTabIndicator( + width: 15.w, + color: Color(0xFF9F44F9), + ), + indicatorSize: TabBarIndicatorSize.label, + dividerColor: Colors.transparent, + controller: _tabController, + tabs: _tabs, + ), + ], + ), + Expanded( + child: TabBarView( + controller: _tabController, + children: _pages, + ), + ), + ], + ), + ), + ), + ), + window.locale.languageCode == "ar" + ? SizedBox( + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + child: IgnorePointer( + child: VapView( + scaleType: ScaleType.centerCrop, + onViewCreated: (controller) {}, + ), + ), + ) + : SizedBox.shrink(), + ], + ); + } +} diff --git a/lib/chatvibe_features/home/coupon/coupon_route.dart b/lib/chatvibe_features/home/coupon/coupon_route.dart new file mode 100644 index 0000000..c7e33df --- /dev/null +++ b/lib/chatvibe_features/home/coupon/coupon_route.dart @@ -0,0 +1,37 @@ +import 'dart:convert'; + +import 'package:fluro/fluro.dart'; +import 'package:aslan/chatvibe_features/home/coupon/record/coupon_record_page.dart'; +import 'package:aslan/chatvibe_features/home/coupon/send/coupon_send_page.dart'; +import 'package:aslan/chatvibe_core/routes/at_router_init.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_prop_coupon_list_res.dart'; +import 'package:aslan/chatvibe_features/home/coupon/coupon_list_page.dart'; + +class CouponRoute implements ATIRouterProvider { + static String couponList = 'home/coupon/couponList'; + static String couponRecord = 'home/coupon/record/couponRecord'; + static String couponSend = 'home/coupon/create/couponSend'; + + @override + void initRouter(FluroRouter router) { + router.define( + couponList, + handler: Handler(handlerFunc: (_, params) => CouponListPage()), + ); + router.define( + couponRecord, + handler: Handler(handlerFunc: (_, params) => CouponRecordPage()), + ); + + router.define( + couponSend, + handler: Handler( + handlerFunc: (_, Map> params) { + String? couponJson = params['couponJson']?.first; + var sendCoupon = Records.fromJson(jsonDecode(couponJson??"")); + return CouponSendPage(sendCoupon: sendCoupon); + }, + ), + ); + } +} diff --git a/lib/chatvibe_features/home/coupon/coupon_tab_page.dart b/lib/chatvibe_features/home/coupon/coupon_tab_page.dart new file mode 100644 index 0000000..e2595bc --- /dev/null +++ b/lib/chatvibe_features/home/coupon/coupon_tab_page.dart @@ -0,0 +1,374 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_prop_coupon_list_res.dart'; +import 'package:aslan/chatvibe_ui/widgets/countdown_timer.dart'; +import 'package:aslan/chatvibe_features/home/coupon/coupon_route.dart'; + +class CouponTabPage extends ATPageList { + int? type; + + CouponTabPage({this.type}); + + @override + _CouponTabPageState createState() => _CouponTabPageState(type: type); +} + +class _CouponTabPageState extends ATPageListState { + int? type; + + _CouponTabPageState({this.type}); + + @override + void initState() { + super.initState(); + enablePullUp = false; + isGridView = true; + gridViewCount = 2; + padding = EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w); + backgroundColor = Colors.transparent; + gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridViewCount, + mainAxisSpacing: 8.w, + crossAxisSpacing: 8.w, + childAspectRatio: 1.5, + ); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: Directionality( + textDirection: TextDirection.ltr, + child: buildList(context), + ), + ); + } + + @override + Widget buildItem(Records res) { + return Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + res.couponType == 1 + ? "atu_images/coupon/at_icon_coupon_headdress_item_bg.png" + : (res.couponType == 2 + ? "atu_images/coupon/at_icon_coupon_mountains_item_bg.png" + : "atu_images/coupon/at_icon_coupon_vip_item_bg.png"), + ), + fit: BoxFit.contain, + ), + ), + child: Column( + children: [ + SizedBox(height: 12.w), + Row( + children: [ + SizedBox(width: 6.w), + netImage(url: res.propIcon ?? "", width: 52.w, height: 52.w), + SizedBox(width: 5.w), + Expanded( + child: Column( + children: [ + Row( + children: [ + Expanded( + child: text( + "${res.propName}*${res.propDays??0}${(res.propDays??0)>1?ATAppLocalizations.of(context)!.days:ATAppLocalizations.of(context)!.day}", + fontWeight: FontWeight.bold, + fontSize: 12.sp, + textColor: + res.couponType == 1 + ? Color(0xff002F89) + : (res.couponType == 2 + ? Color(0xff890000) + : Color(0xff893200)), + ), + ), + ], + ), + + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + CountdownTimer( + expiryDate: DateTime.fromMillisecondsSinceEpoch( + res.expireTime ?? 0, + ), + color: + res.couponType == 3 ? Colors.red : Colors.white, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 10.w), + ], + ), + ], + ), + ), + ], + ), + SizedBox(height: 22.w), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(width: 15.w), + GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + width: 55.w, + height: 25.w, + decoration: BoxDecoration( + color: Color(0xffF7D882), + borderRadius: BorderRadius.all(Radius.circular(22.w)), + ), + child: text( + ATAppLocalizations.of(context)!.use, + fontWeight: FontWeight.bold, + fontSize: 12.sp, + textColor: + res.couponType == 1 + ? Color(0xff002F89) + : (res.couponType == 2 + ? Color(0xff890000) + : Color(0xff893200)), + ), + ), + onTap: () { + _showUseConfirm(res); + }, + ), + SizedBox(width: 15.w), + GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + width: 55.w, + height: 25.w, + decoration: BoxDecoration( + color: Color(0xffF7D882), + borderRadius: BorderRadius.all(Radius.circular(22.w)), + ), + child: text( + ATAppLocalizations.of(context)!.send, + fontWeight: FontWeight.bold, + fontSize: 12.sp, + textColor: + res.couponType == 1 + ? Color(0xff002F89) + : (res.couponType == 2 + ? Color(0xff890000) + : Color(0xff893200)), + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${CouponRoute.couponSend}?couponJson=${Uri.encodeComponent(jsonEncode(res.toJson()))}", + ).then((result) { + loadData(1); + }); + }, + ), + SizedBox(width: 15.w), + ], + ), + ], + ), + ); + } + + void _showUseConfirm(Records res) { + SmartDialog.show( + tag: "showUseConfirm", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return Container( + margin: EdgeInsets.symmetric(horizontal: 25.w), + padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 15.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15.w), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 8.w), + text( + ATAppLocalizations.of(context)!.tips, + fontSize: 16.sp, + fontWeight: FontWeight.bold, + textColor: Colors.black, + ), + SizedBox(height: 10.w), + netImage(url: res.propIcon ?? "", width: 78.w, height: 78.w), + SizedBox(height: 15.w), + text( + "${res.propName}*${res.propDays}${ATAppLocalizations.of(context)!.days}", + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.black, + ), + SizedBox(height: 10.w), + text( + maxLines: 3, + ATAppLocalizations.of(context)!.useCoupontips, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.black54, + ), + SizedBox(height: 15.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + height: height(43), + alignment: Alignment.center, + child: Row( + children: [ + Expanded( + child: GestureDetector( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + border: Border.all( + color: Color(0xffE6E6E6), + width: 1.w, + ), + ), + alignment: Alignment.center, + child: text( + ATAppLocalizations.of(context)!.cancel, + fontSize: 14.sp, + textColor: Colors.black54, + fontWeight: FontWeight.bold, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showUseConfirm"); + }, + ), + ), + SizedBox(width: 15.w), + Expanded( + child: GestureDetector( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xffC670FF).withOpacity(0.6), + Color(0xff7726FF).withOpacity(0.6), + ], + ), + ), + alignment: Alignment.center, + child: text( + ATAppLocalizations.of(context)!.confirm, + fontSize: 14.sp, + textColor: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + onTap: () { + ATLoadingManager.exhibitOperation(); + AccountRepository() + .couponUse(res.couponNo ?? "") + .then((result) { + loadData(1); + ATLoadingManager.veilRoutine(); + ATTts.show( + ATAppLocalizations.of( + context, + )!.operationSuccessful, + ); + SmartDialog.dismiss(tag: "_showUseConfirm"); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + SmartDialog.dismiss(tag: "_showUseConfirm"); + }); + }, + ), + ), + ], + ), + ), + SizedBox(height: 15.w), + ], + ), + ); + }, + ); + } + + @override + empty() { + List list = []; + list.add(SizedBox(height: height(100))); + list.add( + Image.asset('atu_images/room/icon_room_music_empty.png', width: 120.w), + ); + list.add(SizedBox(height: height(15))); + list.add( + Text( + ATAppLocalizations.of(context)!.youDontHaveAnyCouponsYet, + style: TextStyle( + fontSize: sp(14), + color: Color(0xff999999), + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + height: 1, + ), + ), + ); + return Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: list, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var result = await AccountRepository().propCouponList( + pageCount, + page, + couponType: type, + ); + List list = []; + result.records?.forEach((rd) { + if (rd.status != 1) { + list.add(rd); + } + }); + onSuccess(list); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } +} diff --git a/lib/chatvibe_features/home/coupon/record/coupon_record_page.dart b/lib/chatvibe_features/home/coupon/record/coupon_record_page.dart new file mode 100644 index 0000000..11033ff --- /dev/null +++ b/lib/chatvibe_features/home/coupon/record/coupon_record_page.dart @@ -0,0 +1,191 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_prop_coupon_record_list_res.dart'; + +class CouponRecordPage extends ATPageList { + @override + _CouponRecordPageState createState() => _CouponRecordPageState(); +} + +class _CouponRecordPageState extends ATPageListState { + @override + void initState() { + super.initState(); + enablePullUp = true; + backgroundColor = Colors.transparent; + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/index/at_icon_coupon_head_bg.png", + width: ScreenUtil().screenWidth, + height: 150.w, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.couponRecord, + actions: [], + ), + body: SafeArea( + top: false, + child: Container( + margin: EdgeInsetsDirectional.only(top: 15.w), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadiusDirectional.only( + topStart: Radius.circular(35.w), + ), + ), + child: Column( + children: [ + SizedBox(height: 20.w), + Expanded(child: buildList(context)), + ], + ), + ), + ), + ), + ], + ); + } + + @override + builderDivider() { + // return Divider( + // height: 1.w, + // color: Color(0xff3D3277).withOpacity(0.5), + // indent: 15.w, + // ); + return Container(height: 8.w); + } + + @override + Widget buildItem(Records res) { + return GestureDetector( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 10.w), + padding: EdgeInsets.all(5.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(8.0)), + border: Border.all(color: Colors.black12, width: 1.w), + ), + child: Row( + children: [ + netImage(url: res.propIcon ?? "", width: 52.w, height: 52.w), + SizedBox(width: 5.w), + text( + maxLines: 1, + "${res.propName}*${res.propDays}${ATAppLocalizations.of(context)!.days}", + fontWeight: FontWeight.w600, + textColor: Colors.black, + fontSize: 14.sp, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Container( + constraints: BoxConstraints(maxWidth: 120.w), + margin: EdgeInsetsDirectional.only(end: 5.w), + child: text( + maxLines: 2, + res.actionType == 2 + ? ATAppLocalizations.of( + context, + )!.sendUserId("${res.receiverAccount}") + : ATAppLocalizations.of(context)!.use, + fontSize: 12.sp, + fontWeight: FontWeight.bold, + textColor: Colors.black38, + ), + ), + SizedBox(height: 5.w), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + text( + ATMDateUtils.formatDateTime2( + DateTime.fromMillisecondsSinceEpoch( + res.createTime ?? 0, + ), + ), + fontSize: 12.sp, + textColor: Colors.black38, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 5.w), + ], + ), + ], + ), + ), + ], + ), + ), + onTap: () {}, + ); + } + + @override + empty() { + List list = []; + list.add(SizedBox(height: height(100))); + list.add( + Image.asset('atu_images/room/at_icon_room_music_empty.png', width: 120.w), + ); + list.add(SizedBox(height: height(15))); + list.add( + Text( + ATAppLocalizations.of(context)!.noData, + style: TextStyle( + fontSize: sp(14), + color: Color(0xff999999), + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + height: 1, + ), + ), + ); + return Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: list, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var result = await AccountRepository().couponRecordList(pageCount, page); + onSuccess(result.records ?? []); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } +} diff --git a/lib/chatvibe_features/home/coupon/send/coupon_send_page.dart b/lib/chatvibe_features/home/coupon/send/coupon_send_page.dart new file mode 100644 index 0000000..745d60f --- /dev/null +++ b/lib/chatvibe_features/home/coupon/send/coupon_send_page.dart @@ -0,0 +1,395 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/chatvibe_gradient_button.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_prop_coupon_list_res.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +class CouponSendPage extends StatefulWidget { + Records? sendCoupon; + + CouponSendPage({super.key, this.sendCoupon}); + + @override + _CouponSendPageState createState() => _CouponSendPageState(); +} + +class _CouponSendPageState extends State + with SingleTickerProviderStateMixin { + final TextEditingController _textEditingController = TextEditingController(); + List users = []; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/index/at_icon_coupon_head_bg.png", + width: ScreenUtil().screenWidth, + height: 150.w, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.sendUser, + actions: [], + ), + body: SafeArea( + top: false, + child: Container( + margin: EdgeInsetsDirectional.only(top: 15.w), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadiusDirectional.only( + topStart: Radius.circular(35.w), + ), + ), + child: Column( + children: [ + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: searchWidget( + hint: ATAppLocalizations.of(context)!.searchUserId, + controller: _textEditingController, + borderColor: Colors.black12, + textColor: Colors.grey, + ), + ), + chatvibeGradientButton( + text: ATAppLocalizations.of(context)!.search, + radius: 25, + textSize: 14.sp, + textColor: Colors.black, + gradient: LinearGradient( + colors: [Colors.transparent, Colors.transparent], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + onPress: () { + if (_textEditingController.text.isNotEmpty) { + _searchUser(); + } + }, + ), + SizedBox(width: 10.w), + ], + ), + users.isNotEmpty + ? Expanded( + child: ListView.separated( + itemBuilder: (context, i) => buildItem(users[i]), + itemCount: users.length, + padding: EdgeInsets.zero, + separatorBuilder: + (context, i) => Divider( + height: height(1), + color: ChatVibeTheme.dividerColor, + indent: width(28), //25+50+10 + endIndent: width(15), + ), + ), + ) + : mainEmpty(), + ], + ), + ), + ), + ), + ], + ); + } + + Widget buildItem(ChatVibeUserProfile data) { + return Container( + padding: EdgeInsets.symmetric(vertical: 5.w), + margin: EdgeInsets.symmetric(horizontal: 25.w), + child: Row( + children: [ + GestureDetector( + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == data.id}&tageId=${data.id}", + ); + }, + behavior: HitTestBehavior.opaque, + child: head(url: data.userAvatar ?? "", width: 55.w), + ), + SizedBox(width: 2.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + xb( + data.userSex, + color: + data.userSex == 1 ? Colors.blue : Colors.purpleAccent, + ), + SizedBox(width: 5.w), + // richText( + // txt: data.userNickname ?? "", + // key: (widget as SearchUserList).text, + // highlight: ChatVibeTheme.primaryColor, + // defaultColor: Colors.black, + // ), + Expanded( + child: text( + data.userNickname ?? "", + fontSize: 15.sp, + textColor: Colors.black, + ), + ), + SizedBox(width: 5.w), + // dj(data.level.toString()), + ], + ), + Row( + children: [ + GestureDetector( + child: Container( + padding: EdgeInsets.symmetric(vertical: 8.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + (data.hasSpecialId() ?? false) + ? "atu_images/general/at_icon_special_id_bg.png" + : "atu_images/general/at_icon_id_bg.png", + ), + fit: BoxFit.fitWidth, + ), + ), + child: Row( + textDirection: TextDirection.ltr, + children: [ + SizedBox(width: 38.w), + text( + data.getID(), + fontSize: 12.sp, + textColor: Colors.white, + fontWeight: FontWeight.bold, + ), + SizedBox(width: 5.w), + Image.asset( + "atu_images/room/at_icon_user_card_copy_id.png", + width: 12.w, + height: 12.w, + ), + SizedBox(width: 8.w), + ], + ), + ), + onTap: () { + Clipboard.setData(ClipboardData(text: data.getID())); + ATTts.show( + ATAppLocalizations.of(context)!.copiedToClipboard, + ); + }, + ), + ], + ), + ], + ), + ), + GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + width: 55.w, + height: 25.w, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(22.w)), + border: Border.all(color: Colors.black12, width: 1.w), + ), + child: text( + ATAppLocalizations.of(context)!.send, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + textColor: Colors.black38, + ), + ), + onTap: () { + _showSendConfirm(data.id ?? ""); + }, + ), + SizedBox(width: 5.w), + ], + ), + ); + } + + void _searchUser() async { + try { + ATLoadingManager.exhibitOperation(); + users.clear(); + var result = await AccountRepository().searchUser( + _textEditingController.text, + ); + users.add(result); + ATLoadingManager.veilRoutine(); + setState(() {}); + } catch (e) { + ATLoadingManager.veilRoutine(); + } + } + + void _showSendConfirm(String userId) { + SmartDialog.show( + tag: "showSendConfirm", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return Container( + margin: EdgeInsets.symmetric(horizontal: 25.w), + padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 15.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15.w), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 8.w), + text( + ATAppLocalizations.of(context)!.tips, + fontSize: 16.sp, + fontWeight: FontWeight.bold, + textColor: Colors.black, + ), + SizedBox(height: 10.w), + netImage( + url: widget.sendCoupon?.propIcon ?? "", + width: 78.w, + height: 78.w, + ), + SizedBox(height: 15.w), + text( + "${widget.sendCoupon?.propName}*${widget.sendCoupon?.propDays}${ATAppLocalizations.of(context)!.days}", + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.black, + ), + SizedBox(height: 10.w), + text( + maxLines: 3, + ATAppLocalizations.of(context)!.sendCoupontips, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.black54, + ), + SizedBox(height: 15.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + height: height(43), + alignment: Alignment.center, + child: Row( + children: [ + Expanded( + child: GestureDetector( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + border: Border.all( + color: Color(0xffE6E6E6), + width: 1.w, + ), + ), + alignment: Alignment.center, + child: text( + ATAppLocalizations.of(context)!.cancel, + fontSize: 14.sp, + textColor: Colors.black54, + fontWeight: FontWeight.bold, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showSendConfirm"); + }, + ), + ), + SizedBox(width: 15.w), + Expanded( + child: GestureDetector( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xffC670FF).withOpacity(0.6), + Color(0xff7726FF).withOpacity(0.6), + ], + ), + ), + alignment: Alignment.center, + child: text( + ATAppLocalizations.of(context)!.confirm, + fontSize: 14.sp, + textColor: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + onTap: () { + ATLoadingManager.exhibitOperation(); + AccountRepository() + .couponSend( + widget.sendCoupon?.couponNo ?? "", + userId, + ) + .then((result) { + ATLoadingManager.veilRoutine(); + ATTts.show( + ATAppLocalizations.of( + context, + )!.operationSuccessful, + ); + SmartDialog.dismiss(tag: "showSendConfirm"); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + SmartDialog.dismiss(tag: "showSendConfirm"); + }); + }, + ), + ), + ], + ), + ), + SizedBox(height: 15.w), + ], + ), + ); + }, + ); + } +} diff --git a/lib/chatvibe_features/home/explore/explore_page.dart b/lib/chatvibe_features/home/explore/explore_page.dart new file mode 100644 index 0000000..044887b --- /dev/null +++ b/lib/chatvibe_features/home/explore/explore_page.dart @@ -0,0 +1,480 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; + +import 'package:aslan/chatvibe_ui/components/custom_cached_image.dart'; +import 'package:aslan/chatvibe_core/utilities/at_banner_utils.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_index_banner_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_res.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/country/at_country_flag_image.dart'; + +class ExplorePage extends StatefulWidget { + @override + _ExplorePageState createState() => _ExplorePageState(); +} + +class _ExplorePageState extends State + with SingleTickerProviderStateMixin { + bool _isDrawerOpen = false; + final RefreshController _refreshController = RefreshController( + initialRefresh: false, + ); + List rooms = []; + List drawerRooms = []; + bool isLoading = false; + + @override + void initState() { + super.initState(); + loadData(); + } + + @override + void dispose() { + super.dispose(); + } + + loadData() { + setState(() { + isLoading = true; + }); + ChatRoomRepository() + .discovery(allRegion: true) + .then((values) { + rooms = values; + drawerRooms.clear(); + if (rooms.length > 6) { + drawerRooms.addAll(rooms.sublist(6, rooms.length)); + } + isLoading = false; + _refreshController.refreshCompleted(); + _refreshController.loadComplete(); + if (mounted) setState(() {}); + }) + .catchError((e) { + _refreshController.loadNoData(); + _refreshController.refreshCompleted(); + isLoading = false; + if (mounted) setState(() {}); + }); + Provider.of(context, listen: false).loadMainBanner(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + body: SmartRefresher( + enablePullDown: true, + enablePullUp: false, + controller: _refreshController, + onRefresh: () { + loadData(); + }, + onLoading: () {}, + child: + rooms.isEmpty + ? GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + loadData(); + }, + child: + isLoading + ? Center(child: CupertinoActivityIndicator()) + : mainEmpty( + msg: ATAppLocalizations.of(context)!.noData, + ), + ) + : SingleChildScrollView( + child: Column( + children: [ + Row( + children: [ + SizedBox(width: 10.w), + text( + ATAppLocalizations.of(context)!.hotRooms, + fontSize: 14.sp, + textColor: Colors.black, + fontWeight: FontWeight.w600, + ), + ], + ), + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: + rooms.isNotEmpty + ? _buildItem(true, rooms[0]) + : Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12.w), + ), + ), + child: CustomCachedImage( + imageUrl: "", + fit: BoxFit.cover, + borderRadius: 12.w, + width: 230.w, + height: 230.w, + ), + ), + ), + SizedBox(width: 10.w), + Column( + children: [ + rooms.length > 1 + ? _buildItem(false, rooms[1]) + : Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12.w), + ), + ), + child: CustomCachedImage( + imageUrl: "", + fit: BoxFit.cover, + borderRadius: 12.w, + width: 109.w, + height: 109.w, + ), + ), + SizedBox(height: 10.w), + rooms.length > 2 + ? _buildItem(false, rooms[2]) + : Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12.w), + ), + ), + child: CustomCachedImage( + imageUrl: "", + fit: BoxFit.cover, + borderRadius: 12.w, + width: 109.w, + height: 109.w, + ), + ), + ], + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: + rooms.length > 3 + ? _buildItem(false, rooms[3]) + : Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12.w), + ), + ), + child: CustomCachedImage( + imageUrl: "", + fit: BoxFit.cover, + borderRadius: 12.w, + width: 109.w, + height: 109.w, + ), + ), + ), + SizedBox(width: 10.w), + Expanded( + child: + rooms.length > 4 + ? _buildItem(false, rooms[4]) + : Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12.w), + ), + ), + child: CustomCachedImage( + imageUrl: "", + fit: BoxFit.cover, + borderRadius: 12.w, + width: 109.w, + height: 109.w, + ), + ), + ), + SizedBox(width: 10.w), + Expanded( + child: + rooms.length > 5 + ? _buildItem(false, rooms[5]) + : Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12.w), + ), + ), + child: CustomCachedImage( + imageUrl: "", + fit: BoxFit.cover, + borderRadius: 12.w, + width: 109.w, + height: 109.w, + ), + ), + ), + SizedBox(width: 10.w), + ], + ), + _buildDrawerContent(), + _buildDrawerHandle(), + + Container( + alignment: AlignmentDirectional.centerStart, + margin: EdgeInsetsDirectional.only( + start: 10.w, + bottom: 10.w, + ), + child: text( + ATAppLocalizations.of(context)!.activity, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + textColor: Colors.black, + ), + ), + Selector>( + selector: + (_, provider) => + List.unmodifiable( + provider.exploreBanners, + ), + builder: (context, exploreBanners, child) { + return _banner(exploreBanners); + }, + ), + ], + ), + ), + ), + ); + } + + _banner(List exploreBanners) { + return exploreBanners.isNotEmpty + ? Container( + margin: EdgeInsets.symmetric(horizontal: 10.w), + child: ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: exploreBanners.length, + // 列表项数量 + padding: EdgeInsets.only(bottom: 15.w), + itemBuilder: (context, index) { + return GestureDetector( + onTap: () { + print('ads:${exploreBanners[index].toJson()}'); + ATBannerUtils.openBanner(exploreBanners[index], context); + }, + child: netImage( + height: 118.w, + url: exploreBanners[index].cover ?? "", + fit: BoxFit.fill, + borderRadius: BorderRadius.all(Radius.circular(12.w)), + ), + ); + }, + separatorBuilder: (BuildContext context, int index) { + return SizedBox(height: 10.w); + }, + ), + ) + : Container(); + } + + _buildItem(bool big, ChatVibeRoomRes res) { + return GestureDetector( + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(12.w)), + ), + width: big ? 230.w : 109.w, + height: big ? 230.w : 109.w, + ), + CustomCachedImage( + imageUrl: res.roomCover ?? "", + fit: BoxFit.cover, + borderRadius: 12.w, + width: big ? 230.w : 109.w, + height: big ? 230.w : 109.w, + ), + Container( + height: big ? 44.w : 30.w, + width: big ? 230.w : 109.w, + decoration: BoxDecoration( + color: Colors.black38, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(12.w), + bottomRight: Radius.circular(12.w), + ), + ), + child: Row( + children: [ + SizedBox(width: 10.w), + ATCountryFlagImage( + countryName: res.countryName, + width: big ? 20.w : 15.w, + height: big ? 13.w : 8.w, + borderRadius: BorderRadius.circular(big ? 2.w : 1.w), + ), + SizedBox(width: big ? 10.w : 5.w), + Expanded( + child: Container( + constraints: BoxConstraints(maxHeight: big ? 21.w : 18.w), + child: + (res.roomName?.length ?? 0) > 10 + ? Marquee( + text: res.roomName ?? "", + style: TextStyle( + fontSize: big ? 15.sp : 10.sp, + color: Colors.white, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration(milliseconds: 550), + decelerationCurve: Curves.easeOut, + ) + : Text( + res.roomName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: big ? 15.sp : 10.sp, + color: Colors.white, + decoration: TextDecoration.none, + ), + ), + ), + ), + + SizedBox(width: big ? 5.w : 3.w), + (res.extValues?.roomSetting?.password?.isEmpty ?? false) + ? Image.asset( + "atu_images/general/at_icon_online_user.gif", + width: big ? 14.w : 12.w, + height: big ? 14.w : 12.w, + ) + : Image.asset( + "atu_images/index/at_icon_room_suo.png", + width: big ? 20.w : 16.w, + height: big ? 20.w : 16.w, + ), + (res.extValues?.roomSetting?.password?.isEmpty ?? false) + ? SizedBox(width: 3.w) + : Container(height: 10.w), + (res.extValues?.roomSetting?.password?.isEmpty ?? false) + ? text( + res.extValues?.memberQuantity ?? "0", + textColor: Colors.white, + fontSize: big ? 12.sp : 10.sp, + ) + : Container(height: 10.w), + SizedBox(width: 10.w), + ], + ), + ), + ], + ), + onTap: () { + Provider.of( + context, + listen: false, + ).joinRoom(context, res.id ?? ""); + }, + ); + } + + void _toggleDrawer() { + setState(() { + _isDrawerOpen = !_isDrawerOpen; + }); + } + + Widget _buildDrawerHandle() { + return GestureDetector( + onTap: _toggleDrawer, + child: Container( + height: 45.w, + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.viewMore, + fontSize: 14.sp, + textColor: Colors.black38, + ), + AnimatedSwitcher( + duration: const Duration(milliseconds: 350), + child: + _isDrawerOpen + ? const Icon(Icons.keyboard_arrow_up, size: 20) + : const Icon(Icons.keyboard_arrow_down, size: 20), + ), + ], + ), + ), + ); + } + + Widget _buildDrawerContent() { + return _isDrawerOpen + ? (drawerRooms.isNotEmpty + ? GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + childAspectRatio: 1, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + ), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: EdgeInsets.all(10), + itemCount: drawerRooms.length, + itemBuilder: (context, index) { + return _buildItem(false, drawerRooms[index]); + }, + ) + : Container()) + : Container(); + } +} diff --git a/lib/chatvibe_features/home/explore/explore_page2.dart b/lib/chatvibe_features/home/explore/explore_page2.dart new file mode 100644 index 0000000..1311f11 --- /dev/null +++ b/lib/chatvibe_features/home/explore/explore_page2.dart @@ -0,0 +1,774 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_banner_utils.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_index_banner_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_res.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/explore_banner_view.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; +import 'package:aslan/chatvibe_ui/widgets/country/at_country_flag_image.dart'; + +class ExplorePage2 extends StatefulWidget { + @override + _ExplorePage2State createState() => _ExplorePage2State(); +} + +class _ExplorePage2State extends State + with SingleTickerProviderStateMixin { + bool _isDrawerOpen = false; + final RefreshController _refreshController = RefreshController( + initialRefresh: false, + ); + List rooms = []; + List drawerRooms = []; + bool isLoading = false; + bool isGrid = true; + + // 获取业务逻辑策略 + BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; + + @override + void initState() { + super.initState(); + loadData(); + } + + @override + void dispose() { + super.dispose(); + } + + loadData() { + setState(() { + isLoading = true; + }); + ChatRoomRepository() + .discovery(allRegion: true) + .then((values) { + rooms = values; + drawerRooms.clear(); + // 使用策略获取房间显示阈值 + final threshold = _strategy.getExploreRoomDisplayThreshold(); + if (rooms.length > threshold) { + drawerRooms.addAll(rooms.sublist(threshold, rooms.length)); + } + isLoading = false; + _refreshController.refreshCompleted(); + _refreshController.loadComplete(); + if (mounted) setState(() {}); + }) + .catchError((e) { + _refreshController.loadNoData(); + _refreshController.refreshCompleted(); + isLoading = false; + if (mounted) setState(() {}); + }); + Provider.of(context, listen: false).loadMainBanner(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + body: Stack( + alignment: AlignmentDirectional.bottomCenter, + children: [ + SmartRefresher( + enablePullDown: true, + enablePullUp: false, + controller: _refreshController, + onRefresh: () { + loadData(); + }, + onLoading: () {}, + child: + rooms.isEmpty + ? GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + loadData(); + }, + child: + isLoading + ? Center(child: CupertinoActivityIndicator()) + : mainEmpty( + msg: ATAppLocalizations.of(context)!.noData, + ), + ) + : SingleChildScrollView( + child: Column( + children: [ + Row( + children: [ + SizedBox(width: 10.w), + text( + ATAppLocalizations.of(context)!.hotRooms, + fontSize: 14.sp, + textColor: Colors.black, + fontWeight: FontWeight.w600, + ), + Spacer(), + ATDebounceWidget( + child: Image.asset( + isGrid + ? _strategy.getExploreGridIcon() + : _strategy.getExploreListIcon(), + height: 25.w, + ), + onTap: () { + setState(() { + isGrid = !isGrid; + }); + }, + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 10.w), + isGrid + ? Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: + rooms.isNotEmpty + ? _buildItem(true, rooms[0], 0) + : Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12.w), + ), + ), + child: SizedBox( + width: 230.w, + height: 230.w, + ), + ), + ), + SizedBox(width: 10.w), + Column( + children: [ + rooms.length > 1 + ? _buildItem(false, rooms[1], 1) + : Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12.w), + ), + ), + child: SizedBox( + width: 109.w, + height: 109.w, + ), + ), + SizedBox(height: 10.w), + rooms.length > 2 + ? _buildItem(false, rooms[2], 2) + : Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12.w), + ), + ), + child: SizedBox( + width: 109.w, + height: 109.w, + ), + ), + ], + ), + SizedBox(width: 10.w), + ], + ) + : Column( + spacing: 8.w, + children: [ + rooms.isNotEmpty + ? _buildItem(true, rooms[0], 0) + : SizedBox.shrink(), + rooms.length > 1 + ? _buildItem(false, rooms[1], 1) + : SizedBox.shrink(), + rooms.length > 2 + ? _buildItem(false, rooms[2], 2) + : SizedBox.shrink(), + ], + ), + SizedBox(height: 10.w), + isGrid + ? Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: + rooms.length > 3 + ? _buildItem(false, rooms[3], 3) + : Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12.w), + ), + ), + child: SizedBox( + width: 109.w, + height: 109.w, + ), + ), + ), + SizedBox(width: 10.w), + Expanded( + child: + rooms.length > 4 + ? _buildItem(false, rooms[4], 4) + : Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12.w), + ), + ), + child: SizedBox( + width: 109.w, + height: 109.w, + ), + ), + ), + SizedBox(width: 10.w), + Expanded( + child: + rooms.length > 5 + ? _buildItem(false, rooms[5], 5) + : Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12.w), + ), + ), + child: SizedBox( + width: 109.w, + height: 109.w, + ), + ), + ), + SizedBox(width: 10.w), + ], + ) + : Container(), + _buildDrawerContent(), + _buildDrawerHandle(), + Container( + alignment: AlignmentDirectional.centerStart, + margin: EdgeInsetsDirectional.only( + start: 10.w, + bottom: 10.w, + ), + child: text( + ATAppLocalizations.of(context)!.activity, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + textColor: Colors.black, + ), + ), + Selector>( + selector: + (_, provider) => + List.unmodifiable( + provider.exploreBanners, + ), + builder: (context, exploreBanners, child) { + return _banner(exploreBanners); + }, + ), + ], + ), + ), + ), + _isDrawerOpen + ? GestureDetector( + onTap: _toggleDrawer, + child: Container( + height: 45.w, + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.viewMore, + fontSize: 14.sp, + textColor: Colors.black54, + ), + AnimatedSwitcher( + duration: const Duration(milliseconds: 350), + child: const Icon(Icons.keyboard_arrow_up, size: 20), + ), + ], + ), + ), + ) + : Container(), + ], + ), + ); + } + + _banner(List exploreBanners) { + return exploreBanners.isNotEmpty + ? Container( + margin: EdgeInsets.symmetric(horizontal: 10.w), + child: ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: exploreBanners.length, + // 列表项数量 + padding: EdgeInsets.only(bottom: 15.w), + itemBuilder: (context, index) { + return GestureDetector( + onTap: () { + print('ads:${exploreBanners[index].toJson()}'); + ATBannerUtils.openBanner(exploreBanners[index], context); + }, + child: netImage( + height: 118.w, + url: exploreBanners[index].cover ?? "", + fit: BoxFit.fill, + borderRadius: BorderRadius.all(Radius.circular(12.w)), + ), + ); + }, + separatorBuilder: (BuildContext context, int index) { + return SizedBox(height: 10.w); + }, + ), + ) + : Container(); + } + + _buildItem(bool big, ChatVibeRoomRes res, int index) { + return GestureDetector( + child: + isGrid + ? Stack( + alignment: Alignment.bottomCenter, + children: [ + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(12.w)), + ), + width: big ? 230.w : 109.w, + height: big ? 230.w : 109.w, + ), + netImage( + url: res.roomCover ?? "", + fit: BoxFit.cover, + borderRadius: BorderRadius.all(Radius.circular(12.w)), + border: + index < 3 + ? null + : Border.all( + color: _strategy.getExploreRoomBorderColor(), + width: _strategy.getExploreRoomBorderWidth().w, + ), + width: big ? 230.w : 109.w, + height: big ? 230.w : 109.w, + ), + Container( + height: big ? 44.w : 30.w, + width: big ? 230.w : 109.w, + decoration: BoxDecoration( + color: Colors.black38, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(12.w), + bottomRight: Radius.circular(12.w), + ), + ), + child: Row( + children: [ + SizedBox(width: 10.w), + ATCountryFlagImage( + countryName: res.countryName, + width: big ? 20.w : 15.w, + height: big ? 13.w : 8.w, + borderRadius: BorderRadius.all( + Radius.circular(big ? 2.w : 1.w), + ), + ), + SizedBox(width: big ? 10.w : 5.w), + Expanded( + child: Container( + constraints: BoxConstraints( + maxHeight: big ? 21.w : 18.w, + ), + child: + (res.roomName?.length ?? 0) > + _strategy + .getExploreRoomNameMarqueeThreshold() + ? Marquee( + text: res.roomName ?? "", + style: TextStyle( + fontSize: big ? 15.sp : 10.sp, + color: Colors.white, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration( + seconds: 1, + ), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + res.roomName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: big ? 15.sp : 10.sp, + color: Colors.white, + decoration: TextDecoration.none, + ), + ), + ), + ), + + SizedBox(width: big ? 5.w : 3.w), + (res.extValues?.roomSetting?.password?.isEmpty ?? false) + ? Image.asset( + "atu_images/general/at_icon_online_user.png", + width: big ? 14.w : 12.w, + height: big ? 14.w : 12.w, + ) + : (_strategy.shouldShowPasswordRoomIcon() + ? Image.asset( + "atu_images/index/at_icon_room_suo.png", + width: big ? 20.w : 16.w, + height: big ? 20.w : 16.w, + ) + : Container( + width: big ? 20.w : 16.w, + height: big ? 20.w : 16.w, + )), + (res.extValues?.roomSetting?.password?.isEmpty ?? false) + ? SizedBox(width: 3.w) + : Container(height: 10.w), + (res.extValues?.roomSetting?.password?.isEmpty ?? false) + ? text( + res.extValues?.memberQuantity ?? "0", + textColor: Colors.white, + fontSize: big ? 12.sp : 10.sp, + ) + : Container(height: 10.w), + SizedBox(width: 10.w), + ], + ), + ), + index < 3 + ? IgnorePointer( + child: Image.asset( + _strategy.getExploreRankIconPattern(true, index + 1), + width: big ? 230.w : 109.w, + height: big ? 230.w : 109.w, + ), + ) + : Container(), + ], + ) + : SizedBox( + height: 105.w, + child: Stack( + alignment: Alignment.center, + children: [ + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(13.w), + border: + index < 3 + ? null + : Border.all( + color: _strategy.getExploreRoomBorderColor(), + width: + _strategy.getExploreRoomBorderWidth().w, + ), + ), + margin: EdgeInsetsDirectional.symmetric( + horizontal: 10.w, + vertical: 3.w, + ), + ), + Row( + children: [ + SizedBox(width: 20.w), + netImage( + url: res.roomCover ?? "", + fit: BoxFit.cover, + borderRadius: BorderRadius.all(Radius.circular(12.w)), + width: 85.w, + height: 85.w, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 15.w), + Row( + children: [ + SizedBox(width: 8.w), + ATCountryFlagImage( + countryName: res.countryName, + width: 25.w, + height: 15.w, + borderRadius: BorderRadius.all( + Radius.circular(3.w), + ), + ), + SizedBox(width: 5.w), + Container( + constraints: BoxConstraints( + maxHeight: 21.w, + maxWidth: 150.w, + ), + child: + (res.roomName?.length ?? 0) > + _strategy + .getExploreRoomNameMarqueeThreshold() + ? Marquee( + text: res.roomName ?? "", + style: TextStyle( + fontSize: 15.sp, + color: Colors.black, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration( + seconds: 1, + ), + accelerationDuration: Duration( + seconds: 1, + ), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + res.roomName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 15.sp, + color: Colors.black, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + SizedBox(height: 3.w), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(width: 8.w), + text( + "ID:${res.roomAccount}", + fontWeight: FontWeight.w600, + textColor: Colors.black, + fontSize: 15.sp, + ), + ], + ), + SizedBox(height: 3.w), + Row( + children: [ + SizedBox(width: 8.w), + Image.asset( + "atu_images/family/at_icon_announcement_tag.png", + height: 25.w, + width: 25.w, + ), + SizedBox(width: 4.w), + Container( + constraints: BoxConstraints( + maxHeight: 21.w, + maxWidth: 170.w, + ), + child: + (res.roomDesc?.length ?? 0) > + _strategy + .getExploreRoomDescMarqueeThreshold() + ? Marquee( + text: res.roomDesc ?? "", + style: TextStyle( + fontSize: 15.sp, + color: Colors.black, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration( + seconds: 1, + ), + accelerationDuration: Duration( + seconds: 1, + ), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + res.roomDesc ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 15.sp, + color: Colors.black, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + ], + ), + ], + ), + index < 3 + ? IgnorePointer( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 10.w), + child: Image.asset( + _strategy.getExploreRankIconPattern( + false, + index + 1, + ), + fit: BoxFit.fill, + ), + ), + ) + : Container(), + ], + ), + ), + onTap: () { + Provider.of( + context, + listen: false, + ).joinRoom(context, res.id ?? ""); + }, + ); + } + + void _toggleDrawer() { + setState(() { + _isDrawerOpen = !_isDrawerOpen; + }); + } + + Widget _buildDrawerHandle() { + return !_isDrawerOpen + ? GestureDetector( + onTap: _toggleDrawer, + child: Container( + height: 45.w, + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.viewMore, + fontSize: 14.sp, + textColor: Colors.black38, + ), + AnimatedSwitcher( + duration: const Duration(milliseconds: 350), + child: + _isDrawerOpen + ? const Icon(Icons.keyboard_arrow_up, size: 20) + : const Icon(Icons.keyboard_arrow_down, size: 20), + ), + ], + ), + ), + ) + : Container(); + } + + Widget _buildDrawerContent() { + return _isDrawerOpen + ? (drawerRooms.isNotEmpty + ? Column( + spacing: 8.w, + mainAxisSize: MainAxisSize.min, + children: [ + if (isGrid) SizedBox(height: 8.w), + ExploreBannerView(), + if (!isGrid && rooms.length > 3) _buildItem(false, rooms[3], 3), + if (!isGrid && rooms.length > 4) _buildItem(false, rooms[4], 4), + if (!isGrid && rooms.length > 5) _buildItem(false, rooms[5], 5), + isGrid + ? GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: + _strategy.getExploreDrawerGridCrossAxisCount(), + childAspectRatio: 1, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + ), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: EdgeInsets.all(10), + itemCount: drawerRooms.length, + itemBuilder: (context, index) { + return _buildItem(false, drawerRooms[index], index + 6); + }, + ) + : ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: drawerRooms.length, + itemBuilder: (context, index) { + return _buildItem(false, drawerRooms[index], index + 9); + }, + separatorBuilder: (BuildContext context, int index) { + return SizedBox(height: 8.w); + }, + ), + ], + ) + : Container()) + : Container(); + } +} diff --git a/lib/chatvibe_features/home/game/at_index_game_page.dart b/lib/chatvibe_features/home/game/at_index_game_page.dart new file mode 100644 index 0000000..a31ec8e --- /dev/null +++ b/lib/chatvibe_features/home/game/at_index_game_page.dart @@ -0,0 +1,453 @@ +import 'dart:ui' as ui; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_banner_utils.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/config_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_get_list_game_config_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_index_banner_res.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:provider/provider.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; + +import '../../../chatvibe_data/models/enum/at_game_origin_type.dart'; +import '../../../chatvibe_data/sources/repositories/room_repository_imp.dart'; +import '../../../chatvibe_domain/models/res/at_game_ranking_res.dart'; + +/// Game页面 +class ATIndexGamePage extends StatefulWidget { + const ATIndexGamePage({super.key}); + + @override + State createState() => _ATIndexGamePageState(); +} + +class _ATIndexGamePageState extends State { + static const Color _panelBorderColor = Color(0xffEDB745); + static const Color _titleTextColor = Color(0xcc000000); + + List games = []; + int _currentIndex = 0; + final RefreshController _refreshController = RefreshController( + initialRefresh: false, + ); + List gameRecords = []; + + @override + void initState() { + super.initState(); + loadGames(); + } + + @override + void dispose() { + _refreshController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.game, + actions: [], + showBackButton: false, + // leading: Transform.translate( + // offset: Offset(ATGlobalConfig.lang == "ar" ? -22.w : 22.w, 0), + // child: Transform.scale( + // scale: 1.43, + // child: GestureDetector( + // child: Consumer( + // builder: (context, ref, child) { + // return netImage( + // url: + // AccountStorage() + // .getCurrentUser() + // ?.userProfile + // ?.userAvatar ?? + // "", + // defaultImg: + // ATGlobalConfig.businessLogicStrategy + // .getMePageDefaultAvatarImage(), + // width: 32.w, + // height: 32.w, + // shape: BoxShape.circle, + // ); + // }, + // ), + // onTap: () { + // ATNavigatorUtils.push( + // context, + // "${MainRoute.person}?isMe=true&tageId=${AccountStorage().getCurrentUser()?.userProfile?.id}", + // ); + // }, + // ), + // ), + // ), + ), + body: SmartRefresher( + enablePullDown: true, + enablePullUp: false, + controller: _refreshController, + onRefresh: () { + loadGames(); + }, + onLoading: () {}, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Selector>( + selector: + (_, provider) => List.unmodifiable( + provider.gameBanners, + ), + builder: (context, gameBanners, child) { + return _banner(gameBanners); + }, + ), + SizedBox(height: 5.w), + _buildPlayLogSection(context), + SizedBox(height: 12.w), + _buildAllGamesSection(context), + SizedBox(height: 20.w), + ], + ), + ), + ), + ), + ], + ); + } + + Widget _buildPlayLogSection(BuildContext context) { + return _buildPanel( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildSectionHeader(ATAppLocalizations.of(context)!.playLog), + SizedBox(height: 10.w), + Container( + height: 50.w, + padding: EdgeInsets.symmetric(horizontal: 12.w), + child: + gameRecords.isEmpty + ? _buildCompactEmpty(context) + : LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: ConstrainedBox( + constraints: BoxConstraints( + minWidth: constraints.maxWidth, + ), + child: Row( + spacing: 5.w, + mainAxisAlignment: MainAxisAlignment.start, + children: List.generate(gameRecords.length, ( + index, + ) { + return _buildPlayLogItem(gameRecords[index]); + }), + ), + ), + ); + }, + ), + ), + SizedBox(height: 10.w), + ], + ), + ); + } + + Widget _buildAllGamesSection(BuildContext context) { + return _buildPanel( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildSectionHeader( + ATAppLocalizations.of(context)!.allGames, + showIndicator: true, + ), + SizedBox(height: 10.w), + games.isEmpty + ? Padding( + padding: EdgeInsets.only(bottom: 10.w), + child: _buildCompactEmpty(context), + ) + : GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 10.w), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + childAspectRatio: 1, + mainAxisSpacing: 5.w, + crossAxisSpacing: 5.w, + ), + itemCount: games.length, + itemBuilder: (context, index) { + return _buildGameGridItem(games[index]); + }, + ), + ], + ), + ); + } + + Widget _buildPanel({required Widget child}) { + return Container( + width: double.infinity, + margin: EdgeInsets.symmetric(horizontal: 10.w), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: _panelBorderColor, width: 1.w), + borderRadius: BorderRadius.circular(12.w), + ), + child: ClipRRect(borderRadius: BorderRadius.circular(1.w), child: child), + ); + } + + Widget _buildSectionHeader(String title, {bool showIndicator = false}) { + return Container( + height: 31.w, + width: double.infinity, + padding: EdgeInsets.symmetric(horizontal: 10.w), + child: Stack( + alignment: AlignmentDirectional.centerStart, + children: [ + Text( + title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: _titleTextColor, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + if (showIndicator) + PositionedDirectional( + start: 31.w, + bottom: 1.w, + child: Container( + width: 15.w, + height: 3.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(22.w), + color: const Color(0xffFF9500), + ), + ), + ), + ], + ), + ); + } + + Widget _buildPlayLogItem(ATGetListGameConfigRes game) { + return GestureDetector( + onTap: () { + openGame(game.gameOrigin ?? "", game.gameCode ?? "", game.id ?? ""); + }, + child: netImage( + url: game.cover ?? "", + width: 50.w, + height: 50.w, + fit: BoxFit.cover, + borderRadius: BorderRadius.circular(7.w), + border: Border.all(color: _panelBorderColor, width: 1.w), + ), + ); + } + + Widget _buildGameGridItem(Records game) { + return GestureDetector( + onTap: () { + openGame(game.gameOrigin ?? "", game.gameUrl ?? "", game.gameId ?? ""); + }, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 105.w, + height: 105.w, + child: Stack( + fit: StackFit.expand, + clipBehavior: Clip.none, + children: [ + netImage( + url: game.gameCover ?? "", + fit: BoxFit.cover, + borderRadius: BorderRadius.circular(14.w), + border: Border.all(color: _panelBorderColor, width: 2.w), + ), + if (game.latest ?? false) + PositionedDirectional( + start: 4.w, + bottom: 2.w, + child: Image.asset( + ATGlobalConfig.businessLogicStrategy.getGameNewTagImage(), + height: 22.w, + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildCompactEmpty(BuildContext context) { + return Center( + child: Text( + ATAppLocalizations.of(context)!.noData, + style: TextStyle( + color: Colors.black38, + fontSize: 12.sp, + fontWeight: FontWeight.w500, + decoration: TextDecoration.none, + ), + ), + ); + } + + _banner(List gameBanners) { + return gameBanners.isEmpty + ? Container() + : Stack( + alignment: AlignmentDirectional.bottomCenter, + children: [ + Container( + margin: EdgeInsets.symmetric(horizontal: 17.w), + child: CarouselSlider( + options: CarouselOptions( + height: 98.w, + autoPlay: true, + // 启用自动播放 + enlargeCenterPage: false, + // 居中放大当前页面 + enableInfiniteScroll: true, + // 启用无限循环 + autoPlayAnimationDuration: Duration(milliseconds: 800), + // 自动播放动画时长 + viewportFraction: 1, + // 视口分数 + onPageChanged: (index, reason) { + _currentIndex = index; + setState(() {}); + }, + ), + items: + gameBanners.map((item) { + return GestureDetector( + child: netImage( + url: item.cover ?? "", + height: 98.w, + width: ScreenUtil().screenWidth * 0.9, + fit: BoxFit.contain, + borderRadius: BorderRadius.circular(12.w), + ), + onTap: () { + print('ads:${item.toJson()}'); + ATBannerUtils.openBanner(item, context); + }, + ); + }).toList(), + ), + ), + + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: + gameBanners.asMap().entries.map((entry) { + return Container( + width: 6.0, + height: 6.0, + margin: EdgeInsets.symmetric( + vertical: 15.0, + horizontal: 4.0, + ), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: + _currentIndex == entry.key + ? Colors.blue + : Colors.white, + ), + ); + }).toList(), + ), + ], + ); + ; + } + + void loadGames() { + ATLoadingManager.exhibitOperation(); + games.clear(); + gameRecords.clear(); + Future.wait([ + ChatRoomRepository().gameLudoHistoryRecent("OUT_ROOM"), + ConfigRepositoryImp().gameRanking(1, "WEEK", size: 110), + ]) + .then((result) { + gameRecords = result[0] as List ?? []; + games = (result[1] as ATGameRankingRes).records ?? []; + _refreshController.refreshCompleted(); + _refreshController.loadComplete(); + if (mounted) setState(() {}); + ATLoadingManager.veilRoutine(); + }) + .catchError((e) { + _refreshController.loadNoData(); + _refreshController.refreshCompleted(); + if (mounted) setState(() {}); + ATLoadingManager.veilRoutine(); + }); + Provider.of(context, listen: false).loadMainBanner(); + } + + void openGame(String gameOrigin, String gameUrl, String gameId) { + if (gameOrigin == ATGameOriginType.LINGXIAN.name) { + ATNavigatorUtils.push( + context, + "${MainRoute.gameLxWebviewPage}?url=${Uri.encodeComponent(gameUrl)}&gameId=$gameId&height=-1&width=-1", + replace: false, + ); + } else if (gameOrigin == ATGameOriginType.BAISHUN.name) { + ATNavigatorUtils.push( + context, + "${MainRoute.gameBsWebviewPage}?url=${Uri.encodeComponent(gameUrl)}&gameId=$gameId&height=-1&width=-1", + replace: false, + ); + } else if (gameOrigin == ATGameOriginType.HOTGAME.name) { + ATNavigatorUtils.push( + context, + "${MainRoute.gameRyWebviewPage}?url=${Uri.encodeComponent(gameUrl)}&gameId=$gameId&height=-1&width=-1", + replace: false, + ); + } else if (gameOrigin == ATGameOriginType.YOMI.name) { + ATNavigatorUtils.push( + context, + "${MainRoute.gameYmWebviewPage}?url=${Uri.encodeComponent(gameUrl)}&gameId=$gameId&height=-1&width=-1", + replace: false, + ); + } + } +} diff --git a/lib/chatvibe_features/home/game/index_game_item_page.dart b/lib/chatvibe_features/home/game/index_game_item_page.dart new file mode 100644 index 0000000..9ff0e26 --- /dev/null +++ b/lib/chatvibe_features/home/game/index_game_item_page.dart @@ -0,0 +1,66 @@ +import 'dart:ui' as ui; +import 'package:flutter/cupertino.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/config_repository_imp.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_get_list_game_config_res.dart'; + +class IndexGameItemPage extends StatefulWidget { + String? category; + + IndexGameItemPage({this.category}); + + @override + _IndexGameItemPageState createState() => _IndexGameItemPageState(); +} + +class _IndexGameItemPageState extends State { + List games = []; + + @override + void initState() { + super.initState(); + ConfigRepositoryImp() + .getListGameConfig( + category: widget.category, + countryCode: ui.window.locale.countryCode, + ) + .then((result) { + games = result; + setState(() {}); + }) + .catchError((e) {}); + } + + @override + Widget build(BuildContext context) { + return games.isEmpty + ? mainEmpty(msg: ATAppLocalizations.of(context)!.noData) + : SingleChildScrollView( + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, // 每行2个 + childAspectRatio: 1, // 宽高比 + mainAxisSpacing: 10, + crossAxisSpacing: 10, + ), + padding: EdgeInsets.all(10), + itemCount: games.length, + itemBuilder: (context, index) { + return _buildItem(games[index]); + }, + ), + ); + } + + _buildItem(ATGetListGameConfigRes game) { + return netImage( + url: game.cover ?? "", + fit: BoxFit.fill, + borderRadius: BorderRadius.circular(12), + ); + } +} diff --git a/lib/chatvibe_features/home/honor/honor_by_type_page.dart b/lib/chatvibe_features/home/honor/honor_by_type_page.dart new file mode 100644 index 0000000..663bdaf --- /dev/null +++ b/lib/chatvibe_features/home/honor/honor_by_type_page.dart @@ -0,0 +1,184 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_material_badge_res.dart'; +import 'package:aslan/chatvibe_ui/widgets/countdown_timer.dart'; +import 'package:aslan/chatvibe_features/home/honor/honor_detail_dialog.dart'; + +class HonorByTypePage extends ATPageList { + String type = ""; + + HonorByTypePage(this.type); + + @override + _HonorByTypePageState createState() => _HonorByTypePageState(type); +} + +class _HonorByTypePageState + extends ATPageListState { + String type = ""; + + _HonorByTypePageState(this.type); + + @override + void initState() { + super.initState(); + enablePullUp = false; + isGridView = true; + gridViewCount = 2; + padding = EdgeInsets.symmetric(horizontal: 6.w).copyWith(bottom: 6.w); + backgroundColor = Colors.transparent; + gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridViewCount, + mainAxisSpacing: 8.w, + crossAxisSpacing: 4.w, + childAspectRatio: 1.0, + ); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: buildList(context), + ); + } + + @override + empty() { + return mainEmpty( + image: Image.asset( + 'atu_images/room/at_icon_room_music_empty.png', + width: 121.w, + fit: BoxFit.fitWidth, + ), + msg: ATAppLocalizations.of(context)!.youDontHaveAnyHonorYet, + textColor: Colors.white54, + ); + } + + @override + Widget buildItem(BadgeTables res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/index/at_icon_honor_item_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + SizedBox(height: (res.activation ?? false) ? 25.w : 45.w), + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: netImage( + url: + (res.activation ?? false) + ? res.badgeConfig?.selectUrl ?? "" + : res.badgeConfig?.notSelectUrl ?? "", + height: 35.w, + fit: BoxFit.contain, + ), + ), + SizedBox(width: 10.w), + ], + ), + + SizedBox(height: 15.w), + (res.activation ?? false) + ? Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 8.w), + Image.asset( + "atu_images/store/at_icon_bag_clock.png", + width: 22.w, + height: 22.w, + color: Colors.white, + ), + SizedBox(width: 5.w), + CountdownTimer( + color: Colors.white, + expiryDate: DateTime.fromMillisecondsSinceEpoch( + res.expireTime ?? 0, + ), + ), + SizedBox(width: 8.w), + ], + ) + : Container(), + SizedBox(height: 15.w), + Expanded( + child: Container( + margin: EdgeInsetsDirectional.symmetric(horizontal: 12.w), + alignment: AlignmentDirectional.center, + child: text( + maxLines: 2, + textAlign: TextAlign.center, + res.badgeConfig?.badgeName ?? "", + fontSize: 13.sp, + letterSpacing: 0.1, + lineHeight: 1, + fontWeight: FontWeight.w600, + textColor: + res.activation ?? false ? Colors.white : Colors.white54, + ), + ), + ), + SizedBox(height: (res.activation ?? false) ? 0.w : 35.w), + ], + ), + ), + onTap: () { + _showHonorDetail(res); + }, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var badgeListResult = await AccountRepository().materialBadge( + type: type, + ); + List list = []; + for (var badge in badgeListResult) { + if ((badge.badgeTables?.isNotEmpty ?? false)) { + list.addAll(badge.badgeTables!); + } + } + onSuccess(list); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + void _showHonorDetail(BadgeTables res) { + SmartDialog.show( + tag: "showHonorDetail", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return HonorDetailDialog(res); + }, + ); + } +} diff --git a/lib/chatvibe_features/home/honor/honor_detail_dialog.dart b/lib/chatvibe_features/home/honor/honor_detail_dialog.dart new file mode 100644 index 0000000..aaaf50f --- /dev/null +++ b/lib/chatvibe_features/home/honor/honor_detail_dialog.dart @@ -0,0 +1,108 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_path_utils.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_material_badge_res.dart'; + +import 'package:aslan/chatvibe_ui/widgets/headdress/headdress_widget.dart'; + +class HonorDetailDialog extends StatefulWidget { + BadgeTables res; + + HonorDetailDialog(this.res); + + @override + _HonorDetailDialogState createState() => _HonorDetailDialogState(); +} + +class _HonorDetailDialogState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Container( + width: 220.w, + height: 150.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/index/at_icon_honor_detail_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: + widget.res.badgeConfig?.animationUrl != null && + ATPathUtils.fileTypeIsPicOperation( + widget.res.badgeConfig?.animationUrl ?? "", + ) + ? netImage( + url: widget.res.badgeConfig?.animationUrl ?? "", + height: 35.w, + fit: BoxFit.contain, + ) + : (ATPathUtils.fetchFileExtensionFunction( + widget.res.badgeConfig?.animationUrl ?? + "", + ).toLowerCase() == + ".svga" + ? SVGAHeadwearWidget( + height: 35.w, + resource: + widget.res.badgeConfig?.animationUrl ?? + "", + ) + : netImage( + url: + widget.res.badgeConfig?.animationUrl ?? + "", + height: 35.w, + fit: BoxFit.contain, + )), + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 15.w), + Container( + margin: EdgeInsetsDirectional.symmetric(horizontal: 8.w), + alignment: AlignmentDirectional.center, + child: text( + textAlign: TextAlign.center, + maxLines: 2, + widget.res.badgeConfig?.badgeName ?? "", + fontSize: 14.sp, + fontWeight: FontWeight.bold, + textColor: CupertinoColors.white, + ), + ), + ], + ), + ), + Positioned( + right: 15.w, + top: 10.w, + child: GestureDetector( + child: Icon(Icons.close, size: 20.w, color: Color(0xFFF8E090)), + onTap: () { + SmartDialog.dismiss(tag: "showHonorDetail"); + }, + ), + ), + ], + ); + } +} diff --git a/lib/chatvibe_features/home/honor/honor_list_page.dart b/lib/chatvibe_features/home/honor/honor_list_page.dart new file mode 100644 index 0000000..f0fca2c --- /dev/null +++ b/lib/chatvibe_features/home/honor/honor_list_page.dart @@ -0,0 +1,244 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_features/home/honor/wear_honor_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_features/home/honor/honor_by_type_page.dart'; + +class HonorListPage extends StatefulWidget { + @override + _HonorListPageState createState() => _HonorListPageState(); +} + +class _HonorListPageState extends State { + int selectIndex = 0; + + // 使用 PageController 来管理页面切换 + final PageController _pageController = PageController(); + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/room/at_icon_honor_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + appBar: ChatVibeStandardAppBar( + backButtonColor: Colors.white, + title: ATAppLocalizations.of(context)!.honor, + actions: [], + ), + backgroundColor: Colors.transparent, + body: SafeArea( + top: false, + child: Container( + width: ScreenUtil().screenWidth, + margin: EdgeInsets.only(top: 25.w), + child: Column( + children: [ + Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/index/at_icon_honor_userinfo_bg.png", + ), + fit: BoxFit.fill, + ), + ), + height: 88.w, + child: Row( + children: [ + SizedBox(width: 10.w), + head( + url: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userAvatar ?? + "", + width: 55.w, + ), + SizedBox(width: 5.w), + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + chatvibeNickNameText( + maxWidth: 125.w, + fontWeight: FontWeight.bold, + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname ?? + "", + fontSize: 15.sp, + type: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.getVIP() + ?.name ?? + "", + needScroll: + (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname + ?.characters + .length ?? + 0) > + 10, + ), + SizedBox(height: 3.w), + ], + ), + Spacer(), + GestureDetector( + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 10.w, + vertical: 4.w, + ), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/index/at_icon_honor_userinfo_btn.png", + ), + ), + ), + child: text( + ATAppLocalizations.of(context)!.wearHonor, + fontSize: 14.sp, + textColor: Colors.white, + ), + ), + onTap: () { + _showWearHonor(); + }, + ), + SizedBox(width: 15.w), + ], + ), + ), + SizedBox(height: 15.w), + Expanded( + child: Stack( + children: [ + Transform.flip( + flipX: selectIndex == 0 ? false : true, // 水平翻转 + flipY: false, // 垂直翻转设为 false + child: Image.asset( + "atu_images/room/at_icon_honor_content_bg.png", + width: ScreenUtil().screenWidth, + fit: BoxFit.fill, + ), + ), + Row( + textDirection: TextDirection.ltr, + children: [ + Expanded( + child: GestureDetector( + child: Container( + alignment: Alignment.center, + margin: EdgeInsets.only( + top: selectIndex == 0 ? 0 : 15.w, + ), + height: 28.w, + child: text( + ATAppLocalizations.of(context)!.activityHonor, + fontSize: selectIndex == 0 ? 18.sp : 13.sp, + textColor: Colors.white, + fontWeight: + selectIndex == 0 + ? FontWeight.bold + : FontWeight.w600, + ), + ), + onTap: () { + selectIndex = 0; + setState(() {}); + _pageController.jumpToPage(0); + }, + ), + ), + SizedBox(width: 15.w), + Expanded( + child: GestureDetector( + child: Container( + alignment: Alignment.center, + margin: EdgeInsets.only( + top: selectIndex == 1 ? 0 : 15.w, + ), + height: 28.w, + child: text( + ATAppLocalizations.of(context)!.identity, + fontSize: selectIndex == 1 ? 18.sp : 13.sp, + textColor: Colors.white, + fontWeight: + selectIndex == 1 + ? FontWeight.bold + : FontWeight.w600, + ), + ), + onTap: () { + selectIndex = 1; + setState(() {}); + _pageController.jumpToPage(1); + }, + ), + ), + ], + ), + + Container( + margin: EdgeInsets.only(top: 55.w), + child: Directionality( + textDirection: TextDirection.ltr, + child: PageView( + controller: _pageController, + physics: NeverScrollableScrollPhysics(), // 禁用滑动 + children: [ + HonorByTypePage("HONOR_ACTIVITY"), + HonorByTypePage("HONOR_ADMIN"), + ], + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ], + ); + } + + void _showWearHonor() { + SmartDialog.show( + tag: "showWearHonor", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return WearHonorDialog(); + }, + ); + } +} diff --git a/lib/chatvibe_features/home/honor/wear_honor_dialog.dart b/lib/chatvibe_features/home/honor/wear_honor_dialog.dart new file mode 100644 index 0000000..96d8db6 --- /dev/null +++ b/lib/chatvibe_features/home/honor/wear_honor_dialog.dart @@ -0,0 +1,218 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/countdown_timer.dart'; + +class WearHonorDialog extends StatefulWidget { + @override + _WearHonorDialogState createState() => _WearHonorDialogState(); +} + +class _WearHonorDialogState extends State { + List wearBadgeList = []; + + @override + void initState() { + super.initState(); + _loadData(); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: Container( + height: 420.w, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/index/at_icon_wear_honor_dialog_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 12.w), + text( + ATAppLocalizations.of(context)!.wearHonor, + fontSize: 15.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + SizedBox(height: 5.w), + Expanded( + child: + wearBadgeList.isNotEmpty + ? GridView.builder( + shrinkWrap: true, + padding: EdgeInsets.symmetric(horizontal: 12.w), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 1.1, + mainAxisSpacing: 8.w, + crossAxisSpacing: 8.w, + ), + itemCount: wearBadgeList.length, + itemBuilder: (BuildContext context, int index) { + return _bagItem(wearBadgeList[index]); + }, + ) + : mainEmpty(textColor: Colors.white), + ), + SizedBox(height: 6.w), + ], + ), + ), + ); + } + + Widget _bagItem(WearBadge wearBadge) { + return Container( + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + (wearBadge.use ?? false) + ? "atu_images/index/at_icon_wear_honor_dialog_item_un_use.png" + : "atu_images/index/at_icon_wear_honor_dialog_item_on_use.png", + ), + fit: BoxFit.fill, + ), + ), + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + Column( + children: [ + SizedBox(height: 25.w), + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: netImage( + url: wearBadge.selectUrl ?? "", + height: 35.w, + fit: BoxFit.contain, + ), + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 10.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 8.w), + Image.asset( + "atu_images/store/at_icon_bag_clock.png", + width: 22.w, + height: 22.w, + color: Colors.white, + ), + SizedBox(width: 5.w), + CountdownTimer( + color: Colors.white, + expiryDate: DateTime.fromMillisecondsSinceEpoch( + wearBadge.expireTime ?? 0, + ), + ), + SizedBox(width: 8.w), + ], + ), + ], + ), + PositionedDirectional( + bottom: 3.w, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + width: 120.w, + padding: EdgeInsets.symmetric(vertical: 2.w), + alignment: AlignmentDirectional.center, + child: text( + (wearBadge.use ?? false) + ? ATAppLocalizations.of(context)!.inUse + : ATAppLocalizations.of(context)!.use, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + textColor: Colors.white, + ), + ), + onTap: () { + _badgeToggle(wearBadge); + }, + ), + ), + ], + ), + ); + } + + void _badgeToggle(WearBadge wearBadge) { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + (wearBadge.use ?? false) + ? ATAppLocalizations.of(context)!.confirmUnUseTips + : ATAppLocalizations.of(context)!.confirmUseTips, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + _use(wearBadge, wearBadge.use ?? false); + }, + ); + }, + ); + } + + void _loadData() { + AccountRepository() + .badgeOwnList("HONOR") + .then((result) { + wearBadgeList = result; + ATLoadingManager.veilRoutine(); + setState(() {}); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } + + void _use(WearBadge wearBadge, bool unload) { + ATLoadingManager.exhibitOperation(); + AccountRepository() + .badgeToggle(wearBadge.id ?? "") + .then((b) { + _loadData(); + Provider.of( + context, + listen: false, + ).getMyUserInfo(needLoadUserCountGuard: false); + if (!unload) { + ATTts.show(ATAppLocalizations.of(context)!.successfulWear); + } else { + ATTts.show(ATAppLocalizations.of(context)!.successfullyUnloaded); + } + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } +} diff --git a/lib/chatvibe_features/home/index_home_page.dart b/lib/chatvibe_features/home/index_home_page.dart new file mode 100644 index 0000000..12bc720 --- /dev/null +++ b/lib/chatvibe_features/home/index_home_page.dart @@ -0,0 +1,240 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_ui/widgets/countdown_timer.dart'; + +import '../../chatvibe_managers/room_manager.dart'; + +///Home页面 +class ATIndexHomePage extends StatefulWidget { + @override + _ATIndexHomePageState createState() => _ATIndexHomePageState(); +} + +class _ATIndexHomePageState extends State + with TickerProviderStateMixin { + TabController? _tabController; + final List _pages = []; + final List _tabs = []; + bool refresh = false; // 👈 改为 false,初始显示加载 + + @override + void initState() { + super.initState(); + // 在构建完成后加载数据 + WidgetsBinding.instance.addPostFrameCallback((_) { + _updateTabs(); + // 监听家族状态变化 + Provider.of( + context, + listen: false, + ).bindFamilyUpdate(() { + _updateTabs(); + // 数据更新后短暂显示加载,然后恢复(可根据需要调整) + setState(() { + refresh = false; + }); + Future.delayed(Duration(milliseconds: 550), () { + if (mounted) { + setState(() { + refresh = true; + }); + } + }); + }); + }); + Provider.of(context, listen: false).getMyRoom(); + } + + void _updateTabs() { + _tabController?.dispose(); + _pages.clear(); + + final strategy = ATGlobalConfig.businessLogicStrategy; + _pages.addAll(strategy.getHomeTabPages(context)); + + _tabController = TabController( + length: _pages.length, + vsync: this, + initialIndex: strategy.getHomeInitialTabIndex(), + ); + // 可选:监听切换事件 + _tabController?.addListener(() {}); + + // 数据更新完成后,刷新界面 + if (mounted) { + setState(() { + refresh = true; + }); + } + } + + @override + void dispose() { + _tabController?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + // 如果数据未准备好,显示加载指示器 + if (!refresh || _tabController == null || _pages.isEmpty) { + return Center( + child: Container( + decoration: BoxDecoration( + color: Colors.black26, + borderRadius: BorderRadius.circular(8), + ), + width: 55.w, + height: 55.w, + child: const CupertinoActivityIndicator(), + ), + ); + } + + // 正常渲染界面 + _tabs.clear(); + final strategy = ATGlobalConfig.businessLogicStrategy; + _tabs.addAll(strategy.getHomeTabLabels(context)); + + return Stack( + children: [ + Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + body: Column( + children: [ + // 自定义顶部栏(包含状态栏和 TabBar) + SafeArea( + child: Row( + children: [ + // TabBar 直接放在这里 + TabBar( + labelColor: Colors.white, + tabAlignment: + ATGlobalConfig.businessLogicStrategy + .shouldShowFamilyTab() + ? TabAlignment.start + : null, + isScrollable: + ATGlobalConfig.businessLogicStrategy + .shouldShowFamilyTab(), + splashFactory: NoSplash.splashFactory, + overlayColor: MaterialStateProperty.all( + Colors.transparent, + ), + indicator: const BoxDecoration(), + unselectedLabelColor: Colors.white, + labelStyle: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 17.sp, + ), + unselectedLabelStyle: TextStyle( + fontWeight: FontWeight.normal, + fontSize: 14.sp, + ), + indicatorColor: Colors.transparent, + dividerColor: Colors.transparent, + controller: _tabController, + tabs: _tabs, + ), + Spacer(), + Container( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + icon: Image.asset( + "atu_images/index/at_icon_serach.png", + width: 20.w, + height: 20.w, + ), + onPressed: + () => ATNavigatorUtils.push( + context, + MainRoute.mainSearch, + ), + ), + ], + ), + ), + ], + ), + ), + // TabBarView 直接作为剩余空间 + Expanded( + child: TabBarView(controller: _tabController, children: _pages), + ), + ], + ), + ), + Selector( + selector: + (_, __) => + ATGlobalConfig.businessLogicStrategy + .shouldShowFirstRechargePrompt(), + builder: (context, shouldShowFirstRechargePrompt, child) { + if (ATGlobalConfig.isReview) return Container(); + final strategy = ATGlobalConfig.businessLogicStrategy; + if (shouldShowFirstRechargePrompt) { + final position = strategy.getFirstRechargePosition(); + return PositionedDirectional( + top: position['top'], + bottom: position['bottom'], + start: position['start'], + end: position['end'], + child: GestureDetector( + child: Column( + children: [ + Image.asset( + "atu_images/index/at_icon_first_recharge_tag.png", + height: 70.w, + ), + Transform.translate( + offset: Offset(0, -10), + child: Container( + height: 23.w, + width: 100.w, + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/index/at_icon_first_recharge_btn.png", + ), + fit: BoxFit.fill, + ), + ), + child: CountdownTimer( + expiryDate: DateTime.fromMillisecondsSinceEpoch( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.firstRechargeEndTime ?? + 0, + ), + color: Colors.white, + ), + ), + ), + ], + ), + onTap: () { + strategy.onFirstRechargeTap(context); + }, + ), + ); + } + return Container(); + }, + ), + ], + ); + } +} diff --git a/lib/chatvibe_features/home/medals/badge_detail_dialog.dart b/lib/chatvibe_features/home/medals/badge_detail_dialog.dart new file mode 100644 index 0000000..776355c --- /dev/null +++ b/lib/chatvibe_features/home/medals/badge_detail_dialog.dart @@ -0,0 +1,96 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_path_utils.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_material_badge_res.dart'; + +import 'package:aslan/chatvibe_ui/widgets/headdress/headdress_widget.dart'; + +class BadgeDetailDialog extends StatefulWidget { + BadgeTables res; + + BadgeDetailDialog(this.res); + + @override + _BadgeDetailDialogState createState() => _BadgeDetailDialogState(); +} + +class _BadgeDetailDialogState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Container( + width: 220.w, + height: 220.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/index/at_icon_medal_detail_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + widget.res.badgeConfig?.animationUrl != null && + ATPathUtils.fileTypeIsPicOperation( + widget.res.badgeConfig?.animationUrl ?? "", + ) + ? netImage( + url: widget.res.badgeConfig?.animationUrl ?? "", + width: 120, + height: 120, + ) + : (ATPathUtils.fetchFileExtensionFunction( + widget.res.badgeConfig?.animationUrl ?? "", + ).toLowerCase() == + ".svga" + ? SVGAHeadwearWidget( + width: 120.w, + height: 120.w, + resource: widget.res.badgeConfig?.animationUrl ?? "", + ) + : netImage( + url: widget.res.badgeConfig?.animationUrl ?? "", + width: 120.w, + height: 120.w, + )), + + SizedBox(height: 5.w), + Container( + margin: EdgeInsetsDirectional.symmetric(horizontal: 8.w), + alignment: AlignmentDirectional.center, + child: text( + textAlign: TextAlign.center, + maxLines: 2, + widget.res.badgeConfig?.badgeName ?? "", + fontSize: 14.sp, + fontWeight: FontWeight.bold, + textColor: CupertinoColors.white, + ), + ), + ], + ), + ), + Positioned( + right: 15.w, + top: 10.w, + child: GestureDetector( + child: Icon(Icons.close, size: 20.w, color: Color(0xFFF8E090)), + onTap: () { + SmartDialog.dismiss(tag: "showBadgeDetail"); + }, + ), + ), + ], + ); + } +} diff --git a/lib/chatvibe_features/home/medals/medal_by_type_page.dart b/lib/chatvibe_features/home/medals/medal_by_type_page.dart new file mode 100644 index 0000000..3d4e2dc --- /dev/null +++ b/lib/chatvibe_features/home/medals/medal_by_type_page.dart @@ -0,0 +1,143 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_material_badge_res.dart'; +import 'package:aslan/chatvibe_features/home/medals/badge_detail_dialog.dart'; + +class MedalByTypePage extends ATPageList { + String type = ""; + + MedalByTypePage(this.type); + + @override + _MedalByTypePageState createState() => _MedalByTypePageState(type); +} + +class _MedalByTypePageState + extends ATPageListState { + String type = ""; + + _MedalByTypePageState(this.type); + + @override + void initState() { + super.initState(); + enablePullUp = false; + isGridView = true; + gridViewCount = 2; + padding = EdgeInsets.symmetric(horizontal: 6.w).copyWith(bottom: 6.w); + backgroundColor = Colors.transparent; + gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridViewCount, + mainAxisSpacing: 8.w, + crossAxisSpacing: 4.w, + childAspectRatio: 0.88, + ); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: buildList(context), + ); + } + + @override + empty() { + return mainEmpty( + image: Image.asset( + 'atu_images/general/at_icon_loading.png', + width: 121.w, + fit: BoxFit.fitWidth, + ), + msg: ATAppLocalizations.of(context)!.noData, + textColor: Colors.white54, + ); + } + + @override + Widget buildItem(BadgeTables res) { + return GestureDetector( + child: Column( + children: [ + Stack( + alignment: Alignment.center, + children: [ + Image.asset( + res.activation ?? false + ? "atu_images/index/at_icon_medals_en.png" + : "atu_images/index/at_icon_medals_no.png", + height: 160.w, + ), + netImage( + width: 75.w, + url: + res.activation ?? false + ? res.badgeConfig?.selectUrl ?? "" + : res.badgeConfig?.notSelectUrl ?? "", + ), + ], + ), + SizedBox(height: 5.w), + Expanded( + child: Container( + margin: EdgeInsetsDirectional.symmetric(horizontal: 12.w), + alignment: AlignmentDirectional.center, + child: text( + maxLines: 2, + letterSpacing: 0.1, + lineHeight: 1, + textAlign: TextAlign.center, + res.badgeConfig?.badgeName ?? "", + fontSize: 13.sp, + fontWeight: FontWeight.w600, + textColor: + res.activation ?? false ? Colors.white : Colors.white54, + ), + ), + ), + ], + ), + onTap: () { + _showDetail(res); + }, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var badgeList = await AccountRepository().materialBadge(type: type); + onSuccess(badgeList.first.badgeTables ?? []); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + void _showDetail(BadgeTables res) { + SmartDialog.show( + tag: "showBadgeDetail", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return BadgeDetailDialog(res); + }, + ); + } +} diff --git a/lib/chatvibe_features/home/medals/medals_list_page.dart b/lib/chatvibe_features/home/medals/medals_list_page.dart new file mode 100644 index 0000000..12f5b79 --- /dev/null +++ b/lib/chatvibe_features/home/medals/medals_list_page.dart @@ -0,0 +1,270 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_features/home/medals/wear_medal_dialog.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_domain/usecases/at_fixed_width_tabIndicator.dart'; +import 'package:aslan/chatvibe_features/home/medals/medal_by_type_page.dart'; + +import '../../../chatvibe_data/models/enum/at_badge_type.dart'; + +///勋章 +class MedalsListPage extends StatefulWidget { + @override + _MedalsListPageState createState() => _MedalsListPageState(); +} + +class _MedalsListPageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + final List _pages = [ + MedalByTypePage(ATBadgeType.ACTIVITY.name), + MedalByTypePage(ATBadgeType.ACHIEVEMENT.name), + ]; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: _pages.length, vsync: this); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/index/at_icon_medals_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + backButtonColor: Colors.white, + title: ATAppLocalizations.of(context)!.medals, + actions: [], + ), + body: SafeArea( + top: false, + child: Column( + children: [ + SizedBox(height: 35.w), + Consumer( + builder: (context, ref, child) { + return Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/index/at_icon_medals_userinfo_bg.png", + ), + fit: BoxFit.fill, + ), + ), + height: 88.w, + child: Row( + children: [ + SizedBox(width: 10.w), + head( + url: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userAvatar ?? + "", + width: 55.w, + ), + SizedBox(width: 5.w), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + chatvibeNickNameText( + maxWidth: 125.w, + fontWeight: FontWeight.bold, + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname ?? + "", + fontSize: 15.sp, + type: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.getVIP() + ?.name ?? + "", + needScroll: + (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname + ?.characters + .length ?? + 0) > + 10, + ), + SizedBox(height: 3.w), + Row( + children: [ + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.wearBadge + ?.isNotEmpty ?? + false + ? SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SizedBox( + width: 204.w, + height: 25.w, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.wearBadge + ?.length ?? + 0, + itemBuilder: (context, index) { + return netImage( + width: 25.w, + height: 25.w, + url: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.wearBadge?[index] + .selectUrl ?? + "", + ); + }, + separatorBuilder: ( + BuildContext context, + int index, + ) { + return SizedBox(width: 5.w); + }, + ), + ), + ) + : Container(), + ], + ), + Row( + children: [ + text( + ATAppLocalizations.of(context)!.credits( + "${AccountStorage().getCurrentUser()?.userProfile?.wearBadge?.length??0}", + ), + fontSize: 14.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + Spacer(), + GestureDetector( + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 10.w, + vertical: 4.w, + ), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/index/at_icon_honor_userinfo_btn.png", + ), + ), + ), + child: text( + ATAppLocalizations.of(context)!.wearMedal, + fontSize: 14.sp, + textColor: Colors.white, + ), + ), + onTap: () { + _showWearMedal(); + }, + ), + SizedBox(width: 15.w), + ], + ), + ], + ), + ), + ], + ), + ); + }, + ), + SizedBox(height: 28.w), + Row( + children: [ + SizedBox(width: 5.w), + Expanded( + child: TabBar( + tabs: + [ + ATAppLocalizations.of(context)!.activityMedal, + ATAppLocalizations.of(context)!.achievementMedal, + ].map((e) => Tab(text: e)).toList(), + indicator: ATFixedWidthTabIndicator( + width: 40.w, + color: Color(0xFFFED864), + ), + //indicatorColor: Color(0xFF9F44F9), + indicatorSize: TabBarIndicatorSize.label, + labelPadding: EdgeInsetsDirectional.only( + start: 10, + end: 10, + ), + //Tab之间的间距,默认是kTabLabelPadding + isScrollable: false, + controller: _tabController, + labelColor: Color(0xFFFED864), + dividerColor: Colors.transparent, + unselectedLabelColor: Colors.white54, + labelStyle: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + ), + unselectedLabelStyle: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + ), + ), + SizedBox(width: 5.w), + ], + ), + Expanded( + child: TabBarView(controller: _tabController, children: _pages), + ), + ], + )), + ), + ], + ); + } + + void _showWearMedal() { + SmartDialog.show( + tag: "showWearMedal", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return WearMedalDialog(); + }, + ); + } +} diff --git a/lib/chatvibe_features/home/medals/wear_medal_dialog.dart b/lib/chatvibe_features/home/medals/wear_medal_dialog.dart new file mode 100644 index 0000000..5d2bdf2 --- /dev/null +++ b/lib/chatvibe_features/home/medals/wear_medal_dialog.dart @@ -0,0 +1,218 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/countdown_timer.dart'; + +class WearMedalDialog extends StatefulWidget { + @override + _WearMedalDialogState createState() => _WearMedalDialogState(); +} + +class _WearMedalDialogState extends State { + List wearBadgeList = []; + + @override + void initState() { + super.initState(); + _loadData(); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: Container( + height: 420.w, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/index/at_icon_wear_honor_dialog_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 12.w), + text( + ATAppLocalizations.of(context)!.wearMedal, + fontSize: 15.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + SizedBox(height: 5.w), + Expanded( + child: + wearBadgeList.isNotEmpty + ? GridView.builder( + shrinkWrap: true, + padding: EdgeInsets.symmetric(horizontal: 12.w), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 0.88, + mainAxisSpacing: 8.w, + crossAxisSpacing: 8.w, + ), + itemCount: wearBadgeList.length, + itemBuilder: (BuildContext context, int index) { + return _bagItem(wearBadgeList[index]); + }, + ) + : mainEmpty(textColor: Colors.white), + ), + SizedBox(height: 6.w,) + ], + ), + )); + } + + Widget _bagItem(WearBadge wearBadge) { + return Container( + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + (wearBadge.use ?? false) + ? "atu_images/index/at_icon_wear_honor_dialog_item_un_use.png" + : "atu_images/index/at_icon_wear_honor_dialog_item_on_use.png", + ), + fit: BoxFit.fill, + ), + ), + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + Column( + children: [ + SizedBox(height: 15.w), + netImage(url: wearBadge.selectUrl ?? "", width: 75.w), + SizedBox(height: 5.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 8.w), + Image.asset( + "atu_images/store/at_icon_bag_clock.png", + width: 22.w, + height: 22.w, + color: Colors.white, + ), + SizedBox(width: 5.w), + CountdownTimer( + color: Colors.white, + expiryDate: DateTime.fromMillisecondsSinceEpoch( + wearBadge.expireTime ?? 0, + ), + ), + SizedBox(width: 8.w), + ], + ), + SizedBox(height: 5.w), + Container( + margin: EdgeInsetsDirectional.symmetric(horizontal: 12.w), + alignment: AlignmentDirectional.center, + child: text( + maxLines: 2, + textAlign: TextAlign.center, + wearBadge.badgeName ?? "", + fontSize: 13.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + ], + ), + PositionedDirectional( + bottom: 5.w, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + width: 120.w, + padding: EdgeInsets.symmetric(vertical: 3.w), + alignment: AlignmentDirectional.center, + child: text( + (wearBadge.use ?? false) + ? ATAppLocalizations.of(context)!.inUse + : ATAppLocalizations.of(context)!.use, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + textColor: Colors.white, + ), + ), + onTap: () { + _badgeToggle(wearBadge); + }, + ), + ), + ], + ), + ); + } + + void _badgeToggle(WearBadge wearBadge) { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + (wearBadge.use ?? false) + ? ATAppLocalizations.of(context)!.confirmUnUseTips + : ATAppLocalizations.of(context)!.confirmUseTips, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + _use(wearBadge, wearBadge.use ?? false); + }, + ); + }, + ); + } + + void _loadData() { + AccountRepository() + .badgeOwnList("") + .then((result) { + wearBadgeList = result; + ATLoadingManager.veilRoutine(); + setState(() {}); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } + + void _use(WearBadge wearBadge, bool unload) { + ATLoadingManager.exhibitOperation(); + AccountRepository() + .badgeToggle(wearBadge.id ?? "") + .then((b) { + _loadData(); + Provider.of( + context, + listen: false, + ).getMyUserInfo(needLoadUserCountGuard: false); + if (!unload) { + ATTts.show(ATAppLocalizations.of(context)!.successfulWear); + } else { + ATTts.show(ATAppLocalizations.of(context)!.successfullyUnloaded); + } + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } +} diff --git a/lib/chatvibe_features/home/medals_honor/medals_honor_list_page.dart b/lib/chatvibe_features/home/medals_honor/medals_honor_list_page.dart new file mode 100644 index 0000000..c51c0b3 --- /dev/null +++ b/lib/chatvibe_features/home/medals_honor/medals_honor_list_page.dart @@ -0,0 +1,119 @@ +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/models/enum/at_badge_type.dart'; +import 'package:aslan/chatvibe_features/home/medals_honor/medals_honor_tab_page.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +import '../../../chatvibe_domain/usecases/at_fixed_width_tabIndicator.dart'; + +class MedalsHonorListPage extends StatefulWidget { + const MedalsHonorListPage({super.key}); + + @override + State createState() => _MedalsHonorListPageState(); +} + +class _MedalsHonorListPageState extends State + with SingleTickerProviderStateMixin { + late final TabController _tabController; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 2, vsync: this); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final localizations = ATAppLocalizations.of(context)!; + return Stack( + children: [ + Positioned.fill( + child: Image.asset( + "atu_images/index/atu_icon_medals_honor_bg.png", + fit: BoxFit.fill, + ), + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: AppBar( + backgroundColor: Colors.transparent, + title: TabBar( + tabs: + [ + ATAppLocalizations.of(context)!.badge, + ATAppLocalizations.of(context)!.glory, + ].map((e) => Tab(text: e)).toList(), + indicator: ATFixedWidthTabIndicator( + width: 20.w, + height: 4.w, + color: Colors.white, + ), + //indicatorColor: Color(0xFF9F44F9), + indicatorSize: TabBarIndicatorSize.label, + labelPadding: EdgeInsets.only(left: 20, right: 20), + //Tab之间的间距,默认是kTabLabelPadding + isScrollable: false, + controller: _tabController, + labelColor: Colors.white, + dividerColor: Colors.transparent, + unselectedLabelColor: Colors.white54, + labelStyle: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + ), + unselectedLabelStyle: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + ), + actions: [Container(width: 20.w)], + leading: ATDebounceWidget( + child: Icon( + Icons.arrow_back_outlined, + color: Colors.white, + size: 20.w, + ), + onTap: () { + ATNavigatorUtils.goBack(context); + }, + ), + ), + body: SafeArea( + top: false, + child: Column( + children: [ + SizedBox(height: 12.w), + Expanded( + child: TabBarView( + physics: const NeverScrollableScrollPhysics(), + controller: _tabController, + children: const [ + MedalsHonorTabPage( + isHonor: false, + types: [ATBadgeType.ALL], + ), + MedalsHonorTabPage( + isHonor: true, + honorTypes: ["HONOR_ACTIVITY", "HONOR_ADMIN"], + ), + ], + ), + ), + ], + ), + ), + ), + ], + ); + } +} diff --git a/lib/chatvibe_features/home/medals_honor/medals_honor_tab_page.dart b/lib/chatvibe_features/home/medals_honor/medals_honor_tab_page.dart new file mode 100644 index 0000000..4d863fa --- /dev/null +++ b/lib/chatvibe_features/home/medals_honor/medals_honor_tab_page.dart @@ -0,0 +1,332 @@ +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_data/models/enum/at_badge_type.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_material_badge_res.dart'; +import 'package:aslan/chatvibe_features/home/honor/honor_detail_dialog.dart'; +import 'package:aslan/chatvibe_features/home/medals/badge_detail_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; + +import '../../../chatvibe_ui/components/text/at_text.dart'; + +class MedalsHonorTabPage extends ATPageList { + final bool isHonor; + final List types; + final List honorTypes; + + const MedalsHonorTabPage({ + super.key, + required this.isHonor, + this.types = const [], + this.honorTypes = const [], + }); + + @override + ATPageListState createState() => _MedalsHonorTabPageState(); +} + +class _MedalsHonorTabPageState + extends ATPageListState { + MedalsHonorTabPage get _page => widget as MedalsHonorTabPage; + late final PageController _heroController; + int _heroIndex = 0; + int _heroPageIndex = 0; + bool _hasSyncedHeroIndex = false; + + @override + void initState() { + super.initState(); + _heroController = PageController(viewportFraction: 0.58); + enablePullUp = false; + isShowDivider = false; + isGridView = true; + gridViewCount = 3; + backgroundColor = Colors.transparent; + padding = EdgeInsets.fromLTRB(16.w, 0, 16.w, 24.w); + gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridViewCount, + crossAxisSpacing: 10.w, + mainAxisSpacing: 10.w, + childAspectRatio: 0.8, + ); + loadData(1); + } + + @override + void dispose() { + _heroController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + _buildHeroShowcase(), + SizedBox(height: 14.w), + Expanded(child: buildList(context)), + ], + ); + } + + @override + Widget empty() { + final localizations = ATAppLocalizations.of(context)!; + return mainEmpty(msg: localizations.noData, textColor: Colors.white70); + } + + @override + Widget buildItem(BadgeTables item) { + final badgeConfig = item.badgeConfig; + final isActive = item.activation ?? false; + return GestureDetector( + onTap: () => _showDetail(item), + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/index/at_icon_medals_honor_item_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + SizedBox(height: 32.w), + netImage( + url: + isActive + ? badgeConfig?.selectUrl ?? "" + : badgeConfig?.notSelectUrl ?? + badgeConfig?.selectUrl ?? + "", + width: 48.w, + fit: BoxFit.contain, + ), + SizedBox(height: 18.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 6.w), + alignment: Alignment.center, + child: text( + badgeConfig?.badgeName ?? "", + maxLines: 2, + fontSize: 11.sp, + textAlign: TextAlign.center, + textColor: Colors.white.withValues( + alpha: isActive ? 0.92 : 0.6, + ), + ), + ), + ], + ), + ), + ); + } + + @override + void loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + final requests = + _page.isHonor + ? _page.honorTypes.map( + (type) => AccountRepository().materialBadge(type: type), + ) + : _page.types.map( + (type) => AccountRepository().materialBadge(type: type.name), + ); + + final results = await Future.wait(requests); + final merged = []; + final seenKeys = {}; + + for (final response in results) { + for (final group in response) { + for (final item in group.badgeTables ?? const []) { + if (!(item.activation ?? false)) { + continue; + } + final key = + item.badgeConfig?.id ?? + item.badgeConfig?.badgeKey ?? + item.badgeConfig?.badgeName ?? + ""; + if (key.isEmpty || seenKeys.add(key)) { + merged.add(item); + } + } + } + } + + merged.sort((left, right) { + final leftActive = left.activation ?? false; + final rightActive = right.activation ?? false; + if (leftActive != rightActive) { + return leftActive ? -1 : 1; + } + final leftLevel = left.badgeConfig?.badgeLevel?.toInt() ?? 0; + final rightLevel = right.badgeConfig?.badgeLevel?.toInt() ?? 0; + return leftLevel.compareTo(rightLevel); + }); + + if (!_hasSyncedHeroIndex && merged.isNotEmpty) { + _heroIndex = _initialHeroIndex(merged); + _heroPageIndex = merged.length > 1 ? _heroIndex + 1 : _heroIndex; + _hasSyncedHeroIndex = true; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted || !_heroController.hasClients) { + return; + } + _heroController.jumpToPage(_heroPageIndex); + setState(() {}); + }); + } + + onSuccess(merged); + } catch (_) { + onErr?.call(); + } + } + + Widget _buildHeroShowcase() { + if (items.isEmpty) { + return SizedBox(height: 208.w); + } + + return SizedBox( + height: 208.w, + child: PageView.builder( + controller: _heroController, + clipBehavior: Clip.none, + padEnds: true, + itemCount: _heroItemCount, + onPageChanged: (pageIndex) { + final realIndex = _realIndexForPage(pageIndex); + if (mounted) { + setState(() { + _heroPageIndex = pageIndex; + _heroIndex = realIndex; + }); + } + _handleHeroLoopJump(pageIndex); + }, + itemBuilder: (context, pageIndex) { + return AnimatedBuilder( + animation: _heroController, + child: _buildHeroCard(items[_realIndexForPage(pageIndex)]), + builder: (context, child) { + double page = _heroPageIndex.toDouble(); + if (_heroController.hasClients && + _heroController.position.hasContentDimensions) { + page = _heroController.page ?? _heroPageIndex.toDouble(); + } + + final distance = (page - pageIndex).abs().clamp(0.0, 1.0); + final scale = 1 - (distance * 0.18); + final opacity = 1 - (distance * 0.48); + final verticalOffset = 22.w * distance; + + return Transform.translate( + offset: Offset(0, verticalOffset), + child: Transform.scale( + scale: scale, + child: Opacity(opacity: opacity, child: child), + ), + ); + }, + ); + }, + ), + ); + } + + Widget _buildHeroCard(BadgeTables item) { + final badgeConfig = item.badgeConfig; + final isActive = item.activation ?? false; + return Container( + width: 145.w, + margin: EdgeInsets.symmetric(horizontal: 15.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/index/at_icon_medals_honor_item_head_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Center( + child: netImage( + url: + isActive + ? badgeConfig?.selectUrl ?? "" + : badgeConfig?.notSelectUrl ?? badgeConfig?.selectUrl ?? "", + width: 88.w, + fit: BoxFit.contain, + ), + ), + ); + } + + int _initialHeroIndex(List list) { + for (var i = 0; i < list.length; i++) { + if (list[i].activation ?? false) { + return i; + } + } + return 0; + } + + int get _heroItemCount => items.length > 1 ? items.length + 2 : items.length; + + int _realIndexForPage(int pageIndex) { + if (items.length <= 1) { + return 0; + } + if (pageIndex == 0) { + return items.length - 1; + } + if (pageIndex == _heroItemCount - 1) { + return 0; + } + return pageIndex - 1; + } + + void _handleHeroLoopJump(int pageIndex) { + if (items.length <= 1) { + return; + } + if (pageIndex != 0 && pageIndex != _heroItemCount - 1) { + return; + } + + final targetPage = pageIndex == 0 ? items.length : 1; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted || !_heroController.hasClients) { + return; + } + _heroController.jumpToPage(targetPage); + setState(() { + _heroPageIndex = targetPage; + }); + }); + } + + void _showDetail(BadgeTables item) { + SmartDialog.show( + tag: _page.isHonor ? "showHonorDetail" : "showBadgeDetail", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return _page.isHonor + ? HonorDetailDialog(item) + : BadgeDetailDialog(item); + }, + ); + } +} diff --git a/lib/chatvibe_features/home/popular/event/home_event_page.dart b/lib/chatvibe_features/home/popular/event/home_event_page.dart new file mode 100644 index 0000000..63a6812 --- /dev/null +++ b/lib/chatvibe_features/home/popular/event/home_event_page.dart @@ -0,0 +1,132 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; +import '../../../../app_localizations.dart'; +import '../../../../chatvibe_core/utilities/at_banner_utils.dart'; +import '../../../../chatvibe_core/utilities/at_loading_manager.dart'; +import '../../../../chatvibe_data/models/enum/at_banner_type.dart'; +import '../../../../chatvibe_data/sources/repositories/config_repository_imp.dart'; +import '../../../../chatvibe_domain/models/res/at_index_banner_res.dart'; +import '../../../../chatvibe_ui/components/at_compontent.dart'; + +class HomeEventPage extends StatefulWidget { + @override + _HomeEventPageState createState() => _HomeEventPageState(); +} + +class _HomeEventPageState extends State + with SingleTickerProviderStateMixin { + bool isLoading = false; + List homeBanners = []; + final RefreshController _refreshController = RefreshController( + initialRefresh: false, + ); + + @override + void initState() { + super.initState(); + _loadData(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + body: Stack( + alignment: AlignmentDirectional.bottomCenter, + children: [ + SmartRefresher( + enablePullDown: true, + enablePullUp: false, + controller: _refreshController, + onRefresh: () { + _loadData(); + }, + onLoading: () {}, + child: + homeBanners.isNotEmpty + ? SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [_banner()], + ), + ) + : GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + _loadData(); + }, + child: + isLoading + ? Center(child: CupertinoActivityIndicator()) + : mainEmpty( + msg: ATAppLocalizations.of(context)!.noData, + ), + ), + ), + ], + ), + ); + } + + _banner() { + return homeBanners.isNotEmpty + ? Container( + margin: EdgeInsets.symmetric(horizontal: 10.w), + child: ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: homeBanners.length, + // 列表项数量 + padding: EdgeInsets.only(bottom: 15.w), + itemBuilder: (context, index) { + return GestureDetector( + onTap: () { + print('ads:${homeBanners[index].toJson()}'); + ATBannerUtils.openBanner(homeBanners[index], context); + }, + child: netImage( + height: 98.w, + url: homeBanners[index].cover ?? "", + fit: BoxFit.contain, + borderRadius: BorderRadius.all(Radius.circular(12.w)), + ), + ); + }, + separatorBuilder: (BuildContext context, int index) { + return SizedBox(height: 10.w); + }, + ), + ) + : Container(); + } + + _loadData() async { + setState(() { + isLoading = true; + }); + ATLoadingManager.exhibitOperation(); + try { + var banners = await ConfigRepositoryImp().getBanner(); + homeBanners.clear(); + for (var v in banners) { + if ((v.displayPosition ?? "").contains(ATBannerType.HOME_ALERT.name)) { + homeBanners.add(v); + } + } + _refreshController.refreshCompleted(); + _refreshController.loadComplete(); + isLoading = false; + if (mounted) setState(() {}); + ATLoadingManager.veilRoutine(); + } catch (e) { + _refreshController.refreshCompleted(); + _refreshController.loadComplete(); + isLoading = false; + if (mounted) setState(() {}); + ATLoadingManager.veilRoutine(); + } + } +} diff --git a/lib/chatvibe_features/home/popular/follow/room_follow_page.dart b/lib/chatvibe_features/home/popular/follow/room_follow_page.dart new file mode 100644 index 0000000..c76c47c --- /dev/null +++ b/lib/chatvibe_features/home/popular/follow/room_follow_page.dart @@ -0,0 +1,367 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/follow_room_res.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/country/at_country_flag_image.dart'; + +import '../../../../chatvibe_data/models/enum/at_vip_type.dart'; + +///关注房间 +class RoomFollowPage extends ATPageList { + @override + _RoomFollowPageState createState() => _RoomFollowPageState(); +} + +class _RoomFollowPageState + extends ATPageListState { + String? lastId; + bool _isLoading = true; // 添加加载状态 + + final mockItems = List.generate( + 6, + (index) => FollowRoomRes(id: "skeleton_$index"), + ); + + @override + void initState() { + super.initState(); + enablePullUp = true; + backgroundColor = Colors.transparent; + isGridView = true; + gridViewCount = 2; + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + body: buildList(context), + ); + } + + Widget _buildCustomSkeletonItem(FollowRoomRes roomRes) { + return Container( + alignment: Alignment.center, + margin: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: Colors.transparent, + ), + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + // 自定义加载图标 + Container( + width: 200.w, + height: 200.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + ), + child: Image.asset( + "atu_images/general/at_icon_loading.webp", + width: 200.w, + height: 200.w, + fit: BoxFit.cover, + ), + ), + + // 底部信息骨架 + Container( + padding: EdgeInsets.symmetric(vertical: 5.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(12.w), + bottomRight: Radius.circular(12.w), + ), + color: Colors.black38, + ), + child: Row( + children: [ + SizedBox(width: 10.w), + // 国旗骨架 + Container( + width: 20.w, + height: 13.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2.w), + color: Colors.grey[400], + ), + ), + SizedBox(width: 5.w), + Expanded( + child: Container( + height: 21.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4.w), + color: Colors.grey[400], + ), + margin: EdgeInsets.symmetric(vertical: 2.w), + ), + ), + SizedBox(width: 5.w), + // 在线用户图标骨架 + Container( + width: 14.w, + height: 14.w, + decoration: BoxDecoration( + color: Colors.grey[400], + shape: BoxShape.circle, + ), + ), + SizedBox(width: 3.w), + // 在线人数骨架 + Container( + width: 20.w, + height: 12.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2.w), + color: Colors.grey[400], + ), + ), + SizedBox(width: 10.w), + ], + ), + ), + ], + ), + ); + } + + @override + Widget buildItem(FollowRoomRes roomRes) { + if (_isLoading) { + return _buildCustomSkeletonItem(roomRes); + } + return GestureDetector( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: Colors.transparent, + ), + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + netImage( + url: roomRes.roomProfile?.roomCover ?? "", + borderRadius: BorderRadius.circular(12.w), + width: 200.w, + height: 200.w, + ), + Container( + padding: EdgeInsets.symmetric(vertical: 5.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(12.w), + bottomRight: Radius.circular(12.w), + ), + color: Colors.black38, + ), + child: Row( + children: [ + SizedBox(width: 10.w), + ATCountryFlagImage( + countryName: roomRes.roomProfile?.countryName, + width: 20.w, + height: 13.w, + borderRadius: BorderRadius.circular(2.w), + ), + SizedBox(width: 5.w), + Expanded( + child: SizedBox( + height: 21.w, + child: text( + roomRes.roomProfile?.roomName ?? "", + fontSize: 15.sp, + textColor: Color(0xffffffff), + fontWeight: FontWeight.w400, + ), + // (roomRes.roomProfile?.roomName?.length ?? 0) > 10 + // ? Marquee( + // text: roomRes.roomProfile?.roomName ?? "", + // style: TextStyle( + // fontSize: 15.sp, + // color: Color(0xffffffff), + // fontWeight: FontWeight.w400, + // decoration: TextDecoration.none, + // ), + // scrollAxis: Axis.horizontal, + // crossAxisAlignment: CrossAxisAlignment.start, + // blankSpace: 20.0, + // velocity: 40.0, + // pauseAfterRound: Duration(seconds: 1), + // accelerationDuration: Duration(seconds: 1), + // accelerationCurve: Curves.easeOut, + // decelerationDuration: Duration( + // milliseconds: 500, + // ), + // decelerationCurve: Curves.easeOut, + // ) + // : Text( + // roomRes.roomProfile?.roomName ?? "", + // maxLines: 1, + // overflow: TextOverflow.ellipsis, + // style: TextStyle( + // fontSize: 15.sp, + // color: Color(0xffffffff), + // fontWeight: FontWeight.w400, + // decoration: TextDecoration.none, + // ), + // ), + ), + ), + SizedBox(width: 5.w), + (roomRes + .roomProfile + ?.extValues + ?.roomSetting + ?.password + ?.isEmpty ?? + false) + ? Image.asset( + "atu_images/general/at_icon_online_user.png", + width: 14.w, + height: 14.w, + ) + : Image.asset( + "atu_images/index/at_icon_room_suo.png", + width: 20.w, + height: 20.w, + ), + (roomRes + .roomProfile + ?.extValues + ?.roomSetting + ?.password + ?.isEmpty ?? + false) + ? SizedBox(width: 3.w) + : Container(height: 10.w), + (roomRes + .roomProfile + ?.extValues + ?.roomSetting + ?.password + ?.isEmpty ?? + false) + ? text( + roomRes.roomProfile?.extValues?.memberQuantity ?? "0", + fontSize: 12.sp, + ) + : Container(height: 10.w), + SizedBox(width: 10.w), + ], + ), + ), + getRoomCoverHeaddress(roomRes).isNotEmpty + ? Transform.translate( + offset: Offset(0, -5.w), + child: Transform.scale( + scaleX: 1.1, + scaleY: 1.12, + child: Image.asset(getRoomCoverHeaddress(roomRes)), + ), + ) + : Container(), + + (roomRes.roomProfile?.roomGameIcon?.isNotEmpty ?? false) + ? PositionedDirectional( + child: netImage( + url: roomRes.roomProfile?.roomGameIcon ?? "", + width: 25.w, + height: 25.w, + borderRadius: BorderRadius.circular(4.w), + ), + top: 8.w, + end: 8.w, + ) + : Container(), + ], + ), + ), + onTap: () { + Provider.of( + context, + listen: false, + ).joinRoom(context, roomRes.roomProfile?.id ?? ""); + }, + ); + } + + String getRoomCoverHeaddress(FollowRoomRes roomRes) { + var vip = roomRes.roomProfile?.userProfile?.getVIP(); + if (vip?.name == ATVIPType.DUKE.name) { + return "atu_images/vip/at_icon_vip4_room_cover_headdress.png"; + } else if (vip?.name == ATVIPType.KING.name) { + return "atu_images/vip/at_icon_vip5_room_cover_headdress.png"; + } else if (vip?.name == ATVIPType.EMPEROR.name) { + return "atu_images/vip/at_icon_vip6_room_cover_headdress.png"; + } + return ""; + } + + @override + empty() { + List list = []; + list.add(SizedBox(height: height(30))); + list.add(Image.asset('atu_images/general/at_icon_loading.png')); + list.add(SizedBox(height: height(15))); + list.add( + Text( + ATAppLocalizations.of(context)!.youHaventFollowed, + style: TextStyle( + fontSize: sp(14), + color: Color(0xff999999), + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + height: 1, + ), + ), + ); + return Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: list, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + if (page == 1) { + lastId = null; + } + if (_isLoading) { + onSuccess(mockItems); + } + try { + var roomList = await AccountRepository().followRoomList(lastId: lastId); + if (roomList.isNotEmpty) { + lastId = roomList.last.id; + } + onSuccess(roomList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } finally { + // 无论成功失败,都隐藏骨架屏 + setState(() { + _isLoading = false; + }); + } + } +} diff --git a/lib/chatvibe_features/home/popular/history/room_history_page.dart b/lib/chatvibe_features/home/popular/history/room_history_page.dart new file mode 100644 index 0000000..21d7c60 --- /dev/null +++ b/lib/chatvibe_features/home/popular/history/room_history_page.dart @@ -0,0 +1,366 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_domain/models/res/follow_room_res.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/country/at_country_flag_image.dart'; + +import '../../../../chatvibe_data/models/enum/at_vip_type.dart'; + +///历史房间 +class RoomHistoryPage extends ATPageList { + @override + _RoomHistoryPageState createState() => _RoomHistoryPageState(); +} + +class _RoomHistoryPageState + extends ATPageListState { + String? lastId; + bool _isLoading = true; // 添加加载状态 + final mockItems = List.generate( + 6, + (index) => FollowRoomRes(id: "skeleton_$index"), + ); + + @override + void initState() { + super.initState(); + enablePullUp = true; + backgroundColor = Colors.transparent; + isGridView = true; + gridViewCount = 2; + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + body: buildList(context), + ); + } + + Widget _buildCustomSkeletonItem(FollowRoomRes roomRes) { + return Container( + alignment: Alignment.center, + margin: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: Colors.transparent, + ), + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + // 自定义加载图标 + Container( + width: 200.w, + height: 200.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + ), + child: Image.asset( + "atu_images/general/at_icon_loading.webp", + width: 200.w, + height: 200.w, + fit: BoxFit.cover, + ), + ), + + // 底部信息骨架 + Container( + padding: EdgeInsets.symmetric(vertical: 5.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(12.w), + bottomRight: Radius.circular(12.w), + ), + color: Colors.black38, + ), + child: Row( + children: [ + SizedBox(width: 10.w), + // 国旗骨架 + Container( + width: 20.w, + height: 13.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2.w), + color: Colors.grey[400], + ), + ), + SizedBox(width: 5.w), + Expanded( + child: Container( + height: 21.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4.w), + color: Colors.grey[400], + ), + margin: EdgeInsets.symmetric(vertical: 2.w), + ), + ), + SizedBox(width: 5.w), + // 在线用户图标骨架 + Container( + width: 14.w, + height: 14.w, + decoration: BoxDecoration( + color: Colors.grey[400], + shape: BoxShape.circle, + ), + ), + SizedBox(width: 3.w), + // 在线人数骨架 + Container( + width: 20.w, + height: 12.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2.w), + color: Colors.grey[400], + ), + ), + SizedBox(width: 10.w), + ], + ), + ), + ], + ), + ); + } + + @override + Widget buildItem(FollowRoomRes roomRes) { + if (_isLoading) { + return _buildCustomSkeletonItem(roomRes); + } + return GestureDetector( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: Colors.transparent, + ), + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + netImage( + url: roomRes.roomProfile?.roomCover ?? "", + borderRadius: BorderRadius.circular(12.w), + width: 200.w, + height: 200.w, + ), + Container( + padding: EdgeInsets.symmetric(vertical: 5.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(12.w), + bottomRight: Radius.circular(12.w), + ), + color: Colors.black38, + ), + child: Row( + children: [ + SizedBox(width: 10.w), + ATCountryFlagImage( + countryName: roomRes.roomProfile?.countryName, + width: 20.w, + height: 13.w, + borderRadius: BorderRadius.circular(2.w), + ), + SizedBox(width: 5.w), + Expanded( + child: SizedBox( + height: 21.w, + child: text( + roomRes.roomProfile?.roomName ?? "", + fontSize: 15.sp, + textColor: Color(0xffffffff), + fontWeight: FontWeight.w400, + ), + + // (roomRes.roomProfile?.roomName?.length ?? 0) > 10 + // ? Marquee( + // text: roomRes.roomProfile?.roomName ?? "", + // style: TextStyle( + // fontSize: 15.sp, + // color: Color(0xffffffff), + // fontWeight: FontWeight.w400, + // decoration: TextDecoration.none, + // ), + // scrollAxis: Axis.horizontal, + // crossAxisAlignment: CrossAxisAlignment.start, + // blankSpace: 20.0, + // velocity: 40.0, + // pauseAfterRound: Duration(seconds: 1), + // accelerationDuration: Duration(seconds: 1), + // accelerationCurve: Curves.easeOut, + // decelerationDuration: Duration( + // milliseconds: 500, + // ), + // decelerationCurve: Curves.easeOut, + // ) + // : Text( + // roomRes.roomProfile?.roomName ?? "", + // maxLines: 1, + // overflow: TextOverflow.ellipsis, + // style: TextStyle( + // fontSize: 15.sp, + // color: Color(0xffffffff), + // fontWeight: FontWeight.w400, + // decoration: TextDecoration.none, + // ), + // ), + ), + ), + SizedBox(width: 5.w), + (roomRes + .roomProfile + ?.extValues + ?.roomSetting + ?.password + ?.isEmpty ?? + false) + ? Image.asset( + "atu_images/general/at_icon_online_user.png", + width: 14.w, + height: 14.w, + ) + : Image.asset( + "atu_images/index/at_icon_room_suo.png", + width: 20.w, + height: 20.w, + ), + (roomRes + .roomProfile + ?.extValues + ?.roomSetting + ?.password + ?.isEmpty ?? + false) + ? SizedBox(width: 3.w) + : Container(height: 10.w), + (roomRes + .roomProfile + ?.extValues + ?.roomSetting + ?.password + ?.isEmpty ?? + false) + ? text( + roomRes.roomProfile?.extValues?.memberQuantity ?? "0", + fontSize: 12.sp, + ) + : Container(height: 10.w), + SizedBox(width: 10.w), + ], + ), + ), + getRoomCoverHeaddress(roomRes).isNotEmpty + ? Transform.translate( + offset: Offset(0, -5.w), + child: Transform.scale( + scaleX: 1.1, + scaleY: 1.12, + child: Image.asset(getRoomCoverHeaddress(roomRes)), + ), + ) + : Container(), + (roomRes.roomProfile?.roomGameIcon?.isNotEmpty ?? false) + ? PositionedDirectional( + child: netImage( + url: roomRes.roomProfile?.roomGameIcon ?? "", + width: 25.w, + height: 25.w, + borderRadius: BorderRadius.circular(4.w), + ), + top: 8.w, + end: 8.w, + ) + : Container(), + ], + ), + ), + onTap: () { + Provider.of( + context, + listen: false, + ).joinRoom(context, roomRes.roomProfile?.id ?? ""); + }, + ); + } + + @override + empty() { + List list = []; + list.add(SizedBox(height: height(30))); + list.add(Image.asset('atu_images/general/at_icon_loading.png')); + list.add(SizedBox(height: height(15))); + list.add( + Text( + ATAppLocalizations.of(context)!.noData, + style: TextStyle( + fontSize: sp(14), + color: Color(0xff999999), + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + height: 1, + ), + ), + ); + return Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: list, + ); + } + + String getRoomCoverHeaddress(FollowRoomRes roomRes) { + var vip = roomRes.roomProfile?.userProfile?.getVIP(); + if (vip?.name == ATVIPType.DUKE.name) { + return "atu_images/vip/at_icon_vip4_room_cover_headdress.png"; + } else if (vip?.name == ATVIPType.KING.name) { + return "atu_images/vip/at_icon_vip5_room_cover_headdress.png"; + } else if (vip?.name == ATVIPType.EMPEROR.name) { + return "atu_images/vip/at_icon_vip6_room_cover_headdress.png"; + } + return ""; + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + if (page == 1) { + lastId = null; + } + if (_isLoading) { + onSuccess(mockItems); + } + try { + var roomList = await AccountRepository().trace(lastId: lastId); + if (roomList.isNotEmpty) { + lastId = roomList.last.id; + } + onSuccess(roomList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } finally { + // 无论成功失败,都隐藏骨架屏 + setState(() { + _isLoading = false; + }); + } + } +} diff --git a/lib/chatvibe_features/home/popular/home_popular_page.dart b/lib/chatvibe_features/home/popular/home_popular_page.dart new file mode 100644 index 0000000..3e70f38 --- /dev/null +++ b/lib/chatvibe_features/home/popular/home_popular_page.dart @@ -0,0 +1,246 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_features/home/popular/follow/room_follow_page.dart'; +import 'package:aslan/chatvibe_features/home/popular/history/room_history_page.dart'; +import 'package:aslan/chatvibe_features/home/popular/remmmd/room_recommend_page.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_banner_leaderboard_res.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/seamless_auto_scroll.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +class HomePopularPage extends StatefulWidget { + @override + _HomePopularPageState createState() => _HomePopularPageState(); +} + +class _HomePopularPageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + final List _pages = [ + RoomRecommendPage(), + RoomFollowPage(), + RoomHistoryPage(), + ]; + final List _tabs = []; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: _pages.length, vsync: this); + Provider.of(context, listen: false).loadMainBanner(); + Provider.of(context, listen: false).appLeaderboard(); + _tabController.addListener(() {}); // 监听切换 + } + + @override + void dispose() { + _tabController.dispose(); // 释放资源 + super.dispose(); + } + + @override + Widget build(BuildContext context) { + _tabs.clear(); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.recommend)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.follow)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.history)); + return Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + body: Column( + children: [ + Container( + margin: EdgeInsets.only(left: 15.w, right: 15.w, top: 5.w), + child: Selector( + selector: (_, provider) => provider.appLeaderResult, + builder: (context, appLeaderResult, child) { + return _banner(appLeaderResult); + }, + ), + ), + Row( + children: [ + SizedBox(width: 5.w), + TabBar( + tabAlignment: TabAlignment.start, + labelPadding: EdgeInsets.symmetric(horizontal: 12.w), + labelColor: Colors.black87, + isScrollable: true, + indicator: BoxDecoration(), + unselectedLabelColor: Colors.black38, + labelStyle: TextStyle( + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + unselectedLabelStyle: TextStyle( + fontSize: 13.sp, + fontWeight: FontWeight.w500, + ), + indicatorColor: Colors.transparent, + dividerColor: Colors.transparent, + controller: _tabController, + tabs: _tabs, + ), + ], + ), + Expanded( + child: TabBarView( + controller: _tabController, + physics: NeverScrollableScrollPhysics(), + children: _pages, + ), + ), + ], + ), + ); + } + + _banner(ATBannerLeaderboardRes? appLeaderResult) { + String avatarAt(List? users, int index) { + if (users == null || users.length <= index) return ""; + return users[index].avatar ?? ""; + } + + final roomGiftWeekly = appLeaderResult?.roomGiftsLeaderboard?.weekly; + final wealthWeekly = appLeaderResult?.giftsSendLeaderboard?.weekly; + final charmWeekly = appLeaderResult?.giftsReceivedLeaderboard?.weekly; + + final roomGiftOneAvatar = avatarAt(roomGiftWeekly, 0); + final roomGiftTwoAvatar = avatarAt(roomGiftWeekly, 1); + final roomGiftThreeAvatar = avatarAt(roomGiftWeekly, 2); + final wealthOneAvatar = avatarAt(wealthWeekly, 0); + final wealthTwoAvatar = avatarAt(wealthWeekly, 1); + final wealthThreeAvatar = avatarAt(wealthWeekly, 2); + final charmOneAvatar = avatarAt(charmWeekly, 0); + final charmTwoAvatar = avatarAt(charmWeekly, 1); + final charmThreeAvatar = avatarAt(charmWeekly, 2); + + return SeamlessAutoScroll( + items: [ + GestureDetector( + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + ATGlobalConfig.businessLogicStrategy + .getPopularLeaderboardBackgroundImage('room'), + ), + fit: BoxFit.contain, + ), + ), + child: Stack( + alignment: AlignmentDirectional.topCenter, + children: [ + Positioned( + left: 38.w, + top: 25.w, + child: head(url: roomGiftTwoAvatar, width: 53.w), + ), + Positioned( + top: 15.w, + child: head(url: roomGiftOneAvatar, width: 55.w), + ), + Positioned( + right: 38.w, + top: 25.w, + child: head(url: roomGiftThreeAvatar, width: 53.w), + ), + ], + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.roomRankUrl)}&showTitle=false", + replace: false, + ); + }, + ), + GestureDetector( + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + ATGlobalConfig.businessLogicStrategy + .getPopularLeaderboardBackgroundImage('wealth'), + ), + fit: BoxFit.contain, + ), + ), + child: Stack( + alignment: AlignmentDirectional.topCenter, + children: [ + Positioned( + left: 38.w, + top: 25.w, + child: head(url: wealthTwoAvatar, width: 53.w), + ), + Positioned( + top: 15.w, + child: head(url: wealthOneAvatar, width: 55.w), + ), + Positioned( + right: 38.w, + top: 25.w, + child: head(url: wealthThreeAvatar, width: 53.w), + ), + ], + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.wealthRankUrl)}&showTitle=false", + replace: false, + ); + }, + ), + GestureDetector( + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + ATGlobalConfig.businessLogicStrategy + .getPopularLeaderboardBackgroundImage('charm'), + ), + fit: BoxFit.contain, + ), + ), + child: Stack( + alignment: AlignmentDirectional.topCenter, + children: [ + Positioned( + left: 38.w, + top: 25.w, + child: head(url: charmTwoAvatar, width: 53.w), + ), + Positioned( + top: 15.w, + child: head(url: charmOneAvatar, width: 55.w), + ), + Positioned( + right: 38.w, + top: 25.w, + child: head(url: charmThreeAvatar, width: 53.w), + ), + ], + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.charmRankUrl)}&showTitle=false", + replace: false, + ); + }, + ), + ], + itemHeight: 110.w, + ); + } +} diff --git a/lib/chatvibe_features/home/popular/mine/home_mine_page.dart b/lib/chatvibe_features/home/popular/mine/home_mine_page.dart new file mode 100644 index 0000000..08c16b2 --- /dev/null +++ b/lib/chatvibe_features/home/popular/mine/home_mine_page.dart @@ -0,0 +1,577 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; +import '../../../../chatvibe_core/config/business_logic_strategy.dart'; +import '../../../../chatvibe_core/constants/at_global_config.dart'; +import '../../../../chatvibe_data/models/enum/at_vip_type.dart'; +import '../../../../chatvibe_data/sources/local/user_manager.dart'; +import '../../../../chatvibe_data/sources/repositories/user_repository_impl.dart'; +import '../../../../chatvibe_domain/models/res/follow_room_res.dart'; +import '../../../../chatvibe_managers/room_manager.dart'; +import '../../../../chatvibe_managers/rtc_manager.dart'; +import '../../../../chatvibe_ui/components/at_compontent.dart'; +import '../../../../chatvibe_ui/widgets/country/at_country_flag_image.dart'; +import '../../../../chatvibe_ui/theme/chatvibe_theme.dart'; + +class HomeMinePage extends ATPageList { + @override + _HomeMinePageState createState() => _HomeMinePageState(); +} + +class _HomeMinePageState extends ATPageListState { + List historyRooms = []; + String? lastId; + + BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; + + bool isLoading = false; + + @override + void initState() { + super.initState(); + enablePullUp = true; + backgroundColor = Colors.transparent; + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + SizedBox(width: 15.w), + text( + ATAppLocalizations.of(context)!.myRoom, + fontSize: 16.sp, + fontWeight: FontWeight.bold, + textColor: Colors.black, + ), + ], + ), + SizedBox(height: 5.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 12.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: Colors.white, + border: Border.all(color: ChatVibeTheme.primaryLight, width: 1.w), + ), + width: ScreenUtil().screenWidth, + height: 85.w, + child: _buildMyRoom(), + ), + SizedBox(height: 5.w), + Row( + children: [ + SizedBox(width: 15.w), + text( + ATAppLocalizations.of(context)!.history, + fontSize: 16.sp, + fontWeight: FontWeight.bold, + textColor: Colors.black, + ), + ], + ), + SizedBox(height: 5.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 12.w), + padding: EdgeInsets.symmetric(horizontal: 10.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: Colors.white, + border: Border.all(color: ChatVibeTheme.primaryLight, width: 1.w), + ), + width: ScreenUtil().screenWidth, + height: 85.w, + child: + historyRooms.isNotEmpty + ? SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + spacing: 6.w, + crossAxisAlignment: CrossAxisAlignment.center, // 垂直居中 + children: List.generate(historyRooms.length, (index) { + return Padding( + padding: EdgeInsets.only( + right: index == historyRooms.length - 1 ? 0 : 6.w, + ), + child: _buildHistoryRoomItem(historyRooms[index]), + ); + }), + ), + ) + : Row( + children: [ + Image.asset( + "atu_images/general/at_icon_no_data_icon2.png", + width: 70.w, + height: 70.w, + ), + SizedBox(width: 8.w), + text( + ATAppLocalizations.of( + context, + )!.noHistoricalRecordsAvailable, + fontSize: 14.sp, + textColor: Colors.black, + ), + ], + ), + ), + SizedBox(height: 5.w), + Row( + children: [ + SizedBox(width: 15.w), + text( + ATAppLocalizations.of(context)!.following, + fontSize: 16.sp, + fontWeight: FontWeight.bold, + textColor: Colors.black, + ), + ], + ), + SizedBox(height: 5.w), + Expanded(child: buildList(context)), + ], + ); + } + + _buildMyRoom() { + return Consumer( + builder: (_, provider, __) { + return provider.myRoom != null + ? GestureDetector( + behavior: HitTestBehavior.opaque, + child: Row( + children: [ + SizedBox(width: 10.w), + netImage( + url: provider.myRoom?.roomCover ?? "", + borderRadius: BorderRadius.all(Radius.circular(8.w)), + width: 70.w, + ), + SizedBox(width: 10.w), + Expanded( + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 6.w), + Container( + constraints: BoxConstraints( + maxWidth: 200.w, + maxHeight: 24.w, + ), + child: + (provider.myRoom?.roomName?.length ?? 0) > 18 + ? Marquee( + text: provider.myRoom?.roomName ?? "", + style: TextStyle( + fontSize: 16.sp, + color: Colors.black, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 40.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration( + seconds: 1, + ), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + provider.myRoom?.roomName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16.sp, + color: Colors.black, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + text( + provider.myRoom?.roomDesc ?? "", + fontSize: 14.sp, + textColor: Colors.black, + ), + text( + "ID:${AccountStorage().getCurrentUser()?.userProfile?.getID()}", + fontSize: 12.sp, + textColor: Colors.black54, + ), + ], + ), + ], + ), + ), + Icon( + ATGlobalConfig.lang == "ar" + ? Icons.keyboard_arrow_left + : Icons.keyboard_arrow_right, + size: 25.w, + color: Colors.white, + ), + SizedBox(width: 12.w), + ], + ), + onTap: () { + String roomId = + Provider.of( + context, + listen: false, + ).myRoom?.id ?? + ""; + Provider.of( + context, + listen: false, + ).joinRoom(context, roomId); + }, + ) + : GestureDetector( + child: Row( + children: [ + SizedBox(width: 10.w), + Image.asset( + "atu_images/general/at_icon_edit_user_info_add_pic.png", + height: 70.w, + width: 70.w, + ), + SizedBox(width: 10.w), + text( + ATAppLocalizations.of(context)!.crateMyRoom, + fontSize: 16.sp, + fontWeight: FontWeight.bold, + textColor: Colors.black, + ), + ], + ), + onTap: () { + provider.createRoom(context); + }, + ); + }, + ); + } + + void _loadOtherData() { + ATLoadingManager.exhibitOperation(); + AccountRepository() + .trace() + .then((res) { + historyRooms = res; + ATLoadingManager.veilRoutine(); + setState(() {}); + }) + .catchError((_) { + ATLoadingManager.veilRoutine(); + }); + } + + _buildHistoryRoomItem(FollowRoomRes roomRes) { + return GestureDetector( + child: SizedBox( + width: 70.w, + height: 70.w, + child: Stack( + children: [ + netImage( + url: roomRes.roomProfile?.roomCover ?? "", + width: 70.w, + height: 70.w, + fit: BoxFit.cover, + borderRadius: BorderRadius.circular(8.w), + ), + (roomRes.roomProfile?.roomGameIcon?.isNotEmpty ?? false) + ? PositionedDirectional( + top: 8.w, + end: 8.w, + child: netImage( + url: roomRes.roomProfile?.roomGameIcon ?? "", + width: 15.w, + height: 15.w, + borderRadius: BorderRadius.circular(4.w), + ), + ) + : Container(), + PositionedDirectional( + top: 3.w, + end: 5.w, + child: + (roomRes + .roomProfile + ?.extValues + ?.roomSetting + ?.password + ?.isNotEmpty ?? + false) + ? Image.asset( + "atu_images/index/at_icon_room_suo.png", + width: 16.w, + height: 16.w, + ) + : Container(), + ), + ], + ), + ), + onTap: () { + Provider.of( + context, + listen: false, + ).joinRoom(context, roomRes.roomProfile?.id ?? ""); + }, + ); + } + + @override + Widget buildItem(FollowRoomRes res) { + return ATDebounceWidget( + child: SizedBox( + height: 105.w, + child: Stack( + alignment: Alignment.center, + children: [ + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(13.w), + border: Border.all( + color: _strategy.getExploreRoomBorderColor(), + width: _strategy.getExploreRoomBorderWidth().w, + ), + ), + margin: EdgeInsetsDirectional.symmetric( + horizontal: 10.w, + vertical: 5.w, + ), + child: Row( + children: [ + Stack( + children: [ + netImage( + url: res.roomProfile?.roomCover ?? "", + fit: BoxFit.cover, + borderRadius: BorderRadius.all(Radius.circular(12.w)), + width: 95.w, + height: 95.w, + ), + PositionedDirectional( + top: 3.w, + end: 5.w, + child: + (res + .roomProfile + ?.extValues + ?.roomSetting + ?.password + ?.isNotEmpty ?? + false) + ? Image.asset( + "atu_images/index/at_icon_room_suo.png", + width: 16.w, + height: 16.w, + ) + : Container(), + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 15.w), + Row( + children: [ + SizedBox(width: 8.w), + ATCountryFlagImage( + countryName: res.roomProfile?.countryName, + width: 25.w, + height: 15.w, + borderRadius: BorderRadius.all( + Radius.circular(3.w), + ), + ), + SizedBox(width: 5.w), + Container( + constraints: BoxConstraints( + maxHeight: 21.w, + maxWidth: 150.w, + ), + child: + (res.roomProfile?.roomName?.length ?? 0) > + _strategy + .getExploreRoomNameMarqueeThreshold() + ? Marquee( + text: res.roomProfile?.roomName ?? "", + style: TextStyle( + fontSize: 15.sp, + color: Colors.black, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration( + seconds: 1, + ), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + res.roomProfile?.roomName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 15.sp, + color: Colors.black, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + SizedBox(height: 3.w), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(width: 8.w), + text( + "ID:${res.roomProfile?.userProfile?.getID()}", + fontWeight: FontWeight.w600, + textColor: Colors.black, + fontSize: 15.sp, + ), + ], + ), + SizedBox(height: 3.w), + Container( + constraints: BoxConstraints(maxWidth: 180.w), + margin: EdgeInsetsDirectional.symmetric( + horizontal: 8.w, + ), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.min, + spacing: 2.w, + children: List.generate( + (res.onlineUsers?.length ?? 0), + (index) { + return netImage( + url: res.onlineUsers?[index].userAvatar ?? "", + width: 23.w, + height: 23.w, + shape: BoxShape.circle, + ); + }, + ), + ), + ), + ), + ], + ), + ], + ), + ), + PositionedDirectional( + end: 35.w, + bottom: 25.w, + child: Row( + children: [ + Image.asset( + "atu_images/index/at_icon_room_flot_ani.gif", + width: 14.w, + height: 14.w, + ), + text( + res.roomProfile?.extValues?.memberQuantity ?? "0", + textColor: Colors.black38, + fontSize: 11.sp, + ), + ], + ), + ), + ], + ), + ), + onTap: () { + Provider.of( + context, + listen: false, + ).joinRoom(context, res.roomProfile?.id ?? ""); + }, + ); + } + + @override + builderDivider() { + // return Divider( + // height: 1.w, + // color: Color(0xff3D3277).withOpacity(0.5), + // indent: 15.w, + // ); + return Container(height: 4.w); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + _loadOtherData(); + if (page == 1) { + lastId = null; + } + try { + var roomList = await AccountRepository().followRoomList(lastId: lastId); + if (roomList.isNotEmpty) { + lastId = roomList.last.id; + } + onSuccess(roomList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + String getRoomCoverHeaddress(FollowRoomRes roomRes) { + var vip = roomRes.roomProfile?.userProfile?.getVIP(); + if (vip?.name == ATVIPType.DUKE.name) { + return "atu_images/vip/at_icon_vip4_room_cover_headdress.png"; + } else if (vip?.name == ATVIPType.KING.name) { + return "atu_images/vip/at_icon_vip5_room_cover_headdress.png"; + } else if (vip?.name == ATVIPType.EMPEROR.name) { + return "atu_images/vip/at_icon_vip6_room_cover_headdress.png"; + } + return ""; + } +} diff --git a/lib/chatvibe_features/home/popular/party/home_party_page.dart b/lib/chatvibe_features/home/popular/party/home_party_page.dart new file mode 100644 index 0000000..a8a5d1e --- /dev/null +++ b/lib/chatvibe_features/home/popular/party/home_party_page.dart @@ -0,0 +1,938 @@ +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; +import '../../../../app_localizations.dart'; +import '../../../../chatvibe_core/constants/at_global_config.dart'; +import '../../../../chatvibe_core/routes/at_fluro_navigator.dart'; +import '../../../../chatvibe_core/utilities/at_banner_utils.dart'; +import '../../../../chatvibe_data/models/enum/at_vip_type.dart'; +import '../../../../chatvibe_data/sources/repositories/room_repository_imp.dart'; +import '../../../../chatvibe_domain/models/res/at_banner_leaderboard_res.dart'; +import '../../../../chatvibe_domain/models/res/at_index_banner_res.dart'; +import '../../../../chatvibe_domain/models/res/country_res.dart'; +import '../../../../chatvibe_domain/models/res/follow_room_res.dart'; +import '../../../../chatvibe_domain/models/res/room_res.dart'; +import '../../../../chatvibe_managers/app_general_manager.dart'; +import '../../../../chatvibe_managers/rtc_manager.dart'; +import '../../../../chatvibe_ui/components/at_compontent.dart'; +import '../../../../chatvibe_ui/components/text/at_text.dart'; +import '../../../../chatvibe_ui/widgets/country/at_country_flag_image.dart'; +import '../../../index/main_route.dart'; + +class HomePartyPage extends StatefulWidget { + @override + _HomePartyPageState createState() => _HomePartyPageState(); +} + +class _HomePartyPageState extends State + with SingleTickerProviderStateMixin { + List historyRooms = []; + String? lastId; + bool isLoading = false; + final RefreshController _refreshController = RefreshController( + initialRefresh: false, + ); + List rooms = []; + int _currentIndex = 0; + final ScrollController _countryTabController = ScrollController(); + final List _countryTabKeys = []; + List _countries = []; + Country? _selectedCountry; + int _dataRequestToken = 0; + bool _countryLoading = false; + + @override + void initState() { + super.initState(); + loadCountries(); + loadData(); + } + + @override + void dispose() { + _countryTabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + body: Stack( + alignment: AlignmentDirectional.bottomCenter, + children: [ + SmartRefresher( + enablePullDown: true, + enablePullUp: false, + controller: _refreshController, + onRefresh: () { + loadData(); + }, + onLoading: () {}, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Selector>( + selector: + (_, provider) => List.unmodifiable( + provider.exploreBanners, + ), + builder: (context, exploreBanners, child) { + return _banner(exploreBanners); + }, + ), + Selector( + selector: (_, provider) => provider.appLeaderResult, + builder: (context, appLeaderResult, child) { + return _banner2(appLeaderResult); + }, + ), + _buildCountryTabs(), + rooms.isEmpty + ? GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + loadData(); + }, + child: + isLoading + ? Center(child: CupertinoActivityIndicator()) + : mainEmpty( + msg: ATAppLocalizations.of(context)!.noData, + ), + ) + : ListView.separated( + shrinkWrap: true, + padding: EdgeInsets.symmetric(vertical: 8.w), + physics: const NeverScrollableScrollPhysics(), + itemCount: rooms.length, + itemBuilder: (context, index) { + return _buildItem(rooms[index], index); + }, + separatorBuilder: (BuildContext context, int index) { + return SizedBox(height: 4.w); + }, + ), + ], + ), + ), + ), + ], + ), + ); + } + + _banner(List exploreBanners) { + return Stack( + alignment: AlignmentDirectional.bottomCenter, + children: [ + Container( + margin: EdgeInsets.symmetric(horizontal: 17.w), + child: CarouselSlider( + options: CarouselOptions( + height: 98.w, + autoPlay: true, + // 启用自动播放 + enlargeCenterPage: false, + // 居中放大当前页面 + enableInfiniteScroll: true, + // 启用无限循环 + autoPlayAnimationDuration: Duration(milliseconds: 800), + // 自动播放动画时长 + viewportFraction: 1, + // 视口分数 + onPageChanged: (index, reason) { + _currentIndex = index; + setState(() {}); + }, + ), + items: + exploreBanners.map((item) { + return GestureDetector( + child: netImage( + url: item.cover ?? "", + height: 98.w, + width: ScreenUtil().screenWidth * 0.9, + fit: BoxFit.contain, + borderRadius: BorderRadius.circular(12.w), + ), + onTap: () { + print('ads:${item.toJson()}'); + ATBannerUtils.openBanner(item, context); + }, + ); + }).toList(), + ), + ), + + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: + exploreBanners.asMap().entries.map((entry) { + return Container( + width: 6.0, + height: 6.0, + margin: EdgeInsets.symmetric(vertical: 15.0, horizontal: 4.0), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: + _currentIndex == entry.key ? Colors.blue : Colors.white, + ), + ); + }).toList(), + ), + ], + ); + } + + _banner2(ATBannerLeaderboardRes? appLeaderResult) { + String avatarAt(List? users, int index) { + if (users == null || users.length <= index) return ""; + return users[index].avatar ?? ""; + } + + final roomGiftWeekly = appLeaderResult?.roomGiftsLeaderboard?.weekly; + final wealthWeekly = appLeaderResult?.giftsSendLeaderboard?.weekly; + final charmWeekly = appLeaderResult?.giftsReceivedLeaderboard?.weekly; + + final roomGiftOneAvatar = avatarAt(roomGiftWeekly, 0); + final roomGiftTwoAvatar = avatarAt(roomGiftWeekly, 1); + final roomGiftThreeAvatar = avatarAt(roomGiftWeekly, 2); + final wealthOneAvatar = avatarAt(wealthWeekly, 0); + final wealthTwoAvatar = avatarAt(wealthWeekly, 1); + final wealthThreeAvatar = avatarAt(wealthWeekly, 2); + final charmOneAvatar = avatarAt(charmWeekly, 0); + final charmTwoAvatar = avatarAt(charmWeekly, 1); + final charmThreeAvatar = avatarAt(charmWeekly, 2); + + return SizedBox( + height: 120.w, + child: Row( + children: [ + Expanded( + child: GestureDetector( + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + PositionedDirectional( + start: 30.w, + top: 63.w, + child: head(url: wealthTwoAvatar, width: 28.w), + ), + Positioned( + top: 35.w, + child: head(url: wealthOneAvatar, width: 36.w), + ), + PositionedDirectional( + end: 30.w, + top: 63.w, + child: head(url: wealthThreeAvatar, width: 28.w), + ), + Image.asset( + ATGlobalConfig.businessLogicStrategy + .getPopularLeaderboardBackgroundImage('wealth'), + fit: BoxFit.fill, + ), + ], + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.wealthRankUrl)}&showTitle=false", + replace: false, + ); + }, + ), + ), + Expanded( + child: GestureDetector( + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + PositionedDirectional( + start: 28.w, + top: 68.w, + child: head(url: roomGiftTwoAvatar, width: 28.w), + ), + Positioned( + top: 35.w, + child: head(url: roomGiftOneAvatar, width: 38.w), + ), + PositionedDirectional( + end: 29.w, + top: 68.w, + child: head(url: roomGiftThreeAvatar, width: 28.w), + ), + Image.asset( + ATGlobalConfig.businessLogicStrategy + .getPopularLeaderboardBackgroundImage('room'), + fit: BoxFit.fill, + ), + ], + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.roomRankUrl)}&showTitle=false", + replace: false, + ); + }, + ), + ), + Expanded( + child: GestureDetector( + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + PositionedDirectional( + start: 28.w, + top: 66.w, + child: head(url: charmTwoAvatar, width: 28.w), + ), + Positioned( + top: 38.w, + child: head(url: charmOneAvatar, width: 37.w), + ), + PositionedDirectional( + end: 31.w, + top: 66.w, + child: head(url: charmThreeAvatar, width: 28.w), + ), + Image.asset( + ATGlobalConfig.businessLogicStrategy + .getPopularLeaderboardBackgroundImage('charm'), + fit: BoxFit.fill, + ), + ], + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.charmRankUrl)}&showTitle=false", + replace: false, + ); + }, + ), + ), + ], + ), + ); + } + + Widget _buildCountryTabs() { + _syncCountryTabKeys(); + return Container( + height: 32.w, + margin: EdgeInsetsDirectional.only(start: 18.w, end: 18.w, top: 4.w), + child: Row( + children: [ + Expanded( + child: SingleChildScrollView( + controller: _countryTabController, + scrollDirection: Axis.horizontal, + physics: const BouncingScrollPhysics(), + child: Row( + children: [ + KeyedSubtree(key: _countryTabKeys[0], child: _buildHotTab()), + ...List.generate(_countries.length, (index) { + final country = _countries[index]; + return KeyedSubtree( + key: _countryTabKeys[index + 1], + child: _buildCountryTab(country, index + 1), + ); + }), + ], + ), + ), + ), + SizedBox(width: 8.w), + ATDebounceWidget( + onTap: _showCountrySheet, + child: Container( + alignment: Alignment.center, + height: 23.w, + width: 23.w, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: const Color(0xff000000), width: 1.w), + ), + child: Icon( + Icons.keyboard_arrow_down_rounded, + size: 18.w, + color: const Color(0xff333333), + ), + ), + ), + ], + ), + ); + } + + Widget _buildHotTab() { + final isSelected = _selectedCountry == null; + + return Padding( + padding: EdgeInsetsDirectional.only(end: 6.w), + child: ATDebounceWidget( + onTap: () => _selectCountry(null, 0), + child: Container( + height: 32.w, + padding: EdgeInsetsDirectional.symmetric(horizontal: 13.w), + decoration: _countryTabDecoration(isSelected), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + "atu_images/index/at_icon_fire_department.png", + height: 19.w, + ), + SizedBox(width: 3.w), + text( + ATAppLocalizations.of(context)!.hot, + textColor: const Color(0xff333333), + fontSize: 14.sp, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, + lineHeight: 1.1, + ), + ], + ), + ), + ), + ); + } + + Widget _buildCountryTab(Country country, int tabIndex) { + final isSelected = _isSameCountry(_selectedCountry, country); + + return Padding( + padding: EdgeInsetsDirectional.only(end: 6.w), + child: ATDebounceWidget( + onTap: () => _selectCountry(country, tabIndex), + child: Container( + height: 32.w, + constraints: BoxConstraints(maxWidth: 144.w), + padding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), + decoration: _countryTabDecoration(isSelected), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + _buildCountryFlag(country, width: 22.w, height: 16.w), + SizedBox(width: 6.w), + Flexible( + child: text( + _countryDisplayName(country), + maxLines: 1, + overflow: TextOverflow.ellipsis, + textColor: isSelected + ? const Color(0xff333333) + : const Color(0xff333333), + fontSize: 14.sp, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, + lineHeight: 1.1, + ), + ), + ], + ), + ), + ), + ); + } + + BoxDecoration _countryTabDecoration(bool isSelected) { + return BoxDecoration( + color: isSelected ? null : const Color(0xffeeeeee), + gradient: + isSelected + ? const LinearGradient( + colors: [Color(0xffffd800), Color(0xffff9500)], + begin: AlignmentDirectional.topCenter, + end: AlignmentDirectional.bottomCenter, + ) + : null, + borderRadius: BorderRadius.circular(40.w), + ); + } + + Widget _buildCountryFlag( + Country country, { + required double width, + required double height, + }) { + final flag = country.nationalFlag ?? ""; + if (flag.isEmpty) { + return Container( + width: width, + height: height, + decoration: BoxDecoration( + color: const Color(0xffd9d9d9), + borderRadius: BorderRadius.circular(3.w), + ), + ); + } + + return netImage( + url: flag, + width: width, + height: height, + fit: BoxFit.cover, + borderRadius: BorderRadius.circular(3.w), + noDefaultImg: true, + ); + } + + void _showCountrySheet() { + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + barrierColor: const Color(0x73000000), + isScrollControlled: true, + builder: (sheetContext) { + return SafeArea( + top: false, + child: Container( + constraints: BoxConstraints( + maxHeight: ScreenUtil().screenHeight * 0.6, + ), + padding: EdgeInsetsDirectional.only( + start: 20.w, + end: 20.w, + top: 20.w, + bottom: MediaQuery.of(sheetContext).padding.bottom + 20.w, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadiusDirectional.vertical( + top: Radius.circular(20.w), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + text( + ATAppLocalizations.of(context)!.selectCountry, + textColor: const Color(0xff333333), + fontWeight: FontWeight.w700, + lineHeight: 1.1, + fontSize: 18.sp, + ), + SizedBox(height: 20.w), + Flexible( + child: + _countries.isEmpty + ? Center( + child: + _countryLoading + ? const CupertinoActivityIndicator() + : mainEmpty( + msg: + ATAppLocalizations.of( + context, + )!.noData, + ), + ) + : GridView.builder( + shrinkWrap: true, + padding: EdgeInsets.zero, + physics: const BouncingScrollPhysics(), + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + mainAxisSpacing: 12.w, + crossAxisSpacing: 8.w, + childAspectRatio: 111 / 30, + ), + itemCount: _countries.length, + itemBuilder: (context, index) { + final country = _countries[index]; + return _buildCountrySheetItem( + country, + index + 1, + sheetContext, + ); + }, + ), + ), + ], + ), + ), + ); + }, + ); + } + + Widget _buildCountrySheetItem( + Country country, + int tabIndex, + BuildContext sheetContext, + ) { + final isSelected = _isSameCountry(_selectedCountry, country); + + return ATDebounceWidget( + onTap: () { + Navigator.of(sheetContext).pop(); + _selectCountry(country, tabIndex); + }, + child: Container( + height: 30.w, + padding: EdgeInsetsDirectional.symmetric(horizontal: 10.w), + decoration: BoxDecoration( + color: isSelected ? null : const Color(0xfff2f2f2), + gradient: + isSelected + ? const LinearGradient( + colors: [Color(0xffffd800), Color(0xffff9500)], + begin: AlignmentDirectional.topCenter, + end: AlignmentDirectional.bottomCenter, + ) + : null, + borderRadius: BorderRadius.circular(8.w), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildCountryFlag(country, width: 18.w, height: 12.w), + SizedBox(width: 6.w), + Flexible( + child: Text( + _countryDisplayName(country), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: const Color(0xff333333), + fontSize: 13.sp, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, + decoration: TextDecoration.none, + height: 1.1, + ), + ), + ), + ], + ), + ), + ); + } + + void _selectCountry(Country? country, int tabIndex) { + if (_isSameCountry(_selectedCountry, country)) { + _scrollCountryTabToIndex(tabIndex); + return; + } + + setState(() { + _selectedCountry = country; + rooms = []; + }); + _scrollCountryTabToIndex(tabIndex); + loadData(); + } + + void _scrollCountryTabToIndex(int tabIndex) { + if (tabIndex < 0 || tabIndex >= _countryTabKeys.length) return; + + WidgetsBinding.instance.addPostFrameCallback((_) { + final tabContext = _countryTabKeys[tabIndex].currentContext; + if (tabContext == null) return; + Scrollable.ensureVisible( + tabContext, + duration: const Duration(milliseconds: 250), + curve: Curves.easeOut, + alignment: 0.5, + ); + }); + } + + void _syncCountryTabKeys() { + final total = _countries.length + 1; + while (_countryTabKeys.length < total) { + _countryTabKeys.add(GlobalKey()); + } + if (_countryTabKeys.length > total) { + _countryTabKeys.removeRange(total, _countryTabKeys.length); + } + } + + bool _isSameCountry(Country? left, Country? right) { + if (left == null || right == null) return left == right; + final leftKey = left.id ?? left.alphaTwo ?? left.countryName; + final rightKey = right.id ?? right.alphaTwo ?? right.countryName; + return leftKey == rightKey; + } + + String _countryDisplayName(Country country) { + return country.countryName ?? ""; + } + + void loadCountries() { + _countryLoading = true; + ChatRoomRepository() + .roomLiveVoiceCountry() + .then((values) { + if (!mounted) return; + setState(() { + _countries = values; + _countryLoading = false; + }); + }) + .catchError((_) { + if (!mounted) return; + setState(() { + _countryLoading = false; + }); + }); + } + + loadData() { + final requestToken = ++_dataRequestToken; + final selectedCountry = _selectedCountry; + setState(() { + isLoading = true; + }); + ATLoadingManager.exhibitOperation(); + final Future> request = + selectedCountry == null + ? ChatRoomRepository().discovery() + : ChatRoomRepository().roomLiveVoiceExplore( + countryCode: selectedCountry.countryCode, + ); + + request + .then((values) { + if (!mounted || requestToken != _dataRequestToken) return; + rooms = values; + isLoading = false; + _refreshController.refreshCompleted(); + _refreshController.loadComplete(); + ATLoadingManager.veilRoutine(); + if (mounted) setState(() {}); + }) + .catchError((e) { + if (!mounted || requestToken != _dataRequestToken) return; + _refreshController.loadNoData(); + _refreshController.refreshCompleted(); + ATLoadingManager.veilRoutine(); + isLoading = false; + if (mounted) setState(() {}); + }); + Provider.of(context, listen: false).loadMainBanner(); + Provider.of(context, listen: false).appLeaderboard(); + } + + String getRoomCoverHeaddress(ChatVibeRoomRes roomRes) { + var vip = roomRes.userProfile?.getVIP(); + if (vip?.name == ATVIPType.DUKE.name) { + return "atu_images/vip/at_icon_vip4_room_cover_headdress.png"; + } else if (vip?.name == ATVIPType.KING.name) { + return "atu_images/vip/at_icon_vip5_room_cover_headdress.png"; + } else if (vip?.name == ATVIPType.EMPEROR.name) { + return "atu_images/vip/at_icon_vip6_room_cover_headdress.png"; + } + return ""; + } + + _buildItem(ChatVibeRoomRes res, int index) { + return ATDebounceWidget( + child: SizedBox( + height: 105.w, + child: Stack( + alignment: Alignment.center, + children: [ + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(13.w), + border: + index < 3 + ? null + : Border.all( + color: + ATGlobalConfig.businessLogicStrategy + .getExploreRoomBorderColor(), + width: + ATGlobalConfig.businessLogicStrategy + .getExploreRoomBorderWidth() + .w, + ), + ), + margin: EdgeInsetsDirectional.symmetric( + horizontal: 18.w, + vertical: 5.w, + ), + child: Row( + children: [ + Stack( + children: [ + netImage( + url: res.roomCover ?? "", + fit: BoxFit.cover, + borderRadius: BorderRadius.all(Radius.circular(12.w)), + width: 95.w, + height: 95.w, + ), + PositionedDirectional( + top: 3.w, + end: 5.w, + child: + (res.extValues?.roomSetting?.password?.isNotEmpty ?? + false) + ? Image.asset( + "atu_images/index/at_icon_room_suo.png", + width: 16.w, + height: 16.w, + ) + : Container(), + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 15.w), + Row( + children: [ + SizedBox(width: 8.w), + ATCountryFlagImage( + countryName: res.countryName, + width: 25.w, + height: 15.w, + borderRadius: BorderRadius.all( + Radius.circular(3.w), + ), + ), + SizedBox(width: 5.w), + Container( + constraints: BoxConstraints( + maxHeight: 21.w, + maxWidth: 150.w, + ), + child: + (res.roomName?.length ?? 0) > + ATGlobalConfig.businessLogicStrategy + .getExploreRoomNameMarqueeThreshold() + ? Marquee( + text: res.roomName ?? "", + style: TextStyle( + fontSize: 15.sp, + color: Colors.black, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration( + seconds: 1, + ), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + res.roomName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 15.sp, + color: Colors.black, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + SizedBox(height: 3.w), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(width: 8.w), + text( + "ID:${res.userProfile?.getID()}", + fontWeight: FontWeight.w600, + textColor: Colors.black, + fontSize: 15.sp, + ), + ], + ), + SizedBox(height: 3.w), + Container( + constraints: BoxConstraints(maxWidth: 180.w), + margin: EdgeInsetsDirectional.symmetric( + horizontal: 8.w, + ), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.min, + spacing: 2.w, + children: List.generate( + (res.onlineUsers?.length ?? 0), + (index) { + return netImage( + url: res.onlineUsers?[index].userAvatar ?? "", + width: 23.w, + height: 23.w, + shape: BoxShape.circle, + ); + }, + ), + ), + ), + ), + ], + ), + ], + ), + ), + index < 3 + ? IgnorePointer( + child: Transform.translate( + offset: Offset(0, -3), + child: Image.asset( + ATGlobalConfig.businessLogicStrategy + .getExploreRankIconPattern(false, index + 1), + fit: BoxFit.fill, + ), + ), + ) + : Container(), + PositionedDirectional( + end: 35.w, + bottom: 25.w, + child: Row( + children: [ + Image.asset( + "atu_images/index/at_icon_room_flot_ani.gif", + width: 14.w, + height: 14.w, + ), + text( + res.extValues?.memberQuantity ?? "0", + textColor: Colors.black38, + fontSize: 11.sp, + ), + ], + ), + ), + ], + ), + ), + onTap: () { + Provider.of( + context, + listen: false, + ).joinRoom(context, res.id ?? ""); + }, + ); + } +} diff --git a/lib/chatvibe_features/home/popular/remmmd/room_recommend_page.dart b/lib/chatvibe_features/home/popular/remmmd/room_recommend_page.dart new file mode 100644 index 0000000..718cf72 --- /dev/null +++ b/lib/chatvibe_features/home/popular/remmmd/room_recommend_page.dart @@ -0,0 +1,486 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:provider/provider.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_res.dart'; +import 'package:aslan/main.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_managers/room_manager.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/country/at_country_flag_image.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/recommend_banner_view.dart'; + +import '../../../../chatvibe_data/models/enum/at_vip_type.dart'; + +///推荐房间 +class RoomRecommendPage extends StatefulWidget { + @override + _RoomRecommendPageState createState() => _RoomRecommendPageState(); +} + +class _RoomRecommendPageState extends State with RouteAware { + final RefreshController _refreshController = RefreshController( + initialRefresh: false, + ); + ChatVibeRoomManager? roomProvider; + + // Timer? timer; + List _items = []; + bool _isLoading = true; // 添加加载状态 + @override + void initState() { + super.initState(); + roomProvider = Provider.of(context, listen: false); + loadData(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + // 注册路由观察 + final route = ModalRoute.of(context); + if (route is PageRoute) { + routeObserver.subscribe(this, route); + } + } + + @override + void didPush() { + // 路由被推入时调用 + _updateVisibility(true); + } + + @override + void didPopNext() { + // 当从下一个页面返回时调用 + _updateVisibility(true); + } + + @override + void didPushNext() { + // 当推入下一个页面时调用 + _updateVisibility(false); + } + + @override + void didPop() { + // 路由弹出时调用 + _updateVisibility(false); + } + + void _updateVisibility(bool isVisible) { + // if (isVisible) { + // ///在可见状态需要定时刷新请求数据 + // timer?.cancel(); + // timer = Timer.periodic(Duration(seconds: 5), (ti) { + // loadData(); + // }); + // } else { + // timer?.cancel(); + // } + } + + @override + void dispose() { + routeObserver.unsubscribe(this); + // timer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + body: Container( + padding: EdgeInsets.symmetric(horizontal: 8.w), + child: SmartRefresher( + enablePullDown: true, + enablePullUp: false, + controller: _refreshController, + onRefresh: () { + loadData(); + }, + onLoading: () {}, + child: _buildContent(), + ), + ), + ); + } + + Widget _buildContent() { + // 如果正在加载,显示骨架屏 + if (_isLoading) { + return _buildCustomSkeleton(); + } + + // 加载完成但没有数据,显示空状态 + if (_items.isEmpty) { + return empty(); + } + + // 有数据时显示正常内容 + return StaggeredGrid.count( + crossAxisCount: 2, + mainAxisSpacing: 8, + crossAxisSpacing: 8, + children: _buidChildren(), + ); + } + + // 自定义骨架屏 + Widget _buildCustomSkeleton() { + // 创建模拟数据用于骨架屏 + final mockItems = List.generate( + 6, + (index) => ChatVibeRoomRes(id: "skeleton_$index"), + ); + + return StaggeredGrid.count( + crossAxisCount: 2, + mainAxisSpacing: 8, + crossAxisSpacing: 8, + children: + mockItems.map((item) { + return StaggeredGridTile.count( + crossAxisCellCount: 1, + mainAxisCellCount: 1, + child: _buildCustomSkeletonItem(item), + ); + }).toList(), + ); + } + + // 完全自定义骨架屏项目 + Widget _buildCustomSkeletonItem(ChatVibeRoomRes roomRes) { + return Container( + alignment: Alignment.center, + margin: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: Colors.transparent, + ), + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + // 自定义加载图标 + Container( + width: 200.w, + height: 200.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + ), + child: Image.asset( + "atu_images/general/at_icon_loading.webp", + width: 200.w, + height: 200.w, + fit: BoxFit.cover, + ), + ), + + // 底部信息骨架 + Container( + padding: EdgeInsets.symmetric(vertical: 5.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(12.w), + bottomRight: Radius.circular(12.w), + ), + color: Colors.black38, + ), + child: Row( + children: [ + SizedBox(width: 10.w), + // 国旗骨架 + Container( + width: 20.w, + height: 13.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2.w), + color: Colors.grey[400], + ), + ), + SizedBox(width: 5.w), + Expanded( + child: Container( + height: 21.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4.w), + color: Colors.grey[400], + ), + margin: EdgeInsets.symmetric(vertical: 2.w), + ), + ), + SizedBox(width: 5.w), + // 在线用户图标骨架 + Container( + width: 14.w, + height: 14.w, + decoration: BoxDecoration( + color: Colors.grey[400], + shape: BoxShape.circle, + ), + ), + SizedBox(width: 3.w), + // 在线人数骨架 + Container( + width: 20.w, + height: 12.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2.w), + color: Colors.grey[400], + ), + ), + SizedBox(width: 10.w), + ], + ), + ), + ], + ), + ); + } + + // 原有的 buildItem 方法保持不变 + Widget buildItem(ChatVibeRoomRes roomRes) { + return GestureDetector( + child: Container( + alignment: Alignment.center, + margin: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: Colors.transparent, + ), + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + netImage( + url: roomRes.roomCover ?? "", + borderRadius: BorderRadius.circular(12.w), + width: 200.w, + height: 200.w, + ), + if (roomRes.extValues?.rocketLevel != null) + PositionedDirectional( + end: 5.w, + bottom: 35.w, + child: Image.asset( + "atu_images/room/at_icon_room_rocket_lv${roomRes.extValues?.rocketLevel}.png", + width: 20.w, + ), + ), + Container( + padding: EdgeInsets.symmetric(vertical: 5.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(12.w), + bottomRight: Radius.circular(12.w), + ), + color: Colors.black38, + ), + child: Row( + children: [ + SizedBox(width: 10.w), + ATCountryFlagImage( + countryName: roomRes.countryName, + width: 20.w, + height: 13.w, + borderRadius: BorderRadius.circular(2.w), + ), + SizedBox(width: 5.w), + Expanded( + child: SizedBox( + height: 21.w, + child: text( + roomRes.roomName ?? "", + fontSize: 15.sp, + textColor: Color(0xffffffff), + fontWeight: FontWeight.w400, + ), + // (roomRes.roomName?.length ?? 0) > 10 + // ? Marquee( + // text: roomRes.roomName ?? "", + // style: TextStyle( + // fontSize: 15.sp, + // color: Color(0xffffffff), + // fontWeight: FontWeight.w400, + // decoration: TextDecoration.none, + // ), + // scrollAxis: Axis.horizontal, + // crossAxisAlignment: CrossAxisAlignment.start, + // blankSpace: 20.0, + // velocity: 40.0, + // pauseAfterRound: Duration(seconds: 1), + // accelerationDuration: Duration(seconds: 1), + // accelerationCurve: Curves.easeOut, + // decelerationDuration: Duration( + // milliseconds: 500, + // ), + // decelerationCurve: Curves.easeOut, + // ) + // : Text( + // roomRes.roomName ?? "", + // maxLines: 1, + // overflow: TextOverflow.ellipsis, + // style: TextStyle( + // fontSize: 15.sp, + // color: Color(0xffffffff), + // fontWeight: FontWeight.w400, + // decoration: TextDecoration.none, + // ), + // ), + ), + ), + SizedBox(width: 5.w), + (roomRes.extValues?.roomSetting?.password?.isEmpty ?? false) + ? Image.asset( + "atu_images/general/at_icon_online_user.gif", + width: 14.w, + height: 14.w, + ) + : Image.asset( + "atu_images/index/at_icon_room_suo.png", + width: 20.w, + height: 20.w, + ), + (roomRes.extValues?.roomSetting?.password?.isEmpty ?? false) + ? SizedBox(width: 3.w) + : Container(height: 10.w), + (roomRes.extValues?.roomSetting?.password?.isEmpty ?? false) + ? text( + roomRes.extValues?.memberQuantity ?? "0", + fontSize: 12.sp, + ) + : Container(height: 10.w), + SizedBox(width: 10.w), + ], + ), + ), + getRoomCoverHeaddress(roomRes).isNotEmpty + ? Transform.translate( + offset: Offset(0, -5.w), + child: Transform.scale( + scaleX: 1.1, + scaleY: 1.12, + child: Image.asset(getRoomCoverHeaddress(roomRes)), + ), + ) + : Container(), + (roomRes.roomGameIcon?.isNotEmpty ?? false) + ? PositionedDirectional( + child: netImage( + url: roomRes.roomGameIcon ?? "", + width: 25.w, + height: 25.w, + borderRadius: BorderRadius.circular(4.w), + ), + top: 8.w, + end: 8.w, + ) + : Container(), + ], + ), + ), + onTap: () { + Provider.of( + context, + listen: false, + ).joinRoom(context, roomRes.id ?? ""); + }, + ); + } + + empty() { + List list = []; + list.add(SizedBox(height: height(30))); + list.add(Image.asset('atu_images/general/at_icon_loading.png')); + list.add(SizedBox(height: height(15))); + list.add( + Text( + ATAppLocalizations.of(context)!.noData, + style: TextStyle( + fontSize: sp(14), + color: Color(0xff999999), + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + height: 1, + ), + ), + ); + return Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: list, + ); + } + + String getRoomCoverHeaddress(ChatVibeRoomRes roomRes) { + var vip = roomRes.userProfile?.getVIP(); + if (vip?.name == ATVIPType.DUKE.name) { + return "atu_images/vip/at_icon_vip4_room_cover_headdress.png"; + } else if (vip?.name == ATVIPType.KING.name) { + return "atu_images/vip/at_icon_vip5_room_cover_headdress.png"; + } else if (vip?.name == ATVIPType.EMPEROR.name) { + return "atu_images/vip/at_icon_vip6_room_cover_headdress.png"; + } + return ""; + } + + ///加载数据 + loadData() async { + Provider.of(context, listen: false).loadMainBanner(); + Provider.of(context, listen: false).appLeaderboard(); + try { + _items = await ChatRoomRepository().discovery(); + if (_items.length < 4) { + _items.add(ChatVibeRoomRes(id: "-10089")); + } else { + _items.insert(4, ChatVibeRoomRes(id: "-10089")); + } + _refreshController.refreshCompleted(); + _refreshController.loadComplete(); + } catch (e) { + _refreshController.loadNoData(); + _refreshController.refreshCompleted(); + } finally { + // 无论成功失败,都隐藏骨架屏 + setState(() { + _isLoading = false; + }); + } + } + + List _buidChildren() { + List ws = []; + for (var item in _items) { + if (item.id == "-10089") { + Provider.of( + context, + listen: false, + ).roomBanners.isNotEmpty + ? ws.add( + StaggeredGridTile.count( + crossAxisCellCount: 2, + mainAxisCellCount: 0.75, + child: RecommendBannerView(), + ), + ) + : Container(); + } else { + ws.add( + StaggeredGridTile.count( + crossAxisCellCount: 1, + mainAxisCellCount: 1, + child: buildItem(item), + ), + ); + } + } + return ws; + } +} diff --git a/lib/chatvibe_features/home/vip/vip_item_page.dart b/lib/chatvibe_features/home/vip/vip_item_page.dart new file mode 100644 index 0000000..fbf8867 --- /dev/null +++ b/lib/chatvibe_features/home/vip/vip_item_page.dart @@ -0,0 +1,904 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_core/utilities/at_string_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/store_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/vip_list_res.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; + +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; + +import '../../../chatvibe_data/models/enum/at_currency_type.dart'; +import '../../../chatvibe_data/models/enum/at_props_type.dart'; + +class VipItemPage extends StatefulWidget { + VipListRes vip; + int index; + + VipItemPage(this.vip, this.index); + + @override + _VipItemPageState createState() => _VipItemPageState(); +} + +class _VipItemPageState extends State + with SingleTickerProviderStateMixin { + bool hasCurrentVip = false; + + @override + void initState() { + super.initState(); + var res = AccountStorage().getCurrentUser()?.userProfile?.getVIP(); + if (res != null) { + if (res.id == widget.vip.propsResources?.id) { + hasCurrentVip = true; + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: Column( + children: [ + SizedBox(height: 20.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + width: 138.w, + height: 138.w, + ATGlobalConfig.businessLogicStrategy.getVipPageLargeIcon( + widget.index, + ), + ), + ], + ), + SizedBox(height: 20.w), + SizedBox( + width: ScreenUtil().screenWidth, + height: 100.w, + child: ListView.separated( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + itemBuilder: (context, i) => _buildPrivilegeItem2(i), + itemCount: 9, + padding: EdgeInsets.zero, + separatorBuilder: (context, i) => Container(width: 8.w), + ), + ), + SizedBox(height: 15.w), + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + Stack( + alignment: Alignment.topCenter, + children: [ + widget.index < 6 + ? Image.asset( + ATGlobalConfig.businessLogicStrategy + .getVipPageBackgroundImage(widget.index), + width: ScreenUtil().screenWidth * 0.95, + ) + : Container(), + Padding( + padding: EdgeInsetsDirectional.symmetric( + horizontal: 15.w, + ), + child: Column( + children: [ + SizedBox(height: 42.w), + Row( + children: [ + Expanded( + child: GestureDetector( + child: _buildItem( + ATGlobalConfig.businessLogicStrategy + .getVipPageFeatureIcon( + widget.index, + 'badge', + ), + ATAppLocalizations.of(context)!.vipBadge, + ), + onTap: () { + _clickBadge(); + }, + ), + ), + Expanded( + child: GestureDetector( + child: _buildItem( + ATGlobalConfig.businessLogicStrategy + .getVipPageFeatureIcon( + widget.index, + 'profile_frame', + ), + ATAppLocalizations.of( + context, + )!.vipProfileFrame, + ), + onTap: () { + _clickProfileFrame(); + }, + ), + ), + Expanded( + child: GestureDetector( + child: _buildItem( + ATGlobalConfig.businessLogicStrategy + .getVipPageFeatureIcon( + widget.index, + 'profile_card', + ), + ATAppLocalizations.of( + context, + )!.vipProfileCard, + ), + onTap: () { + _clickProfileCard(); + }, + ), + ), + ], + ), + SizedBox(height: 15.w), + Row( + children: [ + Expanded( + child: GestureDetector( + child: _buildItem( + ATGlobalConfig.businessLogicStrategy + .getVipPageFeatureIcon( + widget.index, + 'mic_rippl', + ), + ATAppLocalizations.of( + context, + )!.vipMicSoundWave, + ), + onTap: () { + _clickRoomSeat(); + }, + ), + ), + Expanded( + child: GestureDetector( + child: _buildItem( + ATGlobalConfig.businessLogicStrategy + .getVipPageFeatureIcon( + widget.index, + 'entrance_effect', + ), + ATAppLocalizations.of( + context, + )!.vipEntranceEffect, + ), + onTap: () { + _clickEntranceEffect(); + }, + ), + ), + Expanded( + child: GestureDetector( + child: _buildItem( + ATGlobalConfig.businessLogicStrategy + .getVipPageFeatureIcon( + widget.index, + 'chatbox', + ), + ATAppLocalizations.of( + context, + )!.vipChatBox, + ), + onTap: () { + _clickChatBox(); + }, + ), + ), + ], + ), + SizedBox(height: 15.w), + Row( + children: [ + Expanded( + child: + widget.index > 2 + ? Expanded( + child: GestureDetector( + child: _buildItem( + ATGlobalConfig + .businessLogicStrategy + .getVipPageFeatureIcon( + widget.index, + 'special_gift_tassel', + ), + ATAppLocalizations.of( + context, + )!.vipSpecialGiftTassel, + ), + onTap: () { + _clickSpecialGiftTassel(); + }, + ), + ) + : Spacer(), + ), + Expanded( + child: + widget.index > 2 + ? GestureDetector( + child: _buildItem( + ATGlobalConfig + .businessLogicStrategy + .getVipPageFeatureIcon( + widget.index, + 'exclusive_vehicles', + ), + ATAppLocalizations.of( + context, + )!.vipExclusiveVehicles, + ), + onTap: () { + _clickExclusiveVehicles(); + }, + ) + : Spacer(), + ), + Expanded( + child: + widget.index > 3 + ? GestureDetector( + child: _buildItem( + ATGlobalConfig + .businessLogicStrategy + .getVipPageFeatureIcon( + widget.index, + 'rippl_theme', + ), + ATAppLocalizations.of( + context, + )!.vipRippleTheme, + ), + onTap: () { + _clickRoomCoverBorder(); + }, + ) + : Spacer(), + ), + ], + ), + ], + ), + ), + ], + ), + ], + ), + ), + ), + widget.index < 6 + ? Container( + height: 68.w, + padding: EdgeInsets.symmetric(horizontal: 15.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + ATGlobalConfig.businessLogicStrategy + .getVipPagePurchaseBackgroundImage(widget.index), + ), + fit: BoxFit.fill, + ), + ), + child: Row( + children: [ + Expanded( + child: Consumer( + builder: (context, ref, child) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisSize: + hasCurrentVip + ? MainAxisSize.max + : MainAxisSize.min, + children: [ + Expanded( + child: text( + "${widget.vip.propsPrices?.first.amount}${ATAppLocalizations.of(context)!.coins}/${widget.vip.propsPrices?.first.days} ${ATAppLocalizations.of(context)!.days}", + fontSize: hasCurrentVip ? 13.sp : 15.sp, + fontWeight: FontWeight.bold, + maxLines: 2, + lineHeight: 1.0, + textColor: ATGlobalConfig + .businessLogicStrategy + .getVipPageTextColor( + widget.index, + true, + ), + ), + ), + ], + ), + hasCurrentVip + ? text( + maxLines: 2, + ATAppLocalizations.of( + context, + )!.yourVipWillExpire( + ATMDateUtils.formatDateTime3( + DateTime.fromMillisecondsSinceEpoch( + int.parse( + AccountStorage().getVIPExpireTime(), + ), + ), + ), + ), + fontSize: 12.sp, + lineHeight: 1.0, + fontWeight: FontWeight.bold, + textColor: ATGlobalConfig + .businessLogicStrategy + .getVipPageTextColor( + widget.index, + true, + ), + ) + : Container(), + ], + ); + }, + ), + ), + GestureDetector( + child: Container( + margin: EdgeInsets.only(top: 2.w), + width: 115.w, + height: 38.w, + alignment: Alignment.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + ATGlobalConfig.businessLogicStrategy + .getVipPagePurchaseButtonImage(), + ), + fit: BoxFit.fill, + ), + ), + child: text( + maxLines: 2, + textAlign: TextAlign.center, + hasCurrentVip + ? ATAppLocalizations.of(context)!.renewVip + : ATAppLocalizations.of(context)!.buyVip, + fontWeight: FontWeight.bold, + lineHeight: 1.0, + fontSize: 12.sp, + textColor: Colors.white, + ), + ), + onTap: () { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.confirmBuyTips, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + _buy(); + }, + ); + }, + ); + + // if (hasCurrentVip) { + // ATTts.show(ATAppLocalizations.of(context)!.cantBuyVip); + // } else { + // + // } + }, + ), + ], + ), + ) + : Container(), + ], + ), + ); + } + + Widget _buildItem(String icon, String tx) { + return Container( + height: 144.w, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: 101.w, + height: 101.w, + padding: EdgeInsets.all(10.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + ATGlobalConfig.businessLogicStrategy + .getVipPageItemBackgroundImage(widget.index), + ), + fit: BoxFit.fill, + ), + ), + child: + ATStringUtils.isUrl(icon) + ? netImage(url: icon) + : Image.asset(icon, fit: BoxFit.contain), + ), + SizedBox(height: 5.w), + SizedBox( + width: 100.w, + child: text( + textAlign: TextAlign.center, + tx, + lineHeight: 0.9, + maxLines: 3, + textColor: ATGlobalConfig.businessLogicStrategy + .getVipPageTextColor(widget.index, true), + fontSize: 13.sp, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ); + } + + _buildPrivilegeItem2(int index) { + String icon = ""; + String tx = ""; + bool isSelected = false; + if (index == 0) { + isSelected = true; + icon = ATGlobalConfig.businessLogicStrategy.getVipPagePrivilegeIcon( + widget.index, + 1, + isSelected, + ); + tx = + "${widget.vip.nobleVipAbility?.loginRewardsAmount?.ceil()}${ATAppLocalizations.of(context)!.coins}/${ATAppLocalizations.of(context)!.day}"; + } else if (index == 1) { + isSelected = true; + icon = ATGlobalConfig.businessLogicStrategy.getVipPagePrivilegeIcon( + widget.index, + 2, + isSelected, + ); + tx = ATAppLocalizations.of(context)!.userColoredID; + } else if (index == 2) { + if (widget.index > 1) { + isSelected = true; + } + icon = ATGlobalConfig.businessLogicStrategy.getVipPagePrivilegeIcon( + widget.index, + 3, + widget.index > 1, + ); + tx = ATAppLocalizations.of(context)!.userLevelXPBoost( + widget.index == 1 + ? "+0%" + : (widget.index == 2 + ? "+7%" + : (widget.index == 3 + ? "+10%" + : (widget.index == 4 + ? "+15%" + : widget.index == 5 + ? "+20%" + : "+25%"))), + ); + } else if (index == 3) { + if (widget.index > 1) { + isSelected = true; + } + icon = ATGlobalConfig.businessLogicStrategy.getVipPagePrivilegeIcon( + widget.index, + 4, + isSelected, + ); + tx = ATAppLocalizations.of(context)!.gifProfileUpload; + } else if (index == 4) { + if (widget.index > 2) { + isSelected = true; + } + icon = ATGlobalConfig.businessLogicStrategy.getVipPagePrivilegeIcon( + widget.index, + 5, + isSelected, + ); + tx = ATAppLocalizations.of(context)!.dailyTaskRewardBonus( + widget.index == 1 + ? "+0%" + : (widget.index == 2 + ? "+7%" + : (widget.index == 3 + ? "+10%" + : (widget.index == 4 + ? "+15%" + : widget.index == 5 + ? "+20%" + : "+25%"))), + ); + } else if (index == 5) { + if (widget.index > 2) { + isSelected = true; + } + icon = ATGlobalConfig.businessLogicStrategy.getVipPagePrivilegeIcon( + widget.index, + 6, + isSelected, + ); + tx = ATAppLocalizations.of(context)!.membershipFreeChatSpeak; + } else if (index == 6) { + if (widget.index > 3) { + isSelected = true; + } + icon = ATGlobalConfig.businessLogicStrategy.getVipPagePrivilegeIcon( + widget.index, + 7, + isSelected, + ); + tx = ATAppLocalizations.of(context)!.antiBlock; + } else if (index == 7) { + if (widget.index > 4) { + isSelected = true; + } + icon = ATGlobalConfig.businessLogicStrategy.getVipPagePrivilegeIcon( + widget.index, + 8, + isSelected, + ); + tx = ATAppLocalizations.of(context)!.vipBirthdayGift; + } else if (index == 8) { + if (widget.index > 4) { + isSelected = true; + } + icon = ATGlobalConfig.businessLogicStrategy.getVipPagePrivilegeIcon( + widget.index, + 9, + isSelected, + ); + tx = ATAppLocalizations.of(context)!.kickPrevention; + } + return SizedBox( + height: 100.w, + child: Column( + children: [ + Image.asset(icon, width: 60.w, height: 60.w, fit: BoxFit.contain), + SizedBox(height: 2.w), + SizedBox( + width: 110.w, + child: text( + textAlign: TextAlign.center, + tx, + lineHeight: 0.9, + maxLines: 3, + textColor: ATGlobalConfig.businessLogicStrategy + .getVipPageTextColor(widget.index, isSelected), + fontSize: 11.sp, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ); + } + + void _clickBadge() { + // SmartDialog.show( + // tag: "showBadge", + // alignment: Alignment.center, + // animationType: SmartAnimationType.fade, + // builder: (_) { + // return SafeArea( + // top: false, + // child: Container( + // height: 280.w, + // margin: EdgeInsets.symmetric(horizontal: 20.w), + // width: ScreenUtil().screenWidth, + // decoration: BoxDecoration( + // image: DecorationImage( + // image: AssetImage( + // ATGlobalConfig.businessLogicStrategy.getVipPagePreviewImage( + // widget.index, + // 'rev', + // 'badge', + // ), + // ), + // fit: BoxFit.fill, + // ), + // ), + // ), + // ); + // }, + // ); + } + + void _clickProfileFrame() { + // SmartDialog.show( + // tag: "showProfileFrame", + // alignment: Alignment.center, + // animationType: SmartAnimationType.fade, + // builder: (_) { + // return SafeArea( + // top: false, + // child: Container( + // height: 280.w, + // margin: EdgeInsets.symmetric(horizontal: 20.w), + // decoration: BoxDecoration( + // image: DecorationImage( + // image: AssetImage( + // ATGlobalConfig.businessLogicStrategy.getVipPagePreviewImage( + // widget.index, + // 'rev', + // 'profile_frame', + // ), + // ), + // fit: BoxFit.fill, + // ), + // ), + // ), + // ); + // }, + // ); + } + + void _clickProfileCard() { + // SmartDialog.show( + // tag: "showProfileCard", + // alignment: Alignment.center, + // animationType: SmartAnimationType.fade, + // builder: (_) { + // return SafeArea( + // top: false, + // child: Container( + // height: 280.w, + // margin: EdgeInsets.symmetric(horizontal: 20.w), + // decoration: BoxDecoration( + // image: DecorationImage( + // image: AssetImage( + // ATGlobalConfig.businessLogicStrategy.getVipPagePreviewImage( + // widget.index, + // 'rev', + // 'profile_card', + // ), + // ), + // fit: BoxFit.fill, + // ), + // ), + // ), + // ); + // }, + // ); + } + + void _clickEntranceEffect() { + // SmartDialog.show( + // tag: "showEntranceEffect", + // alignment: Alignment.center, + // animationType: SmartAnimationType.fade, + // builder: (_) { + // return SafeArea( + // top: false, + // child: Container( + // height: 280.w, + // margin: EdgeInsets.symmetric(horizontal: 20.w), + // decoration: BoxDecoration( + // image: DecorationImage( + // image: AssetImage( + // ATGlobalConfig.businessLogicStrategy.getVipPagePreviewImage( + // widget.index, + // 'rev', + // 'entrance_effect', + // ), + // ), + // fit: BoxFit.fill, + // ), + // ), + // ), + // ); + // }, + // ); + } + + void _clickSpecialGiftTassel() { + // SmartDialog.show( + // tag: "showSpecialGiftTassel", + // alignment: Alignment.center, + // animationType: SmartAnimationType.fade, + // builder: (_) { + // return SafeArea( + // top: false, + // child: Container( + // height: 280.w, + // margin: EdgeInsets.symmetric(horizontal: 20.w), + // decoration: BoxDecoration( + // image: DecorationImage( + // image: AssetImage( + // ATGlobalConfig.businessLogicStrategy.getVipPagePreviewImage( + // widget.index, + // 'rev', + // 'special_gift_tassel', + // ), + // ), + // fit: BoxFit.fill, + // ), + // ), + // ), + // ); + // }, + // ); + } + + void _clickRoomCoverBorder() { + // SmartDialog.show( + // tag: "showRoomCoverBorder", + // alignment: Alignment.center, + // animationType: SmartAnimationType.fade, + // builder: (_) { + // return SafeArea( + // top: false, + // child: Container( + // height: 280.w, + // margin: EdgeInsets.symmetric(horizontal: 20.w), + // decoration: BoxDecoration( + // image: DecorationImage( + // image: AssetImage( + // ATGlobalConfig.businessLogicStrategy.getVipPagePreviewImage( + // widget.index, + // 'rev', + // 'room_cover_headdress', + // ), + // ), + // fit: BoxFit.fill, + // ), + // ), + // ), + // ); + // }, + // ); + } + + void _clickRoomSeat() { + // SmartDialog.show( + // tag: "showRoomSeat", + // alignment: Alignment.center, + // animationType: SmartAnimationType.fade, + // builder: (_) { + // return SafeArea( + // top: false, + // child: Container( + // height: 280.w, + // margin: EdgeInsets.symmetric(horizontal: 20.w), + // decoration: BoxDecoration( + // image: DecorationImage( + // image: AssetImage( + // ATGlobalConfig.businessLogicStrategy.getVipPagePreviewImage( + // widget.index, + // 'rev', + // 'mic_rippl_theme', + // ), + // ), + // fit: BoxFit.fill, + // ), + // ), + // ), + // ); + // }, + // ); + } + + void _clickChatBox() { + // SmartDialog.show( + // tag: "showChatBox", + // alignment: Alignment.center, + // animationType: SmartAnimationType.fade, + // builder: (_) { + // return SafeArea( + // top: false, + // child: Container( + // height: 280.w, + // margin: EdgeInsets.symmetric(horizontal: 20.w), + // decoration: BoxDecoration( + // image: DecorationImage( + // image: AssetImage( + // ATGlobalConfig.businessLogicStrategy.getVipPagePreviewImage( + // widget.index, + // 'pre', + // 'chatbox', + // ), + // ), + // fit: BoxFit.fill, + // ), + // ), + // ), + // ); + // }, + // ); + } + + void _clickExclusiveVehicles() { + // SmartDialog.show( + // tag: "showExclusiveVehicles", + // alignment: Alignment.center, + // animationType: SmartAnimationType.fade, + // builder: (_) { + // return SafeArea( + // top: false, + // child: Container( + // height: 280.w, + // margin: EdgeInsets.symmetric(horizontal: 20.w), + // decoration: BoxDecoration( + // image: DecorationImage( + // image: AssetImage( + // ATGlobalConfig.businessLogicStrategy.getVipPagePreviewImage( + // widget.index, + // 'rev', + // 'exclusive_vehicles', + // ), + // ), + // fit: BoxFit.fill, + // ), + // ), + // ), + // ); + // }, + // ); + } + + void _buy() { + ATLoadingManager.exhibitOperation(context: context); + StoreRepositoryImp() + .storePurchasing( + widget.vip.id ?? "", + ATPropsType.NOBLE_VIP.name, + ATCurrencyType.GOLD.name, + "${widget.vip.propsPrices?.first.days}", + ) + .then((value) { + ATTts.show(ATAppLocalizations.of(context)!.operationSuccessful); + Provider.of( + context, + listen: false, + ).getMyUserInfo(needLoadUserCountGuard: false); + Provider.of(context, listen: false).needUpDataUserInfo = + true; + hasCurrentVip = true; + Provider.of( + context, + listen: false, + ).updateBalance(value); + setState(() {}); + ATLoadingManager.veilRoutine(); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } +} diff --git a/lib/chatvibe_features/home/vip/vip_list_page.dart b/lib/chatvibe_features/home/vip/vip_list_page.dart new file mode 100644 index 0000000..ae44f5a --- /dev/null +++ b/lib/chatvibe_features/home/vip/vip_list_page.dart @@ -0,0 +1,162 @@ +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/store_repository_imp.dart'; +import 'package:aslan/chatvibe_features/home/vip/vip_item_page.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; + +import '../../../chatvibe_data/models/enum/at_currency_type.dart'; +import '../../../chatvibe_domain/usecases/at_fixed_width_tabIndicator.dart'; +import '../../../chatvibe_ui/theme/chatvibe_theme.dart'; +import '../../user/settings/socialPrivilege/social_privilege_page.dart'; + +class VipListPage extends StatefulWidget { + const VipListPage({super.key}); + + @override + VipListPageState createState() => VipListPageState(); +} + +class VipListPageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + final List _pages = []; + int _currentIndex = 1; + + @override + void initState() { + super.initState(); + StoreRepositoryImp().nobleVipList(ATCurrencyType.GOLD.name).then((value) { + int i = 1; + for (var vip in value) { + _pages.add(VipItemPage(vip, i)); + i = i + 1; + } + _tabController = TabController(length: _pages.length, vsync: this); + _tabController.addListener(() { + _currentIndex = _tabController.index + 1; + setState(() {}); + }); + setState(() {}); + }); + } + + // Widget _buildTab(int vipLevel, int index) { + // return AnimatedBuilder( + // animation: _tabController, + // builder: (context, child) { + // final selectedAsset = ATGlobalConfig.businessLogicStrategy + // .getVipPageTabSelectedIcon(vipLevel); + // final unselectedAsset = ATGlobalConfig.businessLogicStrategy + // .getVipPageTabUnselectedIcon(vipLevel); + // return Image.asset( + // _tabController.index == index ? selectedAsset : unselectedAsset, + // height: 26.w, + // ); + // }, + // ); + // } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + ATGlobalConfig.businessLogicStrategy.getVipPageHeadBackgroundImage( + _currentIndex, + ), + width: ScreenUtil().screenWidth, + height: 550.w, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.vip, + backButtonColor: Colors.white, + actions: [ + ATDebounceWidget( + child: Container( + padding: EdgeInsetsDirectional.only(end: 16.w), + child: Image.asset( + "atu_images/index/at_icon_settings.png", + width: 24.w, + height: 24.w, + color: Colors.white, + ), + ), + onTap: () { + SmartDialog.show( + tag: "socialPrivilegeDialog", + maskColor: Colors.black26, + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + clickMaskDismiss: true, + builder: (_) { + return SocialPrivilegePage(); + }, + ); + }, + ), + ], + ), + body: SafeArea( + top: false, + child: + _pages.isNotEmpty + ? Column( + children: [ + SizedBox( + height: 32.w, + child: TabBar( + tabAlignment: TabAlignment.center, + labelPadding: EdgeInsets.symmetric( + horizontal: 18.w, + ), + labelColor: ChatVibeTheme.primaryLight, + isScrollable: true, + unselectedLabelColor: Colors.white54, + labelStyle: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.bold, + ), + unselectedLabelStyle: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + indicator: ATFixedWidthTabIndicator( + width: 15.w, + color: ChatVibeTheme.primaryLight, + ), + dividerColor: Colors.transparent, + controller: _tabController, + tabs: + ["VIP1", "VIP2", "VIP3", "VIP4", "VIP5"] + .map( + (e) => Padding( + padding: EdgeInsets.only(bottom: 5.w), + child: Text(e), + ), + ) + .toList(), + ), + ), + Expanded( + child: TabBarView( + controller: _tabController, + children: _pages, + ), + ), + ], + ) + : Container(), + ), + ), + ], + ); + } +} diff --git a/lib/chatvibe_features/index/drawer/my_drawer.dart b/lib/chatvibe_features/index/drawer/my_drawer.dart new file mode 100644 index 0000000..4f32534 --- /dev/null +++ b/lib/chatvibe_features/index/drawer/my_drawer.dart @@ -0,0 +1,860 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_features/family/family_route.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_features/user/settings/settings_route.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_managers/room_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_features/home/coupon/coupon_route.dart'; +import 'package:aslan/chatvibe_features/store/store_route.dart'; +import 'package:aslan/chatvibe_features/wallet/wallet_route.dart'; + +import '../../../chatvibe_data/models/enum/at_vip_type.dart'; + +///抽屉 +class MyDrawer extends StatefulWidget { + @override + _MyDrawerState createState() => _MyDrawerState(); +} + +class _MyDrawerState extends State { + @override + void initState() { + super.initState(); + Provider.of(context, listen: false).getMyRoom(); + Provider.of(context, listen: false).getUserIdentity(); + Provider.of( + context, + listen: false, + ).getMyUserInfo(needLoadUserCountGuard: false, needRefreshFamily: true); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsetsDirectional.only( + top: 42.w, + ).copyWith(start: 8.w, end: 8.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/index/at_icon_drawer_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsetsDirectional.symmetric(vertical: 8.w), + decoration: BoxDecoration( + color: Colors.white12, + borderRadius: BorderRadius.all(Radius.circular(10.w)), + border: Border.all(color: Colors.white, width: 0.6.w), + ), + child: Row( + children: [ + SizedBox(width: 10.w), + Consumer( + builder: (context, ref, child) { + return head( + url: + AccountStorage() + .getCurrentUser() + ?.userProfile! + .userAvatar ?? + "", + width: 58.w, + headdress: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.getHeaddress() + ?.sourceUrl, + ); + }, + ), + SizedBox(width: 3.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + chatvibeNickNameText( + maxWidth: 115.w, + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname ?? + "", + fontSize: 16.sp, + fontWeight: FontWeight.bold, + type: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.getVIP() + ?.name ?? + "", + needScroll: + (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname + ?.characters + .length ?? + 0) > + 10, + ), + GestureDetector( + child: Container( + padding: EdgeInsets.symmetric(vertical: 8.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.hasSpecialId() ?? + false) + ? "atu_images/general/at_icon_special_id_bg.png" + : "atu_images/general/at_icon_id_bg.png", + ), + fit: BoxFit.fitWidth, + ), + ), + child: Row( + textDirection: TextDirection.ltr, + children: [ + SizedBox(width: 38.w), + text( + "${AccountStorage().getCurrentUser()?.userProfile?.getID()}", + fontSize: 12.sp, + textColor: Colors.white, + fontWeight: FontWeight.bold, + ), + SizedBox(width: 5.w), + Image.asset( + "atu_images/room/at_icon_user_card_copy_id.png", + width: 12.w, + height: 12.w, + ), + SizedBox(width: 8.w), + ], + ), + ), + onTap: () { + Clipboard.setData( + ClipboardData( + text: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.getID() ?? + "", + ), + ); + ATTts.show( + ATAppLocalizations.of(context)!.copiedToClipboard, + ); + }, + ), + ], + ), + Spacer(), + Icon( + ATGlobalConfig.lang == "ar" + ? Icons.keyboard_arrow_left + : Icons.keyboard_arrow_right, + size: 25.w, + color: Colors.white, + ), + SizedBox(width: 12.w), + ], + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=true&tageId=${AccountStorage().getCurrentUser()?.userProfile?.id}", + replace: false, + ); + }, + ), + SizedBox(height: 10.w), + _buildMyRoom(), + SizedBox(height: 5.w), + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + SizedBox(width: 10.w), + Expanded( + child: GestureDetector( + child: Container( + height: 59.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/index/at_icon_bag.png"), + fit: BoxFit.contain, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 39.w), + Row( + textDirection: TextDirection.ltr, + children: [ + SizedBox(width: 10.w), + Image.asset( + "atu_images/general/at_icon_jb.png", + width: 18.w, + height: 18.w, + ), + Consumer( + builder: (context, ref, child) { + return text( + ref.myBalance > 1000 + ? "${(ref.myBalance / 1000).toStringAsFixed(2)}k" + : "${ref.myBalance}", + fontSize: 11.sp, + textColor: Colors.white, + fontWeight: FontWeight.bold, + ); + }, + ), + ], + ), + ], + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + WalletRoute.recharge, + replace: false, + ); + }, + ), + ), + SizedBox(width: 5.w), + Expanded( + child: GestureDetector( + child: SizedBox( + height: 68.w, + child: Consumer( + builder: (context, ref, child) { + return Stack( + alignment: Alignment.bottomCenter, + children: [ + Container( + height: 47.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(_getVIPTagIcon()), + fit: BoxFit.contain, + ), + ), + child: Column( + children: [ + SizedBox(height: 27.w), + AccountStorage().getVIPExpireTime() != + "0" + ? Row( + textDirection: + TextDirection.ltr, + children: [ + SizedBox(width: 12.w), + text( + "${ATMDateUtils.evaluateDaysDifferenceFunction(DateTime.now(), DateTime.fromMillisecondsSinceEpoch(int.parse(AccountStorage().getVIPExpireTime())))} ${ATAppLocalizations.of(context)!.days}", + fontSize: 11.sp, + textColor: Colors.white, + fontWeight: FontWeight.bold, + ), + ], + ) + : Container(), + ], + ), + ), + Positioned( + top: 0, + right: 2.w, + child: + _getVIPIcon().isNotEmpty + ? Image.asset( + width: 60.w, + height: 60.w, + _getVIPIcon(), + ) + : Container(), + ), + ], + ); + }, + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + MainRoute.vipList, + replace: false, + ); + }, + ), + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 10.w), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(width: 2.w), + _buildItem2( + "atu_images/index/at_icon_shop.png", + ATAppLocalizations.of(context)!.store, + () { + ATNavigatorUtils.push( + context, + StoreRoute.list, + replace: false, + ); + }, + ), + _buildItem2( + "atu_images/index/at_icon_level.png", + ATAppLocalizations.of(context)!.level, + () { + ATNavigatorUtils.push( + context, + MainRoute.levelList, + replace: false, + ); + }, + ), + _buildItem2( + "atu_images/index/at_icon_my_items.png", + ATAppLocalizations.of(context)!.items, + () { + ATNavigatorUtils.push( + context, + StoreRoute.bags, + replace: false, + ); + }, + ), + _buildItem2( + "atu_images/index/at_icon_task.png", + ATAppLocalizations.of(context)!.task, + () { + ATNavigatorUtils.push( + context, + MainRoute.taskList, + replace: false, + ); + }, + ), + SizedBox(width: 2.w), + ], + ), + SizedBox(height: 5.w), + _buildItem( + "atu_images/index/at_icon_paid.png", + ATAppLocalizations.of(context)!.getPaidToRefer, + () { + ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.inviteNewUserUrl)}&showTitle=false", + replace: false, + ); + }, + ), + _buildItem( + "atu_images/index/at_icon_coupon.png", + ATAppLocalizations.of(context)!.coupon, + () { + ATNavigatorUtils.push( + context, + CouponRoute.couponList, + replace: false, + ); + }, + ), + _buildItem( + "atu_images/index/at_icon_medals.png", + ATAppLocalizations.of(context)!.medals, + () { + ATNavigatorUtils.push( + context, + MainRoute.medalsList, + replace: false, + ); + }, + ), + _buildItem( + "atu_images/index/at_icon_honor.png", + ATAppLocalizations.of(context)!.honor, + () { + ATNavigatorUtils.push( + context, + MainRoute.honorList, + replace: false, + ); + }, + ), + Consumer( + builder: (context, ref, child) { + return (ref.userIdentity?.anchor ?? false) + ? (ref.userIdentity?.agent ?? false + ? _buildItem( + "atu_images/index/at_icon_agent_center.png", + ATAppLocalizations.of(context)!.agentCenter, + () { + ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.agencyCenterUrl)}&showTitle=false", + replace: false, + ); + }, + ) + : _buildItem( + "atu_images/index/at_icon_host_center.png", + ATAppLocalizations.of(context)!.hostCenter, + () { + ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.hostCenterUrl)}&showTitle=false", + replace: false, + ); + }, + )) + : _buildItem( + "atu_images/index/at_icon_become_host_center.png", + ATAppLocalizations.of(context)!.becomeHost, + () { + ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.anchorAgentUrl)}&showTitle=false", + replace: false, + ); + }, + ); + }, + ), + + Consumer( + builder: (context, ref, child) { + return (ref.userIdentity?.admin ?? false) + ? Container() + : ((ref.userIdentity?.bdLeader ?? false) + ? _buildItem( + "atu_images/index/at_icon_bd_leader.png", + ATAppLocalizations.of(context)!.bdLeader, + () { + ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.bdLeaderUrl)}&showTitle=false", + replace: false, + ); + }, + ) + : ((ref.userIdentity?.bd ?? false) + ? _buildItem( + "atu_images/index/at_icon_bd_center.png", + ATAppLocalizations.of(context)!.bdCenter, + () { + ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.bdCenterUrl)}&showTitle=false", + replace: false, + ); + }, + ) + : Container())); + }, + ), + + Consumer( + builder: (context, ref, child) { + return (ref.userIdentity?.freightAgent ?? false) + ? _buildItem( + "atu_images/index/at_icon_recharge_agency.png", + ATAppLocalizations.of(context)!.rechargeAgency, + () { + ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.coinSellerUrl)}&showTitle=false", + replace: false, + ); + }, + ) + : Container(); + }, + ), + Consumer( + builder: (context, ref, child) { + return (ref.userIdentity?.admin ?? false) + ? _buildItem( + "atu_images/index/at_icon_admin_center.png", + ATAppLocalizations.of(context)!.adminCenter, + () { + ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.adminUrl)}&showTitle=false", + replace: false, + ); + }, + ) + : Container(); + }, + ), + Consumer( + builder: (context, ref, child) { + return AccountStorage() + .getCurrentUser() + ?.userProfile + ?.familyId == + null + ? _buildItem( + "atu_images/index/at_icon_family.png", + ATAppLocalizations.of(context)!.joinFamily, + () { + ATNavigatorUtils.push( + context, + FamilyRoute.familyList, + replace: false, + ); + }, + ) + : _buildItem( + "atu_images/index/at_icon_family.png", + ATAppLocalizations.of(context)!.family, + () { + ATNavigatorUtils.push( + context, + "${FamilyRoute.familyInfo}?familyId=${AccountStorage().getCurrentUser()?.userProfile?.familyId ?? 0}", + replace: false, + ); + }, + ); + }, + ), + ], + ), + ), + ), + + // _buildItem("images/index/at_icon_task.png", "Task"), + // Divider(color: Colors.black12, thickness: 0.5, height: 28.w), + Divider(color: Colors.black12, thickness: 0.5, height: 28.w), + GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + child: Column( + children: [ + Image.asset( + "atu_images/index/at_icon_settings.png", + width: 30.w, + height: 30.w, + color: Colors.white, + ), + SizedBox(height: 3.w), + text( + ATAppLocalizations.of(context)!.settings, + textColor: Colors.white, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ], + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + SettingsRoute.settings, + replace: false, + ); + }, + ), + SizedBox(height: 15.w), + ], + ), + ); + } + + _buildMyRoom() { + return Consumer( + builder: (_, provider, __) { + return provider.myRoom != null + ? GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsetsDirectional.symmetric(vertical: 8.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(10.w)), + color: Colors.white12, + border: Border.all(color: Colors.white, width: 0.6.w), + ), + child: Row( + children: [ + SizedBox(width: 10.w), + netImage( + url: provider.myRoom?.roomCover ?? "", + borderRadius: BorderRadius.all(Radius.circular(8.w)), + width: 48.w, + ), + SizedBox(width: 10.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + constraints: BoxConstraints( + maxWidth: 115.w, + maxHeight: 24.w, + ), + child: + (provider.myRoom?.roomName?.length ?? 0) > 10 + ? Marquee( + text: provider.myRoom?.roomName ?? "", + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 40.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration( + seconds: 1, + ), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + provider.myRoom?.roomName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + text( + provider.myRoom?.roomDesc ?? "", + fontSize: 14.sp, + textColor: Colors.white, + ), + ], + ), + ), + Icon( + ATGlobalConfig.lang == "ar" + ? Icons.keyboard_arrow_left + : Icons.keyboard_arrow_right, + size: 25.w, + color: Colors.white, + ), + SizedBox(width: 12.w), + ], + ), + ), + onTap: () { + String roomId = + Provider.of( + context, + listen: false, + ).myRoom?.id ?? + ""; + Provider.of( + context, + listen: false, + ).joinRoom(context, roomId); + }, + ) + : GestureDetector( + child: Row( + children: [ + SizedBox(width: 10.w), + Image.asset( + "atu_images/index/at_icon_add_room.png", + width: 36.w, + height: 36.w, + ), + SizedBox(width: 10.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text( + ATAppLocalizations.of(context)!.roomName, + fontSize: 14.sp, + textColor: Colors.white, + fontWeight: FontWeight.bold, + ), + text( + ATAppLocalizations.of(context)!.startVoiceParty, + fontSize: 13.sp, + textColor: Colors.white, + ), + ], + ), + SizedBox(width: 12.w), + ], + ), + onTap: () { + provider.createRoom(context); + }, + ); + }, + ); + } + + _buildItem(String icon, String title, Function() onTap) { + return GestureDetector( + child: Column( + children: [ + SizedBox(height: 10.w), + Container( + alignment: AlignmentDirectional.centerStart, + height: 48.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/index/at_icon_my_drawer_item_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + SizedBox(width: 10.w), + Image.asset( + icon, + width: 26.w, + height: 26.w, + color: Colors.white, + ), + SizedBox(width: 10.w), + Expanded( + child: text( + title, + textColor: Colors.white, + fontSize: 17.sp, + fontWeight: FontWeight.w600, + ), + ), + Icon( + ATGlobalConfig.lang == "ar" + ? Icons.keyboard_arrow_left + : Icons.keyboard_arrow_right, + color: Colors.white, + size: 15.w, + ), + SizedBox(width: 12.w), + ], + ), + ], + ), + ), + ], + ), + onTap: () { + onTap(); + }, + ); + } + + _buildItem2(String icon, String title, Function() onTap) { + return GestureDetector( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset(icon, width: 35.w, height: 35.w), + SizedBox(height: 5.w), + text( + title, + textColor: Colors.white, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 12.w), + ], + ), + onTap: () { + onTap(); + }, + ); + } + + _getVIPTagIcon() { + var vip = AccountStorage().getVIP(); + if (vip != null) { + if (vip.name == ATVIPType.VISCOUNT.name) { + return "atu_images/vip/at_icon_vip_1.png"; + } else if (vip.name == ATVIPType.EARL.name) { + return "atu_images/vip/at_icon_vip_2.png"; + } else if (vip.name == ATVIPType.MARQUIS.name) { + return "atu_images/vip/at_icon_vip_3.png"; + } else if (vip.name == ATVIPType.DUKE.name) { + return "atu_images/vip/at_icon_vip_4.png"; + } else if (vip.name == ATVIPType.KING.name) { + return "atu_images/vip/at_icon_vip_5.png"; + }else if (vip.name == ATVIPType.EMPEROR.name) { + return "atu_images/vip/at_icon_vip_6.png"; + } + } + return "atu_images/vip/at_icon_vip_no.png"; + } + + String _getVIPIcon() { + String name = AccountStorage().getVIP()?.name ?? ""; + if (name == ATVIPType.VISCOUNT.name) { + return "atu_images/vip/at_icon_vip1_ic.webp"; + } else if (name == ATVIPType.EARL.name) { + return "atu_images/vip/at_icon_vip2_ic.webp"; + } else if (name == ATVIPType.MARQUIS.name) { + return "atu_images/vip/at_icon_vip3_ic.webp"; + } else if (name == ATVIPType.DUKE.name) { + return "atu_images/vip/at_icon_vip4_ic.webp"; + } else if (name == ATVIPType.KING.name) { + return "atu_images/vip/at_icon_vip5_ic.webp"; + } else if (name == ATVIPType.EMPEROR.name) { + return "atu_images/vip/at_icon_vip6_ic.webp"; + } + return ""; + } +} diff --git a/lib/chatvibe_features/index/index_page.dart b/lib/chatvibe_features/index/index_page.dart new file mode 100644 index 0000000..9e86769 --- /dev/null +++ b/lib/chatvibe_features/index/index_page.dart @@ -0,0 +1,345 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/utilities/at_heartbeat_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/floating_screen_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/config_repository_imp.dart'; +import 'package:aslan/chatvibe_features/home/index_home_page.dart'; +import 'package:aslan/chatvibe_features/chat/message/at_message_page.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:wakelock_plus/wakelock_plus.dart'; +import 'package:aslan/chatvibe_ui/components/at_float_ichart.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_core/utilities/at_dialog_queue_manager.dart'; +import 'package:aslan/chatvibe_core/utilities/at_dialog_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_index_banner_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_version_manage_latest_res.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/sgin/sgin_page.dart'; +import 'package:aslan/chatvibe_ui/widgets/update/app_version_update_page.dart'; +import '../../chatvibe_data/models/enum/at_heartbeat_status.dart'; +import '../home/game/at_index_game_page.dart'; +import '../user/me_page2.dart'; + +/** + * 首页 + */ +class ATIndexPage extends StatefulWidget { + @override + _ATIndexPageState createState() => _ATIndexPageState(); +} + +class _ATIndexPageState extends State { + int _currentIndex = 0; + final List _pages = []; + final List _bottomItems = []; + AppGeneralManager? generalProvider; + + @override + void initState() { + super.initState(); + generalProvider = Provider.of(context, listen: false); + Provider.of(context, listen: false).init(context); + Provider.of(context, listen: false).init(context); + loadAppVersionAndCheckInToday(); + Provider.of( + context, + listen: false, + ).getUserIdentity(); + Provider.of(context, listen: false).balance(); + ATHeartbeatUtils.scheduleHeartbeat(ATHeartbeatStatus.ONLINE.name, false); + DataPersistence.setLang(ATGlobalConfig.lang); + OverlayManager().activate(); + WakelockPlus.enable(); + String roomId = DataPersistence.getLastTimeRoomId(); + if (roomId.isNotEmpty) { + AccountRepository().quitRoom(roomId).catchError((e) { + return true; // 错误已处理 + }); + } + } + + @override + void dispose() { + WakelockPlus.disable(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + ATFloatIchart().init(context); + _initWidget(); + return WillPopScope( + onWillPop: _doubleExit, + child: Stack( + children: [ + Image.asset( + "atu_images/index/at_icon_index_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + SafeArea( + top: false, + child: Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + body: _pages[_currentIndex], + bottomNavigationBar: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + ), + child: BottomNavigationBar( + elevation: 0, + backgroundColor: Colors.transparent, + selectedLabelStyle: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 13.sp, + ), + unselectedLabelStyle: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 12.sp, + ), + type: BottomNavigationBarType.fixed, + selectedItemColor: ChatVibeTheme.primaryLight, + showUnselectedLabels: true, + showSelectedLabels: true, + items: _bottomItems, + currentIndex: _currentIndex, + onTap: (index) => setState(() => _currentIndex = index), + ), + ), + ), + ), + ], + ), + ); + } + + ///双击退出 + int _lastClickTime = 0; + + Future _doubleExit() { + int nowTime = DateTime.now().microsecondsSinceEpoch; + if (_lastClickTime != 0 && nowTime - _lastClickTime > 1500) { + return Future.value(true); + } else { + _lastClickTime = DateTime.now().microsecondsSinceEpoch; + Future.delayed(const Duration(milliseconds: 1500), () { + _lastClickTime = 0; + }); + return Future.value(false); + } + } + + void _initWidget() { + _pages.clear(); + _bottomItems.clear(); + _pages.add(ATIndexHomePage()); + _pages.add(ATIndexGamePage()); + // _pages.add(IndexDynamicPage()); + _pages.add(ATMessagePage()); + _pages.add(MePage2()); + + _bottomItems.add( + BottomNavigationBarItem( + icon: Image.asset( + "atu_images/index/at_icon_home_no.png", + width: 25.w, + height: 25.w, + ), + activeIcon: Image.asset( + "atu_images/index/at_icon_home_en.png", + width: 25.w, + height: 25.w, + ), + label: ATAppLocalizations.of(context)!.home, + ), + ); + + _bottomItems.add( + BottomNavigationBarItem( + icon: Image.asset( + "atu_images/index/at_icon_game_no.png", + width: 25.w, + height: 25.w, + ), + activeIcon: Image.asset( + "atu_images/index/at_icon_game_en.png", + width: 25.w, + height: 25.w, + ), + label: ATAppLocalizations.of(context)!.game, + ), + ); + + _bottomItems.add( + BottomNavigationBarItem( + icon: Selector( + selector: (c, p) => p.allUnReadCount, + shouldRebuild: (prev, next) => prev != next, + builder: (_, allUnReadCount, __) { + return allUnReadCount > 0 + ? Badge( + backgroundColor: Colors.red, + label: text( + "${allUnReadCount > 99 ? "99+" : allUnReadCount}", + fontSize: 9.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + alignment: AlignmentDirectional.topEnd, + child: Image.asset( + "atu_images/index/at_icon_message_no.png", + width: 25.w, + height: 25.w, + ), + ) + : Image.asset( + "atu_images/index/at_icon_message_no.png", + width: 25.w, + height: 25.w, + ); + }, + ), + activeIcon: Selector( + selector: (c, p) => p.allUnReadCount, + shouldRebuild: (prev, next) => prev != next, + builder: (_, allUnReadCount, __) { + return allUnReadCount > 0 + ? Badge( + backgroundColor: Colors.red, + label: text( + "${allUnReadCount > 99 ? "99+" : allUnReadCount}", + fontSize: 9.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + alignment: AlignmentDirectional.topEnd, + child: Image.asset( + "atu_images/index/at_icon_message_en.png", + width: 25.w, + height: 25.w, + ), + ) + : Image.asset( + "atu_images/index/at_icon_message_en.png", + width: 25.w, + height: 25.w, + ); + }, + ), + label: ATAppLocalizations.of(context)!.message, + ), + ); + _bottomItems.add( + BottomNavigationBarItem( + icon: Image.asset( + "atu_images/index/at_icon_me_no.png", + width: 25.w, + height: 25.w, + ), + activeIcon: Image.asset( + "atu_images/index/at_icon_me_en.png", + width: 25.w, + height: 25.w, + ), + label: ATAppLocalizations.of(context)!.me, + ), + ); + } + + void loadAppVersionAndCheckInToday() { + Future.wait([ + ConfigRepositoryImp().versionManageLatest(), + AccountRepository().checkInToday(), + ConfigRepositoryImp().getBanner(types: ["AD"]), + ]).then((result) { + ATVersionManageLatestRes versionManageLatestRes = + result[0] as ATVersionManageLatestRes; + bool checkInToday = result[1] as bool; + List banners = result[2] as List; + if ((versionManageLatestRes.buildVersion ?? 0) > + int.parse(ATGlobalConfig.build)) { + if (!(versionManageLatestRes.review ?? false)) { + //过审 + ATModalQueue().addDialog(() { + SmartDialog.show( + tag: "showUpdateDialog", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + clickMaskDismiss: versionManageLatestRes.forceUpdate == false, + backType: + versionManageLatestRes.forceUpdate == false + ? null + : SmartBackType.block, + onDismiss: () { + ATModalQueue().showComplete(); + }, + builder: (_) { + return AppVersionUpdatePage(versionManageLatestRes); + }, + ); + }, priority: 1000); + } + } + if (!checkInToday) { + ATModalQueue().addDialog(() { + SmartDialog.show( + tag: "showSginListAward", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + onDismiss: () { + ATModalQueue().showComplete(); + }, + builder: (_) { + return SginPage(true); + }, + ); + }, priority: 500); + } + Provider.of( + context, + listen: false, + ).getMyUserInfo().whenComplete(() { + if (!ATGlobalConfig.isReview) { + if ((AccountStorage().getCurrentUser()?.userProfile?.firstRecharge ?? + false)) { + ///首充弹框 + ATModalQueue().addDialog(() { + ATDialogUtils.showFirstRechargeDialog(context); + }, priority: 100); + } + } + + if (banners.isNotEmpty) { + ///banner弹框 + bool needShow = DataPersistence.getBool( + "IndexBannerTodayShow${ATMDateUtils.formatDateTime(DateTime.now())}", + ); + if (!needShow) { + ATModalQueue().addDialog(() { + ATDialogUtils.revealBannerDialog(context, banners); + }, priority: 100); + } + } + }); + }); + } +} diff --git a/lib/chatvibe_features/index/main_route.dart b/lib/chatvibe_features/index/main_route.dart new file mode 100644 index 0000000..2fb8d67 --- /dev/null +++ b/lib/chatvibe_features/index/main_route.dart @@ -0,0 +1,335 @@ +import 'dart:convert'; + +import 'package:fluro/fluro.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_res.dart'; +import 'package:aslan/chatvibe_features/user/fans/fans_user_list_page.dart'; +import 'package:aslan/chatvibe_features/settings/language/language_page.dart'; +import 'package:aslan/chatvibe_features/report/report_page.dart'; +import 'package:aslan/chatvibe_features/task/at_task_list_page.dart'; +import 'package:aslan/chatvibe_features/webview/webview_page.dart'; + +import 'package:aslan/chatvibe_core/routes/at_router_init.dart'; +import 'package:aslan/chatvibe_features/admin/editing/editing_user_room_page.dart'; +import 'package:aslan/chatvibe_features/admin/search/edit_room_search_admin_page.dart'; +import 'package:aslan/chatvibe_features/admin/search/edit_user_search_admin_page.dart'; +import 'package:aslan/chatvibe_features/user/profile/person_detail_page.dart'; +import 'package:aslan/chatvibe_features/user/profile/relation/cp/cp_list_page.dart'; +import 'package:aslan/chatvibe_features/room/inv_game/inv_game_page.dart'; +import 'package:aslan/chatvibe_features/search/at_search_page.dart'; +import 'package:aslan/chatvibe_features/media/image_preview_page.dart'; +import 'package:aslan/chatvibe_features/media/video_player_page.dart'; +import 'package:aslan/chatvibe_features/webview/game_bs_webview_page.dart'; +import 'package:aslan/chatvibe_features/webview/game_lx_webview_page.dart'; +import 'package:aslan/chatvibe_features/webview/game_ry_webview_page.dart'; +import 'package:aslan/chatvibe_features/webview/game_ym_webview_page.dart'; +import 'package:aslan/chatvibe_features/home/honor/honor_list_page.dart'; +import 'package:aslan/chatvibe_features/home/medals/medals_list_page.dart'; +import 'package:aslan/chatvibe_features/home/vip/vip_list_page.dart'; +import 'package:aslan/chatvibe_features/user/follow/follow_user_list_page.dart'; +import 'package:aslan/chatvibe_features/user/level/level_page.dart'; +import 'package:aslan/chatvibe_features/user/visitor/visitor_user_list_page.dart'; + +import '../chat/message/at_message_page.dart'; +import '../home/medals_honor/medals_honor_list_page.dart'; +import '../user/edit/edit_user_info_page2.dart'; + +class MainRoute implements ATIRouterProvider { + static String report = '/at/main/report'; + static String person = '/at/main/person'; + static String edit = '/at/main/person/edit'; + static String follow = '/at/main/me/follow'; + static String vistors = '/at/main/me/vistors'; + static String fans = '/at/main/me/fans'; + static String mainSearch = '/at/main/at/mainSearch'; + static String cpList = '/at/main/person/cp/cpList'; + static String language = '/at/main/language'; + static String medalsList = '/at/main/medalsList'; + static String medalsHonorListPage = '/at/main/medalsHonorListPage'; + static String vipList = '/at/main/vipList'; + static String levelList = '/level/levelList'; + static String videoPlayer = '/at/main/videoPlayer'; + static String imagePreview = '/at/main/imagePreview'; + static String taskList = '/at/main/taskList'; + static String honorList = '/at/main/honorList'; + static String invGamePage = '/at/main/invGamePage'; + static String message = '/at/main/messagePage'; + static String editRoomSearchAdmin = '/at/main/editRoomSearchAdmin'; + static String editUserSearchAdmin = '/at/main/editUserSearchAdmin'; + static String editingUserRoomAdmin = '/at/main/editingUserRoomAdmin'; + static String webViewPage = '/at/main/webViewPage'; + static String gameBsWebviewPage = '/at/main/gameBsWebviewPage'; + static String gameRyWebviewPage = '/at/main/gameRyWebviewPage'; + static String gameLxWebviewPage = '/at/main/gameLxWebviewPage'; + static String gameYmWebviewPage = '/at/main/gameYmWebviewPage'; + + @override + void initRouter(FluroRouter router) { + router.define( + report, + handler: Handler( + handlerFunc: + (_, params) => + ReportPage( + type: params['type']!.first, + tageId: params['tageId']!.first, + ), + ), + ); + router.define( + person, + handler: Handler( + handlerFunc: + (_, params) => + PersonDetailPage( + isMe: params['isMe']!.first, + tageId: params['tageId']!.first, + ), + ), + ); + router.define( + mainSearch, + handler: Handler(handlerFunc: (_, params) => SearchPage()), + ); + router.define( + message, + handler: Handler( + handlerFunc: (_, Map> params) { + String? isFromRoom = params['isFromRoom']?.first; + return ATMessagePage(isFromRoom: isFromRoom == "true"); + }, + ), + ); + + router.define( + cpList, + handler: Handler(handlerFunc: (_, params) => CpListPage()), + ); + router.define( + language, + handler: Handler(handlerFunc: (_, params) => LanguagePage()), + ); + router.define( + edit, + handler: Handler(handlerFunc: (_, params) => EditUserInfoPage2()), + ); + router.define( + follow, + handler: Handler( + handlerFunc: + (_, params) => FollowUserListPage( + removePreviousPersonDetail: + params['removePreviousPersonDetail']?.first == 'true', + ), + ), + ); + router.define( + vistors, + handler: Handler( + handlerFunc: + (_, params) => VisitorUserListPage( + removePreviousPersonDetail: + params['removePreviousPersonDetail']?.first == 'true', + ), + ), + ); + router.define( + fans, + handler: Handler( + handlerFunc: + (_, params) => FansUserListPage( + removePreviousPersonDetail: + params['removePreviousPersonDetail']?.first == 'true', + ), + ), + ); + router.define( + medalsList, + handler: Handler(handlerFunc: (_, params) => MedalsListPage()), + ); + router.define( + vipList, + handler: Handler(handlerFunc: (_, params) => VipListPage()), + ); + router.define( + levelList, + handler: Handler(handlerFunc: (_, params) => LevelPage()), + ); + router.define( + taskList, + handler: Handler(handlerFunc: (_, params) => ATTaskListPage()), + ); + router.define( + honorList, + handler: Handler(handlerFunc: (_, params) => HonorListPage()), + ); + router.define( + editRoomSearchAdmin, + handler: Handler(handlerFunc: (_, params) => EditRoomSearchAdminPage()), + ); + router.define( + editUserSearchAdmin, + handler: Handler(handlerFunc: (_, params) => EditUserSearchAdminPage()), + ); + router.define( + invGamePage, + handler: Handler(handlerFunc: (_, params) => InvGamePage()), + ); + router.define( + videoPlayer, + handler: Handler( + handlerFunc: (_, Map> params) { + String? rawUrl = params['videoUrl']?.first; + String decodedVideoUrl = + rawUrl != null ? Uri.decodeComponent(rawUrl) : ""; + return VideoPlayerPage(videoUrl: decodedVideoUrl); + }, + ), + ); + router.define( + imagePreview, + handler: Handler( + handlerFunc: (_, Map> params) { + List imageUrls = []; + int initialIndex = 0; + + try { + // 解析 imageUrls 参数(JSON 字符串) + if (params['imageUrls'] != null && + params['imageUrls']!.isNotEmpty) { + String encodedUrls = params['imageUrls']!.first; + String decodedUrls = Uri.decodeComponent(encodedUrls); + List urlList = jsonDecode(decodedUrls); + imageUrls = urlList.map((item) => item.toString()).toList(); + } + + // 解析 initialIndex + if (params['initialIndex'] != null && + params['initialIndex']!.isNotEmpty) { + initialIndex = int.tryParse(params['initialIndex']!.first) ?? 0; + } + } catch (e) { + print('解析图片预览参数失败: $e'); + // 可以返回错误页面或默认页面 + } + + return ImagePreviewPage( + imageUrls: imageUrls, + initialIndex: initialIndex, + ); + }, + ), + ); + + router.define( + editingUserRoomAdmin, + handler: Handler( + handlerFunc: (_, Map> params) { + String? type = params['type']?.first; + String? profileJson = params['profile']?.first; + ChatVibeUserProfile? userProfile; + ChatVibeRoomRes? roomProfile; + if (type == "User") { + userProfile = ChatVibeUserProfile.fromJson( + jsonDecode(profileJson ?? ""), + ); + } else { + roomProfile = ChatVibeRoomRes.fromJson( + jsonDecode(profileJson ?? ""), + ); + } + + return EditingUserRoomPage( + type: type ?? "", + userProfile: userProfile, + roomProfile: roomProfile, + ); + }, + ), + ); + + router.define( + webViewPage, + handler: Handler( + handlerFunc: (_, params) { + String? url = params['url']?.first; + String decodedUrl = url != null ? Uri.decodeComponent(url) : ""; + return WebViewPage( + title: params['title']?.first ?? "", + url: decodedUrl, + showTitle: params['showTitle']?.first ?? "", + ); + }, + ), + ); + + router.define( + gameBsWebviewPage, + handler: Handler( + handlerFunc: (_, params) { + String? url = params['url']?.first; + String decodedUrl = url != null ? Uri.decodeComponent(url) : ""; + return GameBsWebviewPage( + url: decodedUrl, + height: double.parse(params['height']?.first ?? "0"), + width: double.parse(params['width']?.first ?? "0"), + gameId:params['gameId']?.first??"", + ); + }, + ), + ); + + router.define( + gameRyWebviewPage, + handler: Handler( + handlerFunc: (_, params) { + String? url = params['url']?.first; + String decodedUrl = url != null ? Uri.decodeComponent(url) : ""; + return GameRyWebviewPage( + url: decodedUrl, + height: double.parse(params['height']?.first ?? "0"), + width: double.parse(params['width']?.first ?? "0"), + gameId:params['gameId']?.first??"", + ); + }, + ), + ); + + router.define( + gameLxWebviewPage, + handler: Handler( + handlerFunc: (_, params) { + String? url = params['url']?.first; + String decodedUrl = url != null ? Uri.decodeComponent(url) : ""; + return GameLxWebviewPage( + url: decodedUrl, + height: double.parse(params['height']?.first ?? "0"), + width: double.parse(params['width']?.first ?? "0"), + gameId:params['gameId']?.first??"", + ); + }, + ), + ); + + router.define( + gameYmWebviewPage, + handler: Handler( + handlerFunc: (_, params) { + String? url = params['url']?.first; + String decodedUrl = url != null ? Uri.decodeComponent(url) : ""; + return GameYmWebviewPage( + url: decodedUrl, + height: double.parse(params['height']?.first ?? "0"), + width: double.parse(params['width']?.first ?? "0"), + gameId:params['gameId']?.first??"", + ); + }, + ), + ); + + router.define( + medalsHonorListPage, + handler: Handler(handlerFunc: (_, params) => MedalsHonorListPage()), + ); + } +} diff --git a/lib/chatvibe_features/media/image_preview_page.dart b/lib/chatvibe_features/media/image_preview_page.dart new file mode 100644 index 0000000..54b90f0 --- /dev/null +++ b/lib/chatvibe_features/media/image_preview_page.dart @@ -0,0 +1,148 @@ +import 'dart:io'; + +import 'package:extended_image/extended_image.dart'; +import 'package:flutter/material.dart'; +import 'package:photo_view/photo_view.dart'; +import 'package:photo_view/photo_view_gallery.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; + +class ImagePreviewPage extends StatefulWidget { + final List imageUrls; + final int initialIndex; + final PageController? pageController; + + const ImagePreviewPage({ + Key? key, + required this.imageUrls, + this.initialIndex = 0, + this.pageController, + }) : super(key: key); + + @override + _ImagePreviewPageState createState() => _ImagePreviewPageState(); +} + +class _ImagePreviewPageState extends State { + late PageController _pageController; + late int _currentIndex; + bool _showAppBar = true; + + @override + void initState() { + super.initState(); + + _currentIndex = widget.initialIndex; + _pageController = + widget.pageController ?? + PageController(initialPage: widget.initialIndex); + } + + void _onPageChanged(int index) { + setState(() { + _currentIndex = index; + }); + } + + void _toggleAppBar() { + setState(() { + _showAppBar = !_showAppBar; + }); + } + + void _onTap() { + _toggleAppBar(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: ATGlobalConfig.businessLogicStrategy.getImagePreviewBackgroundColor(), + body: Stack( + children: [ + // 图片画廊 + PhotoViewGallery.builder( + scrollPhysics: const BouncingScrollPhysics(), + builder: _buildImage, + itemCount: widget.imageUrls.length, + loadingBuilder: + (context, event) => Center( + child: Container( + width: 20.0, + height: 20.0, + child: CircularProgressIndicator( + value: + event == null + ? 0 + : event.cumulativeBytesLoaded / + event.expectedTotalBytes!, + color: ATGlobalConfig.businessLogicStrategy.getImagePreviewLoadingIndicatorColor(), + ), + ), + ), + backgroundDecoration: BoxDecoration(color: ATGlobalConfig.businessLogicStrategy.getImagePreviewGalleryBackgroundColor()), + pageController: _pageController, + onPageChanged: _onPageChanged, + enableRotation: true, + customSize: MediaQuery.of(context).size, + ), + + // 顶部应用栏 + if (_showAppBar) _buildAppBar(), + ], + ), + ); + } + + PhotoViewGalleryPageOptions _buildImage(BuildContext context, int index) { + final imageUrl = widget.imageUrls[index]; + return PhotoViewGalleryPageOptions( + imageProvider: _getImageProvider(imageUrl), + initialScale: PhotoViewComputedScale.contained, + minScale: PhotoViewComputedScale.contained * 0.8, + maxScale: PhotoViewComputedScale.covered * 2.0, + heroAttributes: PhotoViewHeroAttributes(tag: imageUrl), + onTapUp: (_, __, ___) => _onTap(), + ); + } + + ImageProvider _getImageProvider(String imageUrl) { + if (imageUrl.startsWith('http')) { + return ExtendedNetworkImageProvider( + imageUrl, + cache: true, // 启用缓存(默认即为true) + cacheMaxAge: Duration(days: 7), // 设置缓存有效期 + ); + } else { + return FileImage(File(imageUrl)); + } + } + + Widget _buildAppBar() { + return Positioned( + top: 0, + left: 0, + right: 0, + child: AppBar( + backgroundColor: ATGlobalConfig.businessLogicStrategy.getImagePreviewAppBarBackgroundColor(), + elevation: 0, + leading: IconButton( + icon: Icon(Icons.arrow_back, color: ATGlobalConfig.businessLogicStrategy.getImagePreviewBackIconColor()), + onPressed: () => Navigator.of(context).pop(), + ), + title: Text( + '${_currentIndex + 1} / ${widget.imageUrls.length}', + style: TextStyle(color: ATGlobalConfig.businessLogicStrategy.getImagePreviewTextColor()), + ), + centerTitle: true, + ), + ); + } + + @override + void dispose() { + if (widget.pageController == null) { + _pageController.dispose(); + } + super.dispose(); + } +} diff --git a/lib/chatvibe_features/media/video_player_page.dart b/lib/chatvibe_features/media/video_player_page.dart new file mode 100644 index 0000000..4e58b01 --- /dev/null +++ b/lib/chatvibe_features/media/video_player_page.dart @@ -0,0 +1,314 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/utilities/at_path_utils.dart'; +import 'package:video_player/video_player.dart'; + +class VideoPlayerPage extends StatefulWidget { + final String videoUrl; + + const VideoPlayerPage({Key? key, required this.videoUrl}) : super(key: key); + + @override + _VideoPlayerPageState createState() => _VideoPlayerPageState(); +} + +class _VideoPlayerPageState extends State { + late VideoPlayerController _controller; + late Future _initializeVideoPlayerFuture; + + bool _isPlaying = false; + bool _showControls = true; + Duration _currentPosition = Duration.zero; + Duration _totalDuration = Duration.zero; + + // 控制界面显示和隐藏的定时器 + Timer? _hideControlsTimer; + + @override + void initState() { + super.initState(); + _initializeVideoPlayer(); + } + + void _initializeVideoPlayer() { + final pathType = ATPathUtils.collectPathType(widget.videoUrl); + if (pathType == PathType.file) { + _controller = VideoPlayerController.file(File(widget.videoUrl)); + _initializeVideoPlayerFuture = _controller.initialize().then((_) { + setState(() { + _totalDuration = _controller.value.duration; + }); + }); + } else if (pathType == PathType.network) { + _controller = VideoPlayerController.networkUrl( + Uri.parse(widget.videoUrl), + ); + _initializeVideoPlayerFuture = _controller.initialize().then((_) { + setState(() { + _totalDuration = _controller.value.duration; + }); + }); + } + + // 添加监听器来更新进度 + _controller.addListener(_updateProgress); + } + + void _updateProgress() { + if (mounted) { + setState(() { + _currentPosition = _controller.value.position; + _totalDuration = _controller.value.duration; + _isPlaying = _controller.value.isPlaying; + }); + } + } + + void _togglePlayPause() { + setState(() { + if (_controller.value.isPlaying) { + _controller.pause(); + _isPlaying = false; + } else { + _controller.play(); + _isPlaying = true; + _resetHideControlsTimer(); + } + }); + } + + void _seekToPosition(Duration position) { + _controller.seekTo(position); + } + + void _resetHideControlsTimer() { + _hideControlsTimer?.cancel(); + _hideControlsTimer = Timer(Duration(seconds: ATGlobalConfig.businessLogicStrategy.getVideoPlayerControlsDisplayDuration()), () { + if (mounted && _controller.value.isPlaying) { + setState(() { + _showControls = false; + }); + } + }); + } + + void _toggleControls() { + setState(() { + _showControls = !_showControls; + }); + if (_showControls && _controller.value.isPlaying) { + _resetHideControlsTimer(); + } + } + + String _formatDuration(Duration duration) { + String twoDigits(int n) => n.toString().padLeft(2, '0'); + final hours = twoDigits(duration.inHours); + final minutes = twoDigits(duration.inMinutes.remainder(60)); + final seconds = twoDigits(duration.inSeconds.remainder(60)); + + if (duration.inHours > 0) { + return '$hours:$minutes:$seconds'; + } else { + return '$minutes:$seconds'; + } + } + + @override + void dispose() { + _hideControlsTimer?.cancel(); + _controller.removeListener(_updateProgress); + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: ATGlobalConfig.businessLogicStrategy.getVideoPlayerBackgroundColor(), + body: Center( + child: FutureBuilder( + future: _initializeVideoPlayerFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + return GestureDetector( + onTap: _toggleControls, + child: Stack( + alignment: Alignment.center, + children: [ + // 视频播放器 + AspectRatio( + aspectRatio: _controller.value.aspectRatio, + child: VideoPlayer(_controller), + ), + + // 控制界面 + if (_showControls) ...[ + // 半透明背景 + Container(color: ATGlobalConfig.businessLogicStrategy.getVideoPlayerControlsBackgroundColor()), + + // 播放/暂停按钮 + IconButton( + icon: Icon( + _isPlaying ? Icons.pause : Icons.play_arrow, + color: ATGlobalConfig.businessLogicStrategy.getVideoPlayerIconColor(), + size: 50, + ), + onPressed: _togglePlayPause, + ), + + // 底部控制栏 + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Container( + color: ATGlobalConfig.businessLogicStrategy.getVideoPlayerBottomControlsBackgroundColor(), + padding: const EdgeInsets.all(16), + child: Column( + children: [ + // 进度条 + Slider( + value: + _currentPosition.inMilliseconds.toDouble(), + min: 0, + max: _totalDuration.inMilliseconds.toDouble(), + onChanged: (value) { + final newPosition = Duration( + milliseconds: value.toInt(), + ); + _seekToPosition(newPosition); + }, + onChangeStart: (_) { + _hideControlsTimer?.cancel(); + }, + onChangeEnd: (_) { + if (_controller.value.isPlaying) { + _resetHideControlsTimer(); + } + }, + activeColor: ATGlobalConfig.businessLogicStrategy.getVideoPlayerProgressBarActiveColor(), + inactiveColor: ATGlobalConfig.businessLogicStrategy.getVideoPlayerProgressBarInactiveColor(), + ), + + // 时间信息和控制按钮 + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + // 当前时间 + Text( + _formatDuration(_currentPosition), + style: TextStyle( + color: ATGlobalConfig.businessLogicStrategy.getVideoPlayerTextColor(), + fontSize: 14, + ), + ), + + // 控制按钮 + Row( + children: [ + // 播放/暂停按钮 + IconButton( + icon: Icon( + _isPlaying + ? Icons.pause + : Icons.play_arrow, + color: ATGlobalConfig.businessLogicStrategy.getVideoPlayerIconColor(), + ), + onPressed: _togglePlayPause, + ), + + // 10秒前进 + IconButton( + icon: Icon( + Icons.forward_5, + color: ATGlobalConfig.businessLogicStrategy.getVideoPlayerIconColor(), + ), + onPressed: () { + final newPosition = + _currentPosition + + const Duration(seconds: 5); + if (newPosition <= _totalDuration) { + _seekToPosition(newPosition); + } else { + _seekToPosition(_totalDuration); + } + }, + ), + + // 10秒后退 + IconButton( + icon: Icon( + Icons.replay_5, + color: ATGlobalConfig.businessLogicStrategy.getVideoPlayerIconColor(), + ), + onPressed: () { + final newPosition = + _currentPosition - + const Duration(seconds: 5); + if (newPosition >= Duration.zero) { + _seekToPosition(newPosition); + } else { + _seekToPosition(Duration.zero); + } + }, + ), + ], + ), + + // 总时长 + Text( + _formatDuration(_totalDuration), + style: TextStyle( + color: ATGlobalConfig.businessLogicStrategy.getVideoPlayerTextColor(), + fontSize: 14, + ), + ), + ], + ), + ], + ), + ), + ), + ] else if (!_controller.value.isPlaying) ...[ + // 当视频暂停但控制界面隐藏时,显示播放按钮 + IconButton( + icon: Icon( + Icons.play_arrow, + color: ATGlobalConfig.businessLogicStrategy.getVideoPlayerIconColor(), + size: 50, + ), + onPressed: _togglePlayPause, + ), + ], + + // 加载指示器 + if (snapshot.connectionState == ConnectionState.waiting) + CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(ATGlobalConfig.businessLogicStrategy.getVideoPlayerLoadingIndicatorColor()), + ), + ], + ), + ); + } else { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(ATGlobalConfig.businessLogicStrategy.getVideoPlayerLoadingIndicatorColor()), + ), + SizedBox(height: 16), + Text('loading...', style: TextStyle(color: ATGlobalConfig.businessLogicStrategy.getVideoPlayerTextColor())), + ], + ); + } + }, + ), + ), + ); + } +} diff --git a/lib/chatvibe_features/report/report_page.dart b/lib/chatvibe_features/report/report_page.dart new file mode 100644 index 0000000..e1a2237 --- /dev/null +++ b/lib/chatvibe_features/report/report_page.dart @@ -0,0 +1,424 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_core/utilities/at_pick_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/dynamic_repository_imp.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/custom_cached_image.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; + +///举报 +class ReportPage extends StatefulWidget { + final String type; + + ///被举报的id(用户id或者房间id) + final String tageId; + + const ReportPage({Key? key, required this.type, required this.tageId}) + : super(key: key); + + @override + _ReportPageState createState() => _ReportPageState(); +} + +class _ReportPageState extends State { + num selectedIndex = 0; + String reportReason = ""; + String pic01 = ""; + String pic02 = ""; + String pic03 = ""; + final TextEditingController _descriptionController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return ATDebounceWidget(child: Stack( + children: [ + Image.asset( + ATGlobalConfig.businessLogicStrategy.getReportPageBackgroundImage(), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + actions: [], + title: ATAppLocalizations.of(context)!.report, + backgroundColor: Colors.transparent, + ), + body: SafeArea( + top: false, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + SizedBox(width: 15.w), + text( + ATAppLocalizations.of( + context, + )!.pleaseSelectTheTypeContent, + fontSize: 14.sp, + textColor: ATGlobalConfig.businessLogicStrategy.getReportPageHintTextColor(), + ), + SizedBox(width: 15.w), + ], + ), + Container( + margin: EdgeInsets.only(top: 10.w, left: 15.w, right: 15.w), + padding: EdgeInsets.symmetric(horizontal: 15.w), + child: Column( + children: [ + _item(ATAppLocalizations.of(context)!.spam, 3), + _item(ATAppLocalizations.of(context)!.fraud, 4), + _item( + ATAppLocalizations.of(context)!.maliciousHarassment, + 0, + ), + _item(ATAppLocalizations.of(context)!.other, 1), + Row( + children: [ + text( + ATAppLocalizations.of(context)!.description, + fontSize: 14.sp, + textColor: ATGlobalConfig.businessLogicStrategy.getReportPagePrimaryTextColor(), + ), + Spacer(), + ], + ), + SizedBox(height: 8.w), + Container( + padding: EdgeInsets.all(5.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: ATGlobalConfig.businessLogicStrategy.getReportPageContainerBackgroundColor(), + ), + child: TextField( + controller: _descriptionController, + onChanged: (text) {}, + maxLength: 500, + maxLines: 5, + decoration: InputDecoration( + hintText: + ATAppLocalizations.of(context)!.reportInputTips, + hintStyle: TextStyle( + color: ATGlobalConfig.businessLogicStrategy.getReportPageSecondaryHintTextColor(), + fontSize: 14.sp, + ), + contentPadding: EdgeInsets.only(top: 0.w), + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + // enabledBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // focusedBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // prefixIcon: Padding(padding: EdgeInsets.all(8.w), child: Image.asset("images/login/at_icon_phone.png",width: 20.w, height: 20.w,fit: BoxFit.fill,),), + fillColor: Colors.black45, + ), + style: TextStyle( + fontSize: sp(15), + color: ATGlobalConfig.businessLogicStrategy.getReportPagePrimaryTextColor(), + textBaseline: TextBaseline.alphabetic, + ), + ), + ), + SizedBox(height: 5.w), + Row( + children: [ + text( + ATAppLocalizations.of(context)!.screenshotTips, + fontSize: 14.sp, + textColor: ATGlobalConfig.businessLogicStrategy.getReportPagePrimaryTextColor(), + ), + Spacer(), + ], + ), + SizedBox(height: 8.w), + Row( + children: [ + Expanded( + child: GestureDetector( + child: Stack( + children: [ + pic01.isNotEmpty + ? CustomCachedImage( + imageUrl: pic01, + height: 100.w, + ) + : Image.asset( + ATGlobalConfig.businessLogicStrategy.getReportPageIcon('addPic'), + height: 100.w, + ), + pic01.isNotEmpty + ? Positioned( + top: 5.w, + right: 5.w, + child: GestureDetector( + child: Image.asset( + ATGlobalConfig.businessLogicStrategy.getReportPageIcon('closePic'), + width: 14.w, + height: 14.w, + ), + onTap: () { + setState(() { + pic01 = ""; + }); + }, + ), + ) + : Container(), + ], + ), + onTap: () { + ATPickUtils.pickImageProcess(context, ( + bool success, + String url, + ) { + if (success) { + setState(() { + pic01 = url; + }); + } + }); + }, + ), + ), + SizedBox(width: 5.w), + Expanded( + child: GestureDetector( + child: Stack( + children: [ + pic02.isNotEmpty + ? CustomCachedImage( + imageUrl: pic02, + height: 100.w, + ) + : Image.asset( + ATGlobalConfig.businessLogicStrategy.getReportPageIcon('addPic'), + height: 100.w, + ), + pic02.isNotEmpty + ? Positioned( + top: 5.w, + right: 5.w, + child: GestureDetector( + child: Image.asset( + ATGlobalConfig.businessLogicStrategy.getReportPageIcon('closePic'), + width: 14.w, + height: 14.w, + ), + onTap: () { + setState(() { + pic02 = ""; + }); + }, + ), + ) + : Container(), + ], + ), + onTap: () { + ATPickUtils.pickImageProcess(context, ( + bool success, + String url, + ) { + if (success) { + setState(() { + pic02 = url; + }); + } + }); + }, + ), + ), + SizedBox(width: 5.w), + Expanded( + child: GestureDetector( + child: Stack( + children: [ + pic03.isNotEmpty + ? CustomCachedImage( + imageUrl: pic03, + height: 100.w, + ) + : Image.asset( + ATGlobalConfig.businessLogicStrategy.getReportPageIcon('addPic'), + height: 100.w, + ), + pic03.isNotEmpty + ? Positioned( + top: 5.w, + right: 5.w, + child: GestureDetector( + child: Image.asset( + ATGlobalConfig.businessLogicStrategy.getReportPageIcon('closePic'), + width: 14.w, + height: 14.w, + ), + onTap: () { + setState(() { + pic03 = ""; + }); + }, + ), + ) + : Container(), + ], + ), + onTap: () { + ATPickUtils.pickImageProcess(context, ( + bool success, + String url, + ) { + if (success) { + setState(() { + pic03 = url; + }); + } + }); + }, + ), + ), + ], + ), + SizedBox(height: 15.w), + ], + ), + ), + SizedBox(height: 45.w), + ATDebounceWidget( + onTap: () async { + ATLoadingManager.exhibitOperation(); + String imageUrls = ""; + if (pic01.isNotEmpty) { + imageUrls = "$imageUrls,$pic01"; + } + if (pic02.isNotEmpty) { + imageUrls = "$imageUrls,$pic02"; + } + if (pic03.isNotEmpty) { + imageUrls = "$imageUrls,$pic03"; + } + if (widget.type == "dynamic") { + DynamicRepositoryImp() + .dynamicReport( + widget.tageId, + selectedIndex, + reportedContent: _descriptionController.text, + imageUrls: imageUrls, + ) + .then((result) { + ATLoadingManager.veilRoutine(); + ATTts.show( + ATAppLocalizations.of(context)!.reportSucc, + ); + Navigator.of(context).pop(); + }) + .catchError((_) { + ATLoadingManager.veilRoutine(); + }); + } else { + ChatRoomRepository() + .reported( + AccountStorage().getCurrentUser()?.userProfile?.id ?? + "", + widget.tageId, + selectedIndex, + reportedContent: _descriptionController.text, + imageUrls: imageUrls, + ) + .then((result) { + ATLoadingManager.veilRoutine(); + ATTts.show( + ATAppLocalizations.of(context)!.reportSucc, + ); + Navigator.of(context).pop(); + }) + .catchError((_) { + ATLoadingManager.veilRoutine(); + }); + } + }, + child: Container( + height: 40.w, + width: double.infinity, + alignment: Alignment.center, + margin: EdgeInsets.symmetric(horizontal: 35.w), + decoration: BoxDecoration( + color: ATGlobalConfig.businessLogicStrategy.getReportPageSubmitButtonBackgroundColor(), + borderRadius: BorderRadius.circular(25.w), + ), + child: Text( + ATAppLocalizations.of(context)!.submit, + style: TextStyle(color: ATGlobalConfig.businessLogicStrategy.getReportPageSubmitButtonTextColor(), fontSize: 16.sp), + ), + ), + ), + SizedBox(height: 35.w), + ], + ), + ), + ), + ), + ], + ), onTap: (){ + FocusScope.of(context).unfocus(); + }); + } + + Widget _item(String str, int index) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + FocusScope.of(context).unfocus(); // 彻底取消当前上下文的所有焦点 + setState(() { + reportReason = str; + selectedIndex = index; + }); + }, + child: Container( + width: double.infinity, + height: 40.w, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + text(str, fontSize: 14.sp, textColor: ATGlobalConfig.businessLogicStrategy.getReportPagePrimaryTextColor()), + Spacer(), + selectedIndex == index + ? Image.asset( + ATGlobalConfig.businessLogicStrategy.getReportPageIcon('checked'), + width: 20.w, + fit: BoxFit.fitWidth, + ) + : Container( + width: 20.w, + height: 20.w, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: ATGlobalConfig.businessLogicStrategy.getReportPageUnselectedBorderColor(), width: 2.w), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/chatvibe_features/room/at_voice_chat_room_page.dart b/lib/chatvibe_features/room/at_voice_chat_room_page.dart new file mode 100644 index 0000000..022e20c --- /dev/null +++ b/lib/chatvibe_features/room/at_voice_chat_room_page.dart @@ -0,0 +1,286 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_features/room/chat/all/all_chat_page.dart'; +import 'package:aslan/chatvibe_features/room/chat/chat/chat_page.dart'; +import 'package:aslan/chatvibe_features/room/chat/gift/gift_chat_page.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_bottom_widget.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/at_float_ichart.dart'; +import 'package:aslan/chatvibe_core/at_lk_event_bus.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_domain/models/res/join_room_res.dart'; +import 'package:aslan/chatvibe_managers/gift_animation_manager.dart'; +import 'package:aslan/chatvibe_managers/gift_system_manager.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/anim/l_gift_animal_view.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/anim/room_entrance_screen.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/effect/luck_gift_nomor_anim_widget.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/effect/vapp_svga_layer_widget.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/give_room_luck_with_other_page.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/lucky_gift_tag_widget.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_head_widget.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_msg_item.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_online_user_widget.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_play_widget.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/seat/room_seat_widget.dart'; + +///语聊房 +class ATVoiceChatRoomPage extends StatefulWidget { + const ATVoiceChatRoomPage({super.key}); + + @override + _VoiceRoomPageState createState() => _VoiceRoomPageState(); +} + +class _VoiceRoomPageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + final List _pages = [AllChatPage(), ChatPage(), GiftChatPage()]; + final List _tabs = []; + late StreamSubscription _subscription; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: _pages.length, vsync: this); + Provider.of(context, listen: false).msgFloatingGiftListener = + _floatingGiftListener; + _tabController.addListener(() {}); // 监听切换 + + _subscription = eventBus.on().listen((event) { + if (mounted) { + Provider.of(context, listen: false).reset(); + Provider.of( + context, + listen: false, + ).updateHideLGiftAnimal(false); + } + }); + } + + @override + void dispose() { + _tabController.dispose(); // 释放资源 + _subscription.cancel(); + super.dispose(); + } + + bool roomThemeBackActi(JoinRoomRes? room) { + if (room?.roomProps?.roomTheme != null) { + if (room?.roomProps?.roomTheme?.themeBack != null && + room!.roomProps!.roomTheme!.themeBack!.isNotEmpty) { + if ((room.roomProps?.roomTheme?.expireTime ?? 0) > + DateTime.now().millisecondsSinceEpoch) { + return true; + } + } + } + return false; + } + + @override + Widget build(BuildContext context) { + _tabs.clear(); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.all)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.chat)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.gift)); + return PopScope( + canPop: false, + onPopInvokedWithResult: (bool didPop, Object? result) { + if (!didPop) { + ATFloatIchart().show(); + ATNavigatorUtils.goBack(context); + } + }, + child: Scaffold( + backgroundColor: + ATGlobalConfig.businessLogicStrategy.getVoiceRoomBackgroundColor(), + resizeToAvoidBottomInset: false, + body: SafeArea( + top: false, + child: Stack( + children: [ + Consumer( + builder: (context, ref, child) { + return roomThemeBackActi(ref.currenRoom) + ? netImage( + url: + ref.currenRoom?.roomProps?.roomTheme?.themeBack ?? + "", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + noDefaultImg: true, + fit: BoxFit.cover, + ) + : Image.asset( + ATGlobalConfig.businessLogicStrategy + .getVoiceRoomDefaultBackgroundImage(), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.cover, + ); + }, + ), + Column( + children: [ + SizedBox(height: ScreenUtil().setWidth(42)), + RoomHeadWidget(), + SizedBox(height: ScreenUtil().setWidth(5)), + RoomSeatWidget(), + SizedBox(height: 2.w,), + RoomOnlineUserWidget(), + Expanded( + child: Stack( + children: [ + Column( + children: [_buildChatView(), RoomBottomWidget()], + ), + LGiftAnimalPage(), + Consumer( + builder: (context, ref, child) { + return (ref.playingLudoGame != null && + !ref.isMinLudoGame) + ? Container() + : LuckyGiftTagWidget(); + }, + ), + Transform.translate( + offset: Offset(0, -20), + child: RoomAnimationQueueScreen(), + ), + ], + ), + ), + ], + ), + Consumer( + builder: (context, ref, child) { + return ref.hideLGiftAnimal + ? Container() + : GiveRoomLuckWithOtherWidget(); + }, + ), + // _buildPlayViews(), + VapPlusSvgaPlayer(tag: "room_gift"), + + ///幸运礼物中奖动画 + LuckGiftNomorAnimWidget(), + ], + ), + ), + ), + ); + } + + ///消息 + Widget _buildChatView() { + return Expanded( + child: Stack( + alignment: AlignmentDirectional.bottomEnd, + children: [ + Column( + children: [ + TabBar( + tabAlignment: TabAlignment.start, + labelPadding: ATGlobalConfig.businessLogicStrategy + .getVoiceRoomTabLabelPadding() + .copyWith( + left: + ATGlobalConfig.businessLogicStrategy + .getVoiceRoomTabLabelPadding() + .left * + ScreenUtil().setWidth(1), + right: + ATGlobalConfig.businessLogicStrategy + .getVoiceRoomTabLabelPadding() + .right * + ScreenUtil().setWidth(1), + ), + labelColor: + ATGlobalConfig.businessLogicStrategy + .getVoiceRoomTabLabelColor(), + isScrollable: true, + indicator: BoxDecoration(), + unselectedLabelColor: + ATGlobalConfig.businessLogicStrategy + .getVoiceRoomTabUnselectedLabelColor(), + labelStyle: ATGlobalConfig.businessLogicStrategy + .getVoiceRoomTabLabelStyle() + .copyWith( + fontSize: + ATGlobalConfig.businessLogicStrategy + .getVoiceRoomTabLabelStyle() + .fontSize! * + ScreenUtil().setSp(1), + ), + unselectedLabelStyle: ATGlobalConfig.businessLogicStrategy + .getVoiceRoomTabUnselectedLabelStyle() + .copyWith( + fontSize: + ATGlobalConfig.businessLogicStrategy + .getVoiceRoomTabUnselectedLabelStyle() + .fontSize! * + ScreenUtil().setSp(1), + ), + indicatorColor: + ATGlobalConfig.businessLogicStrategy + .getVoiceRoomTabIndicatorColor(), + dividerColor: + ATGlobalConfig.businessLogicStrategy + .getVoiceRoomTabDividerColor(), + controller: _tabController, + tabs: _tabs, + ), + Expanded( + child: Container( + margin: ATGlobalConfig.businessLogicStrategy + .getVoiceRoomChatContainerMargin() + .copyWith( + end: + ATGlobalConfig.businessLogicStrategy + .getVoiceRoomChatContainerMargin() + .end * + ScreenUtil().setWidth(1), + ), + child: MediaQuery.removePadding( + context: context, + removeTop: true, + child: TabBarView( + controller: _tabController, + children: _pages, + ), + ), + ), + ), + ], + ), + RoomPlayWidget(), + ], + ), + ); + } + + ///礼物上飘动画 + _floatingGiftListener(Msg msg) { + if (Provider.of(context, listen: false).hideLGiftAnimal) { + return; + } + var giftModel = LGiftModel(); + giftModel.labelId = "${msg.gift?.id}${msg.user?.id}${msg.toUser?.id}"; + giftModel.sendUserName = msg.user?.userNickname ?? ""; + giftModel.sendToUserName = msg.toUser?.userNickname ?? ""; + giftModel.sendUserPic = msg.user?.userAvatar ?? ""; + giftModel.giftPic = msg.gift?.giftPhoto ?? ""; + giftModel.giftCount = msg.number ?? 0; + giftModel.viptype = msg.user?.getVIP()?.name ?? ""; + Provider.of( + context, + listen: false, + ).addGift(giftModel); + } +} diff --git a/lib/chatvibe_features/room/block/blocked_list_page.dart b/lib/chatvibe_features/room/block/blocked_list_page.dart new file mode 100644 index 0000000..237fd3a --- /dev/null +++ b/lib/chatvibe_features/room/block/blocked_list_page.dart @@ -0,0 +1,159 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_black_list_res.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +///房间用户 黑名单 +class BlockedListPage extends ATPageList { + String? roomId; + + BlockedListPage({super.key, this.roomId}); + + @override + _BlockedListPageState createState() => _BlockedListPageState(roomId); +} + +class _BlockedListPageState + extends ATPageListState { + String? roomId; + + _BlockedListPageState(this.roomId); + + @override + void initState() { + super.initState(); + enablePullUp = false; + backgroundColor = Colors.transparent; + isShowDivider = false; + loadData(1); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: ClipRect( + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + ), + child: Column( + children: [ + SizedBox(height: 15.w), + text( + ATAppLocalizations.of(context)!.blockedList, + fontSize: 14.sp, + textColor: Colors.black, + ), + SizedBox(height: 10.w), + SizedBox(height: 350.w, child: buildList(context)), + ], + ), + ), + ), + ); + } + + @override + Widget buildItem(RoomBlackListRes res) { + return Container( + margin: REdgeInsets.symmetric(vertical: 5.w, horizontal: 10.w), + padding: REdgeInsets.symmetric(vertical: 10.w), + decoration: BoxDecoration( + color: Color(0xffF2F2F2), + borderRadius: BorderRadius.circular(10.w), + ), + child: Row( + children: [ + SizedBox(width: 10.w), + GestureDetector( + child: ClipOval( + child: CachedNetworkImage( + imageUrl: res.blacklistUserProfile?.userAvatar ?? "", + width: 42.w, + height: 42.w, + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == res.blacklistUserProfile?.id}&tageId=${res.blacklistUserProfile?.id}", + ); + }, + ), + SizedBox(width: 3.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + constraints: BoxConstraints(maxWidth: 180.w), + child: text( + res.blacklistUserProfile?.userNickname ?? "", + fontSize: 14.sp, + textColor: Colors.black, + ), + ), + SizedBox(height: 4.w), + text( + "ID:${res.blacklistUserProfile?.getID()}", + fontSize: 10.sp, + textColor: Colors.black, + ), + ], + ), + ), + SizedBox(width: 3.w), + GestureDetector( + child: Image.asset( + "atu_images/room/at_icon_remve_block.png", + width: 22.w, + height: 22.w, + ), + onTap: () { + ChatRoomRepository() + .removeBlacklist( + res.roomId ?? "", + res.blacklistUserProfile?.id ?? "", + ) + .whenComplete(() { + loadData(1); + }); + }, + ), + SizedBox(width: 15.w), + ], + ), + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var roomList = await AccountRepository().roomBlacklist(roomId ?? "", "0"); + onSuccess(roomList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } +} diff --git a/lib/chatvibe_features/room/chat/all/all_chat_page.dart b/lib/chatvibe_features/room/chat/all/all_chat_page.dart new file mode 100644 index 0000000..d7a3054 --- /dev/null +++ b/lib/chatvibe_features/room/chat/all/all_chat_page.dart @@ -0,0 +1,149 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_msg_item.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; + +class AllChatPage extends StatefulWidget { + @override + _AllChatPageState createState() => _AllChatPageState(); +} + +class _AllChatPageState extends State { + ScrollController _controller = ScrollController(); + bool showGoBottom = true; + bool _isDisposed = false; + List _msgList = []; + late RtmProvider provider; + + @override + void initState() { + super.initState(); + _controller.addListener(_scrollListener); + provider = Provider.of(context, listen: false); + var msgList = provider.roomAllMsgList; + provider.msgAllListener = _onNewMsg; + _msgList.addAll(msgList ??= []); + // 使用安全的方式初始化滚动位置 + _initScrollPosition(); + } + + void _scrollListener() { + final position = _controller.position; + + // 容差范围(5像素)避免精度问题 + const tolerance = 10.0; + + // 判断是否在第一个项目(颠倒列表的底部) + final isAtBottom = position.pixels < position.minScrollExtent + tolerance; + + // 判断是否在最后一个项目(颠倒列表的顶部) + // final isAtTop = position.pixels >= position.maxScrollExtent - tolerance; + if (isAtBottom != showGoBottom) { + showGoBottom = isAtBottom; + if (!_isDisposed) { + setState(() {}); + } + } + } + + // 安全初始化滚动位置 + void _initScrollPosition() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + if (_isDisposed) return; + _controller.jumpTo(0.0); + }); + } + + _onNewMsg(Msg msg) { + if (_isDisposed) return; // 如果组件已销毁,直接返回 + if (msg.groupId == "-1000") { + ///清屏 + _msgList.clear(); + setState(() {}); + return; + } + if (!_msgList.contains(msg)) { + setState(() { + _msgList.insert(0, msg); + }); + _controller.jumpTo(0.0); + } + } + + @override + void dispose() { + _isDisposed = true; + provider.msgAllListener = null; + _controller.removeListener(_scrollListener); + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + body: Stack( + alignment: Alignment.bottomLeft, + children: [ + ListView.builder( + reverse: true, + controller: _controller, + shrinkWrap: true, + itemBuilder: (BuildContext context, int index) { + return MsgItem( + msg: _msgList[index], + onClick: (ChatVibeUserProfile? user) { + Provider.of(context, listen: false).clickSite( + Provider.of( + context, + listen: false, + ).userOnMaiInIndex(user?.id ?? ""), + clickUser: user, + ); + }, + ); + }, + itemCount: _msgList.length, + ), + Visibility( + visible: !showGoBottom, + child: GestureDetector( + onTap: () { + if (_controller.hasClients) { + _controller.jumpTo(0.0); + } + }, + child: Container( + margin: EdgeInsets.symmetric(horizontal: width(15)), + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(52), + color: Colors.white, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + ATAppLocalizations.of(context)!.scrollToTheBottom, + style: TextStyle(fontSize: 10.sp), + ), + SizedBox(width: 4.w), + Icon(Icons.chevron_right, size: 10.w), + ], + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/chatvibe_features/room/chat/chat/chat_page.dart b/lib/chatvibe_features/room/chat/chat/chat_page.dart new file mode 100644 index 0000000..20471f6 --- /dev/null +++ b/lib/chatvibe_features/room/chat/chat/chat_page.dart @@ -0,0 +1,150 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_msg_item.dart'; + +class ChatPage extends StatefulWidget { + @override + _ChatPageState createState() => _ChatPageState(); +} + +class _ChatPageState extends State { + ScrollController _controller = ScrollController(); + bool showGoBottom = true; + bool _isDisposed = false; + List _msgList = []; + late RtmProvider provider; + + @override + void initState() { + super.initState(); + _controller.addListener(_scrollListener); + provider = Provider.of(context, listen: false); + var msgList = provider.roomChatMsgList; + provider.msgChatListener = _onNewMsg; + _msgList.addAll(msgList ??= []); + // 使用安全的方式初始化滚动位置 + _initScrollPosition(); + } + + void _scrollListener() { + final position = _controller.position; + + // 容差范围(5像素)避免精度问题 + const tolerance = 10.0; + + // 判断是否在第一个项目(颠倒列表的底部) + final isAtBottom = position.pixels < position.minScrollExtent + tolerance; + + // 判断是否在最后一个项目(颠倒列表的顶部) + // final isAtTop = position.pixels >= position.maxScrollExtent - tolerance; + if (isAtBottom != showGoBottom) { + showGoBottom = isAtBottom; + if (!_isDisposed) { + setState(() {}); + } + } + } + + // 安全初始化滚动位置 + void _initScrollPosition() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + if (_isDisposed) return; + _controller.jumpTo(0.0); + }); + } + + _onNewMsg(Msg msg) { + if (_isDisposed) return; // 如果组件已销毁,直接返回 + if (msg.groupId == "-1000") { + ///清屏 + _msgList.clear(); + setState(() {}); + return; + } + if (!_msgList.contains(msg)) { + setState(() { + _msgList.insert(0, msg); + }); + _controller.jumpTo(0.0); + } + } + + @override + void dispose() { + _isDisposed = true; + provider.msgChatListener = null; + _controller.removeListener(_scrollListener); + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + body: Stack( + alignment: Alignment.bottomLeft, + children: [ + ListView.builder( + reverse: true, + controller: _controller, + shrinkWrap: true, + itemBuilder: (BuildContext context, int index) { + return MsgItem( + msg: _msgList[index], + onClick: (ChatVibeUserProfile? user) { + Provider.of(context, listen: false).clickSite( + Provider.of( + context, + listen: false, + ).userOnMaiInIndex(user?.id ?? ""), + clickUser: user, + ); + }, + ); + }, + itemCount: _msgList.length, + ), + Visibility( + visible: !showGoBottom, + child: GestureDetector( + onTap: () { + if (_controller.hasClients) { + _controller.jumpTo(0.0); + } + }, + child: Container( + margin: EdgeInsets.symmetric(horizontal: width(15)), + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(52), + color: Colors.white, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + ATAppLocalizations.of(context)!.scrollToTheBottom, + style: TextStyle(fontSize: 10.sp), + ), + SizedBox(width: 4.w), + Icon(Icons.chevron_right, size: 10.w), + ], + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/chatvibe_features/room/chat/gift/gift_chat_page.dart b/lib/chatvibe_features/room/chat/gift/gift_chat_page.dart new file mode 100644 index 0000000..cb96c84 --- /dev/null +++ b/lib/chatvibe_features/room/chat/gift/gift_chat_page.dart @@ -0,0 +1,141 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_msg_item.dart'; + +class GiftChatPage extends StatefulWidget { + @override + _GiftChatPageState createState() => _GiftChatPageState(); +} + +class _GiftChatPageState extends State { + ScrollController _controller = ScrollController(); + bool showGoBottom = true; + bool _isDisposed = false; + List _msgList = []; + late RtmProvider provider; + + @override + void initState() { + super.initState(); + _controller.addListener(_scrollListener); + provider = Provider.of(context, listen: false); + var msgList = provider.roomGiftMsgList; + provider.msgGiftListener = _onNewMsg; + _msgList.addAll(msgList ??= []); + // 使用安全的方式初始化滚动位置 + _initScrollPosition(); + } + + void _scrollListener() { + final position = _controller.position; + + // 容差范围(5像素)避免精度问题 + const tolerance = 10.0; + + // 判断是否在第一个项目(颠倒列表的底部) + final isAtBottom = position.pixels < position.minScrollExtent + tolerance; + + // 判断是否在最后一个项目(颠倒列表的顶部) + // final isAtTop = position.pixels >= position.maxScrollExtent - tolerance; + if (isAtBottom != showGoBottom) { + showGoBottom = isAtBottom; + if (!_isDisposed) { + setState(() {}); + } + } + } + + // 安全初始化滚动位置 + void _initScrollPosition() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + if (_isDisposed) return; + _controller.jumpTo(0.0); + }); + } + + _onNewMsg(Msg msg) { + if (_isDisposed) return; // 如果组件已销毁,直接返回 + if (msg.groupId == "-1000") { + ///清屏 + _msgList.clear(); + setState(() {}); + return; + } + if (!_msgList.contains(msg)) { + setState(() { + _msgList.insert(0, msg); + }); + _controller.jumpTo(0.0); + } + } + + @override + void dispose() { + _isDisposed = true; + provider.msgGiftListener = null; + _controller.removeListener(_scrollListener); + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + body: Stack( + alignment: Alignment.bottomLeft, + children: [ + ListView.builder( + reverse: true, + controller: _controller, + shrinkWrap: true, + itemBuilder: (BuildContext context, int index) { + return MsgItem( + msg: _msgList[index], + onClick: (ChatVibeUserProfile? user) {}, + ); + }, + itemCount: _msgList.length, + ), + Visibility( + visible: !showGoBottom, + child: GestureDetector( + onTap: () { + if (_controller.hasClients) { + _controller.jumpTo(0.0); + } + }, + child: Container( + margin: EdgeInsets.symmetric(horizontal: width(15)), + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(52), + color: Colors.white, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + ATAppLocalizations.of(context)!.scrollToTheBottom, + style: TextStyle(fontSize: 10.sp), + ), + SizedBox(width: 4.w), + Icon(Icons.chevron_right, size: 10.w), + ], + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/chatvibe_features/room/detail/room_detail_page.dart b/lib/chatvibe_features/room/detail/room_detail_page.dart new file mode 100644 index 0000000..25d8142 --- /dev/null +++ b/lib/chatvibe_features/room/detail/room_detail_page.dart @@ -0,0 +1,830 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_lk_dialog_util.dart'; +import 'package:aslan/chatvibe_features/room/manager/room_member_page.dart'; +import 'package:aslan/chatvibe_features/room/voice_room_route.dart'; +import '../../../chatvibe_data/models/enum/at_room_roles_type.dart'; +import '../../../chatvibe_data/sources/local/user_manager.dart'; +import '../../../chatvibe_data/sources/repositories/room_repository_imp.dart'; +import '../../../chatvibe_ui/components/at_compontent.dart'; +import '../../../chatvibe_ui/components/at_debounce_widget.dart'; +import '../../../chatvibe_ui/components/at_tts.dart'; +import '../../../chatvibe_ui/components/dialog/dialog_base.dart'; +import '../../../chatvibe_ui/components/text/at_text.dart'; +import '../../../main.dart'; +import '../../index/main_route.dart'; + +///房间详情 +class RoomDetailPage extends StatefulWidget { + ///是否是房主 + final bool isHomeowner; + + const RoomDetailPage(this.isHomeowner, {super.key}); + + @override + _RoomDetailPageState createState() => _RoomDetailPageState(); +} + +class _RoomDetailPageState extends State { + @override + void initState() { + super.initState(); + Provider.of(context, listen: false).loadRoomInfo( + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.id ?? + "", + ); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Consumer( + builder: (context, ref, child) { + return Container( + height: widget.isHomeowner ? 300.w : 400.w, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + ), + child: Column( + children: [ + SizedBox(height: 15.w), + text( + widget.isHomeowner + ? ATAppLocalizations.of(context)!.roomSetting + : ATAppLocalizations.of(context)!.roomDetails, + fontSize: 14.sp, + textColor: Colors.black, + fontWeight: FontWeight.w600, + ), + Expanded( + child: SingleChildScrollView( + child: Column( + spacing: 10.w, + children: [ + SizedBox(height: 15.w), + GestureDetector( + child: Container( + decoration: BoxDecoration( + color: Color(0xffF2F2F2), + borderRadius: BorderRadius.circular(10.w), + ), + margin: EdgeInsets.symmetric(horizontal: 15.w), + padding: EdgeInsets.all(15.w), + child: Row( + children: [ + netImage( + url: + ref + .currenRoom + ?.roomProfile + ?.roomProfile + ?.roomCover ?? + "", + width: 48.w, + height: 48.w, + borderRadius: BorderRadius.all( + Radius.circular(8.w), + ), + ), + SizedBox(width: 8.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + constraints: BoxConstraints( + maxWidth: + ScreenUtil().screenWidth * 0.6, + ), + child: text( + textColor: Colors.black, + ref + .currenRoom + ?.roomProfile + ?.roomProfile + ?.roomName ?? + "", + fontSize: 14.sp, + ), + ), + SizedBox(width: 3.w), + widget.isHomeowner + ? Image.asset( + "atu_images/room/at_icon_room_edit.png", + width: 13.w, + height: 13.w, + ) + : Container(), + ], + ), + SizedBox(height: 5.w), + Row( + children: [ + text( + "ID:${ref.currenRoom?.roomProfile?.userProfile?.getID()}", + fontSize: 12.sp, + textColor: Colors.black, + ), + SizedBox(width: 3.w), + GestureDetector( + child: Container( + padding: EdgeInsets.all(3.w), + child: Image.asset( + "atu_images/room/at_icon_user_card_copy_id.png", + width: 11.w, + height: 11.w, + color: Colors.black, + ), + ), + onTap: () { + Clipboard.setData( + ClipboardData( + text: + ref + .currenRoom + ?.roomProfile + ?.roomProfile + ?.roomAccount ?? + "", + ), + ); + ATTts.show( + ATAppLocalizations.of( + context, + )!.copiedToClipboard, + ); + }, + ), + ], + ), + ], + ), + ], + ), + ), + onTap: () { + if (!widget.isHomeowner) { + return; + } + ATNavigatorUtils.push( + context, + "${VoiceRoomRoute.roomEdit}?need=false", + replace: false, + ); + }, + ), + if (!widget.isHomeowner) + ATDebounceWidget( + child: Container( + decoration: BoxDecoration( + color: Color(0xffF2F2F2), + borderRadius: BorderRadius.circular(10.w), + ), + margin: EdgeInsets.symmetric(horizontal: 15.w), + padding: EdgeInsets.all(15.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text( + "${ATAppLocalizations.of(context)!.roomOwner}:", + fontSize: 15.sp, + textColor: Colors.black, + ), + SizedBox(height: 3.w), + Row( + children: [ + netImage( + url: + ref + .currenRoom + ?.roomProfile + ?.userProfile + ?.userAvatar ?? + "", + shape: BoxShape.circle, + width: 40.w, + height: 40.w, + ), + SizedBox(width: 10.w), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + chatvibeNickNameText( + maxWidth: 135.w, + textColor: Colors.black, + ref + .currenRoom + ?.roomProfile + ?.userProfile + ?.userNickname ?? + "", + fontSize: 14.sp, + fontWeight: FontWeight.w600, + type: + ref + .currenRoom + ?.roomProfile + ?.userProfile + ?.getVIP() + ?.name ?? + "", + needScroll: + (ref + .currenRoom + ?.roomProfile + ?.userProfile + ?.userNickname + ?.characters + .length ?? + 0) > + 13, + ), + GestureDetector( + child: SizedBox( + child: Row( + children: [ + getIdIcon( + ref.currenRoom?.roomProfile?.userProfile?.wealthLevel ?? 0, + width: 28.w, + height: 28.w, + textColor: Color(0xFF333333) + ), + chatvibeNickNameText( + maxWidth: 135.w, + ref.currenRoom?.roomProfile?.userProfile?.getID() ?? "", + textColor: Color(0xFF333333), + fontSize: 12.sp, + fontWeight: FontWeight.w600, + type: ref.currenRoom?.roomProfile?.userProfile?.getVIP()?.name ?? "", + needScroll: + (ref.currenRoom?.roomProfile?.userProfile + ?.getID() + .characters + .length ?? + 0) > + 13, + ), + ], + ), + ), + onTap: () { + Clipboard.setData( + ClipboardData(text: ref.currenRoom?.roomProfile?.userProfile?.getID() ?? ""), + ); + ATTts.show( + ATAppLocalizations.of(context)!.copiedToClipboard, + ); + }, + ), + ], + ), + ], + ), + ], + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + replace: false, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == ref.currenRoom?.roomProfile?.userProfile?.id}&tageId=${ref.currenRoom?.roomProfile?.userProfile?.id}", + ); + }, + ), + ATDebounceWidget( + child: Container( + decoration: BoxDecoration( + color: Color(0xffF2F2F2), + borderRadius: BorderRadius.circular(10.w), + ), + margin: EdgeInsets.symmetric(horizontal: 15.w), + padding: EdgeInsets.all(15.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + text( + textColor: Colors.black, + fontSize: 14.sp, + ATAppLocalizations.of( + context, + )!.roomAnnouncement, + ), + Spacer(), + widget.isHomeowner + ? Icon( + Icons.keyboard_arrow_right_sharp, + color: Colors.black, + size: 15.w, + ) + : Container(), + ], + ), + Row( + children: [ + Consumer( + builder: (context, ref, child) { + return Expanded( + child: text( + ref + .currenRoom + ?.roomProfile + ?.roomProfile + ?.roomDesc ?? + "", + fontSize: 13.sp, + maxLines: 2, + textColor: Color(0xffB1B1B1), + ), + ); + }, + ), + ], + ), + ], + ), + ), + onTap: () { + if (!widget.isHomeowner) { + return; + } + ATNavigatorUtils.push( + context, + "${VoiceRoomRoute.roomEdit}?need=false", + replace: false, + ); + }, + ), + widget.isHomeowner + ? Row( + children: [ + SizedBox(width: 15.w), + Expanded( + child: ATDebounceWidget( + child: Container( + height: 45.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + 10.w, + ), + color: Color(0xffF2F2F2), + ), + child: Row( + children: [ + SizedBox(width: 20.w), + text( + ATAppLocalizations.of( + context, + )!.roomMember, + textColor: Colors.black, + fontSize: 14.sp, + ), + Spacer(), + Icon( + Icons.keyboard_arrow_right_sharp, + color: Colors.black, + size: 20.w, + ), + SizedBox(width: 15.w), + ], + ), + ), + onTap: () { + Navigator.of(context).pop(); + showBottomInBottomDialog( + context, + RoomMemberPage( + roomId: + Provider.of( + context!, + listen: false, + ) + .currenRoom + ?.roomProfile + ?.roomProfile + ?.id ?? + "", + isHomeowner: widget.isHomeowner, + ), + ); + }, + ), + ), + SizedBox(width: 10.w), + Expanded( + child: ATDebounceWidget( + child: Container( + height: 45.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + 10.w, + ), + color: Color(0xffF2F2F2), + ), + child: Row( + children: [ + SizedBox(width: 20.w), + text( + ATAppLocalizations.of( + context, + )!.roomEdit, + textColor: Colors.black, + fontSize: 14.sp, + ), + Spacer(), + Icon( + Icons.keyboard_arrow_right_sharp, + color: Colors.black, + size: 20.w, + ), + SizedBox(width: 15.w), + ], + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${VoiceRoomRoute.roomEdit}?need=false", + replace: false, + ); + }, + ), + ), + SizedBox(width: 15.w), + ], + ) + : Container(), + Row( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 15.w, + children: [ + ref.isTourists() + ? ATDebounceWidget( + child: Container( + width: 160.w, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xffFFD800), + Color(0xffFF9500), + ], + ), + borderRadius: BorderRadius.circular( + 35.w, + ), + ), + height: 42.w, + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Image.asset( + "atu_images/room/at_icon_join_room_member.png", + height: 20.w, + width: 20.w, + ), + SizedBox(width: 8.w), + text( + ref.isTourists() + ? ATAppLocalizations.of( + context, + )!.join + : ATAppLocalizations.of( + context, + )!.giveUpIdentity, + textColor: Colors.white, + fontSize: 15.sp, + fontWeight: FontWeight.bold, + ), + SizedBox(width: 3.w), + text( + "(${ref.currenRoom?.roomProfile?.roomSetting?.joinGolds ?? 0})", + textColor: Colors.white, + fontSize: 15.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + ), + onTap: () { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: + ATAppLocalizations.of( + context, + )!.tips, + msg: + ref.isTourists() + ? ATAppLocalizations.of( + context, + )!.joinMemberTips2 + : ATAppLocalizations.of( + context, + )!.leaveRoomIdentityTips, + btnText: + ATAppLocalizations.of( + context, + )!.confirm, + onEnsure: () { + ChatRoomRepository() + .changeRoomRole( + ref + .currenRoom + ?.roomProfile + ?.roomProfile + ?.id ?? + "", + ref.isTourists() + ? ATRoomRolesType + .ADMIN + .name + : ATRoomRolesType + .TOURIST + .name, + UserManager() + .getCurrentUser() + ?.userProfile + ?.id ?? + "", + UserManager() + .getCurrentUser() + ?.userProfile + ?.id ?? + "", + "ONESELF_JOIN", + ) + .then((reslt) { + ref.currenRoom = ref + .currenRoom + ?.copyWith( + entrants: ref + .currenRoom + ?.entrants + ?.copyWith( + roles: + ATRoomRolesType + .MEMBER + .name, + ), + ); + setState(() {}); + ATTts.show( + ATAppLocalizations.of( + navigatorKey + .currentState! + .context, + )!.operationSuccessful, + ); + }); + }, + ); + }, + ); + }, + ) + : SizedBox.shrink(), + ref.currenRoom?.roomProfile?.roomProfile?.userId == + UserManager() + .getCurrentUser() + ?.userProfile + ?.id + ? SizedBox.shrink() + : Selector( + selector: + (c, p) => + p.isFollowRoomRes?.followRoom ?? + false, + shouldRebuild: (prev, next) => prev != next, + builder: (_, follow, __) { + return ATDebounceWidget( + child: Container( + width: 160.w, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xffFFD800), + Color(0xffFF9500), + ], + ), + borderRadius: BorderRadius.circular( + 35.w, + ), + ), + height: 42.w, + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Row( + children: [ + Image.asset( + !follow + ? "atu_images/room/at_icon_follow_room_un.png" + : "atu_images/room/at_icon_follow_room_en.png", + width: 20.w, + height: 20.w, + ), + SizedBox(width: 8.w), + text( + !follow + ? ATAppLocalizations.of( + context, + )!.follow + : ATAppLocalizations.of( + context, + )!.following, + textColor: Colors.white, + fontSize: 15.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + ], + ), + ), + onTap: () { + if (follow) { + SmartDialog.show( + tag: "unFollowDialog", + alignment: Alignment.center, + animationType: + SmartAnimationType.fade, + builder: (_) { + return Container( + width: + ScreenUtil().screenWidth * + 0.75, + padding: EdgeInsets.symmetric( + horizontal: 10.w, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.all( + Radius.circular(12.w), + ), + ), + child: Column( + mainAxisSize: + MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment + .center, + children: [ + SizedBox(height: 15.w), + text( + ATAppLocalizations.of( + context, + )!.unFollow, + fontSize: 16.sp, + textColor: Colors.black, + fontWeight: + FontWeight.bold, + ), + SizedBox(height: 15.w), + text( + ATAppLocalizations.of( + context, + )!.sureUnfollowThisRoom, + fontWeight: + FontWeight.w600, + textColor: Colors.grey, + fontSize: 14.sp, + ), + SizedBox(height: 15.w), + Row( + mainAxisSize: + MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment + .center, + children: [ + GestureDetector( + behavior: + HitTestBehavior + .opaque, + onTap: () { + SmartDialog.dismiss( + tag: + "unFollowDialog", + ); + }, + child: Container( + alignment: + AlignmentDirectional + .center, + decoration: + BoxDecoration( + borderRadius: + BorderRadius.circular( + 35.w, + ), + color: Color( + 0xffF2F2F2, + ), + ), + height: 35.w, + width: 95.w, + child: text( + ATAppLocalizations.of( + context, + )!.cancel, + textColor: + Colors.grey, + fontSize: 15.sp, + fontWeight: + FontWeight + .w600, + ), + ), + ), + SizedBox(width: 18.w), + GestureDetector( + onTap: () { + ref.followRoom(); + }, + behavior: + HitTestBehavior + .opaque, + child: Container( + alignment: + AlignmentDirectional + .center, + decoration: BoxDecoration( + color: + ChatVibeTheme + .primaryLight, + borderRadius: + BorderRadius.circular( + 35.w, + ), + ), + height: 35.w, + width: 95.w, + child: text( + ATAppLocalizations.of( + context, + )!.unFollow, + textColor: + Colors + .white, + fontSize: 15.sp, + fontWeight: + FontWeight + .w600, + ), + ), + ), + ], + ), + SizedBox(height: 25.w), + ], + ), + ); + }, + ); + } else { + ref.followRoom(); + } + }, + ); + }, + ), + ], + ), + SizedBox(height: 10.w,) + ], + ), + ), + ), + ], + ), + ); + }, + ), + ); + } +} diff --git a/lib/chatvibe_features/room/edit/room_edit_page.dart b/lib/chatvibe_features/room/edit/room_edit_page.dart new file mode 100644 index 0000000..67864af --- /dev/null +++ b/lib/chatvibe_features/room/edit/room_edit_page.dart @@ -0,0 +1,653 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_pick_utils.dart'; +import 'package:aslan/chatvibe_managers/room_manager.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import '../../../chatvibe_core/utilities/at_lk_dialog_util.dart'; +import '../../../chatvibe_data/models/enum/at_room_info_event_type.dart'; +import '../../../chatvibe_data/sources/repositories/room_repository_imp.dart'; +import '../../../chatvibe_domain/usecases/at_accurate_length_limiting_textInput_formatter.dart'; +import '../../../chatvibe_ui/widgets/pop/pop_route.dart'; +import '../../../chatvibe_ui/widgets/room/pwd/set_room_pwd_page.dart'; +import '../../../chatvibe_ui/widgets/room/room_membership_fee_page.dart'; +import '../../../chatvibe_ui/widgets/room/switch_model/room_mic_switch_page.dart'; +import '../../../main.dart'; +import '../block/blocked_list_page.dart'; +import '../voice_room_route.dart'; + +///编辑房间信息 +class RoomEditPage extends StatefulWidget { + String needRestCurrentRoomInfo = "false"; + + RoomEditPage({super.key, this.needRestCurrentRoomInfo = "false"}); + + @override + _RoomEditPageState createState() => _RoomEditPageState(); +} + +class _RoomEditPageState extends State { + RtcProvider? rtcProvider; + bool isEdit = false; + final TextEditingController _roomNameController = TextEditingController(); + final TextEditingController _roomAnnouncementController = + TextEditingController(); + final int _roomNameMaxLength = 24; + int _roomNameCurrentLength = 0; + String roomCover = ""; + final int _roomAnnouncementMaxLength = 100; + int _roomAnnouncementCurrentLength = 0; + + @override + void initState() { + super.initState(); + rtcProvider = Provider.of(context, listen: false); + _roomNameController.addListener(() { + setState(() { + _roomNameCurrentLength = _getActualCharacterCount( + _roomNameController.text, + ); + }); + }); + _roomAnnouncementController.addListener(() { + setState(() { + _roomAnnouncementCurrentLength = _getActualCharacterCount( + _roomAnnouncementController.text, + ); + }); + }); + _roomNameController.text = + rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomName ?? ""; + _roomNameCurrentLength = _getActualCharacterCount(_roomNameController.text); + _roomAnnouncementController.text = + rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomDesc ?? ""; + _roomAnnouncementCurrentLength = _getActualCharacterCount( + _roomAnnouncementController.text, + ); + roomCover = + rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomCover ?? ""; + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () { + if (!isEdit) { + ATNavigatorUtils.goBack(context); + return Future.value(false); + } + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.theModificationsMade, + onEnsure: () { + ATNavigatorUtils.goBack(context); + }, + ); + }, + ); + return Future.value(false); + }, + child: Stack( + children: [ + Image.asset( + "atu_images/person/at_icon_edit_userinfo_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.roomSetting, + onTag: () { + if (widget.needRestCurrentRoomInfo == "true") { + Provider.of(context, listen: false).currenRoom = + null; + } + Navigator.pop(context); + }, + actions: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 5.w), + margin: EdgeInsetsDirectional.only(end: 8.w), + child: text( + ATAppLocalizations.of(context)!.save, + textColor: Colors.black45, + fontSize: 15.sp, + ), + ), + onTap: () { + submit(context); + }, + ), + ], + ), + body: SingleChildScrollView( + child: Column( + children: [ + SizedBox(height: 35.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + child: Stack( + alignment: Alignment.bottomRight, + children: [ + netImage( + url: roomCover, + borderRadius: BorderRadius.all( + Radius.circular(12), + ), + width: 90.w, + height: 90.w, + ), + Icon(Icons.camera_alt, size: 25.w), + ], + ), + onTap: () { + ATPickUtils.pickImageProcess(context, ( + bool success, + String url, + ) { + if (success) { + setState(() { + roomCover = url; + }); + } + }); + }, + ), + ], + ), + SizedBox(height: 25.w), + Row( + children: [ + SizedBox(width: 25.w), + text( + ATAppLocalizations.of(context)!.roomName, + textColor: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 15.sp, + ), + ], + ), + SizedBox(height: 10.w), + Row( + children: [ + Expanded( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 25.w), + padding: EdgeInsets.only( + left: width(12), + right: width(12), + ), + alignment: AlignmentDirectional.centerStart, + height: 45.w, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + color: Color(0xffF2F2F2), + borderRadius: BorderRadius.all( + Radius.circular(height(8)), + ), + ), + child: TextField( + controller: _roomNameController, + onChanged: (text) { + isEdit = true; + }, + inputFormatters: [ + ATAccurateLengthLimitingTextInputFormatter( + _roomNameMaxLength, + ), + ], + decoration: InputDecoration( + hintText: + ATAppLocalizations.of(context)!.enterRoomName, + hintStyle: TextStyle( + color: Colors.black45, + fontSize: 14.sp, + ), + contentPadding: EdgeInsets.only(top: 0.w), + counterText: '', + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + // enabledBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // focusedBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // prefixIcon: Padding(padding: EdgeInsets.all(8.w), child: Image.asset("images/login/at_icon_phone.png",width: 20.w, height: 20.w,fit: BoxFit.fill,),), + fillColor: Colors.black45, + suffix: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 3.w), + text( + '$_roomNameCurrentLength/$_roomNameMaxLength', + textColor: Color(0xFF999999), + ), + ], + ), + ), + style: TextStyle( + fontSize: sp(15), + color: Colors.black, + textBaseline: TextBaseline.alphabetic, + ), + ), + ), + ), + ], + ), + SizedBox(height: 15.w), + Row( + children: [ + SizedBox(width: 25.w), + text( + ATAppLocalizations.of(context)!.roomAnnouncement, + textColor: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 15.sp, + ), + ], + ), + SizedBox(height: 10.w), + Row( + children: [ + Expanded( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 25.w), + padding: EdgeInsets.only( + top: 5.w, + left: width(12), + right: width(12), + ), + alignment: AlignmentDirectional.centerStart, + height: 110.w, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + color: Color(0xffF2F2F2), + borderRadius: BorderRadius.all( + Radius.circular(height(8)), + ), + ), + child: TextField( + controller: _roomAnnouncementController, + onChanged: (text) { + isEdit = true; + }, + inputFormatters: [ + ATAccurateLengthLimitingTextInputFormatter( + _roomAnnouncementMaxLength, + ), + ], + maxLines: 5, + decoration: InputDecoration( + hintText: "", + hintStyle: TextStyle( + color: Colors.black45, + fontSize: 14.sp, + ), + contentPadding: EdgeInsets.only(top: 0.w), + counterText: '', + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + // enabledBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // focusedBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // prefixIcon: Padding(padding: EdgeInsets.all(8.w), child: Image.asset("images/login/at_icon_phone.png",width: 20.w, height: 20.w,fit: BoxFit.fill,),), + fillColor: Colors.black45, + suffix: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 3.w), + text( + '$_roomAnnouncementCurrentLength/$_roomAnnouncementMaxLength', + textColor: Color(0xFF999999), + ), + ], + ), + ), + style: TextStyle( + fontSize: sp(15), + color: Colors.black, + textBaseline: TextBaseline.alphabetic, + ), + ), + ), + ), + ], + ), + widget.needRestCurrentRoomInfo != "true" + ? Column( + children: [ + SizedBox(height: 12.w), + ATDebounceWidget( + child: Row( + children: [ + SizedBox(width: 25.w), + text( + ATAppLocalizations.of(context)!.roomTheme2, + textColor: Colors.black, + fontWeight: FontWeight.w500, + fontSize: 15.sp, + ), + Spacer(), + Icon( + Icons.keyboard_arrow_right, + color: Colors.black, + size: 20.w, + ), + SizedBox(width: 25.w), + ], + ), + onTap: () { + ATNavigatorUtils.push( + context, + VoiceRoomRoute.roomTheme, + replace: false, + ); + }, + ), + SizedBox(height: 12.w), + ATDebounceWidget( + child: Row( + children: [ + SizedBox(width: 25.w), + text( + ATAppLocalizations.of(context)!.roomPassword, + textColor: Colors.black, + fontWeight: FontWeight.w500, + fontSize: 15.sp, + ), + Spacer(), + Consumer( + builder: (context, ref, child) { + return text( + ref + .currenRoom + ?.roomProfile + ?.roomSetting + ?.password ?? + "", + textColor: Color(0xffB1B1B1), + fontSize: 13.sp, + ); + }, + ), + + Icon( + Icons.keyboard_arrow_right, + color: Colors.black, + size: 20.w, + ), + SizedBox(width: 25.w), + ], + ), + onTap: () { + _setPwd(); + }, + ), + SizedBox(height: 12.w), + ATDebounceWidget( + child: Row( + children: [ + SizedBox(width: 25.w), + text( + ATAppLocalizations.of(context)!.micManagement, + textColor: Colors.black, + fontWeight: FontWeight.w500, + fontSize: 15.sp, + ), + Spacer(), + Icon( + Icons.keyboard_arrow_right, + color: Colors.black, + size: 20.w, + ), + SizedBox(width: 25.w), + ], + ), + onTap: () { + SmartDialog.show( + tag: "showRoomMicSwitch", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + debounce: true, + builder: (_) { + return RoomMicSwitchPage(); + }, + ); + }, + ), + SizedBox(height: 12.w), + ATDebounceWidget( + child: Row( + children: [ + SizedBox(width: 25.w), + text( + ATAppLocalizations.of(context)!.blockedList2, + textColor: Colors.black, + fontWeight: FontWeight.w500, + fontSize: 15.sp, + ), + Spacer(), + Icon( + Icons.keyboard_arrow_right, + color: Colors.black, + size: 20.w, + ), + SizedBox(width: 25.w), + ], + ), + onTap: () { + showBottomInBottomDialog( + context, + BlockedListPage( + roomId: + Provider.of( + context!, + listen: false, + ) + .currenRoom + ?.roomProfile + ?.roomProfile + ?.id ?? + "", + ), + ); + }, + ), + SizedBox(height: 12.w), + ATDebounceWidget( + child: Row( + children: [ + SizedBox(width: 25.w), + text( + ATAppLocalizations.of(context)!.roomMemberFee, + textColor: Colors.black, + fontWeight: FontWeight.w500, + fontSize: 15.sp, + ), + Spacer(), + Consumer( + builder: (context, ref, child) { + return text( + "${ref.currenRoom?.roomProfile?.roomSetting?.joinGolds}", + textColor: Color(0xffB1B1B1), + fontSize: 13.sp, + ); + }, + ), + Icon( + Icons.keyboard_arrow_right, + color: Colors.black, + size: 20.w, + ), + SizedBox(width: 25.w), + ], + ), + onTap: () { + Navigator.push( + context, + PopRoute(child: RoomMembershipFeePage()), + ); + }, + ), + SizedBox(height: 15.w), + ], + ) + : Container(), + ], + ), + ), + ), + ], + ), + ); + } + + int _getActualCharacterCount(String text) { + return text.characters.length; + } + + void submit(BuildContext context) async { + if (_roomNameController.text.trim().isEmpty) { + ATTts.show("Room name not empty!"); + return; + } + if (_roomAnnouncementController.text.trim().isEmpty) { + ATTts.show("Room announcement not empty!"); + return; + } + ATLoadingManager.exhibitOperation(context: context); + var roomInfo = await AccountRepository().editRoomInfo( + rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "", + roomCover, + _roomNameController.text, + _roomAnnouncementController.text, + ATRoomInfoEventType.AVAILABLE.name, + ); + Provider.of( + context, + listen: false, + ).updateMyRoomInfo(roomInfo); + if (widget.needRestCurrentRoomInfo == "true") { + ///需要创建群组 + var c = await Provider.of( + context, + listen: false, + ).createRoomGroup( + rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "", + rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomName ?? "", + ); + if (c.code == 0) { + ATLoadingManager.veilRoutine(); + ATNavigatorUtils.goBack(context); + Provider.of( + context, + listen: false, + ).joinRoom(context, roomInfo.id ?? "", clearRoomData: true); + } else { + ATLoadingManager.veilRoutine(); + ATTts.show("${c.code}"); + } + } else { + Provider.of( + context, + listen: false, + ).loadRoomInfo(roomInfo.id ?? ""); + ATLoadingManager.veilRoutine(); + ATNavigatorUtils.goBack(context); + } + } + + void _setPwd() { + if (Provider.of( + context, + listen: false, + ).currenRoom!.roomProfile!.roomSetting!.password != + null && + Provider.of( + context, + listen: false, + ).currenRoom!.roomProfile!.roomSetting!.password!.isNotEmpty) { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.cancelRoomPassword, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + ChatRoomRepository() + .roomUnlocked( + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.id ?? + "", + ) + .whenComplete(() { + Provider.of( + navigatorKey.currentState!.context, + listen: false, + ).loadRoomInfo( + Provider.of( + navigatorKey.currentState!.context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.id ?? + "", + ); + }); + }, + ); + }, + ); + } else { + Navigator.push(context, PopRoute(child: SetRoomPwdPage())); + } + } +} diff --git a/lib/chatvibe_features/room/inv_game/inv_game_page.dart b/lib/chatvibe_features/room/inv_game/inv_game_page.dart new file mode 100644 index 0000000..21ea66d --- /dev/null +++ b/lib/chatvibe_features/room/inv_game/inv_game_page.dart @@ -0,0 +1,340 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/chatvibe_gradient_button.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_core/constants/at_room_msg_type.dart'; +import 'package:aslan/chatvibe_data/models/message/big_broadcast_group_message.dart'; +import 'package:aslan/chatvibe_data/models/message/at_floating_message.dart'; +import 'package:aslan/chatvibe_domain/models/res/follow_user_res.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +class InvGamePage extends ATPageList { + @override + _InvGamePageState createState() => _InvGamePageState(); +} + +class _InvGamePageState extends ATPageListState + with SingleTickerProviderStateMixin { + final TextEditingController _textEditingController = TextEditingController(); + String? lastId; + + @override + void initState() { + super.initState(); + enablePullUp = true; + backgroundColor = Colors.transparent; + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/index/at_icon_coupon_head_bg.png", + width: ScreenUtil().screenWidth, + height: 150.w, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.fansList, + actions: [], + ), + body: SafeArea( + top: false, + child: Container( + margin: EdgeInsetsDirectional.only(top: 15.w), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadiusDirectional.only( + topStart: Radius.circular(35.w), + ), + ), + child: Column( + children: [ + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: searchWidget( + hint: ATAppLocalizations.of(context)!.searchUserId, + controller: _textEditingController, + borderColor: Colors.black12, + textColor: Colors.grey, + ), + ), + chatvibeGradientButton( + text: ATAppLocalizations.of(context)!.search, + radius: 25, + textSize: 14.sp, + textColor: Colors.black, + gradient: LinearGradient( + colors: [Colors.transparent, Colors.transparent], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + onPress: () { + if (_textEditingController.text.isNotEmpty) { + _searchUser(); + }else{ + ATLoadingManager.exhibitOperation(); + loadData(1); + } + }, + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 8.w,), + Expanded(child: buildList(context)), + ], + ), + ), + ), + ), + ], + ); + } + + @override + Widget buildItem(FollowUserRes res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration(), + child: Row( + children: [ + SizedBox(width: 10.w), + GestureDetector(onTap: (){ + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == res.userProfile?.id}&tageId=${res.userProfile?.id}", + ); + },child: head( + url: res.userProfile?.userAvatar ?? "", + width: 55.w, + headdress: res.userProfile?.getHeaddress()?.sourceUrl, + ),), + SizedBox(width: 5.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + chatvibeNickNameText( + res.userProfile + ?.userNickname ?? + "", + fontSize: 14.sp, + textColor: Colors.black, + type: + res.userProfile + ?.getVIP() + ?.name ?? + "", + needScroll: + ( res.userProfile + ?.userNickname + ?.characters.length ?? + 0) > + 22, + ), + Row( + children: [ + Container( + height: 35.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + (res.userProfile?.hasSpecialId() ?? false) + ? "atu_images/general/at_icon_special_id_bg.png" + : "atu_images/general/at_icon_id_bg.png", + ), + fit: BoxFit.contain, + ), + ), + child: Row( + textDirection: TextDirection.ltr, + children: [ + SizedBox(width: 38.w), + text( + "${res.userProfile?.getID()}", + fontSize: 12.sp, + textColor: Colors.white, + fontWeight: FontWeight.bold, + ), + SizedBox(width: 5.w), + Image.asset( + "atu_images/room/at_icon_user_card_copy_id.png", + width: 12.w, + height: 12.w, + ), + SizedBox(width: 8.w), + ], + ), + ), + SizedBox(width: 12.w), + Container( + width: (res.userProfile?.age ?? 0) > 999 ? 58.w : 48.w, + height: 24.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + res.userProfile?.userSex == 0 + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png", + ), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + xb(res.userProfile?.userSex), + text( + "${res.userProfile?.age}", + textColor: Colors.white, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 3.w), + ], + ), + ), + SizedBox(width: 8.w), + getWealthLevel( + res.userProfile?.wealthLevel ?? 0, + width: 58.w, + height: 30.w, + ), + SizedBox(width: 8.w), + getUserLevel( + res.userProfile?.charmLevel ?? 0, + width: 58.w, + height: 30.w, + ), + ], + ), + ], + ), + ), + SizedBox(width: 15.w), + ], + ), + ), + onTap: () { + _showSendConfirm(res.userProfile?.account??"",res.userProfile?.id??""); + }, + ); + } + + void _searchUser() async { + try { + ATLoadingManager.exhibitOperation(); + items.clear(); + var result = await AccountRepository().fansMyList( + account: _textEditingController.text, + ); + items.addAll(result); + ATLoadingManager.veilRoutine(); + setState(() {}); + } catch (e) { + ATLoadingManager.veilRoutine(); + } + } + + @override + builderDivider() { + // return Divider( + // height: 1.w, + // color: Color(0xff3D3277).withOpacity(0.5), + // indent: 15.w, + // ); + return Container(height: 8.w); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + if (page == 1) { + lastId = null; + } + try { + var fansList = await AccountRepository().fansMyList(lastId: lastId); + ATLoadingManager.veilRoutine(); + if (fansList.isNotEmpty) { + lastId = fansList.last.id; + } + onSuccess(fansList); + } catch (e) { + ATLoadingManager.veilRoutine(); + if (onErr != null) { + onErr(); + } + } + } + + void _showSendConfirm(String account,String userId) { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of( + context, + )!.confirmInviteThisUserToTheRoom("$account"), + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + BigBroadcastGroupMessage msg = BigBroadcastGroupMessage( + ATRoomMsgType.inviteRoom, + ATFloatingMessage( + userId: AccountStorage().getCurrentUser()?.userProfile?.account, + toUserId: userId, + userAvatarUrl: + AccountStorage().getCurrentUser()?.userProfile?.userAvatar ?? + "", + roomId: + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.id ?? + "", + ), + ); + Provider.of( + context, + listen: false, + ).sendBigBroadcastGroup(msg); + SmartDialog.dismiss(tag: "showConfirmDialog"); + }, + ); + }, + ); + } +} diff --git a/lib/chatvibe_features/room/manager/room_member_page.dart b/lib/chatvibe_features/room/manager/room_member_page.dart new file mode 100644 index 0000000..b6237b5 --- /dev/null +++ b/lib/chatvibe_features/room/manager/room_member_page.dart @@ -0,0 +1,442 @@ +import 'dart:ui' as ui; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_debouncer/flutter_debouncer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:provider/provider.dart'; + +import '../../../app_localizations.dart'; +import '../../../chatvibe_core/constants/at_room_msg_type.dart'; +import '../../../chatvibe_core/constants/at_screen.dart'; +import '../../../chatvibe_core/utilities/at_user_utils.dart'; +import '../../../chatvibe_data/models/enum/at_room_roles_type.dart'; +import '../../../chatvibe_data/sources/local/user_manager.dart'; +import '../../../chatvibe_data/sources/repositories/room_repository_imp.dart'; +import '../../../chatvibe_domain/models/res/room_member_res.dart'; +import '../../../chatvibe_managers/rtc_manager.dart'; +import '../../../chatvibe_managers/rtm_manager.dart'; +import '../../../chatvibe_ui/components/at_compontent.dart'; +import '../../../chatvibe_ui/components/at_page_list.dart'; +import '../../../chatvibe_ui/components/at_tts.dart'; +import '../../../chatvibe_ui/components/text/at_text.dart'; +import '../../../chatvibe_ui/widgets/room/room_msg_item.dart'; + +///房间成员列表 +class RoomMemberPage extends ATPageList { + String? roomId = ""; + bool isHomeowner = false; + + RoomMemberPage({super.key, this.roomId, this.isHomeowner = false}); + + @override + _RoomMemberPageState createState() => + _RoomMemberPageState(roomId, isHomeowner); +} + +class _RoomMemberPageState + extends ATPageListState { + String? roomId = ""; + String? lastId; + bool isHomeowner = false; + String optTips = ""; + + _RoomMemberPageState(this.roomId, this.isHomeowner); + + final debouncer = Debouncer(); + + @override + void initState() { + super.initState(); + enablePullUp = true; + enablePullDown = false; + // Cursor pagination: treat only an empty response as "no more data". + pageCount = 1; + backgroundColor = Colors.transparent; + loadData(1); + } + + @override + Widget build(BuildContext context) { + optTips = ATAppLocalizations.of(context)!.operationSuccessful; + return ClipRect( + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 15, sigmaY: 15), + child: SafeArea( + top: false, + child: Container( + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + ), + child: Column( + children: [ + SizedBox(height: 15.w), + text( + ATAppLocalizations.of(context)!.roomMember, + fontSize: 14.sp, + textColor: Colors.white, + ), + SizedBox(height: 10.w), + SizedBox(height: 350.w, child: buildList(context)), + ], + ), + ), + ), + ), + ); + } + + @override + Widget buildItemOne(ChatVibeRoomMemberRes userInfo, int index) { + final bool showRoleTag = _shouldShowRoleTag1(index, userInfo.roles); + return Column( + children: [ + if (showRoleTag) buildTag1(userInfo.roles), + if (showRoleTag) SizedBox(height: 3.w), + _buildMemberItem1(userInfo), + ], + ); + } + + bool _shouldShowRoleTag1(int index, String? currentRole) { + if (index == 0) { + return true; + } + return items[index - 1].roles != currentRole; + } + + Widget _buildMemberItem1(ChatVibeRoomMemberRes userInfo) { + return GestureDetector( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.w), + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: Colors.white24, + ), + child: Row( + children: [ + head( + url: userInfo.userProfile?.userAvatar ?? "", + width: 52.w, + headdress: userInfo.userProfile?.getHeaddress()?.sourceUrl, + ), + SizedBox(width: 3.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 240.w, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + msgRoleTag( + userInfo.roles ?? "", + width: 20.w, + height: 20.w, + ), + SizedBox(width: 3.w), + chatvibeNickNameText( + maxWidth: 160.w, + userInfo.userProfile?.userNickname ?? "", + fontSize: 14.sp, + type: userInfo.userProfile?.getVIP()?.name ?? "", + needScroll: + (userInfo + .userProfile + ?.userNickname + ?.characters + .length ?? + 0) > + 14, + ), + SizedBox(width: 3.w), + userInfo.userProfile?.getVIP() != null + ? netImage( + url: userInfo.userProfile?.getVIP()?.cover ?? "", + width: 25.w, + height: 25.w, + ) + : Container(), + SizedBox(width: 5.w), + userInfo.userProfile?.wearBadge?.isNotEmpty ?? false + ? SizedBox( + height: 25.w, + child: ListView.separated( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: + userInfo.userProfile?.wearBadge?.length ?? + 0, + itemBuilder: (context, index) { + return netImage( + width: 25.w, + height: 25.w, + url: + userInfo + .userProfile + ?.wearBadge?[index] + .selectUrl ?? + "", + ); + }, + separatorBuilder: ( + BuildContext context, + int index, + ) { + return SizedBox(width: 5.w); + }, + ), + ) + : Container(), + ], + ), + ), + ), + SizedBox(height: 3.w), + GestureDetector( + child: Row( + textDirection: TextDirection.ltr, + children: [ + Row( + children: [ + getIdIcon( + userInfo.userProfile?.wealthLevel??0, + width: 28.w, + height: 28.w, + textColor: Colors.white, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + ), + chatvibeNickNameText( + maxWidth: 135.w, + userInfo.userProfile?.getID() ?? "", + textColor: Colors.white, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + type: + userInfo.userProfile?.getVIP()?.name ?? "", + needScroll: + (userInfo.userProfile + ?.getID() + .characters + .length ?? + 0) > + 13, + ), + ], + ) +, + ], + ), + onTap: () { + Clipboard.setData( + ClipboardData(text: userInfo.userProfile?.getID() ?? ""), + ); + ATTts.show( + ATAppLocalizations.of(context)!.copiedToClipboard, + ); + }, + ), + ], + ), + Spacer(), + userInfo.roles != ATRoomRolesType.HOMEOWNER.name && isHomeowner + ? GestureDetector( + child: Image.asset( + "atu_images/room/at_icon_remve_block.png", + width: 20.w, + height: 20.w, + ), + onTap: () { + debouncer.debounce( + duration: Duration(milliseconds: 500), + onDebounce: () { + ChatRoomRepository() + .changeRoomRole( + roomId ?? "", + ATRoomRolesType.TOURIST.name, + userInfo.userProfile?.id ?? "", + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id ?? + "", + "OTHER_CHANGE", + ) + .whenComplete(() { + ATTts.show(optTips); + Msg msg = Msg( + groupId: + Provider.of( + context, + listen: false, + ) + .currenRoom + ?.roomProfile + ?.roomProfile + ?.roomAccount, + msg: 'TOURIST', + toUser: userInfo.userProfile, + type: ATRoomMsgType.roomRoleChange, + ); + Provider.of( + context, + listen: false, + ).sendMsg(msg, addLocal: true); + Navigator.of(context).pop(); + }); + }, + ); + }, + ) + : Container(), + ], + ), + ), + onTap: () { + Navigator.of(context).pop(); + num index = Provider.of( + context, + listen: false, + ).userOnMaiInIndex(userInfo.id ?? ""); + Provider.of( + context, + listen: false, + ).clickSite(index, clickUser: userInfo.userProfile); + }, + ); + } + + @override + empty() { + List list = []; + list.add(SizedBox(height: height(30))); + list.add(Image.asset('atu_images/general/at_icon_loading.png')); + list.add(SizedBox(height: height(15))); + list.add( + Text( + ATAppLocalizations.of(context)!.noData, + style: TextStyle( + fontSize: sp(14), + color: Color(0xff999999), + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + height: 1, + ), + ), + ); + return Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: list, + ); + } + + @override + builderDivider() { + // return Divider( + // height: 1.w, + // color: Color(0xff3D3277).withOpacity(0.5), + // indent: 15.w, + // ); + return Container(height: 8.w); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + if (page == 1) { + lastId = null; + } + if ((roomId ?? "").isEmpty) { + onSuccess([]); + return; + } + try { + final userList = await ChatRoomRepository().roomMember( + roomId ?? "", + lastId: lastId, + ); + final deduplicated = userList.where((user) => user.id != lastId).toList(); + final visibleList = + deduplicated + .where( + (user) => + user.roles == ATRoomRolesType.HOMEOWNER.name || + user.roles == ATRoomRolesType.ADMIN.name || + user.roles == ATRoomRolesType.MEMBER.name, + ) + .toList(); + if (deduplicated.isNotEmpty) { + lastId = deduplicated.last.id; + } + onSuccess(visibleList); + } catch (e) { + onErr?.call(); + } + } + + Widget buildTag1(String? roles) { + if (roles == ATRoomRolesType.HOMEOWNER.name) { + return Row( + children: [ + SizedBox(width: 5.w), + text( + ATAppLocalizations.of(context)!.owner, + fontSize: 13.sp, + textColor: Colors.white54, + ), + Spacer(), + ], + ); + } else if (roles == ATRoomRolesType.ADMIN.name) { + return Row( + children: [ + SizedBox(width: 5.w), + text( + ATAppLocalizations.of(context)!.admin, + fontSize: 13.sp, + textColor: Colors.white54, + ), + Spacer(), + // Image.asset( + // "images/room/icon_add_user.png", + // width: 25.w, + // height: 25.w, + // ), + // SizedBox(width: 3.w), + ], + ); + } else if (roles == ATRoomRolesType.MEMBER.name) { + return Row( + children: [ + SizedBox(width: 5.w), + text( + ATAppLocalizations.of(context)!.member, + fontSize: 13.sp, + textColor: Colors.white54, + ), + Spacer(), + // Image.asset( + // "images/room/icon_add_user.png", + // width: 25.w, + // height: 25.w, + // ), + // SizedBox(width: 3.w), + ], + ); + } + return Container(); + } +} diff --git a/lib/chatvibe_features/room/music/at_music_seek_bar.dart b/lib/chatvibe_features/room/music/at_music_seek_bar.dart new file mode 100644 index 0000000..d4ef14c --- /dev/null +++ b/lib/chatvibe_features/room/music/at_music_seek_bar.dart @@ -0,0 +1,91 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_managers/audio_manager.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; + +class ATMusicSeekBar extends StatefulWidget { + const ATMusicSeekBar({Key? key}) : super(key: key); + + @override + _ATMusicSeekBarState createState() => _ATMusicSeekBarState(); +} + +class _ATMusicSeekBarState extends State { + int _currentValue = 0; + int _maxValue = 0; + + @override + void initState() { + super.initState(); + Provider.of( + context!, + listen: false, + ).changeCall = audioMixingChangeCall; + } + + void audioMixingChangeCall(int currentValue, int maxValue){ + if (mounted) { + setState(() { + _currentValue = currentValue; + _maxValue = maxValue; + }); + } + } + + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + SizedBox( + height: 25.w, + child: Slider( + value: _currentValue.toDouble().clamp(0, _maxValue.toDouble()), + activeColor: Color(0xff9A47FF), + inactiveColor: Colors.white24, + min: 0.0, + max: _maxValue.toDouble(), + onChanged: (double value) { + setState(() { + _currentValue = value.toInt(); + }); + }, + onChangeEnd: (double value) async { + // 当用户拖动结束时,跳转到指定位置 + await Provider.of( + context, + listen: false, + ).engine!.setAudioMixingPosition(value.toInt()); + }, + ), + ), + Row( + children: [ + SizedBox(width: 25.w), + text( + ATMDateUtils.millisecondsToMinutesMethod(_currentValue), + textColor: Colors.white, + ), + Spacer(), + text( + ATMDateUtils.millisecondsToMinutesMethod(_maxValue), + textColor: Colors.white, + ), + SizedBox(width: 25.w), + ], + ), + ], + ); + } +} diff --git a/lib/chatvibe_features/room/music/at_room_music_page.dart b/lib/chatvibe_features/room/music/at_room_music_page.dart new file mode 100644 index 0000000..acb3079 --- /dev/null +++ b/lib/chatvibe_features/room/music/at_room_music_page.dart @@ -0,0 +1,291 @@ +import 'dart:ui' as ui; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_data/models/at_music_mode.dart'; +import 'package:aslan/chatvibe_managers/audio_manager.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; + +import 'local/at_local_music_page.dart'; + +class ATRoomMusicPage extends StatefulWidget { + @override + _ATRoomMusicPageState createState() => _ATRoomMusicPageState(); +} + +class _ATRoomMusicPageState extends State { + @override + void initState() { + super.initState(); + Provider.of(context, listen: false).loadAtRoomMusics(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + return SafeArea( + top: false, + child: ClipRect( + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 15, sigmaY: 15), + child: Container( + height: 480.w, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + ), + child: Stack( + alignment: AlignmentDirectional.topCenter, + children: [ + Column( + children: [ + SizedBox(height: 15.w), + Row( + children: [ + Spacer(), + text( + ATAppLocalizations.of(context)!.myMusic, + fontSize: 14.sp, + textColor: Colors.black, + fontWeight: FontWeight.bold, + ), + Spacer(), + ], + ), + SizedBox(height: 8.w), + Divider( + color: Colors.white24, + thickness: 3.w, + height: 28.w, + ), + Expanded( + child: + ref.addedSongs.isNotEmpty + ? ListView.separated( + itemBuilder: + (context, i) => _buildItem( + ref.addedSongs[i], + i, + ref, + ), + itemCount: ref.addedSongs.length, + padding: EdgeInsets.zero, + separatorBuilder: + (context, i) => Container(height: 10.w), + ) + : mainEmpty( + center: false, + textColor: Colors.black38, + ), + ), + ], + ), + Container( + margin: EdgeInsets.only(top: 15.w), + child: Row( + children: [ + SizedBox(width: 10.w), + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + color: Colors.transparent, + padding: EdgeInsets.all(3.w), + child: Icon( + Icons.arrow_back_ios, + size: 16.w, + color: Colors.black, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showRoomMusic"); + }, + ), + Spacer(), + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Icon( + Icons.add, + size: 16.w, + color: Colors.black, + ), + onTap: () { + SmartDialog.show( + tag: "showRoomLocalMusic", + alignment: Alignment.bottomCenter, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return ATLocalMusicPage(); + }, + ); + }, + ), + GestureDetector( + child: Container( + color: Colors.transparent, + padding: EdgeInsets.all(3.w), + child: text( + ATAppLocalizations.of(context)!.music, + fontSize: 11.sp, + textColor: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showRoomMusic"); + SmartDialog.show( + tag: "showRoomLocalMusic", + debounce: true, + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return ATLocalMusicPage(); + }, + ); + }, + ), + SizedBox(width: 10.w), + ], + ), + ), + ], + ), + ), + ), + ), + ); + }, + ); + } + + Widget _buildItem(ATMusicMode song, int i, AudioManager ref) { + return GestureDetector( + onTap: () { + bool isOnMai = + Provider.of(context, listen: false).isOnMai(); + if (isOnMai) { + _play(i, song); + } else { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + msg: ATAppLocalizations.of(context)!.pleaseGetOnTheMicFirst, + onEnsure: () { + ///上麦 + num index = + Provider.of( + context, + listen: false, + ).findWheat(); + if (index > -1) { + Provider.of( + context, + listen: false, + ).shangMai(index); + } + }, + ); + }, + ); + } + }, + behavior: HitTestBehavior.opaque, + child: Row( + children: [ + SizedBox(width: 8.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + constraints: BoxConstraints(maxWidth: 260.w), + child: text( + song.title ?? "", + fontWeight: FontWeight.w600, + textColor: + ref.playingIndex == i + ? ChatVibeTheme.primaryColor + : Colors.black, + fontSize: 14.sp, + ), + ), + SizedBox(width: 5.w), + ref.playingIndex == i + ? Image.asset( + "atu_images/general/at_icon_online_user.gif", + width: 18.w, + ) + : Container(), + ], + ), + SizedBox(height: 3.w), + text( + ATAppLocalizations.of(context)!.duration2( + ATMDateUtils.millisecondsToMinutesMethod( + song.duration ?? 0, + ), + ), + fontWeight: FontWeight.w600, + textColor: Colors.black38, + fontSize: 12.sp, + ), + ], + ), + ), + i == 0 + ? Container() + : GestureDetector( + child: Image.asset( + "atu_images/room/at_icon_music_to_up.png", + width: 18.w, + color: Colors.black, + ), + onTap: () { + ref.toUp(song, i); + }, + ), + SizedBox(width: 10.w), + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Image.asset( + "atu_images/room/at_icon_music_delete.png", + width: 18.w, + color: Colors.black, + ), + onTap: () { + ref.addedSongs.removeAt(i); + ref.deleteRoomMusic(context, song); + setState(() {}); + }, + ), + SizedBox(width: 10.w), + ], + ), + ); + } + + void _play(int i, ATMusicMode song) { + Provider.of( + context, + listen: false, + ).playMusic(context, song, i); + } +} diff --git a/lib/chatvibe_features/room/music/at_volume_seek_bar.dart b/lib/chatvibe_features/room/music/at_volume_seek_bar.dart new file mode 100644 index 0000000..e1d8d96 --- /dev/null +++ b/lib/chatvibe_features/room/music/at_volume_seek_bar.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; + +class ATVolumeSeekBar extends StatefulWidget { + const ATVolumeSeekBar({Key? key}) : super(key: key); + + @override + _ATVolumeSeekBarState createState() => _ATVolumeSeekBarState(); +} + +class _ATVolumeSeekBarState extends State { + int _currentValue = 0; + + @override + void initState() { + super.initState(); + _getCurrentVolumeAt(); + } + + void _getCurrentVolumeAt() async { + // 从声网SDK获取当前播放位置 + int currentPos = + await Provider.of( + context, + listen: false, + ).engine!.getAudioMixingPlayoutVolume(); + + if (mounted) { + setState(() { + _currentValue = currentPos; + }); + } + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + SizedBox( + height: 25.w, + child: Slider( + value: _currentValue.toDouble().clamp(0, 100.0), + activeColor: Color(0xff9A47FF), + inactiveColor: Colors.white24, + min: 0.0, + max: 100.0, + onChanged: (double value) { + setState(() { + _currentValue = value.toInt(); + }); + }, + onChangeEnd: (double value) async { + // 当用户拖动结束时,跳转到指定位置 + await Provider.of( + context, + listen: false, + ).engine!.adjustAudioMixingVolume(value.toInt()); + }, + ), + ), + ], + ); + } +} diff --git a/lib/chatvibe_features/room/music/control/at_room_music_control_page.dart b/lib/chatvibe_features/room/music/control/at_room_music_control_page.dart new file mode 100644 index 0000000..dd52ebc --- /dev/null +++ b/lib/chatvibe_features/room/music/control/at_room_music_control_page.dart @@ -0,0 +1,341 @@ +import 'package:agora_rtc_engine/agora_rtc_engine.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_managers/audio_manager.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; + +import '../at_room_music_page.dart'; + +class ATRoomMusicControlPage extends StatefulWidget { + const ATRoomMusicControlPage({super.key}); + + @override + State createState() => _ATRoomMusicControlPageState(); +} + +class _ATRoomMusicControlPageState extends State { + int _currentValue = 0; + int _maxValue = 0; + int _currentVolume = 0; + AudioManager? _audioManager; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) { + return; + } + _audioManager = Provider.of(context, listen: false); + _audioManager?.changeCall = _audioMixingChangeCall; + _loadCurrentVolume(); + }); + } + + void _audioMixingChangeCall(int currentValue, int maxValue) { + if (!mounted) { + return; + } + setState(() { + _currentValue = currentValue; + _maxValue = maxValue; + }); + } + + Future _loadCurrentVolume() async { + final RtcProvider rtcProvider = Provider.of( + context, + listen: false, + ); + final int currentPos = + await rtcProvider.engine?.getAudioMixingPlayoutVolume() ?? 0; + if (!mounted) { + return; + } + setState(() { + _currentVolume = currentPos; + }); + } + + @override + void dispose() { + if (_audioManager?.changeCall == _audioMixingChangeCall) { + _audioManager?.changeCall = null; + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + final bool isPlaying = + ref.state == AudioMixingStateType.audioMixingStatePlaying; + final String title = ref.currentMusicMode?.title ?? ""; + return Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + padding: EdgeInsets.fromLTRB(22.w, 14.w, 22.w, 20.w), + decoration: BoxDecoration( + color: Color(0xFF2A2836), + borderRadius: BorderRadius.circular(24.w), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + SmartDialog.dismiss(tag: "showRoomMusicControl"); + }, + child: Icon( + Icons.keyboard_arrow_down_rounded, + color: Colors.white, + size: 36.w, + ), + ), + SizedBox(height: 8.w), + Row( + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + SmartDialog.dismiss(tag: "showRoomMusicControl"); + SmartDialog.show( + tag: "showRoomMusic", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return ATRoomMusicPage(); + }, + ); + }, + child: Icon( + Icons.queue_music_rounded, + color: Colors.white, + size: 34.w, + ), + ), + SizedBox(width: 14.w), + Expanded( + child: chatvibeNickNameText( + title, + fontSize: 16.sp, + textColor: Colors.white, + type: "", + fontWeight: FontWeight.w600, + needScroll: (title.characters.length ?? 0) > 18, + ), + ), + SizedBox(width: 14.w), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + Provider.of( + context, + listen: false, + ).stopMusic(); + SmartDialog.dismiss(tag: "showRoomMusicControl"); + }, + child: Icon( + Icons.power_settings_new_rounded, + color: Colors.white, + size: 32.w, + ), + ), + ], + ), + SizedBox(height: 20.w), + Row( + children: [ + SizedBox( + width: 44.w, + child: text( + ATMDateUtils.millisecondsToMinutesMethod(_currentValue), + fontSize: 12.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + Expanded( + child: SliderTheme( + data: SliderTheme.of(context).copyWith( + trackHeight: 8.w, + activeTrackColor: Color(0xFFFF9B21), + inactiveTrackColor: Color(0xFF7B7887), + thumbColor: Color(0xFFFF9B21), + overlayColor: Color(0x29FF9B21), + thumbShape: RoundSliderThumbShape( + enabledThumbRadius: 10.w, + ), + overlayShape: RoundSliderOverlayShape( + overlayRadius: 2.w, + ), + ), + child: Slider( + value: _currentValue.toDouble().clamp( + 0, + _maxValue.toDouble(), + ), + min: 0.0, + max: _maxValue <= 0 ? 1.0 : _maxValue.toDouble(), + onChanged: (double value) { + setState(() { + _currentValue = value.toInt(); + }); + }, + onChangeEnd: (double value) async { + await Provider.of( + context, + listen: false, + ).engine!.setAudioMixingPosition(value.toInt()); + }, + ), + ), + ), + SizedBox( + width: 44.w, + child: text( + ATMDateUtils.millisecondsToMinutesMethod(_maxValue), + textAlign: TextAlign.end, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + ], + ), + SizedBox(height: 22.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + ref.switchModel(); + }, + child: Icon( + ref.playModel == 0 + ? Icons.repeat_rounded + : (ref.playModel == 1 + ? Icons.shuffle_rounded + : Icons.repeat_one_rounded), + color: Colors.white, + size: 34.w, + ), + ), + SizedBox(width: 28.w), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + ref.previous(context); + }, + child: Icon( + Icons.skip_previous_rounded, + color: Colors.white, + size: 42.w, + ), + ), + SizedBox(width: 24.w), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + ref.playOrPause(context); + }, + child: Container( + width: 50.w, + height: 50.w, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + colors: [Color(0xFFFF951F), Color(0xFFFFC125)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + boxShadow: [ + BoxShadow( + color: Color(0x66FF9D1F), + blurRadius: 18.w, + offset: Offset(0, 8.w), + ), + ], + ), + child: Icon( + isPlaying + ? Icons.pause_rounded + : Icons.play_arrow_rounded, + color: Colors.white, + size: 28.w, + ), + ), + ), + SizedBox(width: 24.w), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + ref.playNext(context); + }, + child: Icon( + Icons.skip_next_rounded, + color: Colors.white, + size: 42.w, + ), + ), + ], + ), + SizedBox(height: 22.w), + Row( + children: [ + Icon( + Icons.volume_up_rounded, + color: Colors.white, + size: 32.w, + ), + SizedBox(width: 14.w), + SizedBox( + width: 160.w, + child: SliderTheme( + data: SliderTheme.of(context).copyWith( + trackHeight: 6.w, + activeTrackColor: Color(0xFFFF9B21), + inactiveTrackColor: Color(0xFF7B7887), + thumbColor: Color(0xFFFF9B21), + overlayColor: Color(0x29FF9B21), + thumbShape: RoundSliderThumbShape( + enabledThumbRadius: 8.w, + ), + overlayShape: RoundSliderOverlayShape( + overlayRadius: 2.w, + ), + ), + child: Slider( + value: _currentVolume.toDouble().clamp(0, 100.0), + min: 0.0, + max: 100.0, + onChanged: (double value) { + setState(() { + _currentVolume = value.toInt(); + }); + }, + onChangeEnd: (double value) async { + await Provider.of( + context, + listen: false, + ).engine!.adjustAudioMixingVolume(value.toInt()); + }, + ), + ), + ), + ], + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/chatvibe_features/room/music/local/at_local_music_page.dart b/lib/chatvibe_features/room/music/local/at_local_music_page.dart new file mode 100644 index 0000000..32cf972 --- /dev/null +++ b/lib/chatvibe_features/room/music/local/at_local_music_page.dart @@ -0,0 +1,203 @@ +import 'dart:ui' as ui; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:on_audio_query/on_audio_query.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_data/models/at_music_mode.dart'; +import 'package:aslan/chatvibe_managers/audio_manager.dart'; + +import '../at_room_music_page.dart'; + +class ATLocalMusicPage extends StatefulWidget { + @override + _ATLocalMusicPageState createState() => _ATLocalMusicPageState(); +} + +class _ATLocalMusicPageState extends State { + @override + void initState() { + super.initState(); + Provider.of(context, listen: false).getAtSongs(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + return SafeArea( + top: false, + child: ClipRect( + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 15, sigmaY: 15), + child: Container( + height: 480.w, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + ), + child: Stack( + alignment: AlignmentDirectional.topCenter, + children: [ + Column( + children: [ + SizedBox(height: 15.w), + Row( + children: [ + Spacer(), + text( + ATAppLocalizations.of(context)!.localMusic, + fontSize: 14.sp, + textColor: Colors.black, + fontWeight: FontWeight.w600, + ), + Spacer(), + ], + ), + SizedBox(height: 8.w), + Divider( + color: Colors.white24, + thickness: 3.w, + height: 28.w, + ), + Expanded( + child: + ref.localSongs.isNotEmpty + ? ListView.separated( + itemBuilder: + (context, i) => _buildItem( + ref.localSongs[i], + i, + ref, + ), + itemCount: ref.localSongs.length, + padding: EdgeInsets.zero, + separatorBuilder: + (context, i) => Container(height: 10.w), + ) + : mainEmpty( + center: false, + textColor: Colors.black38, + ), + ), + ], + ), + Container( + margin: EdgeInsets.only(top: 15.w), + child: Row( + children: [ + SizedBox(width: 10.w), + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + color: Colors.transparent, + padding: EdgeInsets.all(3.w), + child: Icon( + Icons.arrow_back_ios, + size: 16.w, + color: Colors.black, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showRoomLocalMusic"); + SmartDialog.show( + tag: "showRoomMusic", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return ATRoomMusicPage(); + }, + ); + }, + ), + Spacer(), + GestureDetector( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 6.w), + child: text( + ATAppLocalizations.of(context)!.all, + textColor: Colors.black, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + ), + ), + onTap: () { + ref.selectAll(); + }, + ), + SizedBox(width: 8.w), + ], + ), + ), + ], + ), + ), + ), + ), + ); + }, + ); + } + + Widget _buildItem(SongModel song, int i, AudioManager ref) { + return Row( + children: [ + SizedBox(width: 8.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text( + song.title, + fontWeight: FontWeight.w600, + textColor: Colors.black, + fontSize: 14.sp, + ), + SizedBox(height: 3.w), + text( + ATAppLocalizations.of(context)!.duration2( + ATMDateUtils.millisecondsToMinutesMethod(song.duration ?? 0), + ), + fontWeight: FontWeight.w600, + textColor: Colors.black38, + fontSize: 12.sp, + ), + ], + ), + ), + SizedBox(width: 5.w), + GestureDetector( + child: Image.asset( + ref.addedMusicMap.containsKey(song.title) + ? "atu_images/room/at_icon_room_music_select.png" + : "atu_images/room/at_icon_room_music_add.png", + width: 20.w, + ), + onTap: () { + var mode = ATMusicMode( + title: song.title, + artist: song.artist, + duration: song.duration, + localPath: song.uri, + ); + Provider.of( + context, + listen: false, + ).addRoomMusic(mode); + }, + ), + SizedBox(width: 8.w), + ], + ); + } +} diff --git a/lib/chatvibe_features/room/online/at_room_online_page.dart b/lib/chatvibe_features/room/online/at_room_online_page.dart new file mode 100644 index 0000000..15f8cba --- /dev/null +++ b/lib/chatvibe_features/room/online/at_room_online_page.dart @@ -0,0 +1,234 @@ +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:provider/provider.dart'; + +import '../../../chatvibe_ui/components/at_compontent.dart'; +import '../../../chatvibe_ui/components/at_page_list.dart'; +import '../../../chatvibe_ui/components/at_tts.dart'; +import '../../../chatvibe_ui/components/text/at_text.dart'; + +///房间用户在线列表 +class ATRoomOnlinePage extends ATPageList { + String? roomId = ""; + + ATRoomOnlinePage({super.key, this.roomId}); + + @override + _ATRoomOnlinePageState createState() => _ATRoomOnlinePageState(roomId); +} + +class _ATRoomOnlinePageState + extends ATPageListState { + String? roomId = ""; + + _ATRoomOnlinePageState(this.roomId); + + @override + void initState() { + super.initState(); + enablePullUp = false; + backgroundColor = Colors.transparent; + loadData(1); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + ), + child: Column( + children: [ + SizedBox(height: 15.w), + text( + "User(${items.length})", + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.black, + ), + SizedBox(height: 10.w), + SizedBox(height: 350.w, child: buildList(context)), + ], + ), + ), + ); + } + + @override + Widget buildItem(ChatVibeUserProfile userInfo) { + return GestureDetector( + child: Container( + margin: EdgeInsets.symmetric(vertical: 3.w), + padding: EdgeInsets.symmetric(horizontal: 16.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: Colors.transparent, + ), + child: Row( + children: [ + GestureDetector( + child: head( + url: userInfo.userAvatar ?? "", + width: 55.w, + headdress: userInfo.getHeaddress()?.sourceUrl, + ), + onTap: () { + Navigator.of(context).pop(); + num index = Provider.of( + context, + listen: false, + ).userOnMaiInIndex(userInfo.id ?? ""); + Provider.of( + context, + listen: false, + ).clickSite(index, clickUser: userInfo); + }, + ), + SizedBox(width: 3.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + msgRoleTag(userInfo.roles ?? "", width: 20.w, height: 20.w), + SizedBox(width: 3.w), + chatvibeNickNameText( + textColor: Colors.black, + maxWidth: 200.w, + userInfo.userNickname ?? "", + fontSize: 14.sp, + type: userInfo.getVIP()?.name ?? "", + needScroll: + (userInfo.userNickname?.characters.length ?? 0) > 16, + ), + getVIPBadge( + userInfo.getVIP()?.name, + width: 45.w, + height: 25.w, + ), + // ListView.separated( + // scrollDirection: Axis.horizontal, + // shrinkWrap: true, + // itemCount: userInfo.wearBadge?.length ?? 0, + // itemBuilder: (context, index) { + // return netImage( + // width: 25.w, + // height: 25.w, + // url: userInfo.wearBadge?[index].selectUrl ?? "", + // ); + // }, + // separatorBuilder: (BuildContext context, int index) { + // return SizedBox(width: 5.w); + // }, + // ), + ], + ), + GestureDetector( + child: Container( + child: Row( + textDirection: TextDirection.ltr, + children: [ + Row( + children: [ + getIdIcon( + userInfo.wealthLevel ?? 0, + width: 28.w, + height: 28.w, + textColor: Color(0xFF333333), + fontSize: 12.sp, + fontWeight: FontWeight.w600, + ), + chatvibeNickNameText( + maxWidth: 135.w, + userInfo.getID() ?? "", + textColor: Color(0xFF333333), + fontSize: 12.sp, + fontWeight: FontWeight.w600, + type: userInfo.getVIP()?.name ?? "", + needScroll: + (userInfo.getID().characters.length ?? 0) > + 13, + ), + ], + ), + ], + ), + ), + onTap: () { + Clipboard.setData( + ClipboardData(text: userInfo.getID() ?? ""), + ); + ATTts.show( + ATAppLocalizations.of(context)!.copiedToClipboard, + ); + }, + ), + ], + ), + ], + ), + ), + onTap: () {}, + ); + } + + @override + empty() { + List list = []; + list.add(SizedBox(height: height(30))); + list.add(Image.asset('atu_images/general/at_icon_loading.png')); + list.add(SizedBox(height: height(15))); + list.add( + Text( + "No data", + style: TextStyle( + fontSize: sp(14), + color: Color(0xff999999), + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + height: 1, + ), + ), + ); + return Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: list, + ); + } + + @override + builderDivider() { + // return Divider( + // height: 1.w, + // color: Color(0xff3D3277).withOpacity(0.5), + // indent: 15.w, + // ); + return Container(height: 8.w); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + // var roomList = await ChatRoomRepository().roomOnlineUsers(roomId ?? ""); + await Provider.of(context!, listen: false).getOnlineUsers(); + List userList = + Provider.of(context!, listen: false).onlineUsers; + onSuccess(userList); + } +} diff --git a/lib/chatvibe_features/room/rank/room_gift_rank_page.dart b/lib/chatvibe_features/room/rank/room_gift_rank_page.dart new file mode 100644 index 0000000..4491c2c --- /dev/null +++ b/lib/chatvibe_features/room/rank/room_gift_rank_page.dart @@ -0,0 +1,96 @@ +import 'dart:ui' as ui; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_features/room/rank/room_gift_rank_tab_page.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_reward_info_res.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; + +import '../../../chatvibe_data/models/enum/at_date_type.dart'; + +class RoomGiftRankPage extends StatefulWidget { + @override + _RoomGiftRankPageState createState() => _RoomGiftRankPageState(); +} + +class _RoomGiftRankPageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + final List _pages = []; + final List _tabs = []; + ATRoomRewardInfoRes? res; + + @override + void initState() { + super.initState(); + String roomId = + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.id ?? + ""; + _pages.add(RoomGiftRankTabPage(roomId, ATDateType.DAY.name)); + _pages.add(RoomGiftRankTabPage(roomId, ATDateType.WEEK.name)); + _pages.add(RoomGiftRankTabPage(roomId, ATDateType.MONTH.name)); + _tabController = TabController(length: _pages.length, vsync: this); + } + + @override + Widget build(BuildContext context) { + _tabs.clear(); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.daily)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.weekly)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.monthly)); + return SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.only(topLeft: Radius.circular(15.w), topRight: Radius.circular(15.w)), + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_task_bg_com.png", + ), + fit: BoxFit.cover, + ), + ), + height: 360.w, + child: Column( + children: [ + TabBar( + tabAlignment: TabAlignment.start, + labelPadding: EdgeInsets.symmetric(horizontal: 12.w), + labelColor: Colors.white, + isScrollable: true, + indicator: BoxDecoration(), + unselectedLabelColor: Colors.white54, + labelStyle: TextStyle( + fontSize: 17.sp, + fontWeight: FontWeight.w600, + ), + unselectedLabelStyle: TextStyle( + fontSize: 15.sp, + fontWeight: FontWeight.w500, + ), + indicatorColor: Colors.transparent, + dividerColor: Colors.transparent, + controller: _tabController, + tabs: _tabs, + ), + + Expanded( + child: TabBarView( + controller: _tabController, + children: _pages, + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/chatvibe_features/room/rank/room_gift_rank_tab_page.dart b/lib/chatvibe_features/room/rank/room_gift_rank_tab_page.dart new file mode 100644 index 0000000..0808fe9 --- /dev/null +++ b/lib/chatvibe_features/room/rank/room_gift_rank_tab_page.dart @@ -0,0 +1,222 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/main.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/utilities/at_lk_dialog_util.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_gift_rank_res.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/at_room_user_info_card.dart'; + +class RoomGiftRankTabPage extends ATPageList { + String roomId; + String dataType; + + RoomGiftRankTabPage(this.roomId, this.dataType); + + @override + _RoomGiftRankTabPageState createState() => + _RoomGiftRankTabPageState(roomId, dataType); +} + +class _RoomGiftRankTabPageState + extends ATPageListState { + String roomId; + String dataType; + + _RoomGiftRankTabPageState(this.roomId, this.dataType); + + @override + void initState() { + super.initState(); + enablePullUp = false; + backgroundColor = Colors.transparent; + loadData(1); + } + + @override + Widget build(BuildContext context) { + return buildList(context); + } + + @override + Widget buildItemOne(RoomGiftRankRes userInfo, int index) { + return GestureDetector( + child: Container( + margin: EdgeInsets.symmetric(vertical: 3.w), + padding: EdgeInsets.symmetric(horizontal: 16.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: Colors.transparent, + ), + child: Row( + children: [ + index == 0 + ? Image.asset( + "atu_images/room/at_icon_room_contribute_rank1.png", + width: 22.w, + height: 22.w, + ) + : (index == 1 + ? Image.asset( + "atu_images/room/at_icon_room_contribute_rank2.png", + width: 22.w, + height: 22.w, + ) + : (index == 2 + ? Image.asset( + "atu_images/room/at_icon_room_contribute_rank3.png", + width: 22.w, + height: 22.w, + ) + : Container( + width: 22.w, + alignment: AlignmentDirectional.center, + child: text( + "${index + 1}", + fontWeight: FontWeight.w600, + textColor: Colors.white, + fontSize: 14.sp, + ), + ))), + SizedBox(width: 10.w), + GestureDetector( + child: head( + url: userInfo.userProfile?.userAvatar ?? "", + width: 55.w, + // headdress: userInfo.userProfile?.getHeaddress()?.sourceUrl, + ), + onTap: () { + showBottomInBottomDialog( + navigatorKey.currentState!.context, + ATRoomUserInfoCard(userId: userInfo.userProfile?.id), + ); + SmartDialog.dismiss(tag: "showRoomGiftRankPage"); + }, + ), + SizedBox(width: 3.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + chatvibeNickNameText( + maxWidth: 170.w, + userInfo.userProfile?.userNickname ?? "", + fontSize: 14.sp, + type: userInfo.userProfile?.getVIP()?.name ?? "", + needScroll: + (userInfo.userProfile?.userNickname?.characters.length ?? 0) > + 14, + ), + ], + ), + SizedBox(height: 3.w), + GestureDetector( + child: Row( + children: [ + getIdIcon( + userInfo.userProfile?.wealthLevel ?? 0, + width: 28.w, + height: 28.w, + textColor: Colors.white, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + ), + chatvibeNickNameText( + maxWidth: 135.w, + userInfo.userProfile?.getID() ?? "", + textColor: Colors.white, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + type: userInfo.userProfile?.getVIP()?.name ?? "", + needScroll: + (userInfo.userProfile + ?.getID() + .characters + .length ?? + 0) > + 13, + ), + ], + ), + onTap: () { + Clipboard.setData( + ClipboardData(text: userInfo.userProfile?.getID() ?? ""), + ); + ATTts.show(ATAppLocalizations.of(context)!.copiedToClipboard); + }, + ), + ], + ), + Spacer(), + Image.asset( + "atu_images/general/at_icon_jb.png", + width: 20.w, + height: 20.w, + ), + SizedBox(width: 3.w), + text( + "${userInfo.totalFormat ?? 0}", + fontSize: 12.sp, + textColor: Color(0xFFFFB627), + ), + SizedBox(width: 5.w), + ], + ), + ), + onTap: () {}, + ); + } + + @override + empty() { + return mainEmpty( + image: Image.asset( + 'atu_images/general/at_icon_no_data_icon.png', + width: 121.w, + fit: BoxFit.fitWidth, + ), + msg: ATAppLocalizations.of(context)!.noData, + textColor: Colors.white54, + ); + } + + @override + builderDivider() { + // return Divider( + // height: 1.w, + // color: Color(0xff3D3277).withOpacity(0.5), + // indent: 15.w, + // ); + return Container(height: 4.w); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var list = await ChatRoomRepository().roomContributionRank( + roomId, + dataType, + size: 20, + ); + onSuccess(list); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } +} diff --git a/lib/chatvibe_features/room/rank/room_reward_page.dart b/lib/chatvibe_features/room/rank/room_reward_page.dart new file mode 100644 index 0000000..676f4fa --- /dev/null +++ b/lib/chatvibe_features/room/rank/room_reward_page.dart @@ -0,0 +1,358 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_reward_info_res.dart'; +import 'package:aslan/chatvibe_features/room/rank/room_reward_rule_page.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/redpack/room_redenvelope_config_page.dart'; +import 'package:aslan/chatvibe_ui/widgets/room_reward_countdown_timer.dart'; + +class RoomRewardPage extends StatefulWidget { + @override + _RoomRewardPageState createState() => _RoomRewardPageState(); +} + +class _RoomRewardPageState extends State { + String roomId = ""; + ATRoomRewardInfoRes? res; + + @override + void initState() { + super.initState(); + roomId = + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.id ?? + ""; + loadRoomRewardInfo(); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Container( + height: + Provider.of(context, listen: false).isFz() && + (res?.lastWeekProgress?.amount ?? 0) > 0 + ? ScreenUtil().screenHeight * 0.85 + : ScreenUtil().screenHeight * 0.65, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/room/at_icon_room_reward_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + SizedBox(height: 40.w), + Image.asset( + "atu_images/room/at_icon_room_reward_title.png", + height: 100.w, + ), + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + Row( + children: [ + Spacer(), + GestureDetector( + child: Image.asset( + "atu_images/room/at_icon_room_reward_help.png", + width: 20.w, + ), + onTap: () { + SmartDialog.show( + tag: "showRoomRewardRulePage", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return RoomRewardRulePage(); + }, + ); + }, + ), + SizedBox(width: 25.w), + ], + ), + SizedBox(height: 5.w), + Directionality( + textDirection: TextDirection.ltr, + child: RoomRewardCountdownTimer( + key: UniqueKey(), + targetDate: DateTime.fromMillisecondsSinceEpoch( + res?.countdown ?? 0, + ), + ), + ), + SizedBox(height: 5.w), + Container( + height: 210.w, + width: ScreenUtil().screenWidth, + margin: EdgeInsets.symmetric(horizontal: 15.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_reward_content_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 52.w), + text( + ATAppLocalizations.of(context)!.currentProgress, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + SizedBox(height: 35.w), + Row( + children: [ + SizedBox(width: 25.w), + text( + ATAppLocalizations.of(context)!.currentStage( + "${res?.currentProgress?.amount ?? 0}", + ), + fontSize: 13.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + + SizedBox(height: 15.w), + Row( + children: [ + SizedBox(width: 25.w), + text( + ATAppLocalizations.of(context)!.roomReward2( + "Lv.${res?.currentProgress?.level ?? 0}", + ), + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + ], + ), + ), + SizedBox(height: 5.w), + Provider.of(context, listen: false).isFz() && + (res?.lastWeekProgress?.amount ?? 0) > 0 + ? Container( + height: 210.w, + width: ScreenUtil().screenWidth, + margin: EdgeInsets.symmetric(horizontal: 15.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_reward_content_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 52.w), + text( + ATAppLocalizations.of(context)!.lastWeekProgress, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + SizedBox(height: 22.w), + Row( + children: [ + SizedBox(width: 25.w), + text( + ATAppLocalizations.of(context)!.roomReward2( + "${res?.lastWeekProgress?.amount ?? 0}(Lv.${res?.lastWeekProgress?.level ?? 0})", + ), + fontSize: 13.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + SizedBox(height: 3.w), + Row( + children: [ + SizedBox(width: 25.w), + text( + ATAppLocalizations.of( + context, + )!.ownerIncomeCoins( + "${res?.lastWeekProgress?.ownerIncome ?? 0}", + ), + fontSize: 13.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + Spacer(), + GestureDetector( + child: Container( + width: 90.w, + height: 30.w, + alignment: Alignment.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + (res + ?.lastWeekProgress + ?.ownerIncomeReceived ?? + false) + ? "atu_images/room/at_icon_room_reward_btn_no.png" + : "atu_images/room/at_icon_room_reward_btn_en.png", + ), + ), + ), + child: text( + (res + ?.lastWeekProgress + ?.ownerIncomeReceived ?? + false) + ? ATAppLocalizations.of( + context, + )!.received + : ATAppLocalizations.of( + context, + )!.receive, + fontSize: 10.sp, + ), + ), + onTap: () { + roomRewardReceive(); + }, + ), + SizedBox(width: 25.w), + ], + ), + (res?.lastWeekProgress?.rewardCoins ?? 0) > 0 + ? Transform.translate( + offset: Offset(0, 0), + child: Row( + children: [ + SizedBox(width: 25.w), + text( + ATAppLocalizations.of( + context, + )!.rewardCoins( + "${res?.lastWeekProgress?.rewardCoins ?? 0}", + ), + fontSize: 13.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + Spacer(), + GestureDetector( + child: Container( + width: 90.w, + height: 30.w, + alignment: Alignment.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + (res + ?.lastWeekProgress + ?.rewardCoinsSent ?? + false) + ? "atu_images/room/at_icon_room_reward_btn_no.png" + : "atu_images/room/at_icon_room_reward_btn_en.png", + ), + ), + ), + child: text( + (res + ?.lastWeekProgress + ?.rewardCoinsSent ?? + false) + ? ATAppLocalizations.of( + context, + )!.sent + : ATAppLocalizations.of( + context, + )!.send, + fontSize: 10.sp, + ), + ), + onTap: () { + if ((res + ?.lastWeekProgress + ?.rewardCoinsSent ?? + false)) { + return; + } + SmartDialog.show( + tag: "showRedEnvelopeConfig", + alignment: Alignment.center, + animationType: + SmartAnimationType.fade, + builder: (_) { + return RoomRedenvelopeConfigPage( + 2, + rewardCoins: + res + ?.lastWeekProgress + ?.rewardCoins ?? + 0, + ); + }, + onDismiss: (){ + loadRoomRewardInfo(); + } + ); + }, + ), + SizedBox(width: 25.w), + ], + ), + ) + : Container(), + ], + ), + ) + : Container(), + SizedBox(height: 10.w), + ], + ), + ), + ), + ], + ), + ), + ); + } + + void roomRewardReceive() async { + try { + if ((res?.lastWeekProgress?.ownerIncomeReceived ?? false)) { + return; + } + await ChatRoomRepository().roomRewardReceive(roomId); + loadRoomRewardInfo(); + } catch (e) {} + } + + void loadRoomRewardInfo() { + ChatRoomRepository() + .roomRewardInfo(roomId) + .then((result) { + res = result; + setState(() {}); + }) + .catchError((e) {}); + } +} diff --git a/lib/chatvibe_features/room/rank/room_reward_rule_page.dart b/lib/chatvibe_features/room/rank/room_reward_rule_page.dart new file mode 100644 index 0000000..22b5c8c --- /dev/null +++ b/lib/chatvibe_features/room/rank/room_reward_rule_page.dart @@ -0,0 +1,80 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; + +import 'package:aslan/chatvibe_ui/widgets/room_reward_countdown_timer.dart'; + +class RoomRewardRulePage extends StatefulWidget { + @override + _RoomRewardRulePageState createState() => _RoomRewardRulePageState(); +} + +class _RoomRewardRulePageState extends State { + @override + Widget build(BuildContext context) { + return SafeArea( + child: Container( + height: ScreenUtil().screenHeight * 0.85, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/room/at_icon_room_reward_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + SizedBox(height: 38.w), + Stack( + alignment: Alignment.center, + children: [ + Image.asset( + ATGlobalConfig.lang == "ar" + ? "atu_images/room/at_icon_room_reward_rule_title_ar.png" + : "atu_images/room/at_icon_room_reward_rule_title_en.png", + width: 190.w, + ), + Row( + children: [ + SizedBox(width: 20.w), + Transform.flip( + flipX: ATGlobalConfig.lang == "ar" ? true : false, // 水平翻转 + flipY: false, // 垂直翻转设为 false + child: GestureDetector( + child: Image.asset( + "atu_images/room/at_icon_roomgift_rule_back_bg.png", + width: 20.w, + ), + onTap: () { + SmartDialog.dismiss(tag: "showRoomRewardRulePage"); + }, + ), + ), + ], + ), + ], + ), + Expanded( + child: SingleChildScrollView( + child: Container( + margin: EdgeInsets.symmetric( + horizontal: 15.w, + ).copyWith(top: 15.w), + child: Image.asset( + ATGlobalConfig.lang == "ar" + ? "atu_images/room/at_icon_room_reward_rule_content_ar.png" + : "atu_images/room/at_icon_room_reward_rule_content_en.png", + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/chatvibe_features/room/seat/seat_item.dart b/lib/chatvibe_features/room/seat/seat_item.dart new file mode 100644 index 0000000..1e6a038 --- /dev/null +++ b/lib/chatvibe_features/room/seat/seat_item.dart @@ -0,0 +1,549 @@ +import 'dart:ui'; +import 'package:aslan/chatvibe_data/models/enum/at_vip_type.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/constants/at_room_msg_type.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/utilities/at_path_utils.dart'; +import 'package:aslan/chatvibe_domain/models/res/join_room_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/mic_res.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; + + +///麦位 +class SeatItem extends StatefulWidget { + final num index; + final bool isGameModel; + + const SeatItem({Key? key, required this.index, this.isGameModel = false}) + : super(key: key); + + @override + _SeatItemState createState() => _SeatItemState(); +} + +class _SeatItemState extends State with TickerProviderStateMixin { + RtcProvider? provider; + JoinRoomRes? room; + MicRes? roomSeat; + final GlobalKey _targetKey = GlobalKey(); + + @override + void initState() { + super.initState(); + provider = Provider.of(context, listen: false); + provider?.bindTargetKey(widget.index, _targetKey); + } + + @override + Widget build(BuildContext context) { + roomSeat = provider?.roomWheatMap[widget.index]; + room = provider?.currenRoom; + return GestureDetector( + behavior: HitTestBehavior.opaque, + child: Column( + children: [ + SizedBox( + key: _targetKey, + width: widget.isGameModel ? 40.w : 55.w, + height: widget.isGameModel ? 40.w : 55.w, + child: Stack( + alignment: Alignment.center, + children: [ + Sonic( + index: widget.index, + room: room, + isGameModel: widget.isGameModel, + ), + roomSeat?.user != null + ? head( + url: roomSeat?.user?.userAvatar ?? "", + width: widget.isGameModel ? 40.w : 55.w, + headdress: + ATPathUtils.fetchFileExtensionFunction( + roomSeat?.user + ?.getHeaddress() + ?.sourceUrl ?? + "", + ).toLowerCase() == + ".mp4" && + window.locale.languageCode == "ar" + ? "" + : roomSeat?.user?.getHeaddress()?.sourceUrl, + ) + : (roomSeat!.micLock! + ? Image.asset( + room?.roomProfile?.userProfile?.getVIP()?.name == + ATVIPType.DUKE.name + ? "atu_images/room/at_icon_seat_lock_vip4.png" + : (room?.roomProfile?.userProfile + ?.getVIP() + ?.name == + ATVIPType.KING.name + ? "atu_images/room/at_icon_seat_lock_vip5.png" + : (room?.roomProfile?.userProfile + ?.getVIP() + ?.name == + ATVIPType.EMPEROR.name + ? "atu_images/room/at_icon_seat_lock_vip6.png" + : (widget.isGameModel + ? "atu_images/room/at_icon_room_game_mic_close.png" + : "atu_images/room/at_icon_seat_lock.png"))), + width: widget.isGameModel ? 38.w : 52.w, + height: widget.isGameModel ? 38.w : 52.w, + ) + : Image.asset( + room?.roomProfile?.userProfile?.getVIP()?.name == + ATVIPType.DUKE.name + ? "atu_images/room/at_icon_room_vip4_seat.png" + : (room?.roomProfile?.userProfile + ?.getVIP() + ?.name == + ATVIPType.KING.name + ? "atu_images/room/at_icon_room_vip5_seat.png" + : (room?.roomProfile?.userProfile + ?.getVIP() + ?.name == + ATVIPType.EMPEROR.name + ? "atu_images/room/at_icon_room_vip6_seat.png" + : (widget.isGameModel + ? "atu_images/room/at_icon_room_game_mic_open.png" + : "atu_images/room/at_icon_seat_open.png"))), + width: widget.isGameModel ? 38.w : 52.w, + height: widget.isGameModel ? 38.w : 52.w, + )), + Positioned( + bottom: widget.isGameModel ? 2.w : 5.w, + right: widget.isGameModel ? 2.w : 5.w, + child: + roomSeat!.micMute! + ? Image.asset( + "atu_images/room/at_icon_room_seat_mic_mute.png", + width: 14.w, + height: 14.w, + ) + : Container(), + ), + IgnorePointer( + child: Emoticons( + index: widget.index, + isGameModel: widget.isGameModel, + ), + ), + ], + ), + ), + widget.isGameModel + ? Container() + : (roomSeat?.user != null + ? Container( + width: 64.w, + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // 角色标签 + msgRoleTag( + roomSeat?.user?.roles ?? "", + width: 15.w, + height: 15.w, + ), + // 用户名文本 + Flexible( + child: chatvibeNickNameText( + fontWeight: FontWeight.w600, + roomSeat?.user?.userNickname ?? "", + fontSize: 10.sp, + type: roomSeat?.user?.getVIP()?.name ?? "", + needScroll: + (roomSeat + ?.user + ?.userNickname + ?.characters + .length ?? + 0) > + 8, + ), + ), + ], + ), + ) + : Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: 16.w), + text( + "NO.${widget.index}", + fontSize: 10.sp, + fontWeight: FontWeight.w600, + ), + ], + )), + widget.isGameModel + ? Container() + : (room?.roomProfile?.roomSetting?.showHeartbeat == true + ? Container( + padding: EdgeInsets.symmetric( + vertical: 1.w, + horizontal: 5.w, + ), + margin: EdgeInsets.symmetric(horizontal: 5.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + "atu_images/room/at_icon_gift_heartbeat.png", + width: 13.w, + ), + SizedBox(width: 3.w), + text( + _heartbeatVaFormat(), + fontWeight: FontWeight.w600, + fontSize: 10.sp, + ), + ], + ), + ) + : Container()), + ], + ), + onTap: () { + Provider.of( + context, + listen: false, + ).clickSite(widget.index); + }, + ); + } + + String _heartbeatVaFormat() { + int value = (roomSeat?.user?.heartbeatVal ?? 0).toInt(); + if (value > 99999) { + return "${(value / 1000).toStringAsFixed(0)}k"; + } + return "$value"; + } +} + +class Emoticons extends StatefulWidget { + final num index; + final bool isGameModel; + + const Emoticons({Key? key, required this.index, this.isGameModel = false}) + : super(key: key); + + @override + _EmoticonsState createState() => _EmoticonsState(); +} + +class _EmoticonsState extends State with TickerProviderStateMixin { + late AnimationController emoticonsAniCtr; + late CurvedAnimation curvedAnimation; + + bool showIn = false; + MicRes? playingRes; + + List pathList = []; + String? giftPath; + List giftList = []; + bool showResult = false; + + @override + void initState() { + // TODO: implement initState + emoticonsAniCtr = AnimationController( + vsync: this, + duration: Duration(milliseconds: 350), + ); + curvedAnimation = CurvedAnimation( + curve: Curves.ease, + parent: emoticonsAniCtr, + ); + emoticonsAniCtr.addStatusListener((status) { + if (status == AnimationStatus.dismissed) { + _checkStart(); + } + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (BuildContext context, RtcProvider provider, Widget? child) { + MicRes? micRes = provider.roomWheatMap[widget.index]; + if (micRes?.user == null) { + return Container(); + } + String? emojiPath = provider.roomWheatMap[widget.index]?.emojiPath; + if (emojiPath != null) { + pathList.add(micRes); + micRes?.setEmojiPath = null; + _checkStart(); + } + if (playingRes != null) { + return FadeTransition( + opacity: curvedAnimation, + child: + playingRes?.type == ATRoomMsgType.roomDice + ? FutureBuilder( + future: Future.delayed(Duration(milliseconds: 2000)), + // 传入Future + builder: ( + BuildContext context, + AsyncSnapshot snapshot, + ) { + // 根据snapshot的状态来构建UI + if (snapshot.connectionState == ConnectionState.done) { + return Image.asset( + "atu_images/room/at_icon_dice_${micRes?.number}.png", + height: widget.isGameModel ? 38.w : 45.w, + ); + } else { + return Image.asset( + "atu_images/room/at_icon_dice_animl.webp", + height: widget.isGameModel ? 38.w : 45.w, + ); + } + }, + ) + : (playingRes?.type == ATRoomMsgType.roomRPS + ? FutureBuilder( + future: Future.delayed(Duration(milliseconds: 2000)), + // 传入Future + builder: ( + BuildContext context, + AsyncSnapshot snapshot, + ) { + // 根据snapshot的状态来构建UI + if (snapshot.connectionState == + ConnectionState.done) { + return Image.asset( + "atu_images/room/at_icon_rps_${micRes?.number}.png", + height: widget.isGameModel ? 38.w : 45.w, + ); + } else { + return Image.asset( + "atu_images/room/at_icon_rps_animal.webp", + height: widget.isGameModel ? 38.w : 45.w, + ); + } + }, + ) + : netImage( + url: playingRes?.number ?? "", + width: widget.isGameModel ? 65.w : 75.w, + )), + ); + } else if (giftPath != null) { + return FadeTransition( + opacity: curvedAnimation, + child: netImage( + url: "$giftPath", + width: widget.isGameModel ? 65.w : 75.w, + borderRadius: BorderRadius.circular(4.w), + ), + ); + } + return Container(); + }, + ); + } + + void _checkStart() { + print('emoticonsAniCtr.status:${emoticonsAniCtr.status}'); + print('emoticonsAniCtr.path:${playingRes}'); + if (!showIn) { + if (pathList.isNotEmpty) { + playingRes = pathList.first; + pathList.removeAt(0); + print('播放表情:$playingRes'); + emoticonsAniCtr.forward(); + Future.delayed(Duration(milliseconds: 5)).then((value) { + setState(() { + showIn = true; + }); + }); + + /// 延迟3秒移除表情包 + Future.delayed(Duration(seconds: 3)).then((value) { + emoticonsAniCtr.reverse(); + showIn = false; + playingRes = null; + _checkStart(); + }); + } else if (giftList.isNotEmpty) { + giftPath = giftList.first; + giftList.removeAt(0); + print('播放礼物:$playingRes'); + emoticonsAniCtr.forward(); + Future.delayed(Duration(milliseconds: 10)).then((value) { + setState(() { + showIn = true; + }); + }); + + /// 延迟3秒移除表情包 + Future.delayed(Duration(seconds: 1)).then((value) { + emoticonsAniCtr.reverse(); + showIn = false; + giftPath = null; + _checkStart(); + }); + } + } + } +} + +class Sonic extends StatefulWidget { + final num index; + final bool isGameModel; + final JoinRoomRes? room; + + const Sonic({ + Key? key, + required this.index, + required this.room, + this.isGameModel = false, + }) : super(key: key); + + @override + _SonicState createState() => _SonicState(); +} + +class _SonicState extends State with TickerProviderStateMixin { + List ctrList = []; + int lastIndex = 0; + late RtcProvider provider; + + @override + void initState() { + // TODO: implement initState + super.initState(); + for (int i = 0; i < 4; i++) { + ctrList.add( + AnimationController(duration: Duration(milliseconds: 900), vsync: this), + ); + } + for (var element in ctrList) { + element.addStatusListener((status) { + if (status == AnimationStatus.completed) { + element.reset(); + } + }); + } + provider = Provider.of(context, listen: false); + + provider.addSoundVoiceChangeListener(_onVoiceChange); + } + + @override + void dispose() { + // TODO: implement dispose + provider.removeSoundVoiceChangeListener(_onVoiceChange); + for (var element in ctrList) { + element.dispose(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Stack( + clipBehavior: Clip.none, + children: [ + SonicItem(ctrList[0], widget.room, isGameModel: widget.isGameModel), + SonicItem(ctrList[1], widget.room, isGameModel: widget.isGameModel), + SonicItem(ctrList[2], widget.room, isGameModel: widget.isGameModel), + SonicItem(ctrList[3], widget.room, isGameModel: widget.isGameModel), + ], + ); + } + + void _checkSoundAni(int volume) async { + if (volume <= 20) return; + // await Future.delayed(Duration(milliseconds: 10)); + // var dateTime = DateTime.now(); + // for(int i =0 ; i < 5000000;i++){ + // lastIndex = 1; + // } + // print('执行耗时:${DateTime.now().millisecondsSinceEpoch - dateTime.millisecondsSinceEpoch}'); + ctrList.forEach((element) { + if (element.value == 0) { + if (lastIndex == 0) { + element.forward(); + lastIndex = ctrList.indexOf(element); + return; + } else { + if (ctrList[lastIndex].value >= 0.35 || + ctrList[lastIndex].status == AnimationStatus.dismissed) { + element.forward(); + lastIndex = ctrList.indexOf(element); + return; + } + } + } + }); + } + + _onVoiceChange(num index, int volum) { + print('说话声音:$volum'); + if (widget.index == index) { + _checkSoundAni(volum); + } + } +} + +class SonicItem extends AnimatedWidget { + Animation animation; + JoinRoomRes? room; + bool isGameModel = false; + + SonicItem(this.animation, this.room, {Key? key, this.isGameModel = false}) + : super(listenable: animation); + + @override + Widget build(BuildContext context) { + return Visibility( + visible: animation!.value > 0, + child: ScaleTransition( + scale: Tween(begin: 1.0, end: 1.4).animate(animation!), + child: Container( + height: width(isGameModel ? 34 : 52), + width: width(isGameModel ? 34 : 52), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: getSonicColor().withOpacity(1 - animation!.value), + ), + child: Container(), + ), + ), + ); + } + + Color getSonicColor() { + if (room?.roomProfile?.userProfile?.getVIP()?.name == + ATVIPType.VISCOUNT.name) { + return Color(0xFFFADD5E); + } else if (room?.roomProfile?.userProfile?.getVIP()?.name == + ATVIPType.EARL.name) { + return Color(0xFF5E9AFA); + } else if (room?.roomProfile?.userProfile?.getVIP()?.name == + ATVIPType.MARQUIS.name) { + return Color(0xFF1CC483); + } else if (room?.roomProfile?.userProfile?.getVIP()?.name == + ATVIPType.DUKE.name) { + return Color(0xFFAF5EFA); + } else if (room?.roomProfile?.userProfile?.getVIP()?.name == + ATVIPType.KING.name) { + return Color(0xFFFF4144); + } + return Color(0xFFFFFFFF); + } +} diff --git a/lib/chatvibe_features/room/them/custom/room_theme_custom_page.dart b/lib/chatvibe_features/room/them/custom/room_theme_custom_page.dart new file mode 100644 index 0000000..3bfa572 --- /dev/null +++ b/lib/chatvibe_features/room/them/custom/room_theme_custom_page.dart @@ -0,0 +1,110 @@ +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; + +import 'package:aslan/chatvibe_core/utilities/at_pick_utils.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/store_repository_imp.dart'; + +class RoomThemeCustomPage extends StatefulWidget { + @override + _RoomThemeCustomPageState createState() => _RoomThemeCustomPageState(); +} + +class _RoomThemeCustomPageState extends State { + String picPath = ""; + + bool uploadSucc = false; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: 35.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + child: + picPath.isNotEmpty + ? netImage( + url: picPath, + height: 300.w, + width: 200.w, + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ) + : Image.asset( + "atu_images/general/at_icon_add_pic.png", + height: 120.w, + ), + onTap: () { + ATPickUtils.pickImageProcess( + context, + aspectRatio: 0.56, + backOriginalFile: false, + (bool success, String url) { + if (url.isNotEmpty) { + setState(() { + picPath = url; + uploadSucc = true; + }); + } + }, + ); + }, + ), + ], + ), + SizedBox(height: 10.w), + text( + ATAppLocalizations.of(context)!.themeGoToUploadTips, + maxLines: 6, + fontSize: 14.sp, + textColor: Colors.black45, + ), + SizedBox(height: 30.w), + ATDebounceWidget( + child: Container( + height: 46.w, + width: 250.w, + alignment: Alignment.center, + decoration: BoxDecoration( + color: ChatVibeTheme.primaryLight, + borderRadius: BorderRadius.all(Radius.circular(30.w)), + ), + child: text( + uploadSucc + ? "10000${ATAppLocalizations.of(context)!.coins}/30${ATAppLocalizations.of(context)!.day}" + : ATAppLocalizations.of(context)!.goToUpload, + textColor: Colors.white, + fontSize: 16.sp, + fontWeight: FontWeight.bold, + ), + ), + onTap: () { + if (picPath.isNotEmpty) { + StoreRepositoryImp().roomThemeCustomize(picPath).then(( + value, + ) { + picPath = ""; + setState(() {}); + ATTts.show( + ATAppLocalizations.of(context)!.operationSuccessful, + ); + }); + } + }, + ), + ], + ), + ); + } +} diff --git a/lib/chatvibe_features/room/them/room_theme_page.dart b/lib/chatvibe_features/room/them/room_theme_page.dart new file mode 100644 index 0000000..5488330 --- /dev/null +++ b/lib/chatvibe_features/room/them/room_theme_page.dart @@ -0,0 +1,130 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_features/room/them/custom/room_theme_custom_page.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_features/store/store_route.dart'; +import 'package:aslan/chatvibe_features/user/my_items/theme/bags_theme_page.dart'; + +///房间主题 +class RoomThemePage extends StatefulWidget { + @override + _RoomThemePageState createState() => _RoomThemePageState(); +} + +class _RoomThemePageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + final List _pages = []; + final List _tabs = []; + Timer? _timer; + String themePath = ""; + + @override + void initState() { + super.initState(); + _pages.add( + BagsThemePage((playUrl) { + themePath = playUrl; + setState(() {}); + _timer ??= Timer.periodic(Duration(seconds: 5), (timer) { + themePath = ""; + setState(() {}); + }); + }), + ); + _pages.add(RoomThemeCustomPage()); + _tabController = TabController(length: _pages.length, vsync: this); + _tabController.addListener(() { + _timer?.cancel(); + setState(() { + themePath = ""; + }); + }); // 监听切换 + } + + @override + void dispose() { + super.dispose(); + _timer?.cancel(); + } + + @override + Widget build(BuildContext context) { + _tabs.clear(); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.all)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.custom)); + return Stack( + children: [ + themePath.isNotEmpty + ? netImage( + url: themePath, + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + ) + : Image.asset( + "atu_images/room/at_icon_room_settig_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + appBar: ChatVibeStandardAppBar( + backButtonColor: Colors.black, + title: ATAppLocalizations.of(context)!.theme, + actions: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.all(5.w), + margin: EdgeInsetsDirectional.only(end: 10.w), + child: Image.asset( + "atu_images/store/at_icon_bag_shop.png", + width: 20.w, + height: 20.w, + ), + ), + onTap: () { + ATNavigatorUtils.push(context, StoreRoute.list, replace: true); + }, + ), + ], + ), + body:SafeArea( + top: false, + child: Column( + children: [ + TabBar( + tabAlignment: TabAlignment.center, + labelPadding: EdgeInsets.symmetric(horizontal: 18.w), + labelColor: Colors.black87, + isScrollable: true, + indicator: BoxDecoration(), + unselectedLabelColor: Colors.black38, + labelStyle: TextStyle(fontSize: 15.sp, fontWeight: FontWeight.w600), + unselectedLabelStyle: TextStyle( + fontSize: 13.sp, + fontWeight: FontWeight.w500, + ), + indicatorColor: Colors.transparent, + dividerColor: Colors.transparent, + controller: _tabController, + tabs: _tabs, + ), + Expanded( + child: TabBarView(controller: _tabController, children: _pages), + ), + ], + ),), + ), + ], + ); + } +} diff --git a/lib/chatvibe_features/room/voice_room_route.dart b/lib/chatvibe_features/room/voice_room_route.dart new file mode 100644 index 0000000..dd9de56 --- /dev/null +++ b/lib/chatvibe_features/room/voice_room_route.dart @@ -0,0 +1,21 @@ +import 'package:fluro/fluro.dart'; +import 'package:aslan/chatvibe_features/room/edit/room_edit_page.dart'; +import 'package:aslan/chatvibe_features/room/them/room_theme_page.dart'; +import 'package:aslan/chatvibe_core/routes/at_router_init.dart'; + +import 'at_voice_chat_room_page.dart'; +class VoiceRoomRoute implements ATIRouterProvider { + static String voiceRoom = '/at/room'; + static String roomEdit = '/at/room/roomEdit'; + static String roomTheme = '/at/room/roomTheme'; + + @override + void initRouter(FluroRouter router) { + router.define(voiceRoom, + handler: Handler(handlerFunc: (_, params) => ATVoiceChatRoomPage())); + router.define(roomTheme, + handler: Handler(handlerFunc: (_, params) => RoomThemePage())); + router.define(roomEdit, + handler: Handler(handlerFunc: (_, params) => RoomEditPage(needRestCurrentRoomInfo: params['need']!.first))); + } +} diff --git a/lib/chatvibe_features/search/at_search_page.dart b/lib/chatvibe_features/search/at_search_page.dart new file mode 100644 index 0000000..50d809f --- /dev/null +++ b/lib/chatvibe_features/search/at_search_page.dart @@ -0,0 +1,710 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_res.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/chatvibe_gradient_button.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_domain/usecases/at_fixed_width_tabIndicator.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; + +import '../../chatvibe_ui/components/at_compontent.dart'; +import '../../chatvibe_ui/components/at_debounce_widget.dart'; +import '../../chatvibe_ui/components/at_page_list.dart'; +import '../../chatvibe_ui/components/text/at_text.dart'; + +///搜索房间 +class SearchPage extends ATPageList { + @override + _SearchPageState createState() => _SearchPageState(); +} + +class _SearchPageState extends ATPageListState + with SingleTickerProviderStateMixin { + String search = ''; + List historys = []; + + late TabController _tabController; + + bool enablePullUp = false; + bool enablePullDown = false; + + late TextEditingController _textEditingController; + + @override + void dispose() { + super.dispose(); + } + + @override + void initState() { + super.initState(); + _loadHistory(); + _tabController = TabController(vsync: this, length: 2); + _textEditingController = TextEditingController(); + _textEditingController.addListener(() { + if (_textEditingController.text.isEmpty) { + search = ""; + _loadHistory(); + } + }); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Scaffold( + backgroundColor: + ATGlobalConfig.businessLogicStrategy + .getSearchPageScaffoldBackgroundColor(), + body: Column( + children: [ + Container( + height: kToolbarHeight + ScreenUtil().statusBarHeight, + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), + alignment: AlignmentDirectional.centerStart, + child: Row( + children: [ + SizedBox(width: width(10)), + ATDebounceWidget( + child: Icon( + Icons.chevron_left, + color: + ATGlobalConfig.businessLogicStrategy + .getSearchPageBackIconColor(), + size: 25.w, + ), + onTap: () { + ATNavigatorUtils.goBack(context); + }, + ), + Expanded( + child: searchWidget( + padding: EdgeInsets.symmetric(horizontal: 2.w), + hint: + ATAppLocalizations.of(context)!.pleaseEnterContent, + controller: _textEditingController, + borderColor: + ATGlobalConfig.businessLogicStrategy + .getSearchPageInputBorderColor(), + textColor: + ATGlobalConfig.businessLogicStrategy + .getSearchPageInputTextColor(), + ), + ), + chatvibeGradientButton( + text: ATAppLocalizations.of(context)!.search, + radius: 25, + textSize: 14.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getSearchPageButtonTextColor(), + gradient: LinearGradient( + colors: + ATGlobalConfig.businessLogicStrategy + .getSearchPageButtonGradient(), + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + onPress: () { + _reqSearch(); + }, + ), + SizedBox(width: width(10)), + ], + ), + ), + search.isEmpty || search == "" ? _history() : _result(), + ], + ), + ), + ], + ); + } + + _history() { + if (historys.isEmpty) { + return mainEmpty(msg: ATAppLocalizations.of(context)!.noData); + } else { + return Expanded( + child: Column( + children: [ + SizedBox(height: 20.w), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(width: width(15)), + text( + ATAppLocalizations.of(context)!.history, + fontSize: 16.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getSearchPageHistoryTitleTextColor(), + fontWeight: FontWeight.bold, + ), + Expanded(child: SizedBox(width: width(1))), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + setState(() { + historys.clear(); + _saveHistory(""); + }); + }, + child: Row( + children: [ + Image.asset( + 'atu_images/general/at_icon_delete.png', + width: 17.w, + ), + //SizedBox(width: width(5),), + //Text("清空", style: TextStyle(fontSize: 13.sp, color: Color(0xffaaaaaa)),), + SizedBox(width: width(15)), + ], + ), + ), + ], + ), + SizedBox(height: height(10)), + Expanded( + child: Column( + children: [ + Row( + children: [ + SizedBox(width: 12.w), + Expanded( + child: Wrap( + runSpacing: 10, + spacing: 10, + direction: Axis.horizontal, + children: + historys + .asMap() + .keys + .map((e) => _historyItem(historys[e])) + .toList(), + ), + ), + SizedBox(width: 12.w), + ], + ), + ], + ), + ), + ], + ), + ); + } + } + + _saveHistory(String msg) async { + historys.removeWhere((history) => history == msg || history.isEmpty); + + if (!historys.any((history) => history.startsWith(msg))) { + if (msg.isNotEmpty) { + historys.insert(0, msg); + } + } + + setState(() {}); + DataPersistence.setSearchHistroy( + AccountStorage().getCurrentUser()?.userProfile?.account ?? "", + jsonEncode(historys), + ); + } + + _loadHistory() async { + String json = DataPersistence.getSearchHistroy( + AccountStorage().getCurrentUser()?.userProfile?.account ?? "", + ); + historys = List.from(jsonDecode(json)); + setState(() {}); + } + + Widget _historyItem(String history) { + return GestureDetector( + onTap: () { + _textEditingController.text = history; + _reqSearch(); + }, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 18.w, vertical: 6.w), + decoration: BoxDecoration( + color: + ATGlobalConfig.businessLogicStrategy + .getSearchPageHistoryItemBackgroundColor(), + borderRadius: BorderRadius.circular(52.w), + ), + child: Text( + history, + style: TextStyle( + fontSize: sp(13), + color: + ATGlobalConfig.businessLogicStrategy + .getSearchPageHistoryItemTextColor(), + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + ), + ), + ), + ); + } + + _result() { + return Expanded( + child: Column( + children: [ + Theme( + data: ThemeData( + brightness: Brightness.light, + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + ), + child: Container( + padding: EdgeInsets.symmetric(vertical: 1.w), + decoration: BoxDecoration( + // color: Colors.white, + //border: Border(bottom: BorderSide(color: Color(0xffEEEEEE),width: 0.5.w)) + ), + child: TabBar( + tabs: + [ + ATAppLocalizations.of(context)!.rooms, + ATAppLocalizations.of(context)!.users, + ].map((e) => Tab(text: e)).toList(), + indicator: ATFixedWidthTabIndicator( + width: 15.w, + gradient: LinearGradient( + colors: [ + ATGlobalConfig.businessLogicStrategy + .getSearchPageTabIndicatorGradientStartColor(), + ATGlobalConfig.businessLogicStrategy + .getSearchPageTabIndicatorGradientEndColor(), + ], + ), + ), + indicatorSize: TabBarIndicatorSize.label, + // 控制指示器宽度 + labelPadding: EdgeInsetsDirectional.only(start: 15, end: 15), + isScrollable: false, + controller: _tabController, + labelColor: + ATGlobalConfig.businessLogicStrategy + .getSearchPageTabSelectedLabelColor(), + dividerColor: + ATGlobalConfig.businessLogicStrategy + .getSearchPageTabDividerColor(), + unselectedLabelColor: + ATGlobalConfig.businessLogicStrategy + .getSearchPageTabUnselectedLabelColor(), + unselectedLabelStyle: + ATGlobalConfig.businessLogicStrategy + .getSearchPageTabUnselectedLabelStyle(), + labelStyle: + ATGlobalConfig.businessLogicStrategy + .getSearchPageTabSelectedLabelStyle(), + ), + ), + ), + Container( + height: 8.w, + color: + ATGlobalConfig.businessLogicStrategy + .getSearchPageDividerContainerBackgroundColor(), + ), + Expanded( + child: TabBarView( + controller: _tabController, + children: [ + SearchRoomList(search, (String text) { + _saveHistory(text); + }), + SearchUserList(search, (String text) { + _saveHistory(text); + }), + ], + ), + ), + ], + ), + ); + } + + void _reqSearch() { + search = _textEditingController.text; + setState(() {}); + if (search.trim().isNotEmpty) { + _saveHistory(search); + } else { + items.clear(); + } + } +} + +class SearchRoomList extends StatefulWidget { + final String text; + Function(String text) saveHistroy; + + SearchRoomList(this.text, this.saveHistroy); + + @override + _SearchRoomListState createState() => _SearchRoomListState(saveHistroy); +} + +class _SearchRoomListState extends State { + List? rooms; + Function(String text) saveHistroy; + + _SearchRoomListState(this.saveHistroy); + + @override + void initState() { + loadPage(); + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + void didUpdateWidget(SearchRoomList oldWidget) { + var oldText = oldWidget.text; + var text = widget.text; + if (oldText != text) { + rooms?.clear(); + loadPage(); + } + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + if (rooms == null) { + return Center(child: CupertinoActivityIndicator()); + } else if (rooms!.length == 0) { + return empty(); + } + + return ListView.separated( + itemBuilder: (context, i) => buildItem(rooms![i]), + itemCount: rooms!.length, + padding: EdgeInsets.symmetric(vertical: 10.w), + separatorBuilder: + (context, i) => Divider( + height: height(1), + color: ChatVibeTheme.dividerColor, + indent: width(28), //25+50+10 + endIndent: width(15), + ), + ); + } + + ///构建列表项 + Widget buildItem(ChatVibeRoomRes e) { + //return roomItem(room: e,context: context); + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () async { + if (context != null) { + Provider.of( + context, + listen: false, + ).joinRoom(context, e.id ?? ""); + } + }, + child: Stack( + children: [ + Container( + decoration: BoxDecoration( + border: Border.all(color: Color(0xffF6BE00), width: 0.5), + borderRadius: BorderRadius.circular(10.w), + ), + // color: Colors.white, + padding: EdgeInsets.symmetric(vertical: 12.w, horizontal: 10.w), + margin: EdgeInsets.symmetric(horizontal: 8.w), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + netImage( + url: e.roomCover ?? "", + width: 50.w, + borderRadius: BorderRadius.circular(10.w), + ), + Expanded( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 10.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // richText( + // txt: e.roomName ?? "", + // key: widget.text, + // highlight: ChatVibeTheme.primaryColor, + // defaultColor: Colors.black, + // ), + text( + e.roomName ?? "", + fontSize: 15.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getSearchPageHistoryTitleTextColor(), + ), + text( + "ID:${e.userProfile?.getID()}", + fontSize: 12.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getSearchPageHistoryItemTextColor(), + ), + ], + ), + ), + ), + ], + ), + ), + PositionedDirectional( + end: 18.w, + top: 15.w, + child: Row( + children: [ + (e.extValues?.roomSetting?.password?.isEmpty ?? false) + ? Image.asset( + "atu_images/index/at_icon_room_flot_ani.gif", + width: 14.w, + height: 14.w, + ) + : Container(), + (e.extValues?.roomSetting?.password?.isEmpty ?? false) + ? text( + e.extValues?.memberQuantity ?? "0", + textColor: Colors.black38, + fontSize: 11.sp, + ) + : Container(), + ], + ), + ), + ], + ), + ); + } + + loadPage() async { + widget.saveHistroy(widget.text); + try { + rooms = await ChatRoomRepository().searchRoom(widget.text); + } catch (e) { + } finally { + setState(() {}); + } + } + + empty() { + return mainEmpty(msg: ATAppLocalizations.of(context)!.noData); + } +} + +class SearchUserList extends ATPageList { + final String text; + Function(String text) saveHistroy; + + SearchUserList(this.text, this.saveHistroy); + + @override + _SearchUserListState createState() => _SearchUserListState(saveHistroy); +} + +class _SearchUserListState + extends ATPageListState { + Function(String text) saveHistroy; + + _SearchUserListState(this.saveHistroy); + + @override + void initState() { + // TODO: implement initState + backgroundColor = Colors.transparent; + loadData(1); + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + void didUpdateWidget(ATPageList oldWidget) { + // TODO: implement didUpdateWidget + var oldText = (oldWidget as SearchUserList).text; + var text = (widget as SearchUserList).text; + if (oldText != text) { + items.clear(); + loadData(1); + } + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + return buildList(context); + } + + ///构建列表项 + Widget buildItem(ChatVibeUserProfile data) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == data.id}&tageId=${data.id}", + ); + }, + child: Container( + padding: EdgeInsets.symmetric(vertical: 5.w), + margin: EdgeInsets.symmetric(horizontal: 10.w), + child: Row( + children: [ + head(url: data.userAvatar ?? "", width: 55.w), + SizedBox(width: 2.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + xb( + data.userSex, + color: + data.userSex == 1 + ? Colors.blue + : Colors.purpleAccent, + ), + SizedBox(width: 5.w), + // richText( + // txt: data.userNickname ?? "", + // key: (widget as SearchUserList).text, + // highlight: ChatVibeTheme.primaryColor, + // defaultColor: Colors.black, + // ), + Expanded( + child: chatvibeNickNameText( + textColor: + ATGlobalConfig.businessLogicStrategy + .getSearchPageHistoryTitleTextColor(), + data.userNickname ?? "", + fontSize: 15.sp, + type: data.getVIP()?.name ?? "", + needScroll: + (data.userNickname?.characters.length ?? 0) > 18, + ), + ), + SizedBox(width: 5.w), + // dj(data.level.toString()), + ], + ), + GestureDetector( + child: Row( + children: [ + getIdIcon( + data.wealthLevel ?? 0, + width: 32.w, + height: 32.w, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Color(0xFF333333), + ), + Container( + child: chatvibeNickNameText( + maxWidth: 135.w, + data.getID() ?? "", + textColor: + ATGlobalConfig.businessLogicStrategy + .getSearchPageHistoryTitleTextColor(), + fontSize: 14.sp, + fontWeight: FontWeight.w600, + type: data.getVIP()?.name ?? "", + needScroll: + (data.getID().characters.length ?? 0) > 13, + ), + ), + ], + ), + onTap: () { + Clipboard.setData(ClipboardData(text: data.getID())); + ATTts.show( + ATAppLocalizations.of(context)!.copiedToClipboard, + ); + }, + ), + ], + ), + ), + ], + ), + ), + ); + } + + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + saveHistroy((widget as SearchUserList).text); + items.clear(); + AccountRepository() + .searchUser((widget as SearchUserList).text) + .then((data) { + List users = []; + users.add(data); + onSuccess(users); + }) + .catchError((e) { + if (onErr != null) { + onErr(); + } + }); + } + + @override + empty() { + return mainEmpty( + msg: ATAppLocalizations.of(context)!.noData, + textColor: + ATGlobalConfig.businessLogicStrategy + .getSearchPageHistoryTitleTextColor(), + ); + } + + @override + builderDivider() { + return Divider( + height: height(1), + color: ChatVibeTheme.dividerColor, + indent: width(85), + endIndent: width(15), + ); + } +} diff --git a/lib/chatvibe_features/settings/language/language_page.dart b/lib/chatvibe_features/settings/language/language_page.dart new file mode 100644 index 0000000..33fb0fe --- /dev/null +++ b/lib/chatvibe_features/settings/language/language_page.dart @@ -0,0 +1,246 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; +import 'package:aslan/chatvibe_managers/localization_manager.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; + +///语言选择 +class LanguagePage extends StatefulWidget { + @override + _LanguagePageState createState() => _LanguagePageState(); +} + +class _LanguagePageState extends State { + int selectType = 1; + + @override + void initState() { + super.initState(); + String langCode = DataPersistence.getLang(); + if (langCode == "ar") { + selectType = 1; + }else if(langCode == "tr"){ + selectType = 2; + }else if(langCode == "bn"){ + selectType = 3; + } else { + selectType = 0; + } + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + ATGlobalConfig.businessLogicStrategy.getLanguagePageBackgroundImage(), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: ATGlobalConfig.businessLogicStrategy.getLanguagePageScaffoldBackgroundColor(), + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.language, + actions: [], + ), + body: Column( + children: [ + Row( + children: [ + SizedBox(width: 15.w), + Expanded( + child: text( + "English", + textColor: ATGlobalConfig.businessLogicStrategy.getLanguagePagePrimaryTextColor(), + fontSize: 15.sp, + ), + ), + Checkbox( + value: selectType == 0, + checkColor: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckColor(), + activeColor: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxActiveColor(), + onChanged: (b) { + if (selectType == 0) { + selectType = 1; + } else { + selectType = 0; + } + setState(() {}); + if (b!) { + Provider.of( + context, + listen: false, + ).setLocale(const Locale('en', '')); + } + }, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderRadius()), + ), + side: WidgetStateBorderSide.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return BorderSide( + color: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(), + width: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(), + ); // 选中时保留蓝色边框 + } + return BorderSide( + color: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(), + width: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(), + ); // 未选中时灰色边框 + }), + ), + ], + ), + Row( + children: [ + SizedBox(width: 15.w), + Expanded( + child: text( + "العربية", + textColor: ATGlobalConfig.businessLogicStrategy.getLanguagePagePrimaryTextColor(), + fontSize: 15.sp, + ), + ), + Checkbox( + value: selectType == 1, + checkColor: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckColor(), + activeColor: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxActiveColor(), + onChanged: (b) { + if (selectType == 1) { + selectType = 0; + } else { + selectType = 1; + } + setState(() {}); + if (b!) { + Provider.of( + context, + listen: false, + ).setLocale(const Locale('ar', '')); + } + }, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderRadius()), + ), + side: WidgetStateBorderSide.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return BorderSide( + color: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(), + width: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(), + ); // 选中时保留蓝色边框 + } + return BorderSide( + color: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(), + width: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(), + ); // 未选中时灰色边框 + }), + ), + ], + ), + Row( + children: [ + SizedBox(width: 15.w), + Expanded( + child: text( + "Türkçe", + textColor: ATGlobalConfig.businessLogicStrategy.getLanguagePagePrimaryTextColor(), + fontSize: 15.sp, + ), + ), + Checkbox( + value: selectType == 2, + checkColor: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckColor(), + activeColor: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxActiveColor(), + onChanged: (b) { + if (selectType == 2) { + selectType = 0; + } else { + selectType = 2; + } + setState(() {}); + if (b!) { + Provider.of( + context, + listen: false, + ).setLocale(const Locale('tr', '')); + } + }, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderRadius()), + ), + side: WidgetStateBorderSide.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return BorderSide( + color: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(), + width: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(), + ); // 选中时保留蓝色边框 + } + return BorderSide( + color: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(), + width: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(), + ); // 未选中时灰色边框 + }), + ), + ], + ), Row( + children: [ + SizedBox(width: 15.w), + Expanded( + child: text( + "বাংলা", + textColor: ATGlobalConfig.businessLogicStrategy.getLanguagePagePrimaryTextColor(), + fontSize: 15.sp, + ), + ), + Checkbox( + value: selectType == 3, + checkColor: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckColor(), + activeColor: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxActiveColor(), + onChanged: (b) { + if (selectType == 3) { + selectType = 0; + } else { + selectType = 3; + } + setState(() {}); + if (b!) { + Provider.of( + context, + listen: false, + ).setLocale(const Locale('bn', '')); + } + }, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderRadius()), + ), + side: WidgetStateBorderSide.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return BorderSide( + color: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(), + width: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(), + ); // 选中时保留蓝色边框 + } + return BorderSide( + color: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderColor(), + width: ATGlobalConfig.businessLogicStrategy.getLanguagePageCheckboxBorderWidth(), + ); // 未选中时灰色边框 + }), + ), + ], + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/chatvibe_features/splash/splash_page.dart b/lib/chatvibe_features/splash/splash_page.dart new file mode 100644 index 0000000..6f78872 --- /dev/null +++ b/lib/chatvibe_features/splash/splash_page.dart @@ -0,0 +1,420 @@ +import 'dart:async'; +import 'dart:math'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/utilities/at_version_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:marquee/marquee.dart'; + +import 'package:aslan/chatvibe_core/routes/at_routes.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/local/file_cache_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/config_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_start_page_res.dart'; +import 'package:aslan/chatvibe_features/auth/login_route.dart'; + +class SplashPage extends StatefulWidget { + @override + _SplashPageState createState() => _SplashPageState(); +} + +class _SplashPageState extends State { + Timer? _timer; + int countdown = 6; + int status = 0; + ATStartPageRes? currentStartPage; + + List startPages = []; + + @override + void initState() { + super.initState(); + FileCacheManager.getInstance().getFilePath(); + getStartPage(); + _startTimer(context); + } + + ///加载启动页配置 + void getStartPage() { + ConfigRepositoryImp().getStartPage().then((result) { + startPages = result; + if (startPages.length > 1) { + int index = Random().nextInt(startPages.length); + currentStartPage = result[index]; + } else { + currentStartPage = result.first; + } + + status = 1; + setState(() {}); + }); + } + + @override + void dispose() { + _cancelTimer(); + super.dispose(); + } + + /// 启动倒计时 + void _startTimer(BuildContext context) { + // 计时器(`Timer`)组件的定期(`periodic`)构造函数,创建一个新的重复计时器。 + _timer = Timer.periodic(Duration(seconds: 1), (timer) { + if (countdown == 0) { + _cancelTimer(); + _goMainPage(context); + return; + } + countdown--; + setState(() {}); + }); + } + + @override + Widget build(BuildContext context) { + return Material( + child: Stack( + alignment: Alignment.center, + children: [ + Image.asset( + ATGlobalConfig.businessLogicStrategy.getSplashPageBackgroundImage(), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.cover, + ), + Image.asset( + ATGlobalConfig.businessLogicStrategy.getSplashPageIcon(), + width: 107.w, + height: 159.w, + ), + // status == 0 ? Container() : KingGamesScreenWidget(currentStartPage), + // status == 0 + // ? Container() + // : (currentStartPage?.type == "CP" + // ? CPScreenWidget(currentStartPage) + // : (currentStartPage?.type == "KING_GAMES" + // ? KingGamesScreenWidget(currentStartPage) + // : Container())), + // status == 1 + // ? PositionedDirectional( + // top: 45.w, + // end: 15.w, + // child: ATDebounceWidget( + // debounceTime: Duration(milliseconds: 1000), + // onTap: () { + // _cancelTimer(); + // _goMainPage(context); + // }, + // child: Container( + // alignment: Alignment.center, + // width: 60.w, + // height: 24.w, + // decoration: BoxDecoration( + // image: DecorationImage( + // image: AssetImage( + // ATGlobalConfig.businessLogicStrategy.getSplashPageSkipButtonBackground(), + // ), + // fit: BoxFit.fill, + // ), + // ), + // child: text( + // ATAppLocalizations.of(context)!.skip("$countdown"), + // textColor: ATGlobalConfig.businessLogicStrategy.getSplashPageSkipButtonTextColor(), + // fontSize: 12.sp, + // ), + // ), + // ), + // ) + // : Container(), + ], + ), + ); + } + + void _goMainPage(BuildContext context) async { + try { + await ATVersionUtils.checkReview(); + var user = AccountStorage().getCurrentUser(); + var token = AccountStorage().getToken(); + if (user != null && token.isNotEmpty) { + ATNavigatorUtils.push(context, ATRoutes.home, replace: true); + } else { + ATNavigatorUtils.push(context, LoginRouter.login, replace: true); + } + } catch (e) { + ATNavigatorUtils.push(context, LoginRouter.login, replace: true); + } + } + + /// 取消倒计时的计时器。 + void _cancelTimer() { + // 计时器(`Timer`)组件的取消(`cancel`)方法,取消计时器。 + _timer?.cancel(); + } +} + +class KingGamesScreenWidget extends StatefulWidget { + ATStartPageRes? startPage; + + KingGamesScreenWidget(this.startPage); + + @override + _KingGamesScreenWidgetState createState() => _KingGamesScreenWidgetState(); +} + +class _KingGamesScreenWidgetState extends State { + StartPageUser? user1; + + @override + void initState() { + super.initState(); + user1 = widget.startPage?.user?.first; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + netImage( + url: user1?.avatar ?? "", + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + shape: BoxShape.circle, + height: 105.w, + width: 105.w, + ), + netImage( + url: widget.startPage?.cover ?? "", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + noDefaultImg: true, + fit: BoxFit.cover, + ), + Column( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.max, + children: [ + Container( + alignment: Alignment.center, + padding: EdgeInsetsDirectional.only( + start: 32.w, + end: 32.w, + top: 10.w, + ), + width: 250.w, + height: 58.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + ATGlobalConfig.businessLogicStrategy.getSplashPageKingGamesNameBackground(), + ), + fit: BoxFit.fill, + ), + ), + child: + (user1?.nickName?.length ?? 0) > 10 + ? Marquee( + text: user1?.nickName ?? "", + style: TextStyle( + fontSize: 26.sp, + color: ATGlobalConfig.businessLogicStrategy.getSplashPageKingGamesTextColor(), + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration(milliseconds: 550), + decelerationCurve: Curves.easeOut, + ) + : Text( + user1?.nickName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 26.sp, + color: ATGlobalConfig.businessLogicStrategy.getSplashPageKingGamesTextColor(), + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + SizedBox(height: 150.w), + ], + ), + ], + ), + ), + ); + } +} + +class CPScreenWidget extends StatefulWidget { + ATStartPageRes? startPage; + + CPScreenWidget(this.startPage); + + @override + _CPScreenWidgetState createState() => _CPScreenWidgetState(); +} + +class _CPScreenWidgetState extends State { + StartPageUser? user1; + StartPageUser? user2; + + @override + void initState() { + super.initState(); + user1 = widget.startPage?.user?.first; + user2 = widget.startPage?.user?.last; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + netImage( + url: user1?.avatar ?? "", + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + shape: BoxShape.circle, + height: 125.w, + width: 125.w, + ), + SizedBox(width: 38.w), + netImage( + url: user2?.avatar ?? "", + defaultImg: "atu_images/general/at_icon_avar_defalt.png", + shape: BoxShape.circle, + height: 125.w, + width: 125.w, + ), + ], + ), + netImage( + url: widget.startPage?.cover ?? "", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + noDefaultImg: true, + fit: BoxFit.cover, + ), + Column( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.max, + children: [ + Container( + alignment: Alignment.center, + margin: EdgeInsetsDirectional.symmetric(horizontal: 15.w), + height: 35.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + ATGlobalConfig.businessLogicStrategy.getSplashPageCpNameBackground(), + ), + fit: BoxFit.fill, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 130.w, + alignment: Alignment.center, + child: + (user1?.nickName?.length ?? 0) > 12 + ? Marquee( + text: user1?.nickName ?? "", + style: TextStyle( + fontSize: 22.sp, + color: ATGlobalConfig.businessLogicStrategy.getSplashPageCpTextColor(), + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + user1?.nickName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 22.sp, + color: ATGlobalConfig.businessLogicStrategy.getSplashPageCpTextColor(), + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + SizedBox(width: 30.w), + Container( + alignment: Alignment.center, + width: 130.w, + child: + (user2?.nickName?.length ?? 0) > 12 + ? Marquee( + text: user2?.nickName ?? "", + style: TextStyle( + fontSize: 22.sp, + color: ATGlobalConfig.businessLogicStrategy.getSplashPageCpTextColor(), + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + user2?.nickName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 22.sp, + color: ATGlobalConfig.businessLogicStrategy.getSplashPageCpTextColor(), + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + ), + SizedBox(height: ScreenUtil().screenHeight/2-122.w), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/chatvibe_features/store/chatbox/store_chatbox_page.dart b/lib/chatvibe_features/store/chatbox/store_chatbox_page.dart new file mode 100644 index 0000000..6555c49 --- /dev/null +++ b/lib/chatvibe_features/store/chatbox/store_chatbox_page.dart @@ -0,0 +1,261 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/store_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/store_list_res.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/store/props_store_chatbox_detail_dialog.dart'; +import '../../../chatvibe_data/models/enum/at_currency_type.dart'; +import '../../../chatvibe_data/models/enum/at_props_type.dart'; +import '../../../chatvibe_data/models/enum/at_vip_type.dart'; + +///聊天气泡框 +class StoreChatboxPage extends ATPageList { + @override + _StoreChatboxPageState createState() => _StoreChatboxPageState(); +} + +class _StoreChatboxPageState + extends ATPageListState { + //折扣 + double disCount = 1; + + @override + void initState() { + super.initState(); + enablePullUp = false; + isGridView = true; + gridViewCount = 3; + padding = EdgeInsets.symmetric(horizontal: 6.w); + backgroundColor = Colors.transparent; + gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridViewCount, + mainAxisSpacing: 12.w, + crossAxisSpacing: 12.w, + childAspectRatio: 0.82, + ); + if (AccountStorage().getVIP()?.name == ATVIPType.MARQUIS.name) { + disCount = 0.9; + } else if (AccountStorage().getVIP()?.name == ATVIPType.DUKE.name) { + disCount = 0.85; + } else if (AccountStorage().getVIP()?.name == ATVIPType.KING.name) { + disCount = 0.8; + } else if (AccountStorage().getVIP()?.name == ATVIPType.EMPEROR.name) { + disCount = 0.7; + } + disCount = 1; + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: buildList(context), + ); + } + + @override + Widget buildItem(StoreListResBean res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + color: + res.isSelecte + ? Color(0xffFFF4D9) + : ATGlobalConfig.businessLogicStrategy + .getStoreItemBackgroundColor(), + border: Border.all( + color: + res.isSelecte + ? ATGlobalConfig.businessLogicStrategy + .getStoreItemSelectedBorderColor() + : ATGlobalConfig.businessLogicStrategy + .getStoreItemUnselectedBorderColor(), + width: 1.w, + ), + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + child: Stack( + children: [ + Column( + children: [ + SizedBox(height: 20.w), + netImage( + url: res.res.propsResources?.cover ?? "", + height: 45.w, + fit: BoxFit.contain, + ), + SizedBox(height: 5.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 10.w), + Expanded( + child: Text.rich( + textAlign: TextAlign.center, + TextSpan( + children: [ + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Image.asset( + ATGlobalConfig.businessLogicStrategy + .getStoreItemGoldIcon(), + width: 15.w, + ), + ), + TextSpan(text: " "), + disCount < 1 + ? TextSpan( + text: "${res.res.propsPrices![0].amount}", + style: TextStyle( + fontSize: 12.sp, + color: + ATGlobalConfig.businessLogicStrategy + .getStoreItemPriceTextColor(), + fontWeight: FontWeight.w600, + decoration: TextDecoration.lineThrough, + ), + ) + : TextSpan(), + disCount < 1 ? TextSpan(text: " ") : TextSpan(), + TextSpan( + text: + "${((res.res.propsPrices![0].amount ?? 0) * disCount).toInt()}/${res.res.propsPrices![0].days} ${ATAppLocalizations.of(context)!.day}", + style: TextStyle( + fontSize: 12.sp, + color: + ATGlobalConfig.businessLogicStrategy + .getStoreItemPriceTextColor(), + fontWeight: FontWeight.w600, + ), + ), + ], + ), + strutStyle: StrutStyle( + height: 1.3, // 行高倍数 + fontWeight: FontWeight.w500, + forceStrutHeight: true, // 强制应用行高 + ), + ), + ), + SizedBox(width: 10.w), + ], + ), + ], + ), + Positioned( + top: 5.w, + right: 5.w, + child: GestureDetector( + onTap: () { + _showDetail(res.res); + _selectItem(res); + }, + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.all(5.w), + child: Image.asset( + ATGlobalConfig.businessLogicStrategy.getStoreItemActionIcon('chatbox'), + width: 13.w, + height: 13.w, + ), + ), + ), + ), + ], + ), + ), + onTap: () { + _selectItem(res); + _showDetail(res.res); + }, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + List beans = []; + var storeList = await StoreRepositoryImp().storeList( + ATCurrencyType.GOLD.name, + ATPropsType.CHAT_BUBBLE.name, + ); + for (var value in storeList) { + beans.add(StoreListResBean(value, false)); + } + onSuccess(beans); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + void _showDetail(StoreListRes res) { + SmartDialog.show( + tag: "showPropsDetail", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return PropsStoreChatboxDetailDialog(res,disCount,); + }, + ); + } + + void _selectItem(StoreListResBean res) { + for (var value in items) { + value.isSelecte = false; + } + res.isSelecte = true; + setState(() {}); + } + + void _buy(StoreListResBean res) { + if (res.res.propsPrices!.isEmpty) { + return; + } + ATLoadingManager.exhibitOperation(); + num days = res.res.propsPrices![0].days ?? 0; + StoreRepositoryImp() + .storePurchasing( + res.res.id ?? "", + ATPropsType.CHAT_BUBBLE.name, + ATCurrencyType.GOLD.name, + "$days", + ) + .then((value) { + ATTts.show(ATAppLocalizations.of(context)!.purchaseIsSuccessful); + Provider.of( + context, + listen: false, + ).updateBalance(value); + ATLoadingManager.veilRoutine(); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } +} + +class StoreListResBean { + StoreListRes res; + bool isSelecte = false; + + StoreListResBean(this.res, this.isSelecte); +} diff --git a/lib/chatvibe_features/store/headdress/store_headdress_page.dart b/lib/chatvibe_features/store/headdress/store_headdress_page.dart new file mode 100644 index 0000000..db2b1d8 --- /dev/null +++ b/lib/chatvibe_features/store/headdress/store_headdress_page.dart @@ -0,0 +1,247 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/store_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/store_list_res.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/store/props_store_headdress_detail_dialog.dart'; + +import '../../../chatvibe_data/models/enum/at_currency_type.dart'; +import '../../../chatvibe_data/models/enum/at_props_type.dart'; +import '../../../chatvibe_data/models/enum/at_vip_type.dart'; + +///头饰 +class StoreHeaddressPage extends ATPageList { + @override + _StoreHeaddressPageState createState() => _StoreHeaddressPageState(); +} + +class _StoreHeaddressPageState + extends ATPageListState { + //折扣 + double disCount = 1; + + @override + void initState() { + super.initState(); + enablePullUp = false; + isGridView = true; + gridViewCount = 3; + padding = EdgeInsets.symmetric(horizontal: 6.w); + backgroundColor = Colors.transparent; + gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridViewCount, + mainAxisSpacing: 12.w, + crossAxisSpacing: 12.w, + childAspectRatio: 0.82, + ); + if (AccountStorage().getVIP()?.name == ATVIPType.MARQUIS.name) { + disCount = 0.9; + } else if (AccountStorage().getVIP()?.name == ATVIPType.DUKE.name) { + disCount = 0.85; + } else if (AccountStorage().getVIP()?.name == ATVIPType.KING.name) { + disCount = 0.8; + } else if (AccountStorage().getVIP()?.name == ATVIPType.EMPEROR.name) { + disCount = 0.7; + } + disCount = 1; + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: buildList(context), + ); + } + + @override + Widget buildItem(StoreListResBean res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + color: + res.isSelecte + ? Color(0xffFFF4D9) + : ATGlobalConfig.businessLogicStrategy + .getStoreItemBackgroundColor(), + border: Border.all( + color: + res.isSelecte + ? ATGlobalConfig.businessLogicStrategy + .getStoreItemSelectedBorderColor() + : ATGlobalConfig.businessLogicStrategy + .getStoreItemUnselectedBorderColor(), + width: 1.w, + ), + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + child: Stack( + children: [ + Column( + children: [ + SizedBox(height: 20.w), + netImage( + url: res.res.propsResources?.cover ?? "", + width: 55.w, + height: 55.w, + ), + SizedBox(height: 5.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 10.w), + Expanded( + child: Text.rich( + textAlign: TextAlign.center, + TextSpan( + children: [ + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Image.asset( + ATGlobalConfig.businessLogicStrategy + .getStoreItemGoldIcon(), + width: 15.w, + ), + ), + TextSpan(text: " "), + disCount < 1 + ? TextSpan( + text: "${res.res.propsPrices![0].amount}", + style: TextStyle( + fontSize: 12.sp, + color: + ATGlobalConfig.businessLogicStrategy + .getStoreItemPriceTextColor(), + fontWeight: FontWeight.w600, + decoration: TextDecoration.lineThrough, + ), + ) + : TextSpan(), + disCount < 1 ? TextSpan(text: " ") : TextSpan(), + TextSpan( + text: + "${((res.res.propsPrices![0].amount ?? 0) * disCount).toInt()}/${res.res.propsPrices![0].days} ${ATAppLocalizations.of(context)!.day}", + style: TextStyle( + fontSize: 12.sp, + color: + ATGlobalConfig.businessLogicStrategy + .getStoreItemPriceTextColor(), + fontWeight: FontWeight.w600, + ), + ), + ], + ), + strutStyle: StrutStyle( + height: 1.3, // 行高倍数 + fontWeight: FontWeight.w500, + forceStrutHeight: true, // 强制应用行高 + ), + ), + ), + SizedBox(width: 10.w), + ], + ), + ], + ), + ], + ), + ), + onTap: () { + _selectItem(res); + _showDetail(res.res); + + }, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + List beans = []; + var storeList = await StoreRepositoryImp().storeList( + ATCurrencyType.GOLD.name, + ATPropsType.AVATAR_FRAME.name, + ); + for (var value in storeList) { + beans.add(StoreListResBean(value, false)); + } + onSuccess(beans); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + void _showDetail(StoreListRes res) { + SmartDialog.show( + tag: "showPropsDetail", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return PropsStoreHeaddressDetailDialog(res, disCount); + }, + ); + } + + void _selectItem(StoreListResBean res) { + for (var value in items) { + value.isSelecte = false; + } + res.isSelecte = true; + setState(() {}); + } + + void _buy(StoreListResBean res) { + if (res.res.propsPrices!.isEmpty) { + return; + } + ATLoadingManager.exhibitOperation(); + num days = res.res.propsPrices![0].days ?? 0; + StoreRepositoryImp() + .storePurchasing( + res.res.id ?? "", + ATPropsType.AVATAR_FRAME.name, + ATCurrencyType.GOLD.name, + "$days", + ) + .then((value) { + ATTts.show(ATAppLocalizations.of(context)!.purchaseIsSuccessful); + Provider.of( + context, + listen: false, + ).updateBalance(value); + ATLoadingManager.veilRoutine(); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } +} + +class StoreListResBean { + StoreListRes res; + bool isSelecte = false; + + StoreListResBean(this.res, this.isSelecte); +} diff --git a/lib/chatvibe_features/store/mountains/store_mountains_page.dart b/lib/chatvibe_features/store/mountains/store_mountains_page.dart new file mode 100644 index 0000000..f24876e --- /dev/null +++ b/lib/chatvibe_features/store/mountains/store_mountains_page.dart @@ -0,0 +1,242 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/store_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/store_list_res.dart'; +import 'package:aslan/chatvibe_ui/widgets/store/props_store_mountains_detail_dialog.dart'; +import 'package:aslan/chatvibe_features/store/headdress/store_headdress_page.dart'; + +import '../../../chatvibe_data/models/enum/at_currency_type.dart'; +import '../../../chatvibe_data/models/enum/at_props_type.dart'; +import '../../../chatvibe_data/models/enum/at_vip_type.dart'; + +///坐骑 +class StoreMountainsPage extends ATPageList { + Function(String playUrl) playCall; + + StoreMountainsPage(this.playCall); + + @override + _StoreMountainsPageState createState() => _StoreMountainsPageState(playCall); +} + +class _StoreMountainsPageState + extends ATPageListState { + Function(String playUrl) playCall; + + _StoreMountainsPageState(this.playCall); + + //折扣 + double disCount = 1; + + @override + void initState() { + super.initState(); + enablePullUp = false; + isGridView = true; + gridViewCount = 3; + padding = EdgeInsets.symmetric(horizontal: 6.w); + backgroundColor = Colors.transparent; + gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridViewCount, + mainAxisSpacing: 12.w, + crossAxisSpacing: 12.w, + childAspectRatio: 0.82, + ); + if (AccountStorage().getVIP()?.name == ATVIPType.MARQUIS.name) { + disCount = 0.9; + } else if (AccountStorage().getVIP()?.name == ATVIPType.DUKE.name) { + disCount = 0.85; + } else if (AccountStorage().getVIP()?.name == ATVIPType.KING.name) { + disCount = 0.8; + } else if (AccountStorage().getVIP()?.name == ATVIPType.EMPEROR.name) { + disCount = 0.7; + } + disCount = 1; + loadData(1); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: buildList(context), + ); + } + + @override + Widget buildItem(StoreListResBean res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + color: + res.isSelecte + ? Color(0xffFFF4D9) + : ATGlobalConfig.businessLogicStrategy + .getStoreItemBackgroundColor(), + border: Border.all( + color: + res.isSelecte + ? ATGlobalConfig.businessLogicStrategy + .getStoreItemSelectedBorderColor() + : ATGlobalConfig.businessLogicStrategy + .getStoreItemUnselectedBorderColor(), + width: 1.w, + ), + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + child: Stack( + children: [ + Column( + children: [ + SizedBox(height: 20.w), + netImage( + url: res.res.propsResources?.cover ?? "", + width: 55.w, + height: 55.w, + ), + SizedBox(height: 5.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 10.w), + Expanded( + child: Text.rich( + textAlign: TextAlign.center, + TextSpan( + children: [ + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Image.asset( + ATGlobalConfig.businessLogicStrategy.getStoreItemGoldIcon(), + width: 15.w, + ), + ), + TextSpan(text: " "), + disCount < 1 + ? TextSpan( + text: "${res.res.propsPrices![0].amount}", + style: TextStyle( + fontSize: 12.sp, + color: ATGlobalConfig.businessLogicStrategy.getStoreItemPriceTextColor(), + fontWeight: FontWeight.w600, + decoration: TextDecoration.lineThrough, + ), + ) + : TextSpan(), + disCount < 1 ? TextSpan(text: " ") : TextSpan(), + TextSpan( + text: + "${((res.res.propsPrices![0].amount ?? 0) * disCount).toInt()}/${res.res.propsPrices![0].days} ${ATAppLocalizations.of(context)!.day}", + style: TextStyle( + fontSize: 12.sp, + color: ATGlobalConfig.businessLogicStrategy.getStoreItemPriceTextColor(), + fontWeight: FontWeight.w600, + ), + ), + ], + ), + strutStyle: StrutStyle( + height: 1.3, // 行高倍数 + fontWeight: FontWeight.w500, + forceStrutHeight: true, // 强制应用行高 + ), + ), + ), + SizedBox(width: 10.w), + ], + ), + ], + ), + Positioned( + top: 5.w, + right: 5.w, + child: ATDebounceWidget( + onTap: () { + // _showDetail(res.res); + _play(res.res); + _selectItem(res); + }, + child: Container( + padding: EdgeInsets.all(5.w), + child: Image.asset( + ATGlobalConfig.businessLogicStrategy.getStoreItemActionIcon( + 'mountains', + ), + width: 18.w, + height: 18.w, + ), + ), + ), + ), + ], + ), + ), + onTap: () { + _selectItem(res); + _showDetail(res.res); + }, + ); + } + + void _play(StoreListRes res) { + playCall(res.propsResources?.sourceUrl ?? ""); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + List beans = []; + var storeList = await StoreRepositoryImp().storeList( + ATCurrencyType.GOLD.name, + ATPropsType.RIDE.name, + ); + for (var value in storeList) { + beans.add(StoreListResBean(value, false)); + } + onSuccess(beans); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + void _showDetail(StoreListRes res) { + SmartDialog.show( + tag: "showPropsDetail", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return PropsStoreMountainsDetailDialog(res, disCount); + }, + ); + } + + void _selectItem(StoreListResBean res) { + for (var value in items) { + value.isSelecte = false; + } + res.isSelecte = true; + setState(() {}); + } +} diff --git a/lib/chatvibe_features/store/store_page.dart b/lib/chatvibe_features/store/store_page.dart new file mode 100644 index 0000000..4ae83cf --- /dev/null +++ b/lib/chatvibe_features/store/store_page.dart @@ -0,0 +1,251 @@ +import 'dart:async'; + +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_features/store/store_route.dart'; +import 'package:aslan/chatvibe_features/store/theme/store_theme_page.dart'; +import 'package:provider/provider.dart'; +import 'package:tancent_vap/utils/constant.dart'; +import 'package:tancent_vap/widgets/vap_view.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_path_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/file_cache_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/headdress/headdress_widget.dart'; +import 'package:aslan/chatvibe_features/wallet/wallet_route.dart'; +import 'package:aslan/chatvibe_features/store/chatbox/store_chatbox_page.dart'; +import 'package:aslan/chatvibe_features/store/headdress/store_headdress_page.dart'; +import 'package:aslan/chatvibe_features/store/mountains/store_mountains_page.dart'; + +import '../../chatvibe_domain/usecases/at_fixed_width_tabIndicator.dart'; + +class StorePage extends StatefulWidget { + @override + _StorePageState createState() => _StorePageState(); +} + +class _StorePageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + final List _pages = []; + final List _tabs = []; + String effPlayPath = ""; + String themePath = ""; + VapController? vapController; + Timer? _timer; + + @override + void initState() { + super.initState(); + _pages.add(StoreHeaddressPage()); + _pages.add( + StoreMountainsPage((playUrl) { + effPlayPath = ""; + vapController?.stop(); + setState(() {}); + try { + Future.delayed(Duration(milliseconds: 350), () { + if (ATPathUtils.fetchFileExtensionFunction(playUrl).toLowerCase() == ".mp4") { + FileCacheManager.getInstance().getFile(url: playUrl).then((file) { + vapController?.playFile(file.path); + }); + } else { + effPlayPath = playUrl; + setState(() {}); + } + }); + } catch (e) {} + }), + ); + _pages.add( + StoreThemePage((playUrl) { + themePath = playUrl; + setState(() {}); + _timer ??= Timer.periodic(Duration(seconds: 5), (timer) { + themePath = ""; + setState(() {}); + }); + }), + ); + _pages.add(StoreChatboxPage()); + _tabController = TabController(length: _pages.length, vsync: this); + _tabController.addListener(() { + vapController?.stop(); + _timer?.cancel(); + setState(() { + effPlayPath = ""; + themePath = ""; + }); + }); + Provider.of(context, listen: false).balance(); + } + + @override + void dispose() { + super.dispose(); + vapController?.dispose(); + _timer?.cancel(); + } + + @override + Widget build(BuildContext context) { + _tabs.clear(); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.headdress)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.mountains)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.theme)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.chatBox)); + return Stack( + children: [ + themePath.isNotEmpty + ? netImage( + url: themePath, + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + ) + : Image.asset( + ATGlobalConfig.businessLogicStrategy.getStorePageBackgroundImage(), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.store, + actions: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.all(5.w), + margin: EdgeInsetsDirectional.only( + end: ATGlobalConfig.businessLogicStrategy + .getStorePageShoppingBagMargin() + .end + .w, + ), + child: Image.asset( + ATGlobalConfig.businessLogicStrategy.getStorePageShoppingBagIcon(), + width: 20.w, + height: 20.w, + ), + ), + onTap: () { + ATNavigatorUtils.push(context, StoreRoute.bags, replace: true); + }, + ), + ], + ), + body: SafeArea(top: false,child:Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(width: 5.w), + TabBar( + tabAlignment: TabAlignment.center, // 改为 center + labelPadding: EdgeInsets.symmetric(horizontal: 12.w), + labelColor: Colors.black87, + isScrollable: true, + unselectedLabelColor: Colors.black38, + labelStyle: TextStyle(fontSize: 15.sp, fontWeight: FontWeight.w600), + unselectedLabelStyle: TextStyle(fontSize: 13.sp, fontWeight: FontWeight.w500), + indicator: ATFixedWidthTabIndicator( + width: 15.w, + color: ATGlobalConfig.businessLogicStrategy.getStorePageTabIndicatorColor(), + ), + dividerColor: ChatVibeTheme.transparent, + controller: _tabController, + tabs: _tabs, + ), + ], + ), + SizedBox(height: 4.w,), + Container(color: ChatVibeTheme.primaryLight,width: ScreenUtil().screenWidth,height: 0.5.w,), + SizedBox(height: 6.w,), + Expanded( + child: TabBarView(controller: _tabController, children: _pages), + ), + Divider( + color: ATGlobalConfig.businessLogicStrategy.getStorePageBottomDividerColor(), + thickness: ATGlobalConfig.businessLogicStrategy.getStorePageBottomDividerThickness(), + height: ATGlobalConfig.businessLogicStrategy.getStorePageBottomDividerHeight().w, + ), + Consumer( + builder: (context, ref, child) { + return GestureDetector( + child: Container( + padding: EdgeInsets.only(bottom: 12.w), + child: Row( + children: [ + SizedBox(width: 10.w), + Image.asset( + ATGlobalConfig.businessLogicStrategy.getStorePageGoldIcon(), + width: 25.w, + height: 25.w, + ), + SizedBox(width: 5.w), + text( + "${ref.myBalance}", + fontSize: 14.sp, + textColor: + ATGlobalConfig.businessLogicStrategy.getStorePageGoldTextColor(), + ), + SizedBox(width: 4.w), + Icon( + Icons.chevron_right, + size: 20.w, + color: + ATGlobalConfig.businessLogicStrategy.getStorePageGoldIconColor(), + ), + ], + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + WalletRoute.recharge, + replace: false, + ); + }, + ); + }, + ), + ], + ) ,), + ), + SizedBox( + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + child: IgnorePointer( + child: VapView( + scaleType: ScaleType.centerCrop, + onViewCreated: (controller) { + vapController = controller; + }, + ), + ), + ), + ATPathUtils.fetchFileExtensionFunction(effPlayPath).toLowerCase() == ".svga" + ? IgnorePointer( + child: SVGAHeadwearWidget( + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + resource: effPlayPath, + loops: 1, + ), + ) + : Container(), + ], + ); + } +} diff --git a/lib/chatvibe_features/store/store_route.dart b/lib/chatvibe_features/store/store_route.dart new file mode 100644 index 0000000..a9a27b9 --- /dev/null +++ b/lib/chatvibe_features/store/store_route.dart @@ -0,0 +1,22 @@ +import 'package:fluro/fluro.dart'; +import 'package:aslan/chatvibe_features/store/store_page.dart'; +import 'package:aslan/chatvibe_features/user/my_items/my_items_page.dart'; + +import 'package:aslan/chatvibe_core/routes/at_router_init.dart'; + +class StoreRoute implements ATIRouterProvider { + static String list = '/main/store/list'; + static String bags = '/main/store/bag'; + + @override + void initRouter(FluroRouter router) { + router.define( + list, + handler: Handler(handlerFunc: (_, params) => StorePage()), + ); + router.define( + bags, + handler: Handler(handlerFunc: (_, params) => MyItemsPage()), + ); + } +} diff --git a/lib/chatvibe_features/store/theme/store_theme_page.dart b/lib/chatvibe_features/store/theme/store_theme_page.dart new file mode 100644 index 0000000..d222683 --- /dev/null +++ b/lib/chatvibe_features/store/theme/store_theme_page.dart @@ -0,0 +1,263 @@ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/store_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/store_list_res.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/store/props_store_theme_detail_dialog.dart'; +import 'package:aslan/chatvibe_features/store/headdress/store_headdress_page.dart'; + +import '../../../chatvibe_data/models/enum/at_currency_type.dart'; +import '../../../chatvibe_data/models/enum/at_props_type.dart'; +import '../../../chatvibe_data/models/enum/at_vip_type.dart'; + +///房间主题 +class StoreThemePage extends ATPageList { + Function(String playUrl) playCall; + + StoreThemePage(this.playCall); + + @override + _StoreThemePagePageState createState() => _StoreThemePagePageState(playCall); +} + +class _StoreThemePagePageState + extends ATPageListState { + Function(String playUrl) playCall; + + _StoreThemePagePageState(this.playCall); + + //折扣 + double disCount = 1; + + @override + void initState() { + super.initState(); + enablePullUp = false; + isGridView = true; + gridViewCount = 3; + padding = EdgeInsets.symmetric(horizontal: 6.w); + backgroundColor = Colors.transparent; + gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridViewCount, + mainAxisSpacing: 12.w, + crossAxisSpacing: 12.w, + childAspectRatio: 0.82, + ); + if (AccountStorage().getVIP()?.name == ATVIPType.MARQUIS.name) { + disCount = 0.9; + } else if (AccountStorage().getVIP()?.name == ATVIPType.DUKE.name) { + disCount = 0.85; + } else if (AccountStorage().getVIP()?.name == ATVIPType.KING.name) { + disCount = 0.8; + } else if (AccountStorage().getVIP()?.name == ATVIPType.EMPEROR.name) { + disCount = 0.7; + } + disCount = 1; + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: buildList(context), + ); + } + + @override + Widget buildItem(StoreListResBean res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + color: + res.isSelecte + ? Color(0xffFFF4D9) + : ATGlobalConfig.businessLogicStrategy + .getStoreItemBackgroundColor(), + border: Border.all( + color: + res.isSelecte + ? ATGlobalConfig.businessLogicStrategy + .getStoreItemSelectedBorderColor() + : ATGlobalConfig.businessLogicStrategy + .getStoreItemUnselectedBorderColor(), + width: 1.w, + ), + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + child: Stack( + children: [ + Column( + children: [ + SizedBox(height: 20.w), + netImage( + url: res.res.propsResources?.cover ?? "", + width: 55.w, + height: 55.w, + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + SizedBox(height: 5.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 10.w), + Expanded( + child: Text.rich( + textAlign: TextAlign.center, + TextSpan( + children: [ + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Image.asset( + ATGlobalConfig.businessLogicStrategy.getStoreItemGoldIcon(), + width: 15.w, + ), + ), + TextSpan(text: " "), + disCount < 1 + ? TextSpan( + text: "${res.res.propsPrices![0].amount}", + style: TextStyle( + fontSize: 12.sp, + color: ATGlobalConfig.businessLogicStrategy.getStoreItemPriceTextColor(), + fontWeight: FontWeight.w600, + decoration: TextDecoration.lineThrough, + ), + ) + : TextSpan(), + disCount < 1 ? TextSpan(text: " ") : TextSpan(), + TextSpan( + text: + "${((res.res.propsPrices![0].amount ?? 0) * disCount).toInt()}/${res.res.propsPrices![0].days} ${ATAppLocalizations.of(context)!.day}", + style: TextStyle( + fontSize: 12.sp, + color: ATGlobalConfig.businessLogicStrategy.getStoreItemPriceTextColor(), + fontWeight: FontWeight.w600, + ), + ), + ], + ), + strutStyle: StrutStyle( + height: 1.3, // 行高倍数 + fontWeight: FontWeight.w500, + forceStrutHeight: true, // 强制应用行高 + ), + ), + ), + SizedBox(width: 10.w), + ], + ), + ], + ), + Positioned( + top: 5.w, + right: 5.w, + child: ATDebounceWidget( + onTap: () { + _play(res.res); + _selectItem(res); + }, + child: Container( + padding: EdgeInsets.all(5.w), + child: Image.asset( + ATGlobalConfig.businessLogicStrategy.getStoreItemActionIcon('theme'), + width: 13.w, + height: 13.w, + ), + ), + ), + ), + ], + ), + ), + onTap: () { + _selectItem(res); + _showDetail(res.res); + }, + ); + } + + void _showDetail(StoreListRes res) { + SmartDialog.show( + tag: "showPropsDetail", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return PropsStoreThemeDetailDialog(res, disCount); + }, + ); + } + + void _play(StoreListRes res) { + playCall(res.propsResources?.cover ?? ""); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + List beans = []; + var storeList = await StoreRepositoryImp().storeList( + ATCurrencyType.GOLD.name, + ATPropsType.THEME.name, + ); + for (var value in storeList) { + beans.add(StoreListResBean(value, false)); + } + onSuccess(beans); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + void _selectItem(StoreListResBean res) { + for (var value in items) { + value.isSelecte = false; + } + res.isSelecte = true; + setState(() {}); + } + + void _buy(StoreListResBean res) { + num days = 0; + if (res.res.propsPrices!.length > 1) { + days = res.res.propsPrices![1].days ?? 0; + } else { + days = res.res.propsPrices![0].days ?? 0; + } + StoreRepositoryImp() + .storePurchasing( + res.res.id ?? "", + ATPropsType.THEME.name, + ATCurrencyType.GOLD.name, + "$days", + ) + .then((value) { + ATTts.show(ATAppLocalizations.of(context)!.purchaseIsSuccessful); + Provider.of( + context, + listen: false, + ).updateBalance(value); + }); + } +} diff --git a/lib/chatvibe_features/task/at_task_list_page.dart b/lib/chatvibe_features/task/at_task_list_page.dart new file mode 100644 index 0000000..5054e86 --- /dev/null +++ b/lib/chatvibe_features/task/at_task_list_page.dart @@ -0,0 +1,609 @@ +import 'package:aslan/chatvibe_core/utilities/at_user_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_features/store/store_route.dart'; +import 'package:provider/provider.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import '../../chatvibe_core/utilities/at_room_utils.dart'; +import '../../chatvibe_data/models/enum/at_vip_type.dart'; +import '../../chatvibe_domain/models/res/at_task_list_res.dart'; +import '../../chatvibe_domain/models/res/my_room_res.dart'; +import '../../chatvibe_domain/models/res/room_res.dart'; +import '../../chatvibe_managers/app_general_manager.dart'; +import '../../chatvibe_managers/room_manager.dart'; +import '../../main.dart'; +import '../index/main_route.dart'; + +class ATTaskListPage extends StatefulWidget { + @override + _ATTaskListPageState createState() => _ATTaskListPageState(); +} + +class _ATTaskListPageState extends State + with SingleTickerProviderStateMixin { + final RefreshController _refreshController = RefreshController( + initialRefresh: false, + ); + + //折扣 + double disCount = 1; + List growthTasks = []; + List dailyTasks = []; + + @override + void initState() { + super.initState(); + if (AccountStorage().getVIP()?.name == ATVIPType.VISCOUNT.name) { + // disCount = 1.05; + } else if (AccountStorage().getVIP()?.name == ATVIPType.EARL.name) { + // disCount = 1.07; + } else if (AccountStorage().getVIP()?.name == ATVIPType.MARQUIS.name) { + disCount = 1.10; + } else if (AccountStorage().getVIP()?.name == ATVIPType.DUKE.name) { + disCount = 1.15; + } else if (AccountStorage().getVIP()?.name == ATVIPType.KING.name) { + disCount = 1.20; + } else if (AccountStorage().getVIP()?.name == ATVIPType.EMPEROR.name) { + disCount = 1.25; + } + loadData(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + ATGlobalConfig.businessLogicStrategy.getTaskPageBackgroundImage(), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + appBar: ChatVibeStandardAppBar( + backButtonColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPageBackButtonColor(), + title: ATAppLocalizations.of(context)!.task, + actions: [], + ), + backgroundColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPageAppBarBackgroundColor(), + body: SafeArea( + top: false, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 8.w), + child: SmartRefresher( + enablePullDown: true, + enablePullUp: false, + controller: _refreshController, + onRefresh: () { + loadData(); + }, + onLoading: () {}, + child: SingleChildScrollView( + child: Column( + children: [ + GestureDetector( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 2.w), + child: Image.asset( + ATGlobalConfig.businessLogicStrategy + .getTaskPageHeadBackgroundImage(), + fit: BoxFit.contain, + ), + ), + onTap: () { + // SmartDialog.show( + // tag: "showSginListAward", + // alignment: Alignment.center, + // animationType: SmartAnimationType.fade, + // builder: (_) { + // return SginPage(false); + // }, + // ); + }, + ), + Container( + width: ScreenUtil().screenWidth, + margin: EdgeInsets.symmetric( + horizontal: 2.w, + vertical: 5.w, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.symmetric( + vertical: 8.w, + horizontal: 10.w, + ), + child: text( + "${ATAppLocalizations.of(context)!.dailyTasks}:${getVipAcceleratingText()}", + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPagePrimaryTextColor(), + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ), + ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: dailyTasks.length, + itemBuilder: (context, index) { + return _buildTaskItem(dailyTasks[index], index); + }, + separatorBuilder: ( + BuildContext context, + int index, + ) { + return SizedBox(height: 10.w); + }, + ), + ], + ), + ), + Container( + width: ScreenUtil().screenWidth, + margin: EdgeInsets.symmetric(horizontal: 2.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.symmetric( + vertical: 8.w, + horizontal: 10.w, + ), + child: text( + "${ATAppLocalizations.of(context)!.improvementTasks}:${getVipAcceleratingText()}", + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPagePrimaryTextColor(), + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ), + ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: growthTasks.length, + // 列表项数量 + padding: EdgeInsets.only(bottom: 15.w), + itemBuilder: (context, index) { + return _buildTaskItem( + growthTasks[index], + index, + ); + }, + separatorBuilder: ( + BuildContext context, + int index, + ) { + return SizedBox(height: 10.w); + }, + ), + ], + ), + ), + SizedBox(height: 10.w), + ], + ), + ), + ), + ), + ), + ), + ], + ); + } + + _buildTaskItem(ATTaskListRes task, int index) { + return Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_task_list_item_bg.png", + ), + fit: BoxFit.fill, + ), + ), + margin: EdgeInsets.symmetric(horizontal: 10.w), + padding: EdgeInsets.symmetric(horizontal: 2.w, vertical: 10.w), + child: Row( + children: [ + SizedBox(width: 5.w), + netImage(url: task.taskIcon ?? "", width: 45.w), + SizedBox(width: 5.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + constraints: BoxConstraints(maxWidth: 158.w), + child: text( + "${task.taskDesc}", + maxLines: 2, + fontWeight: FontWeight.w600, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPagePrimaryTextColor(), + fontSize: 13.sp, + ), + ), + SizedBox(width: 3.w), + text( + "(${task.completedValue ?? 0}/${task.targetValue ?? 0})", + fontWeight: FontWeight.w600, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPagePrimaryTextColor(), + fontSize: 13.sp, + ), + ], + ), + SizedBox(height: 3.w), + task.conditionType == "GOLD-CUSTOMIZE" + ? Row( + children: [ + Image.asset( + ATGlobalConfig.businessLogicStrategy + .getTaskPageGoldIcon(), + width: 16.w, + ), + SizedBox(width: 2.w), + disCount > 1 + ? text( + "${task.conditionValue?.split("-").first}", + fontSize: 13.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPageGoldTextColor(), + fontWeight: FontWeight.w600, + decoration: TextDecoration.lineThrough, + ) + : Container(), + disCount > 1 ? SizedBox(width: 3.w) : Container(), + text( + "${(int.parse((task.conditionValue ?? "0-").split("-").first) * disCount + 1e-9).floor()}", + fontSize: 13.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPageGoldTextColor(), + fontWeight: FontWeight.w600, + ), + SizedBox(width: 4.w), + Image.asset( + ATGlobalConfig.businessLogicStrategy + .getTaskPageExpIcon(), + width: 20.w, + ), + SizedBox(width: 4.w), + + disCount > 1 + ? text( + "${task.conditionValue?.split("-").last}", + fontSize: 13.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPageExpTextColor(), + fontWeight: FontWeight.w600, + decoration: TextDecoration.lineThrough, + ) + : Container(), + disCount > 1 ? SizedBox(width: 3.w) : Container(), + text( + "${(int.parse((task.conditionValue ?? "0-").split("-").last) * disCount + 1e-9).floor()}", + fontSize: 13.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPageExpTextColor(), + fontWeight: FontWeight.w600, + ), + ], + ) + : (task.conditionType == "CUSTOMIZE" + ? Row( + children: [ + Image.asset( + ATGlobalConfig.businessLogicStrategy + .getTaskPageExpIcon(), + width: 20.w, + ), + SizedBox(width: 4.w), + disCount > 1 + ? text( + "${task.conditionValue}", + fontSize: 13.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPageExpTextColor(), + decoration: TextDecoration.lineThrough, + ) + : Container(), + disCount > 1 ? SizedBox(width: 3.w) : Container(), + text( + "${(int.parse(task.conditionValue ?? "0") * disCount + 1e-9).floor()}", + fontSize: 13.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPageExpTextColor(), + fontWeight: FontWeight.w600, + ), + ], + ) + : (task.taskId == 11 + ? Row( + children: [ + text( + "Gift Bag x1", + fontSize: 14.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPageThemeColor(), + fontWeight: FontWeight.w600, + ), + ], + ) + : Container())), + ], + ), + Spacer(), + SizedBox(width: 3.w), + task.taskStatus == 0 + ? GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + width: 60.w, + height: 30.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_task_list_item_go_btn.png", + ), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + text( + ATAppLocalizations.of(context)!.go, + fontSize: 13.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPageThemeLightColor(), + ), + SizedBox(width: 2.w), + Icon( + Icons.arrow_forward_ios, + color: + ATGlobalConfig.businessLogicStrategy + .getTaskPageThemeLightColor(), + size: 12.w, + ), + ], + ), + ), + onTap: () { + goTask(task.taskId ?? 0); + }, + ) + : (task.isRewardCollected == 0 + ? GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + width: 60.w, + height: 30.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_task_list_item_act_btn.png", + ), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + text( + ATAppLocalizations.of(context)!.receive, + fontSize: 13.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPageButtonTextColor(), + ), + SizedBox(width: 2.w), + ], + ), + ), + onTap: () { + receiveReward(task.taskId ?? 0); + }, + ) + : Container( + alignment: AlignmentDirectional.center, + width: 60.w, + height: 30.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_task_list_item_complete_btn.png", + ), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + text( + ATAppLocalizations.of(context)!.done, + fontSize: 13.sp, + textColor: Color(0xff888888), + ), + SizedBox(width: 2.w), + ], + ), + )), + SizedBox(width: 3.w), + ], + ), + ); + } + + void loadData() { + AccountRepository() + .tasks() + .then((result) { + dailyTasks.clear(); + growthTasks.clear(); + for (var task in result) { + if (task.taskType == 1) { + //每日任务 + dailyTasks.add(task); + } else if (task.taskType == 2) { + //成长任务 + growthTasks.add(task); + } + } + _refreshController.refreshCompleted(); + _refreshController.loadComplete(); + setState(() {}); + }) + .catchError((e) { + _refreshController.loadNoData(); + _refreshController.refreshCompleted(); + setState(() {}); + }); + } + + void goTask(num taskId) { + if (taskId == 1) { + //去房间上麦 + goMyRoom(false, false, false); + } else if (taskId == 2) { + //去房间送礼 + goMyRoom(false, false, true); + } else if (taskId == 3) { + //去商店 + ATNavigatorUtils.push(context, StoreRoute.list); + } else if (taskId == 4) { + //去玩游戏 + goMyRoom(true, false, false); + } else if (taskId == 5) { + //创建房间 + _openMyDrawer(true); + } else if (taskId == 6) { + //加好友 + goHotGame(false, false); + } else if (taskId == 7) { + //去玩游戏 + goHotGame(true, false); + } else if (taskId == 8) { + //去赠送幸运礼物 + goMyRoom(false, true, false); + } else if (taskId == 9) { + goHotGame(false, false); + } else if (taskId == 10) { + //加入房间 + goHotGame(false, false); + } else if (taskId == 11) { + //去邀请用户注册 + if (ATGlobalConfig.isReview) { + return; + } + ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.inviteNewUserUrl)}&showTitle='false'", + replace: false, + ); + } + } + + ///领取奖励 + void receiveReward(num taskId) { + if (taskId == 11) { + ///直接跳转到邀请新用户页面不领了 + if (ATGlobalConfig.isReview) { + return; + } + ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.inviteNewUserUrl)}&showTitle='false'", + replace: false, + ); + } else { + AccountRepository() + .taskReward(taskId) + .then((result) { + if (result) { + ATTts.show(ATAppLocalizations.of(context)!.receiveSucc); + loadData(); + } + }) + .catchError((e) { + ATTts.show(e); + }); + } + } + + void goMyRoom( + bool openGameDialog, + bool openGiftDialogToLuckyGift, + bool openGiftDialogToGift, + ) { + MyRoomRes? myRoom = + Provider.of(context, listen: false).myRoom; + if (myRoom != null) { + ATNavigatorUtils.goBack(context); + ATRoomUtils.goRoom( + myRoom.id ?? "", + navigatorKey.currentState!.context, + openGameDialog: openGameDialog, + openGiftDialogToLuckyGift: openGiftDialogToLuckyGift, + openGiftDialogToGift: openGiftDialogToGift, + ); + } else { + _openMyDrawer(true); + } + } + + void goHotGame(bool openGameDialog, bool openGiftDialogToLuckyGift) { + ATNavigatorUtils.goBack(context); + ChatVibeRoomRes? hotRoom = + Provider.of(context, listen: false).hotRoom; + if (hotRoom != null) { + ATRoomUtils.goRoom( + hotRoom.id ?? "", + navigatorKey.currentState!.context, + openGameDialog: openGameDialog, + openGiftDialogToLuckyGift: openGiftDialogToLuckyGift, + ); + } + } + + void _openMyDrawer(bool highlightMyRoom) { + ATNavigatorUtils.goBack(context); + // Wait for pop transition, then switch tab on existing IndexPage instance. + // Future.delayed(const Duration(milliseconds: 250), () { + // eventBus.fire(OpenDrawerEvent(highlightMyRoom: highlightMyRoom)); + // }); + } + + String getVipAcceleratingText() { + String vipName = AccountStorage().getVIP()?.name ?? ""; + if (vipName.isEmpty) { + return ""; + } + return "(${ATAppLocalizations.of(context)!.vipAccelerating(ATAccountHelper.getVipLevel(vipName))})"; + } +} diff --git a/lib/chatvibe_features/user/crop/crop_image_page.dart b/lib/chatvibe_features/user/crop/crop_image_page.dart new file mode 100644 index 0000000..c49aa87 --- /dev/null +++ b/lib/chatvibe_features/user/crop/crop_image_page.dart @@ -0,0 +1,206 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:image_cropper/image_cropper.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; + +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/general_repository_imp.dart'; + +typedef OnUpLoadCallBack = void Function(bool success, String url); + +class CropImagePage extends StatefulWidget { + final double? aspectRatio; // 需要锁定的宽高比,例如 1 表示 1:1 + final bool? backOriginalFile; + final OnUpLoadCallBack? onUpLoadCallBack; + final File image; // 原始图片文件 + final bool needUpload; + + const CropImagePage(this.image, { + Key? key, + this.onUpLoadCallBack, + this.aspectRatio, + this.backOriginalFile = true, + this.needUpload = true, + }) : super(key: key); + + @override + _CropImageRouteState createState() => _CropImageRouteState(); +} + +class _CropImageRouteState extends State { + late Image imageView; + + @override + void initState() { + super.initState(); + imageView = Image.file(widget.image, fit: BoxFit.contain); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xff333333), + body: SafeArea( + top: false, + child: Stack( + alignment: Alignment.center, + children: [ + // 预览原图 + Align( + alignment: Alignment.center, + child: Container( + width: double.infinity, + height: + ATScreen.designHeight - + (kToolbarHeight * 2) - + ScreenUtil().bottomBarHeight - + ScreenUtil().statusBarHeight, + child: imageView, + ), + ), + + // 顶部栏 + Align( + alignment: Alignment.topCenter, + child: Container( + height: kToolbarHeight + ScreenUtil().statusBarHeight, + width: double.infinity, + padding: EdgeInsets.only( + left: width(15), + right: width(15), + top: ScreenUtil().statusBarHeight, + ), + color: Colors.transparent, + child: Stack( + alignment: Alignment.centerLeft, + children: [ + GestureDetector( + onTap: () => ATNavigatorUtils.goBackWithParams(context, ''), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + ATGlobalConfig.lang == "ar" + ? Icons.keyboard_arrow_right + : Icons.keyboard_arrow_left, + color: Colors.white, + ), + ), + ), + Align( + alignment: Alignment.center, + child: Text( + ATAppLocalizations.of(context)!.crop, + style: TextStyle(fontSize: 18.sp, color: Colors.white), + ), + ), + ], + ), + ), + ), + + // 底部完成按钮 + Align( + alignment: Alignment.bottomCenter, + child: Container( + width: double.infinity, + height: kToolbarHeight + ScreenUtil().bottomBarHeight, + color: const Color(0xff666666), + alignment: Alignment.centerRight, + child: GestureDetector( + onTap: () => + _cropAndUpload(context, widget.image, needUpload:widget.needUpload), + child: Padding( + padding: const EdgeInsets.all(8.0).copyWith(right: 18), + child: Text( + ATAppLocalizations.of(context)!.finish, + style: TextStyle(color: Colors.white, fontSize: 16.sp), + ), + ), + ), + ), + ), + ], + ), + ), + ); + } + + /// 使用 image_cropper 进行裁剪并上传 + Future _cropAndUpload(BuildContext context, + File originalFile, { + bool needUpload = true, + }) async { + // 调用系统裁剪界面 + CroppedFile? cropped = await ImageCropper().cropImage( + sourcePath: originalFile.path, + aspectRatio: + widget.aspectRatio != null + ? CropAspectRatio(ratioX: widget.aspectRatio!, ratioY: 1) + : null, + uiSettings: [ + AndroidUiSettings( + toolbarTitle: ATAppLocalizations.of(context)!.crop, + toolbarColor: Colors.black, + toolbarWidgetColor: Colors.white, + hideBottomControls: true, + lockAspectRatio: widget.aspectRatio != null, + ), + IOSUiSettings( + title: ATAppLocalizations.of(context)!.crop, + resetButtonHidden: true, + rotateButtonsHidden: true, + aspectRatioPickerButtonHidden: true, + rotateClockwiseButtonHidden: true, + aspectRatioLockEnabled: widget.aspectRatio != null, + ), + ], + ); + + // 用户可能直接点“取消”,此时 cropped 为 null + File fileToUpload; + if (cropped == null) { + if (widget.backOriginalFile ?? true) { + fileToUpload = originalFile; + } else { + widget.onUpLoadCallBack?.call(false, ""); + return; + } + } else { + fileToUpload = File(cropped.path); + } + if (needUpload) { + // 弹出上传中的 loading + ATLoadingManager.exhibitOperation(context: context); + await upload(fileToUpload); + } else { + widget.onUpLoadCallBack?.call(true, fileToUpload.path); + ATNavigatorUtils.goBack(context); + } + } + + /// 上传文件至oss + Future upload(File file) async { + try { + String fileUrl = await GeneralRepositoryImp().upload(file); + ATLoadingManager.veilRoutine(); + ATNavigatorUtils.goBack(context); + await Future.delayed(const Duration(milliseconds: 100)); + widget.onUpLoadCallBack?.call(true, fileUrl); + } catch (e) { + ATTts.show("upload fail $e"); + ATLoadingManager.veilRoutine(); + } + } + + /* + * 获取 OssToken(如果依然需要,可保留) + */ + Future _getOssToken() async {} +} diff --git a/lib/chatvibe_features/user/edit/edit_user_info_page.dart b/lib/chatvibe_features/user/edit/edit_user_info_page.dart new file mode 100644 index 0000000..1809337 --- /dev/null +++ b/lib/chatvibe_features/user/edit/edit_user_info_page.dart @@ -0,0 +1,837 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/custom_cached_image.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/routes/at_routes.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_lk_dialog_util.dart'; +import 'package:aslan/chatvibe_core/utilities/at_pick_utils.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/person/draggable_image_grid.dart'; +import 'package:aslan/chatvibe_ui/widgets/person/draggable_image_person_photo_grid.dart'; +import 'package:aslan/chatvibe_features/country/country_route.dart'; + +import '../../../chatvibe_domain/usecases/at_custom_filtering_textinput_formatter.dart'; + +class EditUserInfoPage extends StatefulWidget { + const EditUserInfoPage({Key? key}) : super(key: key); + + @override + _EditUserInfoPageState createState() => _EditUserInfoPageState(); +} + +class _EditUserInfoPageState extends State + with SingleTickerProviderStateMixin { + String? userCover; + bool isEdit = false; + final TextEditingController _nickNameController = TextEditingController(); + final TextEditingController _bioController = TextEditingController(); + final TextEditingController _hobbyController = TextEditingController(); + String? bornMonth; + String? bornDay; + String? bornYear; + DateTime? birthdayDate; + num? sex; + num? age; + List _uploadPhotoImages = []; + + List _backgoundImages = [ + ImageItem('', 0, 0), + ImageItem('', 1, 0), + ImageItem('', 2, 0), + ImageItem('', 3, 0), + ImageItem('', 4, 0), + ]; + + @override + void initState() { + super.initState(); + userCover = AccountStorage().getCurrentUser()?.userProfile?.userAvatar ?? ""; + _nickNameController.text = + AccountStorage().getCurrentUser()?.userProfile?.userNickname ?? ""; + _bioController.text = + AccountStorage().getCurrentUser()?.userProfile?.autograph ?? ""; + _hobbyController.text = + AccountStorage().getCurrentUser()?.userProfile?.hobby ?? ""; + AccountStorage().getCurrentUser()?.userProfile?.bornMonth; + num m = AccountStorage().getCurrentUser()?.userProfile?.bornMonth ?? 0; + if (m < 10) { + bornMonth = "0$m"; + } else { + bornMonth = "$m"; + } + num d = AccountStorage().getCurrentUser()?.userProfile?.bornDay ?? 0; + if (d < 10) { + bornDay = "0$d"; + } else { + bornDay = "$d"; + } + bornYear = "${AccountStorage().getCurrentUser()?.userProfile?.bornYear}"; + age = + DateTime.now().year - + (AccountStorage().getCurrentUser()?.userProfile?.bornYear ?? 0); + _nickNameController.addListener(() { + isEdit = true; + }); + sex = AccountStorage().getCurrentUser()?.userProfile?.userSex; + List photoBackgoundImages = + (AccountStorage().getCurrentUser()?.userProfile?.backgroundPhotos ?? []) + .where((t) => t.status != 2) + .toList(); + for (int i = 0; i < photoBackgoundImages.length; i++) { + var element = photoBackgoundImages[i]; + _backgoundImages[i] = ImageItem( + element.url ?? "", + i, + element.status ?? 0, + ); + } + _uploadPhotoImages = + (AccountStorage().getCurrentUser()?.userProfile?.personalPhotos ?? []) + .where((t) => t.status != 2) + .toList(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/room/at_icon_room_settig_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: true, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.editProfile, + actions: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 5.w), + margin: EdgeInsets.only(right: 8.w), + child: text( + ATAppLocalizations.of(context)!.save, + textColor: Colors.black45, + fontSize: 15.sp, + ), + ), + onTap: () { + submit(context); + }, + ), + ], + ), + body: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom + 20.w, + ), + child: Consumer( + builder: (context, ref, child) { + return Container( + margin: EdgeInsets.symmetric( + horizontal: 15.w, + vertical: 25.w, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + height: 12.w, + width: 3.w, + decoration: BoxDecoration( + color: ChatVibeTheme.primaryColor, + borderRadius: BorderRadius.circular(2.w), + ), + ), + SizedBox(width: 5.w), + text( + "${ATAppLocalizations.of(context)!.spaceBackground}:", + fontWeight: FontWeight.w600, + fontSize: 15.sp, + textColor: Colors.black, + ), + ], + ), + SizedBox(height: 5.w), + DraggableImageGrid((List images) { + _backgoundImages = images; + isEdit = true; + }), + SizedBox(height: 10.w), + Row( + children: [ + Container( + height: 12.w, + width: 3.w, + decoration: BoxDecoration( + color: ChatVibeTheme.primaryColor, + borderRadius: BorderRadius.circular(2.w), + ), + ), + SizedBox(width: 5.w), + text( + "${ATAppLocalizations.of(context)!.information}:", + fontWeight: FontWeight.w600, + fontSize: 15.sp, + textColor: Colors.black, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + child: Stack( + alignment: AlignmentDirectional.bottomEnd, + children: [ + head( + url: userCover ?? "", + width: 100.w, + borderRadius: BorderRadius.circular(12.w), + ), + PositionedDirectional( + end: 15.w, + bottom: 12.w, + child: Image.asset( + "atu_images/general/at_icon_edit_head_camera_alt.png", + width: 20.w, + ), + ), + ], + ), + onTap: () { + ATPickUtils.pickImageProcess(context, ( + bool success, + String url, + ) { + if (success) { + setState(() { + userCover = url; + isEdit = true; + }); + } + }); + }, + ), + ], + ), + SizedBox(height: 10.w), + text( + "${ATAppLocalizations.of(context)!.myPhoto}:", + fontWeight: FontWeight.w600, + fontSize: 15.sp, + textColor: Colors.black, + ), + SizedBox(height: 10.w), + DraggableImagePersonPhotoGrid( + callback: (List images) { + _uploadPhotoImages = images; + isEdit = true; + }, + ), + SizedBox(height: 15.w), + text( + ATAppLocalizations.of(context)!.nickName, + textColor: Colors.black38, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + ), + TextField( + controller: _nickNameController, + onChanged: (text) { + isEdit = true; + }, + inputFormatters: [ + ATCustomFilteringTextInputFormatter(), + ], + maxLength: 38, + decoration: InputDecoration( + hintStyle: TextStyle( + color: Colors.black45, + fontSize: 14.sp, + ), + contentPadding: EdgeInsets.only(top: 0.w), + counterText: '', + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + // enabledBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // focusedBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // prefixIcon: Padding(padding: EdgeInsets.all(8.w), child: Image.asset("images/login/at_icon_phone.png",width: 20.w, height: 20.w,fit: BoxFit.fill,),), + fillColor: Colors.black45, + ), + style: TextStyle( + fontSize: sp(15), + color: Colors.black, + textBaseline: TextBaseline.alphabetic, + ), + ), + Divider( + color: Colors.black12, + thickness: 0.5, + height: 28.w, + ), + GestureDetector( + onTap: () { + _showSexDialog(); + }, + behavior: HitTestBehavior.opaque, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + text( + ATAppLocalizations.of(context)!.gender, + textColor: Colors.black38, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + Spacer(), + ], + ), + SizedBox(height: 5.w), + xb(sex, color: Colors.black54), + ], + ), + ), + Divider( + color: Colors.black12, + thickness: 0.5, + height: 28.w, + ), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + _selectDate(); + }, + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text( + ATAppLocalizations.of(context)!.dateOfBirth, + textColor: Colors.black38, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + ), + ], + ), + SizedBox(height: 5.w), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text( + "$bornMonth-$bornDay-$bornYear", + textColor: Colors.black, + fontSize: 14.sp, + ), + ], + ), + ], + ), + ), + Divider( + color: Colors.black12, + thickness: 0.5, + height: 28.w, + ), + GestureDetector( + onTap: () { + ATNavigatorUtils.push( + context, + CountryRoute.country, + replace: false, + ); + }, + behavior: HitTestBehavior.opaque, + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text( + ATAppLocalizations.of(context)!.country, + textColor: Colors.black38, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + Consumer( + builder: (_, provider, __) { + return Container( + padding: EdgeInsets.only( + right: width(12), + ), + height: 43.w, + width: 245.w, + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.all( + Radius.circular(height(32)), + ), + ), + child: + provider.selectCountryInfo != null + ? Row( + mainAxisSize: + MainAxisSize.min, + children: [ + CustomCachedImage( + imageUrl: + provider + .selectCountryInfo! + .nationalFlag ?? + "", + width: 26.w, + height: 16.w, + ), + SizedBox(width: 5.w), + text( + provider + .selectCountryInfo! + .aliasName ?? + "", + textColor: Colors.black, + fontSize: 14.sp, + ), + ], + ) + : Row( + mainAxisSize: + MainAxisSize.min, + children: [ + netImage( + url: + provider + .getCountryByName( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.countryName ?? + "", + ) + ?.nationalFlag ?? + "", + width: 26.w, + height: 16.w, + ), + SizedBox(width: 5.w), + text( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.countryName ?? + "", + textColor: Colors.black, + fontSize: 14.sp, + ), + ], + ), + ); + }, + ), + ], + ), + Spacer(), + Icon( + Icons.arrow_forward_ios, + size: 14.w, + color: Colors.grey, + ), + ], + ), + ), + text( + ATAppLocalizations.of(context)!.bio, + textColor: Colors.black38, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + ), + TextField( + controller: _bioController, + onChanged: (text) { + isEdit = true; + }, + maxLength: 50, + decoration: InputDecoration( + hintStyle: TextStyle( + color: Colors.black45, + fontSize: 14.sp, + ), + contentPadding: EdgeInsets.only(top: 0.w), + counterText: '', + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + // enabledBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // focusedBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // prefixIcon: Padding(padding: EdgeInsets.all(8.w), child: Image.asset("images/login/at_icon_phone.png",width: 20.w, height: 20.w,fit: BoxFit.fill,),), + fillColor: Colors.black45, + ), + style: TextStyle( + fontSize: sp(15), + color: Colors.black, + textBaseline: TextBaseline.alphabetic, + ), + ), + Divider( + color: Colors.black12, + thickness: 0.5, + height: 28.w, + ), + + text( + ATAppLocalizations.of(context)!.hobby, + textColor: Colors.black38, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + ), + TextField( + controller: _hobbyController, + onChanged: (text) { + isEdit = true; + }, + maxLength: 50, + decoration: InputDecoration( + hintStyle: TextStyle( + color: Colors.black45, + fontSize: 14.sp, + ), + contentPadding: EdgeInsets.only(top: 0.w), + counterText: '', + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + // enabledBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // focusedBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // prefixIcon: Padding(padding: EdgeInsets.all(8.w), child: Image.asset("images/login/at_icon_phone.png",width: 20.w, height: 20.w,fit: BoxFit.fill,),), + fillColor: Colors.black45, + ), + style: TextStyle( + fontSize: sp(15), + color: Colors.black, + textBaseline: TextBaseline.alphabetic, + ), + ), + Divider( + color: Colors.black12, + thickness: 0.5, + height: 28.w, + ), + ], + ), + ); + }, + ), + ); + }, + ), + ), + ], + ); + } + + Future _selectDate() async { + showCupertinoModalPopup( + context: context, + builder: + (context) => SafeArea( + top: false, + child: Container( + height: 240.w, + padding: EdgeInsets.only(top: 6), + color: CupertinoColors.systemBackground, + child: Column( + children: [ + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 10.w), + GestureDetector( + child: Container( + alignment: Alignment.topCenter, + child: text( + ATAppLocalizations.of(context)!.cancel, + textColor: Colors.black87, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + Navigator.of(context).pop(); + }, + ), + Expanded( + child: Container( + alignment: Alignment.topCenter, + child: text( + ATAppLocalizations.of(context)!.birthday, + textColor: Colors.black87, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + GestureDetector( + child: Container( + alignment: Alignment.topCenter, + child: text( + ATAppLocalizations.of(context)!.confirm, + textColor: Color(0xff7726FF), + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + num m = birthdayDate!.month; + if (m < 10) { + bornMonth = "0$m"; + } else { + bornMonth = "$m"; + } + num d = birthdayDate!.day; + if (d < 10) { + bornDay = "0$d"; + } else { + bornDay = "$d"; + } + bornYear = "${birthdayDate?.year}"; + setState(() {}); + Navigator.of(context).pop(); + }, + ), + SizedBox(width: 10.w), + ], + ), + Expanded( + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.date, + initialDateTime: _getBefor18(), + maximumDate: _getBefor18(), + onDateTimeChanged: (date) { + isEdit = true; + birthdayDate = date; + age = DateTime.now().year - date.year; + }, + ), + ), + ], + ), + ), + ), + ); + } + + DateTime _getBefor18() { + DateTime currentDate = DateTime.now(); + DateTime eighteenYearsAgo = DateTime( + currentDate.year - 18, + currentDate.month, + currentDate.day, + currentDate.hour, + currentDate.minute, + currentDate.second, + currentDate.millisecond, + currentDate.microsecond, + ); + return eighteenYearsAgo; + } + + int sTime = 0; + + void submit(BuildContext context) async { + if (_nickNameController.text.isEmpty) { + ATTts.show(ATAppLocalizations.of(context)!.pleaseEnterNickname); + return; + } + if (Provider.of( + context, + listen: false, + ).selectCountryInfo != + null) { + isEdit = true; + } + List backgroundPhotos = []; + for (var value in _backgoundImages) { + if (value.path.isNotEmpty) { + backgroundPhotos.add(value.path); + } + } + List personPhotos = []; + for (var value in _uploadPhotoImages) { + if ((value.url?.isNotEmpty ?? false)) { + personPhotos.add(value.url ?? ""); + } + } + int bTime = DateTime.now().millisecondsSinceEpoch; + if (bTime - sTime > 5000) { + sTime = bTime; + if (isEdit) { + ATLoadingManager.exhibitOperation(); + try { + await AccountRepository().updateUserInfo( + userAvatar: userCover, + userSex: sex, + userNickname: _nickNameController.text, + age: age, + bornDay: birthdayDate?.day, + bornMonth: birthdayDate?.month, + bornYear: birthdayDate?.year, + hobby: _hobbyController.text, + autograph: _bioController.text, + countryId: + Provider.of( + context, + listen: false, + ).selectCountryInfo?.id, + backgroundPhotos: backgroundPhotos, + personalPhotos: personPhotos, + ); + Provider.of(context, listen: false).needUpDataUserInfo = + true; + } catch (e) { + debugPrint(e.toString()); + ATLoadingManager.veilRoutine(); + } + Provider.of(context, listen: false).getMyUserInfo(); + ATLoadingManager.veilRoutine(); + } + ATNavigatorUtils.popUntil(context, ModalRoute.withName(ATRoutes.home)); + } + } + + void _showSexDialog() { + showCenterDialog( + context, + Container( + height: 150.w, + width: ScreenUtil().screenWidth * 0.7, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(12)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: 15.w), + text( + ATAppLocalizations.of(context)!.pleaseSelectYourGender, + fontSize: 15.sp, + textColor: Colors.black, + ), + SizedBox(height: 5.w), + Divider(color: Colors.black12, thickness: 0.5, height: 14.w), + SizedBox(height: 8.w), + GestureDetector( + onTap: () { + if (sex != 1) { + isEdit = true; + } + sex = 1; + setState(() {}); + ATNavigatorUtils.goBack(context); + }, + behavior: HitTestBehavior.opaque, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + xb(1, color: Colors.black54, height: 18.w), + SizedBox(width: 5.w), + text( + ATAppLocalizations.of(context)!.man, + textColor: Colors.black, + fontSize: 15.sp, + ), + ], + ), + ), + SizedBox(height: 8.w), + Divider(color: Colors.black12, thickness: 0.5, height: 14.w), + SizedBox(height: 8.w), + GestureDetector( + onTap: () { + if (sex != 0) { + isEdit = true; + } + sex = 0; + setState(() {}); + ATNavigatorUtils.goBack(context); + }, + behavior: HitTestBehavior.opaque, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + xb(0, color: Colors.black54, height: 18.w), + SizedBox(width: 5.w), + text( + ATAppLocalizations.of(context)!.woman, + textColor: Colors.black, + fontSize: 15.sp, + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/chatvibe_features/user/edit/edit_user_info_page2.dart b/lib/chatvibe_features/user/edit/edit_user_info_page2.dart new file mode 100644 index 0000000..f3d0569 --- /dev/null +++ b/lib/chatvibe_features/user/edit/edit_user_info_page2.dart @@ -0,0 +1,804 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/main.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_lk_dialog_util.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import '../../../chatvibe_core/utilities/at_pick_utils.dart'; +import '../../../chatvibe_data/models/enum/at_vip_type.dart'; +import '../../../chatvibe_domain/models/res/country_res.dart'; +import '../../../chatvibe_domain/usecases/at_accurate_length_limiting_textInput_formatter.dart'; +import '../../country/country_route.dart'; + +class EditUserInfoPage2 extends StatefulWidget { + const EditUserInfoPage2({Key? key}) : super(key: key); + + @override + _EditUserInfoPage2State createState() => _EditUserInfoPage2State(); +} + +class _EditUserInfoPage2State extends State + with SingleTickerProviderStateMixin { + String? userCover; + + String? bornMonth; + String? autograph; + String? hobby; + String? bornDay; + String? bornYear; + String nickName = ""; + DateTime? birthdayDate; + num? sex; + num? age; + Country? country; + + @override + void initState() { + super.initState(); + userCover = + AccountStorage().getCurrentUser()?.userProfile?.userAvatar ?? ""; + nickName = + AccountStorage().getCurrentUser()?.userProfile?.userNickname ?? ""; + autograph = AccountStorage().getCurrentUser()?.userProfile?.autograph ?? ""; + hobby = AccountStorage().getCurrentUser()?.userProfile?.hobby ?? ""; + country = Provider.of( + context, + listen: false, + ).getCountryByName( + AccountStorage().getCurrentUser()?.userProfile?.countryName ?? "", + ); + num m = AccountStorage().getCurrentUser()?.userProfile?.bornMonth ?? 0; + if (m < 10) { + bornMonth = "0$m"; + } else { + bornMonth = "$m"; + } + num d = AccountStorage().getCurrentUser()?.userProfile?.bornDay ?? 0; + if (d < 10) { + bornDay = "0$d"; + } else { + bornDay = "$d"; + } + bornYear = "${AccountStorage().getCurrentUser()?.userProfile?.bornYear}"; + age = + DateTime.now().year - + (AccountStorage().getCurrentUser()?.userProfile?.bornYear ?? 0); + sex = AccountStorage().getCurrentUser()?.userProfile?.userSex; + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/person/at_icon_edit_userinfo_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: true, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.editProfile, + actions: [], + ), + body: Consumer( + builder: (context, ref, child) { + return Column( + spacing: 10.w, + children: [ + ATDebounceWidget( + child: Row( + children: [ + SizedBox(width: 15.w), + text( + ATAppLocalizations.of(context)!.profilePhoto, + textColor: Colors.black, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + Spacer(), + Stack( + alignment: AlignmentDirectional.bottomEnd, + children: [ + netImage( + url: userCover ?? "", + width: 45.w, + height: 45.w, + shape: BoxShape.circle, + ), + Image.asset( + "atu_images/person/at_icon_edit_userinfo_camera.png", + width: 15.w, + height: 15.w, + ), + ], + ), + Icon( + Icons.keyboard_arrow_right, + size: 20.w, + color: Colors.black, + ), + SizedBox(width: 8.w), + ], + ), + onTap: () { + _showSelecteImageMenu(); + }, + ), + SizedBox(height: 3.w), + _buildItem( + "${ATAppLocalizations.of(context)!.userName}:", + nickName ?? "", + () { + _showInputBioHobby(nickName, 3); + }, + ), + _buildItem( + "${ATAppLocalizations.of(context)!.gender}:", + sex == 1 + ? ATAppLocalizations.of(context)!.man + : ATAppLocalizations.of(context)!.woman, + () { + _showSexDialog(); + }, + ), + + _buildItem( + "${ATAppLocalizations.of(context)!.birthday}:", + "$bornYear-$bornMonth-$bornDay", + () { + _selectDate(); + }, + ), + _buildControlItem(), + + _buildItem( + "${ATAppLocalizations.of(context)!.bio}:", + autograph ?? "", + () { + _showInputBioHobby(autograph ?? "", 1); + }, + ), + + _buildItem( + "${ATAppLocalizations.of(context)!.hobby}:", + hobby ?? "", + () { + _showInputBioHobby(hobby ?? "", 2); + }, + ), + ], + ); + }, + ), + ), + ], + ); + } + + Future _selectDate() async { + SmartDialog.show( + tag: "showSelectDate", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + onDismiss: () { + submit(context); + }, + builder: (_) { + return SafeArea( + top: false, + child: Container( + height: 240.w, + padding: EdgeInsets.only(top: 6), + color: CupertinoColors.systemBackground, + child: Column( + children: [ + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 10.w), + GestureDetector( + child: Container( + alignment: Alignment.topCenter, + child: text( + ATAppLocalizations.of(context)!.cancel, + textColor: Colors.black87, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showSelectDate"); + }, + ), + Expanded( + child: Container( + alignment: Alignment.topCenter, + child: text( + ATAppLocalizations.of(context)!.birthday, + textColor: Colors.black87, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + GestureDetector( + child: Container( + alignment: Alignment.topCenter, + child: text( + ATAppLocalizations.of(context)!.confirm, + textColor: Color(0xff7726FF), + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + num m = birthdayDate!.month; + if (m < 10) { + bornMonth = "0$m"; + } else { + bornMonth = "$m"; + } + num d = birthdayDate!.day; + if (d < 10) { + bornDay = "0$d"; + } else { + bornDay = "$d"; + } + bornYear = "${birthdayDate?.year}"; + setState(() {}); + SmartDialog.dismiss(tag: "showSelectDate"); + }, + ), + SizedBox(width: 10.w), + ], + ), + Expanded( + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.date, + initialDateTime: _getBefor18(), + maximumDate: _getBefor18(), + onDateTimeChanged: (date) { + birthdayDate = date; + age = DateTime.now().year - date.year; + }, + ), + ), + ], + ), + ), + ); + }, + ); + } + + DateTime _getBefor18() { + DateTime currentDate = DateTime.now(); + DateTime eighteenYearsAgo = DateTime( + currentDate.year - 18, + currentDate.month, + currentDate.day, + currentDate.hour, + currentDate.minute, + currentDate.second, + currentDate.millisecond, + currentDate.microsecond, + ); + return eighteenYearsAgo; + } + + int sTime = 0; + + void submit(BuildContext context) async { + if (nickName.isEmpty) { + ATTts.show(ATAppLocalizations.of(context)!.pleaseEnterNickname); + return; + } + int bTime = DateTime.now().millisecondsSinceEpoch; + if (bTime - sTime > 5000) { + sTime = bTime; + ATLoadingManager.exhibitOperation(); + try { + await AccountRepository().updateUserInfo( + userAvatar: userCover, + userSex: sex, + userNickname: nickName, + age: age, + bornDay: birthdayDate?.day, + bornMonth: birthdayDate?.month, + bornYear: birthdayDate?.year, + hobby: hobby, + autograph: autograph, + countryId: country?.id, + ); + Provider.of(context, listen: false).needUpDataUserInfo = + true; + ATTts.show(ATAppLocalizations.of(context)!.operationSuccessful); + } catch (e) { + debugPrint(e.toString()); + ATLoadingManager.veilRoutine(); + } + Provider.of( + context, + listen: false, + ).getMyUserInfo(); + ATLoadingManager.veilRoutine(); + } + } + + void _showSexDialog() { + showCenterDialog( + context, + Container( + height: 150.w, + width: ScreenUtil().screenWidth * 0.7, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(12)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: 15.w), + text( + ATAppLocalizations.of(context)!.pleaseSelectYourGender, + fontSize: 15.sp, + textColor: Colors.black, + ), + SizedBox(height: 5.w), + Divider(color: Colors.black12, thickness: 0.5, height: 14.w), + SizedBox(height: 8.w), + GestureDetector( + onTap: () { + sex = 1; + setState(() {}); + submit(context); + ATNavigatorUtils.goBack(context); + }, + behavior: HitTestBehavior.opaque, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + xb(1, color: Colors.black54, height: 18.w), + SizedBox(width: 5.w), + text( + ATAppLocalizations.of(context)!.man, + textColor: Colors.black, + fontSize: 15.sp, + ), + ], + ), + ), + SizedBox(height: 8.w), + Divider(color: Colors.black12, thickness: 0.5, height: 14.w), + SizedBox(height: 8.w), + GestureDetector( + onTap: () { + sex = 0; + setState(() {}); + submit(context); + ATNavigatorUtils.goBack(context); + }, + behavior: HitTestBehavior.opaque, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + xb(0, color: Colors.black54, height: 18.w), + SizedBox(width: 5.w), + text( + ATAppLocalizations.of(context)!.woman, + textColor: Colors.black, + fontSize: 15.sp, + ), + ], + ), + ), + ], + ), + ), + ); + } + + _buildItem(String title, String value, Function() func) { + return ATDebounceWidget( + child: Column( + children: [ + Row( + children: [ + SizedBox(width: 15.w), + text( + title, + textColor: Colors.black, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + Expanded( + child: Container( + alignment: AlignmentDirectional.centerEnd, + child: text( + value, + textColor: Colors.black38, + fontSize: 15.sp, + ), + ), + ), + Icon( + Icons.keyboard_arrow_right, + size: 20.w, + color: Colors.black38, + ), + SizedBox(width: 8.w), + ], + ), + SizedBox(height: 8.w), + Container( + margin: EdgeInsets.only(left: 15.w, right: 15.w), + color: Colors.black12, + height: 0.5.w, + width: ScreenUtil().screenWidth, + ), + ], + ), + onTap: () { + func.call(); + }, + ); + } + + _buildControlItem() { + return ATDebounceWidget( + child: Column( + children: [ + Row( + children: [ + SizedBox(width: 15.w), + text( + ATAppLocalizations.of(context)!.country, + textColor: Colors.black, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + Spacer(), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + netImage( + url: country?.nationalFlag ?? "", + width: 26.w, + height: 16.w, + borderRadius: BorderRadius.all(Radius.circular(2.w)), + ), + SizedBox(width: 5.w), + text( + country?.countryName ?? "", + textColor: Colors.black38, + fontSize: 15.sp, + ), + ], + ), + Icon( + Icons.keyboard_arrow_right, + size: 20.w, + color: Colors.black38, + ), + SizedBox(width: 8.w), + ], + ), + SizedBox(height: 8.w), + Container( + margin: EdgeInsets.only(left: 15.w, right: 15.w), + color: Colors.black12, + height: 0.5.w, + width: ScreenUtil().screenWidth, + ), + ], + ), + onTap: () { + ATNavigatorUtils.push( + context, + CountryRoute.country, + replace: false, + ).then((res) { + var c = + Provider.of( + navigatorKey.currentState!.context, + listen: false, + ).selectCountryInfo; + if (c != null) { + country = c; + submit(navigatorKey.currentState!.context); + setState(() {}); + } + }); + }, + ); + } + + void _showInputBioHobby(String content, int type) { + SmartDialog.dismiss(tag: "showInputBioHobby"); + TextEditingController _inputController = TextEditingController(); + _inputController.text = content; + SmartDialog.show( + tag: "showInputBioHobby", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return Container( + height: 250.w, + margin: EdgeInsets.symmetric(horizontal: 20.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10.w), + ), + child: Column( + children: [ + SizedBox(height: 8.w), + Row( + children: [ + SizedBox(width: 10.w), + GestureDetector( + child: Icon(Icons.close, color: Colors.black, size: 20.w), + onTap: () { + SmartDialog.dismiss(tag: "showInputBioHobby"); + }, + ), + Spacer(), + text( + ATAppLocalizations.of(context)!.pleaseEnterContent, + textColor: Colors.black, + fontSize: 15.sp, + ), + Spacer(), + SizedBox(width: 20.w), + ], + ), + SizedBox(height: 15.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 25.w), + padding: EdgeInsets.only(top: 5.w, left: 12.w, right: 12.w), + alignment: AlignmentDirectional.centerStart, + height: 110.w, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(8.w)), + border: Border.all(color: Color(0xffE6E6E6), width: 0.5.w), + ), + child: TextField( + controller: _inputController, + maxLines: 5, + inputFormatters: [ + ATAccurateLengthLimitingTextInputFormatter(50), + ], + decoration: InputDecoration( + isDense: true, + hintText: "", + hintStyle: TextStyle( + color: Colors.black45, + fontSize: 14.sp, + ), + contentPadding: EdgeInsets.only(top: 0.w), + counterText: '', + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + // enabledBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // focusedBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // prefixIcon: Padding(padding: EdgeInsets.all(8.w), child: Image.asset("images/login/at_icon_phone.png",width: 20.w, height: 20.w,fit: BoxFit.fill,),), + fillColor: Colors.black45, + ), + style: TextStyle( + fontSize: 15.w, + color: Colors.black, + textBaseline: TextBaseline.alphabetic, + ), + ), + ), + SizedBox(height: 15.w), + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + margin: EdgeInsets.symmetric(horizontal: 60.w), + height: 42.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20.w), + gradient: LinearGradient( + colors: [ + const Color(0xffFEB219), // rgb(254, 178, 25) + const Color(0xffFF9326), + ], + ), + ), + child: text( + ATAppLocalizations.of(context)!.confirm, + textColor: Colors.white, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showInputBioHobby"); + if (type == 1) { + autograph = _inputController.text; + setState(() {}); + } else if (type == 2) { + hobby = _inputController.text; + setState(() {}); + } else if (type == 3) { + if (_inputController.text.isNotEmpty) { + nickName = _inputController.text; + setState(() {}); + } else { + ATTts.show( + ATAppLocalizations.of(context)!.pleaseEnterNickname, + ); + return; + } + } + submit(navigatorKey.currentState!.context); + }, + ), + ], + ), + ); + }, + ); + } + + void _showSelecteImageMenu() { + SmartDialog.show( + tag: "showSelecteImageMenuDialog", + alignment: Alignment.bottomCenter, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return SafeArea( + top: false, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + ), + height: 180.w, + child: Column( + children: [ + SizedBox(height: 20.w), + ATDebounceWidget( + child: Row( + children: [ + Spacer(), + text( + ATAppLocalizations.of(context)!.uploadProfilePicture, + textColor: Colors.black, + fontSize: 15.sp, + ), + Spacer(), + ], + ), + onTap: () { + SmartDialog.dismiss(tag: "showSelecteImageMenuDialog"); + ATPickUtils.pickImageProcess(context, ( + bool success, + String url, + ) { + if (success) { + userCover = url; + setState(() {}); + submit(context); + } + }); + }, + ), + SizedBox(height: 20.w), + ATDebounceWidget( + child: Row( + children: [ + Spacer(), + text( + ATAppLocalizations.of(context)!.uploadGifAvatar, + textColor: Colors.black, + fontSize: 15.sp, + ), + Spacer(), + ], + ), + onTap: () { + SmartDialog.dismiss(tag: "showSelecteImageMenuDialog"); + if (AccountStorage().getVIP()?.name == + ATVIPType.EARL.name || + AccountStorage().getVIP()?.name == + ATVIPType.MARQUIS.name || + AccountStorage().getVIP()?.name == + ATVIPType.DUKE.name || + AccountStorage().getVIP()?.name == + ATVIPType.KING.name) { + ATPickUtils.pickImageProcess( + context, + neeCrop: false, + canUploadGif: true, + (bool success, String url) { + if (success) { + userCover = url; + setState(() {}); + submit(context); + } + }, + ); + } else { + ATTts.show( + ATAppLocalizations.of( + context, + )!.pleaseUpgradeYourVipLevelFirst, + ); + } + }, + ), + SizedBox(height: 20.w), + GestureDetector( + child: Container( + decoration: BoxDecoration( + color: Color(0xffF2F2F2), + borderRadius: BorderRadius.all(Radius.circular(32.w)), + ), + width: ScreenUtil().screenWidth * 0.7, + padding: EdgeInsets.symmetric(vertical: 10.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + text( + ATAppLocalizations.of(context)!.cancel, + textColor: Color(0xffB1B1B1), + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + ], + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showSelecteImageMenuDialog"); + }, + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/chatvibe_features/user/fans/fans_user_list_page.dart b/lib/chatvibe_features/user/fans/fans_user_list_page.dart new file mode 100644 index 0000000..0dc9366 --- /dev/null +++ b/lib/chatvibe_features/user/fans/fans_user_list_page.dart @@ -0,0 +1,229 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/follow_user_res.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + + +class FansUserListPage extends ATPageList { + final bool removePreviousPersonDetail; + + const FansUserListPage({ + super.key, + this.removePreviousPersonDetail = false, + }); + + @override + _FansUserListPageState createState() => _FansUserListPageState(); +} + +class _FansUserListPageState + extends ATPageListState { + String? lastId; + bool _didRemovePreviousPersonDetail = false; + + @override + void initState() { + super.initState(); + enablePullUp = true; + backgroundColor = Colors.transparent; + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/person/at_icon_edit_userinfo_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.fansList, + actions: [], + ), + body: buildList(context), + ), + ], + ); + } + + @override + Widget buildItem(FollowUserRes res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration(), + child: Row( + children: [ + SizedBox(width: 10.w), + head( + url: res.userProfile?.userAvatar ?? "", + width: 55.w, + headdress: res.userProfile?.getHeaddress()?.sourceUrl, + ), + SizedBox(width: 5.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + chatvibeNickNameText( + maxWidth: 160.w, + res.userProfile?.userNickname ?? "", + fontSize: 14.sp, + textColor: Colors.black, + type: res.userProfile?.getVIP()?.name ?? "", + needScroll: + (res.userProfile?.userNickname?.characters.length ?? + 0) > + 14, + ), + SizedBox(width: 3.w), + res.userProfile?.getVIP() != null + ? netImage( + url: res.userProfile?.getVIP()?.cover ?? "", + width: 25.w, + height: 25.w, + ) + : Container(), + SizedBox(width: 3.w), + Container( + width: (res.userProfile?.age ?? 0) > 999 ? 56.w : 46.w, + height: 23.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + res.userProfile?.userSex == 0 + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png", + ), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + xb(res.userProfile?.userSex), + text( + "${res.userProfile?.age}", + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 3.w), + ], + ), + ), + ], + ), + Row( + children: [ + GestureDetector( + child: SizedBox( + child: Row( + children: [ + getIdIcon( + res.userProfile?.wealthLevel ?? 0, + width: 28.w, + height: 28.w, + textColor: Color(0xFF333333) + ), + chatvibeNickNameText( + maxWidth: 135.w, + res.userProfile?.getID() ?? "", + textColor: Color(0xFF333333), + fontSize: 12.sp, + fontWeight: FontWeight.w600, + type: res.userProfile?.getVIP()?.name ?? "", + needScroll: + (res.userProfile + ?.getID() + .characters + .length ?? + 0) > + 13, + ), + ], + ), + ), + onTap: () { + Clipboard.setData( + ClipboardData(text: res.userProfile?.getID() ?? ""), + ); + ATTts.show( + ATAppLocalizations.of(context)!.copiedToClipboard, + ); + }, + ), + ], + ), + ], + ), + ), + SizedBox(width: 15.w), + ], + ), + ), + onTap: () { + if (!_didRemovePreviousPersonDetail && + (widget as FansUserListPage).removePreviousPersonDetail) { + _didRemovePreviousPersonDetail = true; + ATNavigatorUtils.removeRouteBelowCurrent(context); + } + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == res.userProfile?.id}&tageId=${res.userProfile?.id}", + ); + }, + ); + } + + @override + builderDivider() { + // return Divider( + // height: 1.w, + // color: Color(0xff3D3277).withOpacity(0.5), + // indent: 15.w, + // ); + return Container(height: 8.w); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + if (page == 1) { + lastId = null; + } + try { + var fansList = await AccountRepository().fansMyList(lastId: lastId); + if (fansList.isNotEmpty) { + lastId = fansList.last.id; + } + onSuccess(fansList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } +} diff --git a/lib/chatvibe_features/user/follow/follow_user_list_page.dart b/lib/chatvibe_features/user/follow/follow_user_list_page.dart new file mode 100644 index 0000000..b2f439d --- /dev/null +++ b/lib/chatvibe_features/user/follow/follow_user_list_page.dart @@ -0,0 +1,228 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/follow_user_res.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +class FollowUserListPage extends ATPageList { + final bool removePreviousPersonDetail; + + const FollowUserListPage({ + super.key, + this.removePreviousPersonDetail = false, + }); + + @override + _FollowUserListPageState createState() => _FollowUserListPageState(); +} + +class _FollowUserListPageState + extends ATPageListState { + String? lastId; + bool _didRemovePreviousPersonDetail = false; + + @override + void initState() { + super.initState(); + enablePullUp = true; + backgroundColor = Colors.transparent; + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/person/at_icon_edit_userinfo_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.followList, + actions: [], + ), + body: buildList(context), + ), + ], + ); + } + + @override + Widget buildItem(FollowUserRes res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration(), + child: Row( + children: [ + SizedBox(width: 10.w), + head( + url: res.userProfile?.userAvatar ?? "", + width: 55.w, + headdress: res.userProfile?.getHeaddress()?.sourceUrl, + ), + SizedBox(width: 5.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + chatvibeNickNameText( + maxWidth: 160.w, + res.userProfile?.userNickname ?? "", + fontSize: 14.sp, + textColor: Colors.black, + type: res.userProfile?.getVIP()?.name ?? "", + needScroll: + (res.userProfile?.userNickname?.characters.length ?? + 0) > + 14, + ), + SizedBox(width: 3.w), + res.userProfile?.getVIP() != null + ? netImage( + url: res.userProfile?.getVIP()?.cover ?? "", + width: 25.w, + height: 25.w, + ) + : Container(), + SizedBox(width: 3.w), + Container( + width: (res.userProfile?.age ?? 0) > 999 ? 56.w : 46.w, + height: 23.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + res.userProfile?.userSex == 0 + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png", + ), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + xb(res.userProfile?.userSex), + text( + "${res.userProfile?.age}", + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 3.w), + ], + ), + ), + ], + ), + Row( + children: [ + GestureDetector( + child: SizedBox( + child: Row( + children: [ + getIdIcon( + res.userProfile?.wealthLevel ?? 0, + width: 28.w, + height: 28.w, + textColor: Color(0xFF333333) + ), + chatvibeNickNameText( + maxWidth: 135.w, + res.userProfile?.getID() ?? "", + textColor: Color(0xFF333333), + fontSize: 12.sp, + fontWeight: FontWeight.w600, + type: res.userProfile?.getVIP()?.name ?? "", + needScroll: + (res.userProfile + ?.getID() + .characters + .length ?? + 0) > + 13, + ), + ], + ), + ), + onTap: () { + Clipboard.setData( + ClipboardData(text: res.userProfile?.getID() ?? ""), + ); + ATTts.show( + ATAppLocalizations.of(context)!.copiedToClipboard, + ); + }, + ), + ], + ), + ], + ), + ), + SizedBox(width: 15.w), + ], + ), + ), + onTap: () { + if (!_didRemovePreviousPersonDetail && + (widget as FollowUserListPage).removePreviousPersonDetail) { + _didRemovePreviousPersonDetail = true; + ATNavigatorUtils.removeRouteBelowCurrent(context); + } + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == res.userProfile?.id}&tageId=${res.userProfile?.id}", + ); + }, + ); + } + + @override + builderDivider() { + // return Divider( + // height: 1.w, + // color: Color(0xff3D3277).withOpacity(0.5), + // indent: 15.w, + // ); + return Container(height: 8.w); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + if (page == 1) { + lastId = null; + } + try { + var followList = await AccountRepository().followMyList(lastId: lastId); + if (followList.isNotEmpty) { + lastId = followList.last.id; + } + onSuccess(followList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } +} diff --git a/lib/chatvibe_features/user/level/level_page.dart b/lib/chatvibe_features/user/level/level_page.dart new file mode 100644 index 0000000..8ada2b1 --- /dev/null +++ b/lib/chatvibe_features/user/level/level_page.dart @@ -0,0 +1,106 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_features/user/level/user/user_level_page.dart'; +import 'package:aslan/chatvibe_features/user/level/wealth/wealth_level_page.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; + +import '../../../chatvibe_domain/usecases/at_fixed_width_tabIndicator.dart'; + +class LevelPage extends StatefulWidget { + @override + _LevelPageState createState() => _LevelPageState(); +} + +class _LevelPageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + final List _pages = [WealthLevelPage(), UserLevelPage()]; + int type = 0; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: _pages.length, vsync: this); + _tabController.addListener(() { + type = _tabController.index; + setState(() {}); + }); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + type == 0 + ? "atu_images/level/at_icon_wealth_level_bg.png" + : "atu_images/level/at_icon_user_level_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + appBar: ChatVibeStandardAppBar( + backButtonColor: Colors.white, + title: ATAppLocalizations.of(context)!.level, + actions: [], + ), + backgroundColor: Colors.transparent, + body: SafeArea( + top: false, + child: Column( + children: [ + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: TabBar( + tabs: + [ + ATAppLocalizations.of(context)!.wealthLevel, + ATAppLocalizations.of(context)!.userLevel, + ].map((e) => Tab(text: e)).toList(), + indicator: ATFixedWidthTabIndicator( + width: 20.w, + height: 4.w, + color: Colors.white, + ), + //indicatorColor: Color(0xFF9F44F9), + indicatorSize: TabBarIndicatorSize.label, + labelPadding: EdgeInsets.only(left: 20, right: 20), + //Tab之间的间距,默认是kTabLabelPadding + isScrollable: false, + controller: _tabController, + labelColor: Colors.white, + dividerColor: Colors.transparent, + unselectedLabelColor: Colors.white54, + labelStyle: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + ), + unselectedLabelStyle: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + ), + ), + SizedBox(width: 10.w), + ], + ), + Expanded( + child: TabBarView( + controller: _tabController, + children: _pages, + ), + ), + ], + ), + ), + ), + ], + ); + } +} diff --git a/lib/chatvibe_features/user/level/user/user_level_page.dart b/lib/chatvibe_features/user/level/user/user_level_page.dart new file mode 100644 index 0000000..4b67f7e --- /dev/null +++ b/lib/chatvibe_features/user/level/user/user_level_page.dart @@ -0,0 +1,507 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_string_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_user_level_exp_res.dart'; +import 'package:aslan/chatvibe_features/store/store_route.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; + +import '../../../../chatvibe_core/constants/at_global_config.dart'; +import '../../../../chatvibe_core/utilities/at_date_utils.dart'; +import '../../../../chatvibe_core/utilities/at_user_utils.dart'; +import '../../../../chatvibe_data/models/enum/at_level_type.dart'; +import '../../../../chatvibe_ui/components/text/at_text.dart'; +import '../../../../chatvibe_ui/theme/chatvibe_theme.dart'; + +class UserLevelPage extends StatefulWidget { + const UserLevelPage({super.key}); + + @override + State createState() => _UserLevelPageState(); +} + +class _UserLevelPageState extends State { + ATUserLevelExpRes? _levelExpRes; + + @override + void initState() { + super.initState(); + _loadLevelInfo(); + } + + Future _loadLevelInfo() async { + final value = await AccountRepository().userLevelConsumptionExp( + AccountStorage().getCurrentUser()?.userProfile?.id ?? "", + ATLevelType.CHARM.name, + ); + + if (!mounted) { + return; + } + + setState(() { + _levelExpRes = value; + }); + } + + @override + Widget build(BuildContext context) { + if (_levelExpRes == null) { + return SizedBox( + height: 420.h, + child: const Center( + child: CupertinoActivityIndicator(color: Colors.white24), + ), + ); + } + + return SingleChildScrollView( + padding: EdgeInsets.fromLTRB(12.w, 14.w, 12.w, 12.w), + child: Column( + children: [ + _buildTopSummaryCard(context), + SizedBox(height: 16.w), + _buildRewardCard(context), + ], + ), + ); + } + + Widget _buildTopSummaryCard(BuildContext context) { + final currentUser = AccountStorage().getCurrentUser()?.userProfile; + final int level = (_levelExpRes?.level ?? 0).toInt(); + final int nextLevel = level >= 50 ? 50 : level + 1; + return Column( + children: [ + Image.asset( + "atu_images/level/at_icon_user_level_tag${_userLevel2(level)}.png", + width: 88.w, + ), + SizedBox(height: 15.w), + ShaderMask( + blendMode: BlendMode.srcIn, + shaderCallback: + (bounds) => const LinearGradient( + colors: [Color(0xFFFED90A), Color(0xFFFFB31E)], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ).createShader(bounds), + child: Text( + "Lv$level", + style: TextStyle( + fontSize: 32.sp, + fontWeight: FontWeight.w800, + color: Colors.white, + height: 1, + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 12.w), + padding: EdgeInsets.fromLTRB(20.w, 12.w, 20.w, 12.w), + decoration: _buildCardDecoration( + beginColor: const Color(0xFF5d1b83), + middleColor: const Color(0xFF38104f), + endColor: const Color(0xFF1c0827), + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + child: Row( + children: [ + netImage( + url: currentUser?.userAvatar ?? "", + width: 45.w, + shape: BoxShape.circle, + ), + Expanded( + child: Column( + children: [ + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: text( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname ?? + "", + fontWeight: FontWeight.w700, + fontSize: 15.sp, + textColor: const Color(0xFFF6E62A), + ), + ), + ], + ), + SizedBox(height: 4.w), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(width: 10.w), + Expanded( + child: Text( + _buildUpgradeHint(nextLevel), + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w500, + color: const Color(0xFFF6E62A), + height: 1.2, + ), + ), + ), + ], + ), + SizedBox(height: 12.w), + Padding( + padding: EdgeInsetsDirectional.only( + start: 10.w, + end: 20.w, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(99.r), + child: SizedBox( + height: 6.w, + child: LinearProgressIndicator( + value: _buildProgressRatio(), + backgroundColor: Colors.white.withValues( + alpha: 0.16, + ), + valueColor: const AlwaysStoppedAnimation( + Color(0xFFFFC700), + ), + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ], + ); + } + + int _userLevel2(int level) { + int level2 = 1; + if (level > 0 && level <= 10) { + level2 = 1; + } else if (level > 10 && level <= 20) { + level2 = 2; + } else if (level > 20 && level <= 30) { + level2 = 3; + } else if (level > 30 && level <= 40) { + level2 = 4; + } else if (level > 40 && level <= 50) { + level2 = 5; + } + return level2; + } + + + + + Widget _buildRewardCard(BuildContext context) { + final localizations = ATAppLocalizations.of(context)!; + final int level = (_levelExpRes?.level ?? 0).toInt(); + return Container( + width: double.infinity, + padding: EdgeInsets.fromLTRB(0.w, 20.w, 0.w, 18.w), + decoration: _buildCardDecoration( + beginColor: const Color(0xFF41135c), + middleColor: const Color(0xFF250b34), + endColor: const Color(0xFF09030d), + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + child: Column( + children: [ + _buildPanelTitle( + title: localizations.medalAndAvatarFrameRewards, + subtitle: localizations.higherLevelFancierAvatarFrame, + ), + SizedBox(height: 16.w), + _buildSectionLabel(localizations.levelIcon), + SizedBox(height: 10.w), + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.w), + child: _buildLevelTrack(level), + ), + Image.asset( + "atu_images/level/at_icon_user_level_center_bg_1.png", + width: double.infinity, + fit: BoxFit.fitWidth, + ), + SizedBox(height: 18.w), + _buildSectionLabel(localizations.levelMedal), + SizedBox(height: 10.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 3.w), + child: Image.asset( + "atu_images/level/at_icon_user_level_center_bg_2.png", + width: double.infinity, + fit: BoxFit.fitWidth, + ), + ), + SizedBox(height: 18.w), + _buildPanelTitle( + title: localizations.howToUpgrade, + subtitle: localizations.spendCoinsToGainExperiencePoints, + ), + SizedBox(height: 16.w), + GestureDetector( + onTap: () { + ATNavigatorUtils.push(context, StoreRoute.list); + }, + child: Container( + width: 160.w, + height: 48.w, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(999.r), + border: Border.all(color: Colors.white, width: 1.w), + gradient: const LinearGradient( + colors: [Color(0xFF7705B4), Color(0xFFA93BE4)], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + boxShadow: [ + BoxShadow( + color: const Color(0xFF7705B4).withValues(alpha: 0.34), + blurRadius: 26, + offset: const Offset(0, 10), + ), + ], + ), + child: Text( + localizations.toConsume, + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w700, + color: Colors.white, + height: 1, + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildLevelTrack(int level) { + const labels = ["LV.1-10", "LV.11-20", "LV.21-30", "LV.31-40", "LV.41-50"]; + final int activeIndex = _resolveLevelStageIndex(level); + + return LayoutBuilder( + builder: (context, constraints) { + final double itemWidth = constraints.maxWidth / labels.length; + final double dotSize = 10.w; + final double trackTop = 20.w; + final double activeCenter = itemWidth * activeIndex + itemWidth / 2; + + return SizedBox( + height: 34.w, + child: Stack( + children: [ + for (int i = 0; i < labels.length; i++) + Positioned( + left: itemWidth * i, + width: itemWidth, + top: 0, + child: Center( + child: text( + labels[i], + textColor: + i == activeIndex + ? ChatVibeTheme.primaryLight + : Colors.white, + fontSize: 11.sp, + ), + ), + ), + Positioned( + left: 0, + right: 0, + top: trackTop + dotSize / 2 - 1.w, + child: Container( + height: 4.w, + decoration: BoxDecoration( + color: Colors.white24, + borderRadius: BorderRadius.circular(50.r), + ), + ), + ), + Positioned( + left: 0, + width: activeCenter, + top: trackTop + dotSize / 2 - 1.w, + child: Container( + height: 4.w, + decoration: BoxDecoration( + color: ChatVibeTheme.primaryLight, + borderRadius: BorderRadius.circular(50.r), + ), + ), + ), + for (int i = 0; i < labels.length; i++) + Positioned( + left: itemWidth * i + (itemWidth - dotSize) / 2, + top: trackTop + 1, + child: Image.asset( + i == activeIndex + ? "atu_images/level/at_icon_level_pd_en.png" + : "atu_images/level/at_icon_level_pd_no.png", + width: dotSize, + height: dotSize, + ), + ), + ], + ), + ); + }, + ); + } + + Widget _buildPanelTitle({required String title, required String subtitle}) { + return Column( + children: [ + ShaderMask( + blendMode: BlendMode.srcIn, + shaderCallback: + (bounds) => const LinearGradient( + colors: [Color(0xFFFDFCF8), Color(0xFFFFB31E)], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ).createShader(bounds), + child: Text( + title, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w800, + color: Colors.white, + height: 1.15, + ), + ), + ), + SizedBox(height: 6.w), + Text( + subtitle, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 11.sp, + fontWeight: FontWeight.w500, + color: Colors.white, + height: 1.3, + ), + ), + ], + ); + } + + Widget _buildSectionLabel(String title) { + return Row( + children: [ + SizedBox(width: 35.w), + Expanded(child: _buildGlowLine()), + SizedBox(width: 8.w), + ShaderMask( + blendMode: BlendMode.srcIn, + shaderCallback: + (bounds) => const LinearGradient( + colors: [Color(0xFFFDFCF8), Color(0xFFFFB31E)], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ).createShader(bounds), + child: Text( + title, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w800, + color: Colors.white, + height: 1, + ), + ), + ), + SizedBox(width: 8.w), + Expanded(child: _buildGlowLine(reverse: true)), + SizedBox(width: 35.w), + ], + ); + } + + Widget _buildGlowLine({bool reverse = false}) { + return Image.asset( + reverse + ? "atu_images/level/at_icon_wealth_title_01_right.png" + : "atu_images/level/at_icon_wealth_title_01_left.png", + height: 14.w, + fit: BoxFit.fill, + ); + } + + BoxDecoration _buildCardDecoration({ + required Color beginColor, + required Color middleColor, + required Color endColor, + required AlignmentGeometry begin, + required AlignmentGeometry end, + }) { + return BoxDecoration( + borderRadius: BorderRadius.circular(18.r), + border: Border.all(color: const Color(0xFFFFD673), width: 1.w), + gradient: LinearGradient( + colors: [beginColor, middleColor, endColor], + begin: begin, + end: end, + stops: const [0, 0.6, 1], + ), + ); + } + + String _buildUpgradeHint(int nextLevel) { + final int needExp = ATStringUtils.toIntFunction( + _levelExpRes?.nextExperience ?? "0", + ); + if (needExp <= 0) { + return "Max Level"; + } + return "You Need ${ATStringUtils.formatNumber(needExp)} EXP To Lv$nextLevel"; + } + + double _buildProgressRatio() { + final int currentExp = ATStringUtils.toIntFunction( + _levelExpRes?.thatExperience ?? "0", + ); + final int nextExp = ATStringUtils.toIntFunction( + _levelExpRes?.nextExperience ?? "0", + ); + final int totalExp = currentExp + nextExp; + if (totalExp <= 0) { + return 0; + } + return (currentExp / totalExp).clamp(0, 1).toDouble(); + } + + int _resolveLevelStageIndex(int level) { + if (level >= 1 && level <= 10) { + return 0; + } + if (level <= 20) { + return 1; + } + if (level <= 30) { + return 2; + } + if (level <= 40) { + return 3; + } + return 4; + } +} diff --git a/lib/chatvibe_features/user/level/wealth/wealth_level_page.dart b/lib/chatvibe_features/user/level/wealth/wealth_level_page.dart new file mode 100644 index 0000000..5cdb3b3 --- /dev/null +++ b/lib/chatvibe_features/user/level/wealth/wealth_level_page.dart @@ -0,0 +1,666 @@ +import 'package:aslan/chatvibe_data/models/enum/at_vip_type.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_string_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_user_level_exp_res.dart'; +import 'package:aslan/chatvibe_features/store/store_route.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; + +import '../../../../chatvibe_core/constants/at_global_config.dart'; +import '../../../../chatvibe_core/utilities/at_date_utils.dart'; +import '../../../../chatvibe_core/utilities/at_user_utils.dart'; +import '../../../../chatvibe_data/models/enum/at_level_type.dart'; +import '../../../../chatvibe_ui/components/text/at_text.dart'; + +class WealthLevelPage extends StatefulWidget { + const WealthLevelPage({super.key}); + + @override + State createState() => _WealthLevelPageState(); +} + +class _WealthLevelPageState extends State { + ATUserLevelExpRes? _levelExpRes; + + @override + void initState() { + super.initState(); + _loadLevelInfo(); + } + + Future _loadLevelInfo() async { + final value = await AccountRepository().userLevelConsumptionExp( + AccountStorage().getCurrentUser()?.userProfile?.id ?? "", + ATLevelType.WEALTH.name, + ); + + if (!mounted) { + return; + } + + setState(() { + _levelExpRes = value; + }); + } + + @override + Widget build(BuildContext context) { + if (_levelExpRes == null) { + return SizedBox( + height: 420.h, + child: const Center( + child: CupertinoActivityIndicator(color: Colors.white24), + ), + ); + } + + return SingleChildScrollView( + padding: EdgeInsets.fromLTRB(12.w, 14.w, 12.w, 12.w), + child: Column( + children: [ + _buildTopSummaryCard(context), + _buildTopPrivilegesCard(context), + SizedBox(height: 16.w), + _buildRewardCard(context), + ], + ), + ); + } + + Widget _buildTopSummaryCard(BuildContext context) { + final currentUser = AccountStorage().getCurrentUser()?.userProfile; + final int level = (_levelExpRes?.level ?? 0).toInt(); + final int nextLevel = level >= 50 ? 50 : level + 1; + return Column( + children: [ + Image.asset( + "atu_images/level/at_icon_wealth_level_tag${_wealthLevel2(level)}.png", + width: 88.w, + ), + SizedBox(height: 15.w), + ShaderMask( + blendMode: BlendMode.srcIn, + shaderCallback: + (bounds) => const LinearGradient( + colors: [Color(0xFFFED90A), Color(0xFFFFB31E)], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ).createShader(bounds), + child: Text( + "Lv$level", + style: TextStyle( + fontSize: 32.sp, + fontWeight: FontWeight.w800, + color: Colors.white, + height: 1, + ), + ), + ), + Container( + margin: EdgeInsets.only(top: 12.w), + padding: EdgeInsets.fromLTRB(20.w, 12.w, 20.w, 12.w), + decoration: _buildCardDecoration( + beginColor: const Color(0xFF563120), + middleColor: const Color(0xFF2b1910), + endColor: const Color(0xFF110a06), + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + child: Row( + children: [ + netImage( + url: currentUser?.userAvatar ?? "", + width: 45.w, + shape: BoxShape.circle, + ), + Expanded( + child: Column( + children: [ + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: text( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname ?? + "", + fontWeight: FontWeight.w700, + fontSize: 15.sp, + textColor: const Color(0xFFF6E62A), + ), + ), + ], + ), + SizedBox(height: 4.w), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(width: 10.w), + Expanded( + child: Text( + _buildUpgradeHint(nextLevel), + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w500, + color: const Color(0xFFF6E62A), + height: 1.2, + ), + ), + ), + ], + ), + SizedBox(height: 12.w), + Padding( + padding: EdgeInsetsDirectional.only( + start: 10.w, + end: 20.w, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(99.r), + child: SizedBox( + height: 6.w, + child: LinearProgressIndicator( + value: _buildProgressRatio(), + backgroundColor: Colors.white.withValues( + alpha: 0.16, + ), + valueColor: const AlwaysStoppedAnimation( + Color(0xFFFFC700), + ), + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ], + ); + } + + int _wealthLevel2(int level) { + int level2 = 1; + if (level > 0 && level <= 10) { + level2 = 1; + } else if (level > 10 && level <= 20) { + level2 = 2; + } else if (level > 20 && level <= 30) { + level2 = 3; + } else if (level > 30 && level <= 40) { + level2 = 4; + } else if (level > 40 && level <= 50) { + level2 = 5; + } + return level2; + } + + Widget _buildRewardCard(BuildContext context) { + final localizations = ATAppLocalizations.of(context)!; + final int level = (_levelExpRes?.level ?? 0).toInt(); + return Container( + width: double.infinity, + padding: EdgeInsets.fromLTRB(0.w, 20.w, 0.w, 18.w), + decoration: _buildCardDecoration( + beginColor: const Color(0xFF563120), + middleColor: const Color(0xFF2b1910), + endColor: const Color(0xFF110a06), + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + child: Column( + children: [ + _buildPanelTitle( + title: localizations.medalAndAvatarFrameRewards, + subtitle: localizations.higherLevelFancierAvatarFrame, + ), + SizedBox(height: 16.w), + _buildSectionLabel(localizations.levelIcon), + SizedBox(height: 10.w), + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.w), + child: _buildLevelTrack(level), + ), + Image.asset( + "atu_images/level/at_icon_user_wealth_center_bg_1.png", + width: double.infinity, + fit: BoxFit.fitWidth, + ), + SizedBox(height: 18.w), + _buildSectionLabel(localizations.levelMedal), + SizedBox(height: 10.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 3.w), + child: Image.asset( + "atu_images/level/at_icon_user_wealth_center_bg_2.png", + width: double.infinity, + fit: BoxFit.fitWidth, + ), + ), + SizedBox(height: 18.w), + _buildPanelTitle( + title: localizations.howToUpgrade, + subtitle: localizations.spendCoinsToGainExperiencePoints, + ), + SizedBox(height: 16.w), + GestureDetector( + onTap: () { + ATNavigatorUtils.push(context, StoreRoute.list); + }, + child: Container( + width: 160.w, + height: 48.w, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(999.r), + border: Border.all(color: Colors.white, width: 1.w), + gradient: const LinearGradient( + colors: [Color(0xFFFF8B2A), Color(0xFFFF4E00)], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + boxShadow: [ + BoxShadow( + color: const Color(0xFFFF5A00).withValues(alpha: 0.34), + blurRadius: 26, + offset: const Offset(0, 10), + ), + ], + ), + child: Text( + localizations.toConsume, + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w700, + color: Colors.white, + height: 1, + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildLevelTrack(int level) { + const labels = ["LV.1-10", "LV.11-20", "LV.21-30", "LV.31-40", "LV.41-50"]; + final int activeIndex = _resolveLevelStageIndex(level); + + return LayoutBuilder( + builder: (context, constraints) { + final double itemWidth = constraints.maxWidth / labels.length; + final double dotSize = 10.w; + final double trackTop = 20.w; + final double activeCenter = itemWidth * activeIndex + itemWidth / 2; + + return SizedBox( + height: 34.w, + child: Stack( + children: [ + for (int i = 0; i < labels.length; i++) + Positioned( + left: itemWidth * i, + width: itemWidth, + top: 0, + child: Center( + child: text( + labels[i], + textColor: + i == activeIndex + ? ChatVibeTheme.primaryLight + : Colors.white, + fontSize: 11.sp, + ), + ), + ), + Positioned( + left: 0, + right: 0, + top: trackTop + dotSize / 2 - 1.w, + child: Container( + height: 4.w, + decoration: BoxDecoration( + color: Colors.white24, + borderRadius: BorderRadius.circular(50.r), + ), + ), + ), + Positioned( + left: 0, + width: activeCenter, + top: trackTop + dotSize / 2 - 1.w, + child: Container( + height: 4.w, + decoration: BoxDecoration( + color: ChatVibeTheme.primaryLight, + borderRadius: BorderRadius.circular(50.r), + ), + ), + ), + for (int i = 0; i < labels.length; i++) + Positioned( + left: itemWidth * i + (itemWidth - dotSize) / 2, + top: trackTop + 1, + child: Image.asset( + i == activeIndex + ? "atu_images/level/at_icon_level_pd_en.png" + : "atu_images/level/at_icon_level_pd_no.png", + width: dotSize, + height: dotSize, + ), + ), + ], + ), + ); + }, + ); + } + + Widget _buildPanelTitle({required String title, required String subtitle}) { + return Column( + children: [ + ShaderMask( + blendMode: BlendMode.srcIn, + shaderCallback: + (bounds) => const LinearGradient( + colors: [Color(0xFFFDFCF8), Color(0xFFFFB31E)], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ).createShader(bounds), + child: Text( + title, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w800, + color: Colors.white, + height: 1.15, + ), + ), + ), + SizedBox(height: 6.w), + Text( + subtitle, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 11.sp, + fontWeight: FontWeight.w500, + color: Colors.white, + height: 1.3, + ), + ), + ], + ); + } + + Widget _buildSectionLabel(String title) { + return Row( + children: [ + SizedBox(width: 35.w), + Expanded(child: _buildGlowLine()), + SizedBox(width: 8.w), + ShaderMask( + blendMode: BlendMode.srcIn, + shaderCallback: + (bounds) => const LinearGradient( + colors: [Color(0xFFFDFCF8), Color(0xFFFFB31E)], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ).createShader(bounds), + child: Text( + title, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w800, + color: Colors.white, + height: 1, + ), + ), + ), + SizedBox(width: 8.w), + Expanded(child: _buildGlowLine(reverse: true)), + SizedBox(width: 35.w), + ], + ); + } + + Widget _buildGlowLine({bool reverse = false}) { + return Image.asset( + reverse + ? "atu_images/level/at_icon_wealth_title_01_right.png" + : "atu_images/level/at_icon_wealth_title_01_left.png", + height: 14.w, + fit: BoxFit.fill, + ); + } + + BoxDecoration _buildCardDecoration({ + required Color beginColor, + required Color middleColor, + required Color endColor, + required AlignmentGeometry begin, + required AlignmentGeometry end, + }) { + return BoxDecoration( + borderRadius: BorderRadius.circular(12.r), + border: Border.all(color: const Color(0xFFFFD673), width: 1.w), + gradient: LinearGradient( + colors: [beginColor, middleColor, endColor], + begin: begin, + end: end, + stops: const [0, 0.6, 1], + ), + ); + } + + String _buildUpgradeHint(int nextLevel) { + final int needExp = ATStringUtils.toIntFunction( + _levelExpRes?.nextExperience ?? "0", + ); + if (needExp <= 0) { + return "Max Level"; + } + return "You Need ${ATStringUtils.formatNumber(needExp)} EXP To Lv$nextLevel"; + } + + double _buildProgressRatio() { + final int currentExp = ATStringUtils.toIntFunction( + _levelExpRes?.thatExperience ?? "0", + ); + final int nextExp = ATStringUtils.toIntFunction( + _levelExpRes?.nextExperience ?? "0", + ); + final int totalExp = currentExp + nextExp; + if (totalExp <= 0) { + return 0; + } + return (currentExp / totalExp).clamp(0, 1).toDouble(); + } + + int _resolveLevelStageIndex(int level) { + if (level >= 1 && level <= 10) { + return 0; + } + if (level <= 20) { + return 1; + } + if (level <= 30) { + return 2; + } + if (level <= 40) { + return 3; + } + return 4; + } + + Widget _buildTopPrivilegesCard(BuildContext context) { + final int level = (_levelExpRes?.level ?? 0).toInt(); + return Container( + margin: EdgeInsets.only(top: 12.w), + height: 140.w, + padding: EdgeInsets.fromLTRB(20.w, 12.w, 20.w, 0.w), + decoration: _buildCardDecoration( + beginColor: const Color(0xFF563120), + middleColor: const Color(0xFF2b1910), + endColor: const Color(0xFF110a06), + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + child: Column( + children: [ + text( + ATAppLocalizations.of( + context, + )!.currentLevelPrivilegesAndCostumes("Lv.$level"), + fontSize: 12.sp, + textColor: const Color(0xFFF6E62A), + ), + Spacer(), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + SizedBox(width: _levelExpRes?.idIconType != null ? 25.w : 50.w), + Expanded( + child: Column( + children: [ + getWealthLevel(level, width: 60.w, height: 30.w), + SizedBox(height: 15.w), + Stack( + alignment: Alignment.center, + children: [ + Image.asset( + "atu_images/level/at_icon_wealth_level_otsb_tg.png", + height: 30.w, + width: 72.w, + fit: BoxFit.fill, + ), + PositionedDirectional( + bottom: 3.w, + child: Container( + alignment: Alignment.center, + width: 72.w, + child: text( + ATAppLocalizations.of(context)!.levelIcon, + fontSize: 10.sp, + ), + ), + ), + ], + ), + ], + ), + ), + if( _levelExpRes?.vipLevel!=null) Expanded( + child: Column( + children: [ + Image.asset( + width: 65.w, + height: 65.w, + ATGlobalConfig.businessLogicStrategy.getVipPageLargeIcon( + int.parse( + ATAccountHelper.getVipLevel2( + _levelExpRes?.vipLevel ?? "", + ), + ), + ), + ), + SizedBox(height: 5.w), + Stack( + children: [ + Image.asset( + "atu_images/level/at_icon_wealth_level_otsb_tg.png", + height: 30.w, + width: 72.w, + fit: BoxFit.fill, + ), + PositionedDirectional( + bottom: 3.w, + child: Container( + alignment: Alignment.center, + width: 72.w, + child: text( + "${ATAccountHelper.getVipLevel(_levelExpRes?.vipLevel ?? "")}*${"${_levelExpRes?.vipDays ?? 0}${ATAppLocalizations.of(context)!.days}"}", + fontSize: 10.sp, + ), + ), + ), + ], + ), + ], + ), + ), + + if (_levelExpRes?.idIconType != null) + Expanded( + child: Column( + children: [ + getIdIconForLevel( + _levelExpRes?.idIconType ?? 0, + width: 50.w, + height: 50.w, + ), + SizedBox(height: 10.w), + Stack( + alignment: Alignment.center, + children: [ + Image.asset( + "atu_images/level/at_icon_wealth_level_otsb_tg.png", + height: 30.w, + width: 72.w, + fit: BoxFit.fill, + ), + PositionedDirectional( + bottom: 3.w, + child: Container( + alignment: Alignment.center, + width: 72.w, + child: text( + ATAppLocalizations.of(context)!.idIcon, + fontSize: 10.sp, + ), + ), + ), + ], + ), + ], + ), + ), + SizedBox(width: _levelExpRes?.idIconType != null ? 25.w : 50.w), + ], + ), + ], + ), + ); + } + + getIdIconForLevel( + num idIconType, { + required double width, + required double height, + }) { + String icon = ""; + if (idIconType == 1) { + icon = "atu_images/level/at_icon_wealth_id_icon_1.png"; + } else if (idIconType == 2) { + icon = "atu_images/level/at_icon_wealth_id_icon_2.png"; + } else if (idIconType == 3) { + icon = "atu_images/level/at_icon_wealth_id_icon_3.png"; + } else if (idIconType == 4) { + icon = "atu_images/level/at_icon_wealth_id_icon_4.png"; + } + return icon.isNotEmpty + ? Image.asset(icon, width: width, height: height) + : Container(); + } +} diff --git a/lib/chatvibe_features/user/me_page.dart b/lib/chatvibe_features/user/me_page.dart new file mode 100644 index 0000000..0ca8421 --- /dev/null +++ b/lib/chatvibe_features/user/me_page.dart @@ -0,0 +1,908 @@ +import 'dart:convert'; +import 'dart:ui'; +import 'dart:ui' as ui; +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_features/dynamic/person_dynamic_list_page.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_features/user/profile/profile/profile_page.dart'; +import 'package:aslan/chatvibe_features/user/profile/props/props_page.dart'; +import 'package:aslan/chatvibe_features/user/profile/relation/relation_ship_page.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; + +import '../../chatvibe_domain/models/res/at_user_counter_res.dart'; +import '../../chatvibe_domain/usecases/at_fixed_width_tabIndicator.dart'; +import '../../chatvibe_domain/usecases/at_shining_text.dart'; + +class MePage extends StatefulWidget { + @override + _MePageState createState() => _MePageState(); +} + +class _MePageState extends State with SingleTickerProviderStateMixin { + late TabController _tabController; + final List _pages = []; + final List _tabs = []; + Map counterMap = {}; + + // 业务逻辑策略访问器 + BusinessLogicStrategy get _strategy => ATGlobalConfig.businessLogicStrategy; + + // 添加滚动控制器 + final ScrollController _scrollController = ScrollController(); + double _opacity = 0.0; + + @override + void initState() { + super.initState(); + // 监听滚动 + _scrollController.addListener(() { + if (_scrollController.hasClients) { + final offset = _scrollController.offset; + // 当滚动到一定位置时,头像和昵称应该完全显示在AppBar上 + // 这里计算透明度,可以根据需要调整 + final newOpacity = (offset / _strategy.getMePageScrollOpacityThreshold()).clamp(0.0, 1.0); + if (newOpacity != _opacity) { + setState(() { + _opacity = newOpacity; + }); + } + } + }); + _pages.add( + PersonDynamicListPage(true,AccountStorage().getCurrentUser()?.userProfile?.id ?? ""), + ); + _pages.add( + ProfilePage(true, AccountStorage().getCurrentUser()?.userProfile?.id ?? ""), + ); + _pages.add( + PropsPage(true, AccountStorage().getCurrentUser()?.userProfile?.id ?? ""), + ); + _pages.add( + RelationShipPage( + true, + AccountStorage().getCurrentUser()?.userProfile?.id ?? "", + ), + ); + _tabController = TabController(initialIndex: _strategy.getMePageInitialTabIndex(),length: _pages.length, vsync: this); + _tabController.addListener(() { + setState(() {}); + }); // 监听切换 + Provider.of(context, listen: false).getMyUserInfo(); + userCounter("${AccountStorage().getCurrentUser()?.userProfile?.id}"); + } + + void userCounter(String userId) async { + counterMap.clear(); + var userCounterList = await AccountRepository().userCounter(userId); + counterMap = Map.fromEntries( + userCounterList.map( + (counter) => MapEntry(counter.counterType ?? "", counter), + ), + ); + setState(() {}); + } + + Widget _buildTab(int index, String text) { + final isSelected = _tabController.index == index; + return isSelected + ? Tab( + icon: ATShiningText( + text: text, + fontSize: 15.sp, + shineColor: _strategy.getMePageShineColor(), + ), + ) + : Tab(text: text); + } + + @override + Widget build(BuildContext context) { + _tabs.clear(); + _tabs.add(_buildTab(0, ATAppLocalizations.of(context)!.dynamicT)); + _tabs.add(_buildTab(1, ATAppLocalizations.of(context)!.profile)); + _tabs.add(_buildTab(2, ATAppLocalizations.of(context)!.props)); + _tabs.add(_buildTab(3, ATAppLocalizations.of(context)!.relationShip)); + return Consumer( + builder: (context, ref, child) { + List backgroundPhotos = []; + backgroundPhotos = + (AccountStorage().getCurrentUser()?.userProfile?.backgroundPhotos ?? + []) + .where((t) => t.status == 1) + .toList(); + return Directionality( + textDirection: + window.locale.languageCode == "ar" + ? TextDirection.rtl + : TextDirection.ltr, // 强制指定为从左到右 + child: Stack( + children: [ + backgroundPhotos.isNotEmpty + ? SizedBox( + height: 300.w, + child: CarouselSlider( + options: CarouselOptions( + height: 300.w, + autoPlay: backgroundPhotos.length > 1, + // 启用自动播放 + enlargeCenterPage: false, + // 居中放大当前页面 + aspectRatio: 1 / 1, + // 宽高比 + enableInfiniteScroll: true, + // 启用无限循环 + autoPlayAnimationDuration: Duration(milliseconds: 800), + // 自动播放动画时长 + viewportFraction: 1, + // 视口分数 + onPageChanged: (index, reason) { + setState(() {}); + }, + ), + items: + backgroundPhotos.map((item) { + return GestureDetector( + child: netImage( + url: item.url ?? "", + width: ScreenUtil().screenWidth, + height: 280.w, + fit: BoxFit.cover, + ), + onTap: () {}, + ); + }).toList(), + ), + ) + : SizedBox( + height: 300.w, + child: Image.asset( + _strategy.getMePageDefaultBackgroundImage(), + width: ScreenUtil().screenWidth, + height: 300.w, + fit: BoxFit.cover, + ), + ), + PositionedDirectional( + top: 260.w, + child: Container( + margin: EdgeInsets.only(bottom: 28.w), + child: Image.asset( + _strategy.getMePageGenderBackgroundImage( + AccountStorage().getCurrentUser()?.userProfile?.userSex == 0, + ), + height: ScreenUtil().screenHeight, + width: ScreenUtil().screenWidth, + fit: BoxFit.fill, + ), + ), + ), + ClipRRect( + child: BackdropFilter( + filter: ui.ImageFilter.blur( + sigmaX: 15 * _opacity, + sigmaY: 15 * _opacity, + ), + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: _strategy.getMePageGradientColors( + AccountStorage().getCurrentUser()?.userProfile?.userSex == 0, + ).map((color) => color.withOpacity(_opacity)).toList(), + begin: AlignmentDirectional.centerStart, + end: AlignmentDirectional.centerEnd, + ), + ), + height: ScreenUtil().screenHeight, + width: ScreenUtil().screenWidth, + ), + ), + ), + Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + appBar: ChatVibeStandardAppBar( + backButtonColor: Colors.white, + leading: Container(), + title: "", + actions: [ + if (_opacity > _strategy.getMePageOpacityThresholdForAvatarDisplay()) ...[ + SizedBox(width: 15.w), + head( + url: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userAvatar ?? + "", + width: 50.w, + height: 50.w, + headdress: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.getHeaddress() + ?.sourceUrl, + ), + SizedBox(width: 8.w), + chatvibeNickNameText( + maxWidth: 135.w, + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname ?? + "", + fontSize: 16.sp, + fontWeight: FontWeight.w600, + type: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.getVIP() + ?.name ?? + "", + needScroll: + ( AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname + ?.characters.length ?? + 0) > + _strategy.getMePageNicknameScrollThreshold(), + ), + SizedBox(width: 8.w), + ], + Spacer(), + IconButton( + icon: Image.asset( + _strategy.getMePageEditUserInfoIcon(), + width: 22.w, + color: Colors.white, + ), + onPressed: () { + ATNavigatorUtils.push( + context, + MainRoute.edit, + replace: false, + ); + }, + ), + ], + ), + body: ExtendedNestedScrollView( + controller: _scrollController, + onlyOneScrollInBody: true, + headerSliverBuilder: ( + BuildContext context, + bool innerBoxIsScrolled, + ) { + return [ + SliverToBoxAdapter( + child: Container( + color: Colors.transparent, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 145.w), // 给AppBar留出空间 + Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: 15.w), + (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.cpList + ?.isNotEmpty ?? + false) + ? SizedBox( + height: 100.w, + child: Stack( + alignment: Alignment.center, + children: [ + Transform.translate( + offset: Offset(0, -5), + child: Row( + mainAxisSize: + MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment + .center, + children: [ + GestureDetector( + child: netImage( + url: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.cpList + ?.first + .meUserAvatar ?? + "", + width: 56.w, + defaultImg: + _strategy.getMePageDefaultAvatarImage(), + shape: + BoxShape.circle, + ), + onTap: () { + String + encodedUrls = Uri.encodeComponent( + jsonEncode([ + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.cpList + ?.first + .meUserAvatar, + ]), + ); + ATNavigatorUtils.push( + context, + "${MainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=0", + ); + }, + ), + SizedBox(width: 32.w), + GestureDetector( + onTap: () { + ATNavigatorUtils.push( + context, + replace: false, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == AccountStorage().getCurrentUser()?.userProfile?.cpList?.first.cpUserId}&tageId=${AccountStorage().getCurrentUser()?.userProfile?.cpList?.first.cpUserId}", + ); + }, + child: netImage( + url: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.cpList + ?.first + .cpUserAvatar ?? + "", + defaultImg: + _strategy.getMePageDefaultAvatarImage(), + width: 56.w, + shape: + BoxShape.circle, + ), + ), + ], + ), + ), + IgnorePointer( + child: Transform.translate( + offset: Offset(0, -15), + child: Image.asset( + _strategy.getMePageCpDialogHeadImage(), + ), + ), + ), + ], + ), + ) + : GestureDetector( + child: head( + url: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userAvatar ?? + "", + width: 88.w, + headdress: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.getHeaddress() + ?.sourceUrl, + ), + onTap: () { + String encodedUrls = + Uri.encodeComponent( + jsonEncode([ + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userAvatar, + ]), + ); + ATNavigatorUtils.push( + context, + "${MainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=0", + ); + }, + ), + ], + ), + SizedBox(height: 6.w), + Row( + children: [ + SizedBox(width: 25.w), + chatvibeNickNameText( + maxWidth: 135.w, + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname ?? + "", + fontSize: 18.sp, + fontWeight: FontWeight.w600, + type: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.getVIP() + ?.name ?? + "", + needScroll: + ( AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname + ?.characters.length ?? + 0) > + _strategy.getMePageNicknameScrollThreshold(), + ), + SizedBox(width: 3.w), + getVIPBadge( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.getVIP() + ?.name, + width: 45.w, + height: 25.w, + ), + ], + ), + SizedBox(height: 5.w), + Row( + children: [ + SizedBox(width: 15.w), + GestureDetector( + child: Container( + padding: EdgeInsets.symmetric( + vertical: 8.w, + ), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.hasSpecialId() ?? + false) + ? _strategy.getMePageIdBackgroundImage(true) + : _strategy.getMePageIdBackgroundImage(false), + ), + fit: BoxFit.fitWidth, + ), + ), + child: Row( + textDirection: TextDirection.ltr, + children: [ + SizedBox(width: 38.w), + text( + "${AccountStorage().getCurrentUser()?.userProfile?.getID()}", + fontSize: 12.sp, + textColor: Colors.white, + fontWeight: FontWeight.bold, + ), + SizedBox(width: 5.w), + Image.asset( + _strategy.getMePageCopyIdIcon(), + width: 12.w, + height: 12.w, + ), + SizedBox(width: 8.w), + ], + ), + ), + onTap: () { + Clipboard.setData( + ClipboardData( + text: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.getID() ?? + "", + ), + ); + ATTts.show( + ATAppLocalizations.of( + context, + )!.copiedToClipboard, + ); + }, + ), + SizedBox(width: 12.w), + Container( + width: (AccountStorage() + .getCurrentUser() + ?.userProfile?.age??0) > _strategy.getMePageAgeDisplayWidthThreshold() ? 58.w : 48.w, + height: 24.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + _strategy.getMePageGenderAgeBackgroundImage( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userSex == 0, + ), + ), + ), + ), + child: Row( + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + xb( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userSex, + ), + text( + "${AccountStorage().getCurrentUser()?.userProfile?.age}", + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 3.w), + ], + ), + ), + SizedBox(width: 8.w), + getWealthLevel( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.wealthLevel ?? + 0, + width: 58.w, + height: 30.w, + ), + SizedBox(width: 8.w), + getUserLevel( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.charmLevel ?? + 0, + width: 58.w, + height: 30.w, + ), + ], + ), + SizedBox(height: 5.w), + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.wearHonor + ?.where((item) { + return item.use ?? false; + }) + .isNotEmpty ?? + false + ? Row( + children: [ + SizedBox(width: 15.w), + Expanded( + child: Wrap( + runSpacing: 2.w, + spacing: 4.w, + direction: Axis.horizontal, + children: + AccountStorage() + .getCurrentUser()! + .userProfile! + .wearHonor! + .where((item) { + return item.use ?? + false; + }) + .map((item) { + return netImage( + url: + item.animationUrl ?? + "", + height: 28.w, + ); + }) + .toList(), + ), + ), + SizedBox(width: 5.w), + ], + ) + : Container(), + (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.wearHonor + ?.where((item) { + return item.use ?? false; + }) + .isNotEmpty ?? + false) + ? SizedBox(height: 5.w) + : Container(), + Container( + margin: EdgeInsetsDirectional.symmetric( + horizontal: 10.w, + ), + alignment: AlignmentDirectional.centerStart, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 5.w), + ref.userProfile?.wearBadge?.where(( + item, + ) { + return item.use ?? false; + }).isNotEmpty ?? + false + ? SizedBox( + height: 35.w, + child: ListView.separated( + scrollDirection: + Axis.horizontal, + shrinkWrap: true, + itemCount: + ref.userProfile?.wearBadge + ?.where((item) { + return item.use ?? + false; + }) + .length ?? + 0, + itemBuilder: ( + context, + index, + ) { + return netImage( + width: 35.w, + height: 35.w, + url: + ref + .userProfile + ?.wearBadge + ?.where((item) { + return item + .use ?? + false; + }) + .toList()[index] + .selectUrl ?? + "", + ); + }, + separatorBuilder: ( + BuildContext context, + int index, + ) { + return SizedBox(width: 5.w); + }, + ), + ) + : Container(), + SizedBox(width: 5.w), + ], + ), + ), + ), + SizedBox(height: 8.w), + Consumer( + builder: (context, ref, child) { + return Container( + height: 72.w, + alignment: AlignmentDirectional.center, + width: ScreenUtil().screenWidth, + margin: EdgeInsets.symmetric( + horizontal: 18.w, + ), + padding: EdgeInsets.symmetric( + horizontal: 35.w, + ), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + _strategy.getMePageVisitorsFollowFansBackgroundImage( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userSex == 0, + ), + ), + fit: BoxFit.fill, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + child: Column( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of( + context, + )!.vistors, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: _strategy.getMePageVisitorsFollowFansTextColor(), + ), + text( + "${counterMap["INTERVIEW"]?.quantity ?? 0}", + fontSize: 17.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + onTap: () { + ATNavigatorUtils.push( + context, + MainRoute.vistors, + ); + }, + ), + GestureDetector( + onTap: () { + ATNavigatorUtils.push( + context, + MainRoute.follow, + ); + }, + child: Column( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of( + context, + )!.follow, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: _strategy.getMePageVisitorsFollowFansTextColor(), + ), + text( + "${counterMap["SUBSCRIPTION"]?.quantity ?? 0}", + fontSize: 17.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + ), + GestureDetector( + child: Column( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of( + context, + )!.fans, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: _strategy.getMePageVisitorsFollowFansTextColor(), + ), + text( + "${counterMap["FANS"]?.quantity ?? 0}", + fontSize: 17.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + onTap: () { + ATNavigatorUtils.push( + context, + MainRoute.fans, + ); + }, + ), + ], + ), + ); + }, + ), + SizedBox(height: 6.w), + ], + ), + ], + ), + ), + ), + ]; + }, + body: Container( + padding: EdgeInsets.symmetric(horizontal: 5.w), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: _strategy.getMePageGradientColors( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userSex == 0, + ).map((color) => color.withOpacity(_opacity)).toList(), + begin: AlignmentDirectional.topStart, + end: AlignmentDirectional.bottomEnd, + ), + ), + child: Column( + children: [ + TabBar( + tabAlignment: TabAlignment.start, + labelPadding: EdgeInsets.symmetric(horizontal: 12.w), + labelColor: Colors.white, + isScrollable: true, + indicator: ATFixedWidthTabIndicator( + width: 20.w, + height: 4.w, + gradient: LinearGradient( + colors: _strategy.getMePageTabIndicatorGradient( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userSex == 0, + ), + ), + ), + unselectedLabelColor: Colors.white54, + labelStyle: TextStyle( + fontSize: 15.sp, + fontWeight: FontWeight.bold, + ), + unselectedLabelStyle: TextStyle( + fontSize: 13.sp, + ), + indicatorColor: Colors.transparent, + dividerColor: Colors.transparent, + controller: _tabController, + tabs: _tabs, + ), + Expanded( + child: TabBarView( + controller: _tabController, + children: _pages, + ), + ), + ], + ), + ), + ), + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/chatvibe_features/user/me_page2.dart b/lib/chatvibe_features/user/me_page2.dart new file mode 100644 index 0000000..fe5791c --- /dev/null +++ b/lib/chatvibe_features/user/me_page2.dart @@ -0,0 +1,737 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_features/user/settings/settings_route.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:provider/provider.dart'; +import '../../chatvibe_core/routes/at_fluro_navigator.dart'; +import '../../chatvibe_data/sources/repositories/user_repository_impl.dart'; +import '../../chatvibe_domain/models/res/at_user_counter_res.dart'; +import '../../chatvibe_managers/user_profile_manager.dart'; +import '../index/main_route.dart'; +import '../store/store_route.dart'; +import '../wallet/wallet_route.dart'; + +/// 新的"我的"页面,基于Figma设计 +/// 包含个人资料、统计信息、钱包和功能卡片 +class MePage2 extends StatefulWidget { + const MePage2({super.key}); + + @override + _MePage2State createState() => _MePage2State(); +} + +class _MePage2State extends State { + Map counterMap = {}; + + @override + void initState() { + super.initState(); + _loadUserData(); + } + + void _loadUserData() { + Provider.of( + context, + listen: false, + ).getMyUserInfo(); + Provider.of( + context, + listen: false, + ).getUserIdentity(); + userCounter("${AccountStorage().getCurrentUser()?.userProfile?.id}"); + Provider.of(context, listen: false).balance(); + } + + void userCounter(String userId) async { + counterMap.clear(); + var userCounterList = await AccountRepository().userCounter(userId); + counterMap = Map.fromEntries( + userCounterList.map( + (counter) => MapEntry(counter.counterType ?? "", counter), + ), + ); + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: '', + actions: [], + showBackButton: false, + backgroundColor: Colors.transparent, + ), + body: _buildContent(), + ); + } + + Widget _buildContent() { + return Consumer( + builder: (context, ref, child) { + return SingleChildScrollView( + physics: BouncingScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 个人资料区域 + _buildProfileSection(), + SizedBox(height: 20.w), + // 统计信息卡片 + _buildStatisticsCard(), + SizedBox(height: 8.w), + _buildWalletSection(), + SizedBox(height: 8.w), + // 功能卡片1 + _buildFunctionCard1(), + SizedBox(height: 8.w), + // 功能卡片2 + _buildFunctionCard2(), + SizedBox(height: 8.w), + // 功能卡片3 + _buildFunctionCard3(), + SizedBox(height: 40.w), + ], + ), + ); + }, + ); + } + + Widget _buildProfileSection() { + return ATDebounceWidget( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 15.w), + child: Row( + children: [ + // 头像 + head( + url: + AccountStorage().getCurrentUser()?.userProfile?.userAvatar ?? + '', + width: 72.w, + height: 72.w, + shape: BoxShape.circle, + headdress: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.getHeaddress() + ?.sourceUrl, + showDefault: true, + ), + SizedBox(width: 8.w), + // 昵称和ID + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 3.w, + children: [ + chatvibeNickNameText( + maxWidth: 135.w, + UserManager() + .getCurrentUser() + ?.userProfile + ?.userNickname ?? + "", + textColor: Color(0xFF333333), + fontSize: 16.sp, + fontWeight: FontWeight.w600, + type: + UserManager() + .getCurrentUser() + ?.userProfile + ?.getVIP() + ?.name ?? + "", + needScroll: + (UserManager() + .getCurrentUser() + ?.userProfile + ?.userNickname + ?.characters + .length ?? + 0) > + 13, + ), + getVIPBadge( + UserManager() + .getCurrentUser() + ?.userProfile + ?.getVIP() + ?.name, + width: 65.w, + height: 28.w, + ), + Container( + width: + (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.age ?? + 0) > + 999 + ? 58.w + : 48.w, + height: 24.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userSex == + 0 + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png", + ), + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + xb( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userSex, + ), + text( + "${AccountStorage().getCurrentUser()?.userProfile?.age}", + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 3.w), + ], + ), + ), + ], + ), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + Row( + children: [ + getIdIcon( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.wealthLevel ?? + 0, + width: 32.w, + height: 32.w, + textColor: Color(0xFF333333), + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + chatvibeNickNameText( + maxWidth: 135.w, + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.getID() ?? + "", + textColor: Color(0xFF333333), + fontSize: 16.sp, + fontWeight: FontWeight.w600, + type: AccountStorage().getVIP()?.name ?? "", + needScroll: + (UserManager() + .getCurrentUser() + ?.userProfile + ?.getID() + .characters + .length ?? + 0) > + 13, + ), + ], + ), + SizedBox(width: 5.w), + getWealthLevel( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.wealthLevel ?? + 0, + width: 58.w, + height: 30.w, + ), + SizedBox(width: 5.w), + getUserLevel( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.charmLevel ?? + 0, + width: 58.w, + height: 30.w, + ), + ], + ), + ), + ], + ), + ), + // 设置/编辑图标 + Icon( + ATGlobalConfig.lang == "ar" + ? Icons.keyboard_arrow_left + : Icons.keyboard_arrow_right, + size: 25.w, + color: Colors.white, + ), + ], + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=true&tageId=${AccountStorage().getCurrentUser()?.userProfile?.id}", + replace: false, + ); + }, + ); + } + + Widget _buildStatisticsCard() { + return Consumer( + builder: (context, ref, child) { + return Container( + height: 72.w, + alignment: AlignmentDirectional.center, + width: ScreenUtil().screenWidth, + margin: EdgeInsets.symmetric(horizontal: 18.w), + padding: EdgeInsets.symmetric(horizontal: 35.w), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + "${counterMap["INTERVIEW"]?.quantity ?? 0}", + fontSize: 16.sp, + textColor: Color(0xff333333), + fontWeight: FontWeight.w600, + ), + text( + ATAppLocalizations.of(context)!.vistors, + fontSize: 14.sp, + textColor: Color(0xffB1B1B1), + ), + ], + ), + onTap: () { + ATNavigatorUtils.push(context, MainRoute.vistors); + }, + ), + _buildStatDivider(), + GestureDetector( + onTap: () { + ATNavigatorUtils.push(context, MainRoute.follow); + }, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + "${counterMap["SUBSCRIPTION"]?.quantity ?? 0}", + fontSize: 16.sp, + textColor: Color(0xff333333), + fontWeight: FontWeight.w600, + ), + text( + ATAppLocalizations.of(context)!.follow, + fontSize: 14.sp, + textColor: Color(0xffB1B1B1), + ), + ], + ), + ), + _buildStatDivider(), + GestureDetector( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + "${counterMap["FANS"]?.quantity ?? 0}", + fontSize: 16.sp, + textColor: Color(0xff333333), + fontWeight: FontWeight.w600, + ), + text( + ATAppLocalizations.of(context)!.fans, + fontSize: 14.sp, + textColor: Color(0xffB1B1B1), + ), + ], + ), + onTap: () { + ATNavigatorUtils.push(context, MainRoute.fans); + }, + ), + ], + ), + ); + }, + ); + } + + Widget _buildStatDivider() { + return Container( + width: 0.5.w, + height: 16.w, + color: Color(0xFFE6E6E6), + margin: EdgeInsets.symmetric(horizontal: 8.w), + ); + } + + Widget _buildWalletSection() { + return ATDebounceWidget( + child: Container( + padding: EdgeInsets.symmetric(vertical: 16.w, horizontal: 20.w), + margin: EdgeInsets.symmetric(horizontal: 15.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('atu_images/index/at_icon_wallet_bg.png'), + ), + ), + child: Row( + children: [ + // 左侧装饰图像 + Image.asset( + 'atu_images/index/at_icon_wallet_icon.png', + width: 60.w, + height: 60.w, + ), + SizedBox(width: 12.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text( + ATAppLocalizations.of(context)!.wallet, + fontSize: 24.sp, + textColor: Colors.white, + fontWeight: FontWeight.w900, + fontStyle: FontStyle.italic, + ), + SizedBox(height: 4.w), + Row( + children: [ + Image.asset( + 'atu_images/general/at_icon_jb.png', + width: 22.w, + height: 22.w, + ), + SizedBox(width: 3.w), + Consumer( + builder: (context, ref, child) { + return text( + ref.myBalance > 1000 + ? "${(ref.myBalance / 1000).toStringAsFixed(2)}k" + : "${ref.myBalance}", + fontSize: 15.sp, + textColor: Colors.white, + fontWeight: FontWeight.bold, + ); + }, + ), + ], + ), + ], + ), + ), + ], + ), + ), + onTap: () { + ATNavigatorUtils.push(context, WalletRoute.recharge); + }, + ); + } + + Widget _buildFunctionCard1() { + return Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12.w), + ), + child: Column( + spacing: 5.w, + mainAxisSize: MainAxisSize.min, + children: [ + _buildItem( + "atu_images/index/at_icon_task.png", + ATAppLocalizations.of(context)!.task, + () { + ATNavigatorUtils.push(context, MainRoute.taskList, replace: false); + }, + ), + _buildItem( + "atu_images/index/at_icon_vip.png", + ATAppLocalizations.of(context)!.vip, + () { + ATNavigatorUtils.push(context, MainRoute.vipList, replace: false); + }, + ), + _buildItem( + "atu_images/index/at_icon_shop.png", + ATAppLocalizations.of(context)!.store, + () { + ATNavigatorUtils.push(context, StoreRoute.list, replace: false); + }, + ), + + _buildItem( + "atu_images/index/at_icon_my_items.png", + ATAppLocalizations.of(context)!.myItems, + () { + ATNavigatorUtils.push(context, StoreRoute.bags, replace: false); + }, + ), + + _buildItem( + "atu_images/index/at_icon_level.png", + ATAppLocalizations.of(context)!.level, + () { + ATNavigatorUtils.push( + context, + MainRoute.levelList, + replace: false, + ); + }, + ), + + _buildItem( + "atu_images/index/at_icon_medals.png", + ATAppLocalizations.of(context)!.badgeHonor, + needLine: false, + () { + ATNavigatorUtils.push( + context, + MainRoute.medalsHonorListPage, + replace: false, + ); + }, + ), + ], + ), + ); + } + + Widget _buildFunctionCard3() { + return Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12.w), + ), + child: Column( + spacing: 5.w, + mainAxisSize: MainAxisSize.min, + children: [ + _buildItem( + "atu_images/index/at_icon_settings.png", + ATAppLocalizations.of(context)!.settings, + needLine: false, + () { + ATNavigatorUtils.push( + context, + SettingsRoute.settings, + replace: false, + ); + }, + ), + ], + ), + ); + } + + Widget _buildFunctionCard2() { + // 一次性获取用户身份信息(避免多次监听) + final userProfile = context.watch(); + final items = []; + + // 1. 主播相关 + if (userProfile.userIdentity?.anchor ?? false) { + if (userProfile.userIdentity?.agent ?? false) { + items.add( + _buildItem( + "atu_images/index/at_icon_agent_center.png", + ATAppLocalizations.of(context)!.agentCenter, + () => ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.agencyCenterUrl)}&showTitle=false", + ), + ), + ); + } else { + items.add( + _buildItem( + "atu_images/index/at_icon_host_center.png", + ATAppLocalizations.of(context)!.hostCenter, + () => ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.hostCenterUrl)}&showTitle=false", + ), + ), + ); + } + } else { + items.add( + _buildItem( + "atu_images/index/at_icon_become_host_center.png", + ATAppLocalizations.of(context)!.becomeHost, + () => ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.anchorAgentUrl)}&showTitle=false", + ), + ), + ); + } + + // 2. BD 相关(非管理员时) + if (!(userProfile.userIdentity?.admin ?? false)) { + if (userProfile.userIdentity?.bdLeader ?? false) { + items.add( + _buildItem( + "atu_images/index/at_icon_bd_leader.png", + ATAppLocalizations.of(context)!.bdLeader, + () => ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.bdLeaderUrl)}&showTitle=false", + ), + ), + ); + } else if (userProfile.userIdentity?.bd ?? false) { + items.add( + _buildItem( + "atu_images/index/at_icon_bd_center.png", + ATAppLocalizations.of(context)!.bdCenter, + () => ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.bdCenterUrl)}&showTitle=false", + ), + ), + ); + } + } + + // 3. 货运代理 + if (userProfile.userIdentity?.freightAgent ?? false) { + items.add( + _buildItem( + "atu_images/index/at_icon_recharge_agency.png", + ATAppLocalizations.of(context)!.rechargeAgency, + () => ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.coinSellerUrl)}&showTitle=false", + ), + ), + ); + } + + // 4. 管理员(注意:你的原逻辑中管理员和上面BD逻辑有重叠,这里按原样单独处理) + if (userProfile.userIdentity?.admin ?? false) { + items.add( + _buildItem( + "atu_images/index/at_icon_admin_center.png", + ATAppLocalizations.of(context)!.adminCenter, + () => ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.adminUrl)}&showTitle=false", + ), + ), + ); + } + + // 没有任何项时,不显示整个卡片 + if (items.isEmpty) return const SizedBox.shrink(); + + return Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12.w), + ), + child: Column( + spacing: 5.w, + mainAxisSize: MainAxisSize.min, + children: items, + ), + ); + } + + _buildItem( + String icon, + String title, + Function() onTap, { + bool needLine = true, + }) { + return GestureDetector( + child: Column( + children: [ + Container( + alignment: AlignmentDirectional.centerStart, + height: 48.w, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + SizedBox(width: 10.w), + Image.asset(icon, width: 26.w, height: 26.w), + SizedBox(width: 10.w), + Expanded( + child: text( + title, + textColor: Color(0xff333333), + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ), + Icon( + ATGlobalConfig.lang == "ar" + ? Icons.keyboard_arrow_left + : Icons.keyboard_arrow_right, + color: Color(0xff333333), + size: 15.w, + ), + SizedBox(width: 12.w), + ], + ), + ], + ), + ), + needLine + ? Container( + margin: EdgeInsets.symmetric(horizontal: 10.w), + height: 0.5.w, + color: Color(0xffE6E6E6), + ) + : Container(), + ], + ), + onTap: () { + onTap(); + }, + ); + } +} diff --git a/lib/chatvibe_features/user/my_items/chatbox/bags_chatbox_page.dart b/lib/chatvibe_features/user/my_items/chatbox/bags_chatbox_page.dart new file mode 100644 index 0000000..5db2d34 --- /dev/null +++ b/lib/chatvibe_features/user/my_items/chatbox/bags_chatbox_page.dart @@ -0,0 +1,371 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/store_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/bags_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/bag/props_bag_chatbox_detail_dialog.dart'; +import 'package:aslan/chatvibe_ui/widgets/countdown_timer.dart'; +import 'package:aslan/chatvibe_features/store/store_route.dart'; + +import '../../../../chatvibe_data/models/enum/at_props_type.dart'; +import '../../../../chatvibe_ui/components/at_debounce_widget.dart'; + +///背包-气泡框 +class BagsChatboxPage extends ATPageList { + @override + _BagsChatboxPageState createState() => _BagsChatboxPageState(); +} + +class _BagsChatboxPageState + extends ATPageListState { + ///自己当前佩戴的气泡框 + PropsResources? myChatbox; + BagsListRes? selecteChatbox; + + @override + void initState() { + super.initState(); + enablePullUp = false; + isGridView = true; + gridViewCount = 3; + padding = EdgeInsets.symmetric(horizontal: 6.w); + backgroundColor = Colors.transparent; + gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridViewCount, + mainAxisSpacing: 12.w, + crossAxisSpacing: 12.w, + childAspectRatio: 0.88, + ); + myChatbox = AccountStorage().getChatbox(); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: Column( + children: [ + Expanded(child: buildList(context)), + selecteChatbox != null + ? Container( + color: Colors.black12, + height: 0.6.w, + width: ScreenUtil().screenWidth, + ) + : Container(), + selecteChatbox != null ? SizedBox(height: 10.w) : Container(), + selecteChatbox != null + ? Column( + children: [ + Row( + spacing: 5.w, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + "${ATAppLocalizations.of(context)!.expirationTime}:", + textColor: Colors.black54, + fontSize: 12.sp, + ), + CountdownTimer( + fontSize: 11.sp, + color: Color(0xffFFB805), + expiryDate: DateTime.fromMillisecondsSinceEpoch( + selecteChatbox?.expireTime ?? 0, + ), + ), + ], + ), + SizedBox(height: 10.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 120.w, + height: 35.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(55.w), + gradient: LinearGradient( + colors: [Color(0xFFFEB219), Color(0xFFFF9326)], + ), + ), + child: text( + ATAppLocalizations.of(context)!.renewal, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + textColor: Colors.white, + ), + ), + onTap: () { + ///过期 续费操作 + ATNavigatorUtils.push( + context, + StoreRoute.list, + replace: false, + ); + }, + ), + SizedBox(width: 10.w), + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 120.w, + height: 35.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(55.w), + gradient: LinearGradient( + colors: + myChatbox?.id == + selecteChatbox?.propsResources?.id + ? [Color(0xffF2F2F2), Color(0xffF2F2F2)] + : [Color(0xFFFEB219), Color(0xFFFF9326)], + ), + ), + child: text( + myChatbox?.id == + selecteChatbox?.propsResources?.id + ? ATAppLocalizations.of(context)!.inUse + : ATAppLocalizations.of(context)!.use, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + textColor: + myChatbox?.id != + selecteChatbox?.propsResources?.id + ? Colors.white + : Color(0xffB1B1B1), + ), + ), + onTap: () { + if (myChatbox?.id != + selecteChatbox?.propsResources?.id) { + ///佩戴 + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.confirmUseTips, + btnText: + ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + _use(selecteChatbox!, false); + }, + ); + }, + ); + } else { + ///卸下 + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.confirmUnUseTips, + btnText: + ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + _use(selecteChatbox!, true); + }, + ); + }, + ); + } + }, + ), + ], + ), + SizedBox(height: 10.w), + ], + ) + : Container(), + ], + ), + ); + } + + @override + Widget buildItem(BagsListRes res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + color: + selecteChatbox?.propsResources?.id == res.propsResources?.id + ? Color(0xffFFF4D9) + : Colors.white, + border: Border.all( + color: + selecteChatbox?.propsResources?.id == res.propsResources?.id + ? Color(0xffFEB219) + : Colors.black12, + width: 1.w, + ), + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + child: Stack( + alignment: Alignment.center, + children: [ + Column( + children: [ + SizedBox(height: 30.w), + netImage( + url: res.propsResources?.cover ?? "", + height: 35.w, + fit: BoxFit.contain + ), + ], + ), + PositionedDirectional( + top: 0, + start: 0, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 5.w), + decoration: BoxDecoration( + color: Color(0xffFEB219), + borderRadius: BorderRadiusDirectional.only( + topStart: Radius.circular(6.w), + topEnd: Radius.circular(12.w), + bottomEnd: Radius.circular(12.w), + ), + ), + child: Row( + children: [ + CountdownTimer( + fontSize: 9.sp, + color: Colors.white, + expiryDate: DateTime.fromMillisecondsSinceEpoch( + res.expireTime ?? 0, + ), + ), + SizedBox(width: 3.w), + Image.asset( + "atu_images/store/at_icon_bag_clock.png", + width: 22.w, + height: 22.w, + ), + ], + ), + ), + ), + PositionedDirectional( + bottom: 5.w, + end: 5.w, + child: GestureDetector( + onTap: () { + selecteChatbox = res; + setState(() { + }); + _showDetail(res); + }, + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.all(5.w), + child: Image.asset( + "atu_images/store/at_icon_shop_item_search.png", + width: 18.w, + height: 18.w, + ), + ), + ), + ), + ], + ), + ), + onTap: () { + selecteChatbox = res; + setState(() {}); }, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var storeList = await StoreRepositoryImp().storeBackpack( + AccountStorage().getCurrentUser()?.userProfile?.id ?? "", + ATPropsType.CHAT_BUBBLE.name, + ); + for (var v in storeList) { + if (v.propsResources?.id == myChatbox?.id) { + selecteChatbox = v; + continue; + } + } + onSuccess(storeList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + void _showDetail(BagsListRes res) { + SmartDialog.show( + tag: "showPropsDetail", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return PropsBagChatboxDetailDialog(res); + }, + onDismiss: () { + myChatbox = AccountStorage().getChatbox(); + setState(() {}); + }, + ); + } + + void _use(BagsListRes res, bool unload) { + ATLoadingManager.exhibitOperation(context: context); + StoreRepositoryImp() + .switchPropsUse( + ATPropsType.CHAT_BUBBLE.name, + res.propsResources?.id ?? "", + unload, + ) + .then((value) async { + setState(() {}); + if (!unload) { + myChatbox = res.propsResources; + ATTts.show(ATAppLocalizations.of(context)!.successfulWear); + } else { + myChatbox = null; + ATTts.show(ATAppLocalizations.of(context)!.successfullyUnloaded); + } + Provider.of(context, listen: false).getMyUserInfo(needLoadUserCountGuard: false); + ATLoadingManager.veilRoutine(); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } +} diff --git a/lib/chatvibe_features/user/my_items/headdress/bags_headdress_page.dart b/lib/chatvibe_features/user/my_items/headdress/bags_headdress_page.dart new file mode 100644 index 0000000..fd90e03 --- /dev/null +++ b/lib/chatvibe_features/user/my_items/headdress/bags_headdress_page.dart @@ -0,0 +1,376 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/store_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/bags_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_features/store/store_route.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_ui/widgets/bag/props_bag_headdress_detail_dialog.dart'; +import 'package:aslan/chatvibe_ui/widgets/countdown_timer.dart'; +import 'package:provider/provider.dart'; + +import '../../../../chatvibe_data/models/enum/at_props_type.dart'; + +///背包-头饰 +class BagsHeaddressPage extends ATPageList { + @override + _BagsHeaddressPageState createState() => _BagsHeaddressPageState(); +} + +class _BagsHeaddressPageState + extends ATPageListState { + ///自己当前佩戴的头饰 + PropsResources? myHeaddress; + BagsListRes? selecteHeaddress; + + @override + void initState() { + super.initState(); + enablePullUp = false; + isGridView = true; + gridViewCount = 3; + padding = EdgeInsets.symmetric(horizontal: 6.w); + backgroundColor = Colors.transparent; + gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridViewCount, + mainAxisSpacing: 12.w, + crossAxisSpacing: 12.w, + childAspectRatio: 0.88, + ); + myHeaddress = AccountStorage().getHeaddress(); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: Column( + children: [ + Expanded(child: buildList(context)), + selecteHeaddress!= null + ? Container( + color: Colors.black12, + height: 0.6.w, + width: ScreenUtil().screenWidth, + ) + : Container(), + selecteHeaddress != null ? SizedBox(height: 10.w) : Container(), + selecteHeaddress != null + ? Column( + children: [ + Row( + spacing: 5.w, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + "${ATAppLocalizations.of(context)!.expirationTime}:", + textColor: Colors.black54, + fontSize: 12.sp, + ), + CountdownTimer( + fontSize: 11.sp, + color: Color(0xffFFB805), + expiryDate: DateTime.fromMillisecondsSinceEpoch( + selecteHeaddress?.expireTime ?? 0, + ), + ), + ], + ), + SizedBox(height: 10.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 120.w, + height: 35.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(55.w), + gradient: LinearGradient( + colors: [Color(0xFFFEB219), Color(0xFFFF9326)], + ), + ), + child: text( + ATAppLocalizations.of(context)!.renewal, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + textColor: Colors.white, + ), + ), + onTap: () { + ///过期 续费操作 + ATNavigatorUtils.push( + context, + StoreRoute.list, + replace: false, + ); + }, + ), + SizedBox(width: 10.w), + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 120.w, + height: 35.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(55.w), + gradient: LinearGradient( + colors: + myHeaddress?.id == + selecteHeaddress?.propsResources?.id + ? [Color(0xffF2F2F2), Color(0xffF2F2F2)] + : [Color(0xFFFEB219), Color(0xFFFF9326)], + ), + ), + child: text( + myHeaddress?.id == + selecteHeaddress?.propsResources?.id + ? ATAppLocalizations.of(context)!.inUse + : ATAppLocalizations.of(context)!.use, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + textColor: + myHeaddress?.id != + selecteHeaddress?.propsResources?.id + ? Colors.white + : Color(0xffB1B1B1), + ), + ), + onTap: () { + if (myHeaddress?.id != + selecteHeaddress?.propsResources?.id) { + ///佩戴 + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.confirmUseTips, + btnText: + ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + _use(selecteHeaddress!, false); + }, + ); + }, + ); + } else { + ///卸下 + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.confirmUnUseTips, + btnText: + ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + _use(selecteHeaddress!, true); + }, + ); + }, + ); + } + }, + ), + ], + ), + SizedBox(height: 10.w), + ], + ) + : Container(), + ], + ), + ); + } + + @override + Widget buildItem(BagsListRes res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + color: + selecteHeaddress?.propsResources?.id == res.propsResources?.id + ? Color(0xffFFF4D9) + : Colors.white, + border: Border.all( + color: + selecteHeaddress?.propsResources?.id == res.propsResources?.id + ? Color(0xffFEB219) + : Colors.black12, + width: 1.w, + ), + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + child: Stack( + alignment: Alignment.center, + children: [ + Column( + children: [ + SizedBox(height: 30.w), + netImage( + url: res.propsResources?.cover ?? "", + width: 55.w, + height: 55.w, + ), + SizedBox(height: 8.w), + ], + ), + PositionedDirectional( + top: 0, + start: 0, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 5.w), + decoration: BoxDecoration( + color: Color(0xffFEB219), + borderRadius: BorderRadiusDirectional.only( + topStart: Radius.circular(6.w), + topEnd: Radius.circular(12.w), + bottomEnd: Radius.circular(12.w), + ), + ), + child: Row( + children: [ + CountdownTimer( + fontSize: 9.sp, + color: Colors.white, + expiryDate: DateTime.fromMillisecondsSinceEpoch( + res.expireTime ?? 0, + ), + ), + SizedBox(width: 3.w), + Image.asset( + "atu_images/store/at_icon_bag_clock.png", + width: 22.w, + height: 22.w, + ), + ], + ), + ), + ), + PositionedDirectional( + bottom: 5.w, + end: 5.w, + child: GestureDetector( + onTap: () { + selecteHeaddress = res; + setState(() { + }); + _showDetail(res); + }, + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.all(5.w), + child: Image.asset( + "atu_images/store/at_icon_shop_item_search.png", + width: 13.w, + height: 13.w, + ), + ), + ), + ), + ], + ), + ), + onTap: () { + selecteHeaddress = res; + setState(() {}); + }, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var storeList = await StoreRepositoryImp().storeBackpack( + AccountStorage().getCurrentUser()?.userProfile?.id ?? "", + ATPropsType.AVATAR_FRAME.name, + ); + for (var v in storeList) { + if (v.propsResources?.id == myHeaddress?.id) { + selecteHeaddress = v; + continue; + } + } + onSuccess(storeList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + void _showDetail(BagsListRes res) { + SmartDialog.show( + tag: "showPropsDetail", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return PropsBagHeaddressDetailDialog(res); + }, + onDismiss: () { + myHeaddress = AccountStorage().getHeaddress(); + setState(() {}); + }, + ); + } + + void _use(BagsListRes res, bool unload) { + ATLoadingManager.exhibitOperation(context: context); + StoreRepositoryImp() + .switchPropsUse( + ATPropsType.AVATAR_FRAME.name, + res.propsResources?.id ?? "", + unload, + ) + .then((value) async { + setState(() {}); + if (!unload) { + myHeaddress = res.propsResources; + ATTts.show(ATAppLocalizations.of(context)!.successfulWear); + } else { + myHeaddress = null; + ATTts.show(ATAppLocalizations.of(context)!.successfullyUnloaded); + } + Provider.of( + context, + listen: false, + ).getMyUserInfo(needLoadUserCountGuard: false); + ATLoadingManager.veilRoutine(); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } +} diff --git a/lib/chatvibe_features/user/my_items/mountains/bags_mountains_page.dart b/lib/chatvibe_features/user/my_items/mountains/bags_mountains_page.dart new file mode 100644 index 0000000..7f56f38 --- /dev/null +++ b/lib/chatvibe_features/user/my_items/mountains/bags_mountains_page.dart @@ -0,0 +1,389 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/widgets/bag/props_bag_mountains_detail_dialog.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/store_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/bags_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/countdown_timer.dart'; +import 'package:aslan/chatvibe_features/store/store_route.dart'; + +import '../../../../chatvibe_data/models/enum/at_props_type.dart'; +import '../../../../chatvibe_ui/components/at_debounce_widget.dart'; +import '../../../../chatvibe_ui/components/dialog/dialog_base.dart'; + +///坐骑 +class BagsMountainsPage extends ATPageList { + Function(String playUrl) playCall; + + BagsMountainsPage(this.playCall); + + @override + _BagsMountainsPageState createState() => _BagsMountainsPageState(playCall); +} + +class _BagsMountainsPageState + extends ATPageListState { + Function(String playUrl) playCall; + BagsListRes? selecteMountains; + ///自己当前佩戴的坐骑 + PropsResources? myMountains; + + _BagsMountainsPageState(this.playCall); + + @override + void initState() { + super.initState(); + enablePullUp = false; + isGridView = true; + gridViewCount = 3; + padding = EdgeInsets.symmetric(horizontal: 6.w); + backgroundColor = Colors.transparent; + gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridViewCount, + mainAxisSpacing: 12.w, + crossAxisSpacing: 12.w, + childAspectRatio: 0.88, + ); + myMountains = AccountStorage().getMountains(); + loadData(1); + } + + @override + void dispose() { + super.dispose(); + } + + void _play(BagsListRes res) { + playCall(res.propsResources?.sourceUrl ?? ""); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: Column( + children: [ + Expanded(child: buildList(context)), + selecteMountains != null + ? Container( + color: Colors.black12, + height: 0.6.w, + width: ScreenUtil().screenWidth, + ) + : Container(), + selecteMountains != null ? SizedBox(height: 10.w) : Container(), + selecteMountains != null + ? Column( + children: [ + Row( + spacing: 5.w, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + "${ATAppLocalizations.of(context)!.expirationTime}:", + textColor: Colors.black54, + fontSize: 12.sp, + ), + CountdownTimer( + fontSize: 11.sp, + color: Color(0xffFFB805), + expiryDate: DateTime.fromMillisecondsSinceEpoch( + selecteMountains?.expireTime ?? 0, + ), + ), + ], + ), + SizedBox(height: 10.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 120.w, + height: 35.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(55.w), + gradient: LinearGradient( + colors: [Color(0xFFFEB219), Color(0xFFFF9326)], + ), + ), + child: text( + ATAppLocalizations.of(context)!.renewal, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () { + ///过期 续费操作 + ATNavigatorUtils.push( + context, + StoreRoute.list, + replace: false, + ); + }, + ), + SizedBox(width: 10.w), + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 120.w, + height: 35.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(55.w), + gradient: LinearGradient( + colors: + myMountains?.id == + selecteMountains?.propsResources?.id + ? [Color(0xffF2F2F2), Color(0xffF2F2F2)] + : [Color(0xFFFEB219), Color(0xFFFF9326)], + ), + ), + child: text( + myMountains?.id == + selecteMountains?.propsResources?.id + ? ATAppLocalizations.of(context)!.inUse + : ATAppLocalizations.of(context)!.use, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: + myMountains?.id != + selecteMountains?.propsResources?.id + ? Colors.white + : Color(0xffB1B1B1), + ), + ), + onTap: () { + if (myMountains?.id != + selecteMountains?.propsResources?.id) { + ///佩戴 + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.confirmUseTips, + btnText: + ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + _use(selecteMountains!, false); + }, + ); + }, + ); + } else { + ///卸下 + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.confirmUnUseTips, + btnText: + ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + _use(selecteMountains!, true); + }, + ); + }, + ); + } + }, + ), + ], + ), + SizedBox(height: 10.w), + ], + ) + : Container(), + ], + ), + ); + } + + @override + Widget buildItem(BagsListRes res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + color: + selecteMountains?.propsResources?.id == res.propsResources?.id + ? Color(0xffFFF4D9) + : Colors.white, + border: Border.all( + color: + selecteMountains?.propsResources?.id == res.propsResources?.id + ? Color(0xffFEB219) + : Colors.black12, + width: 1.w, + ), + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + child: Stack( + alignment: Alignment.center, + children: [ + Column( + children: [ + SizedBox(height: 30.w), + netImage( + url: res.propsResources?.cover ?? "", + width: 55.w, + height: 55.w, + ), + ], + ), + PositionedDirectional( + top: 0, + start: 0, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 5.w), + decoration: BoxDecoration( + color: Color(0xffFEB219), + borderRadius: BorderRadiusDirectional.only( + topStart: Radius.circular(6.w), + topEnd: Radius.circular(12.w), + bottomEnd: Radius.circular(12.w), + ), + ), + child: Row( + children: [ + CountdownTimer( + fontSize: 9.sp, + color: Colors.white, + expiryDate: DateTime.fromMillisecondsSinceEpoch( + res.expireTime ?? 0, + ), + ), + SizedBox(width: 3.w), + Image.asset( + "atu_images/store/at_icon_bag_clock.png", + width: 22.w, + height: 22.w, + ), + ], + ), + ), + ), + PositionedDirectional( + bottom: 5.w, + end: 5.w, + child: GestureDetector( + onTap: () { + _play(res); + }, + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.all(5.w), + child: Image.asset( + "atu_images/store/at_icon_shop_item_play.png", + width: 13.w, + height: 13.w, + ), + ), + ), + ), + ], + ), + ), + onTap: () { + selecteMountains = res; + setState(() {}); + }, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var storeList = await StoreRepositoryImp().storeBackpack( + AccountStorage().getCurrentUser()?.userProfile?.id ?? "", + ATPropsType.RIDE.name, + ); + for (var v in storeList) { + if (v.propsResources?.id == myMountains?.id) { + selecteMountains = v; + continue; + } + } + onSuccess(storeList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + void _showDetail(BagsListRes res) { + SmartDialog.show( + tag: "showPropsDetail", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return PropsBagMountainsDetailDialog(res); + }, + onDismiss: () { + myMountains = AccountStorage().getMountains(); + setState(() {}); + }, + ); + } + + void _use(BagsListRes res, bool unload) { + ATLoadingManager.exhibitOperation(context: context); + StoreRepositoryImp() + .switchPropsUse( + ATPropsType.RIDE.name, + res.propsResources?.id ?? "", + unload, + ) + .then((value) async { + setState(() {}); + if (!unload) { + myMountains = res.propsResources; + ATTts.show(ATAppLocalizations.of(context)!.successfulWear); + } else { + myMountains = null; + ATTts.show(ATAppLocalizations.of(context)!.successfullyUnloaded); + } + Provider.of( + context, + listen: false, + ).getMyUserInfo(needLoadUserCountGuard: false); + ATLoadingManager.veilRoutine(); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } +} diff --git a/lib/chatvibe_features/user/my_items/my_items_page.dart b/lib/chatvibe_features/user/my_items/my_items_page.dart new file mode 100644 index 0000000..d077da8 --- /dev/null +++ b/lib/chatvibe_features/user/my_items/my_items_page.dart @@ -0,0 +1,205 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_features/user/my_items/theme/bags_tab_theme_page.dart'; +import 'package:tancent_vap/utils/constant.dart'; +import 'package:tancent_vap/widgets/vap_view.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_path_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/file_cache_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/headdress/headdress_widget.dart'; +import 'package:aslan/chatvibe_features/store/store_route.dart'; +import 'package:aslan/chatvibe_features/user/my_items/chatbox/bags_chatbox_page.dart'; +import 'package:aslan/chatvibe_features/user/my_items/headdress/bags_headdress_page.dart'; +import 'package:aslan/chatvibe_features/user/my_items/mountains/bags_mountains_page.dart'; + +import '../../../chatvibe_core/constants/at_global_config.dart'; +import '../../../chatvibe_domain/usecases/at_fixed_width_tabIndicator.dart'; + +class MyItemsPage extends StatefulWidget { + @override + _MyItemsPageState createState() => _MyItemsPageState(); +} + +class _MyItemsPageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + final List _pages = []; + final List _tabs = []; + String effPlayPath = ""; + String themePath = ""; + VapController? vapController; + Timer? _timer; + + @override + void initState() { + super.initState(); + _pages.add(BagsHeaddressPage()); + _pages.add( + BagsMountainsPage((playUrl) { + effPlayPath = ""; + vapController?.stop(); + setState(() {}); + try { + Future.delayed(Duration(milliseconds: 350), () { + if (ATPathUtils.fetchFileExtensionFunction(playUrl).toLowerCase() == ".mp4") { + FileCacheManager.getInstance().getFile(url: playUrl).then((file) { + vapController?.playFile(file.path); + }); + } else { + effPlayPath = playUrl; + setState(() {}); + } + }); + } catch (e) {} + }), + ); + _pages.add(BagsChatboxPage()); + _pages.add( + BagsTabThemePage((playUrl) { + themePath = playUrl; + setState(() {}); + _timer ??= Timer.periodic(Duration(seconds: 5), (timer) { + themePath = ""; + setState(() {}); + }); + }), + ); + _tabController = TabController(length: _pages.length, vsync: this); + _tabController.addListener(() { + vapController?.stop(); + _timer?.cancel(); + setState(() { + effPlayPath = ""; + themePath = ""; + }); + }); + } + + @override + void dispose() { + super.dispose(); + vapController?.dispose(); + _timer?.cancel(); + } + + @override + Widget build(BuildContext context) { + _tabs.clear(); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.headdress)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.mountains)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.chatBox)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.theme)); + return Stack( + children: [ + themePath.isNotEmpty + ? netImage( + url: themePath, + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + ) + : Image.asset( + "atu_images/room/at_icon_room_settig_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.myItems, + actions: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.all(5.w), + margin: EdgeInsetsDirectional.only(end: 10.w), + child: Image.asset( + "atu_images/store/at_icon_bag_shop.png", + width: 20.w, + height: 20.w, + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + StoreRoute.list, + replace: true, + ); + }, + ), + ], + ), + body: SafeArea( + top: false, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(width: 5.w), + TabBar( + tabAlignment: TabAlignment.center, // 改为 center + labelPadding: EdgeInsets.symmetric(horizontal: 12.w), + labelColor: Colors.black87, + isScrollable: true, + unselectedLabelColor: Colors.black38, + labelStyle: TextStyle(fontSize: 15.sp, fontWeight: FontWeight.w600), + unselectedLabelStyle: TextStyle(fontSize: 13.sp, fontWeight: FontWeight.w500), + indicator: ATFixedWidthTabIndicator( + width: 15.w, + color: ATGlobalConfig.businessLogicStrategy.getStorePageTabIndicatorColor(), + ), + dividerColor: ChatVibeTheme.transparent, + controller: _tabController, + tabs: _tabs, + ), + ], + ), + SizedBox(height: 4.w,), + Container(color: ChatVibeTheme.primaryLight,width: ScreenUtil().screenWidth,height: 0.5.w,), + SizedBox(height: 6.w,), + Expanded( + child: TabBarView( + controller: _tabController, + children: _pages, + ), + ), + ], + ), + ), + ), + SizedBox( + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + child: IgnorePointer( + child: VapView( + scaleType: ScaleType.centerCrop, + onViewCreated: (controller) { + vapController = controller; + }, + ), + ), + ), + ATPathUtils.fetchFileExtensionFunction(effPlayPath).toLowerCase() == ".svga" + ? IgnorePointer( + child: SVGAHeadwearWidget( + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + resource: effPlayPath, + loops: 1, + ), + ) + : Container(), + ], + ); + } +} diff --git a/lib/chatvibe_features/user/my_items/theme/bags_tab_theme_page.dart b/lib/chatvibe_features/user/my_items/theme/bags_tab_theme_page.dart new file mode 100644 index 0000000..11cfb20 --- /dev/null +++ b/lib/chatvibe_features/user/my_items/theme/bags_tab_theme_page.dart @@ -0,0 +1,79 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_features/room/them/custom/room_theme_custom_page.dart'; +import 'package:aslan/chatvibe_features/user/my_items/theme/bags_theme_page.dart'; + +///背包-房间主题 +class BagsTabThemePage extends StatefulWidget { + Function(String playUrl) playCall; + + BagsTabThemePage(this.playCall); + + @override + _BagsTabThemePageState createState() => _BagsTabThemePageState(playCall); +} + +class _BagsTabThemePageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + final List _pages = []; + final List _tabs = []; + Function(String playUrl) playCall; + + _BagsTabThemePageState(this.playCall); + + @override + void initState() { + super.initState(); + _pages.add( + BagsThemePage((playUrl) { + playCall(playUrl); + }), + ); + _pages.add(RoomThemeCustomPage()); + _tabController = TabController(length: _pages.length, vsync: this); + _tabController.addListener(() {}); // 监听切换 + } + + @override + Widget build(BuildContext context) { + _tabs.clear(); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.all)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.custom)); + return Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + body: Column( + children: [ + TabBar( + tabAlignment: TabAlignment.center, + labelPadding: EdgeInsets.symmetric(horizontal: 18.w), + labelColor: Colors.black87, + isScrollable: true, + indicator: BoxDecoration(), + unselectedLabelColor: Colors.black38, + labelStyle: TextStyle( + fontSize: 15.sp, + fontFamily: 'MyCustomFont', + fontWeight: FontWeight.w600, + ), + unselectedLabelStyle: TextStyle( + fontSize: 13.sp, + fontFamily: 'MyCustomFont', + fontWeight: FontWeight.w500, + ), + indicatorColor: Colors.transparent, + dividerColor: Colors.transparent, + controller: _tabController, + tabs: _tabs, + ), + Expanded( + child: TabBarView(controller: _tabController, children: _pages), + ), + ], + ), + ); + } +} diff --git a/lib/chatvibe_features/user/my_items/theme/bags_theme_page.dart b/lib/chatvibe_features/user/my_items/theme/bags_theme_page.dart new file mode 100644 index 0000000..7f4a744 --- /dev/null +++ b/lib/chatvibe_features/user/my_items/theme/bags_theme_page.dart @@ -0,0 +1,392 @@ +import 'dart:convert'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/store_repository_imp.dart'; +import 'package:aslan/chatvibe_ui/widgets/bag/props_bag_theme_detail_dialog.dart'; +import 'package:aslan/chatvibe_ui/widgets/countdown_timer.dart'; +import 'package:aslan/chatvibe_features/store/store_route.dart'; +import 'package:provider/provider.dart'; +import '../../../../chatvibe_core/constants/at_room_msg_type.dart'; +import '../../../../chatvibe_core/utilities/at_loading_manager.dart'; +import '../../../../chatvibe_domain/models/res/at_room_theme_list_res.dart'; +import '../../../../chatvibe_managers/rtc_manager.dart'; +import '../../../../chatvibe_managers/rtm_manager.dart'; +import '../../../../chatvibe_ui/components/at_debounce_widget.dart'; +import '../../../../chatvibe_ui/components/dialog/dialog_base.dart'; +import '../../../../chatvibe_ui/components/at_tts.dart'; +import '../../../../chatvibe_ui/widgets/room/room_msg_item.dart'; + +///背包-房间主题 +class BagsThemePage extends ATPageList { + Function(String playUrl) playCall; + + BagsThemePage(this.playCall); + + @override + _BagsThemePageState createState() => _BagsThemePageState(playCall); +} + +class _BagsThemePageState + extends ATPageListState { + Function(String playUrl) playCall; + + _BagsThemePageState(this.playCall); + + ATRoomThemeListRes? selecteTheme; + + @override + void initState() { + super.initState(); + enablePullUp = false; + isGridView = true; + gridViewCount = 3; + padding = EdgeInsets.symmetric(horizontal: 6.w); + backgroundColor = Colors.transparent; + gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridViewCount, + mainAxisSpacing: 12.w, + crossAxisSpacing: 12.w, + childAspectRatio: 0.88, + ); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: Column( + children: [ + Expanded(child: buildList(context)), + selecteTheme != null + ? Container( + color: Colors.black12, + height: 0.6.w, + width: ScreenUtil().screenWidth, + ) + : Container(), + selecteTheme != null ? SizedBox(height: 10.w) : Container(), + selecteTheme != null + ? Column( + children: [ + Row( + spacing: 5.w, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + "${ATAppLocalizations.of(context)!.expirationTime}:", + textColor: Colors.black54, + fontSize: 12.sp, + ), + CountdownTimer( + fontSize: 11.sp, + color: Color(0xffFFB805), + expiryDate: DateTime.fromMillisecondsSinceEpoch( + selecteTheme?.expireTime ?? 0, + ), + ), + ], + ), + SizedBox(height: 10.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 120.w, + height: 35.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(55.w), + gradient: LinearGradient( + colors: [Color(0xFFFEB219), Color(0xFFFF9326)], + ), + ), + child: text( + ATAppLocalizations.of(context)!.renewal, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + textColor: Colors.white, + ), + ), + onTap: () { + ///过期 续费操作 + ATNavigatorUtils.push( + context, + StoreRoute.list, + replace: false, + ); + }, + ), + SizedBox(width: 10.w), + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 120.w, + height: 35.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(55.w), + gradient: LinearGradient( + colors: + (selecteTheme?.useTheme ?? false) + ? [Color(0xffF2F2F2), Color(0xffF2F2F2)] + : [Color(0xFFFEB219), Color(0xFFFF9326)], + ), + ), + child: text( + (selecteTheme?.useTheme ?? false) + ? ATAppLocalizations.of(context)!.inUse + : ATAppLocalizations.of(context)!.use, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + textColor: + !(selecteTheme?.useTheme ?? false) + ? Colors.white + : Color(0xffB1B1B1), + ), + ), + onTap: () { + if (!(selecteTheme?.useTheme ?? false)) { + ///佩戴 + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.confirmUseTips, + btnText: + ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + _use(selecteTheme!, false); + }, + ); + }, + ); + } else { + ///卸下 + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.confirmUnUseTips, + btnText: + ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + _use(selecteTheme!, true); + }, + ); + }, + ); + } + }, + ), + ], + ), + SizedBox(height: 10.w), + ], + ) + : Container(), + ], + ), + ); + } + + @override + Widget buildItem(ATRoomThemeListRes res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + color: + selecteTheme?.id == res.id + ? Color(0xffFFF4D9) + : Colors.white, + border: Border.all( + color: + selecteTheme?.id == res.id + ? Color(0xffFEB219) + : Colors.black12, + width: 1.w, + ), + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + child: Stack( + alignment: Alignment.center, + children: [ + Column( + children: [ + SizedBox(height: 25.w), + netImage( + url: res.themeBack ?? "", + width: 55.w, + height: 55.w, + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + SizedBox(height: 8.w), + ], + ), + PositionedDirectional( + top: 0, + start: 0, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 5.w), + decoration: BoxDecoration( + color: Color(0xffFEB219), + borderRadius: BorderRadiusDirectional.only( + topStart: Radius.circular(6.w), + topEnd: Radius.circular(12.w), + bottomEnd: Radius.circular(12.w), + ), + ), + child: Row( + children: [ + CountdownTimer( + fontSize: 9.sp, + color: Colors.white, + expiryDate: DateTime.fromMillisecondsSinceEpoch( + res.expireTime ?? 0, + ), + ), + SizedBox(width: 3.w), + Image.asset( + "atu_images/store/at_icon_bag_clock.png", + width: 22.w, + height: 22.w, + ), + ], + ), + ), + ), + PositionedDirectional( + bottom: 5.w, + end: 5.w, + child: GestureDetector( + onTap: () { + _play(res); + }, + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.all(5.w), + child: Image.asset( + "atu_images/store/at_icon_store_theme_rev.png", + width: 13.w, + height: 13.w, + ), + ), + ), + ), + ], + ), + ), + onTap: () { + selecteTheme = res; + setState(() {}); + }, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var storeList = await StoreRepositoryImp().roomThemeBackpack(); + for (var v in storeList) { + if (v.useTheme ?? false) { + selecteTheme = v; + continue; + } + } + onSuccess(storeList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + void _use(ATRoomThemeListRes res, bool unload) { + ATLoadingManager.exhibitOperation(); + StoreRepositoryImp() + .themeSwitchUse(res.id ?? "", unload) + .then((value) async { + ATLoadingManager.veilRoutine(); + SmartDialog.dismiss(tag: "showPropsDetail"); + if (!unload) { + ATTts.show(ATAppLocalizations.of(context)!.successfulWear); + Provider.of( + context!, + listen: false, + ).updateRoomBG(res); + } else { + Provider.of( + context!, + listen: false, + ).updateRoomBG(ATRoomThemeListRes()); + selecteTheme = null; + ATTts.show(ATAppLocalizations.of(context)!.successfullyUnloaded); + } + loadData(1); + ///发送一条消息更新远端房间信息 + String? groupId = + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.roomAccount; + if (groupId != null) { + Msg msg = Msg( + groupId: groupId, + msg: unload ? "" : jsonEncode(res.toJson()), + type: ATRoomMsgType.roomBGUpdate, + ); + Provider.of( + context!, + listen: false, + ).sendMsg(msg, addLocal: false); + } + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } + + void _showDetail(ATRoomThemeListRes res) { + SmartDialog.show( + tag: "showPropsDetail", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return PropsBagThemeDetailDialog(res, () { + loadData(1); + }); + }, + ); + } + + void _play(ATRoomThemeListRes res) { + playCall(res.themeBack ?? ""); + } +} diff --git a/lib/chatvibe_features/user/profile/person_detail_page.dart b/lib/chatvibe_features/user/profile/person_detail_page.dart new file mode 100644 index 0000000..4c9b4ca --- /dev/null +++ b/lib/chatvibe_features/user/profile/person_detail_page.dart @@ -0,0 +1,1796 @@ +import 'dart:convert'; +import 'dart:ui'; +import 'package:aslan/chatvibe_features/user/profile/relation/relation_ship_page.dart'; +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_features/user/profile/props/giftwall_page.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_core/utilities/at_user_utils.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_features/user/profile/profile/profile_page.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; +import 'package:tencent_cloud_chat_sdk/enum/conversation_type.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/routes/at_routes.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_features/chat/at_chat_route.dart'; + +import '../../../chatvibe_core/constants/at_screen.dart'; +import '../../../chatvibe_domain/models/res/at_user_counter_res.dart'; +import '../../../chatvibe_domain/models/res/at_user_identity_res.dart'; +import '../../../chatvibe_domain/usecases/at_fixed_width_tabIndicator.dart'; + +class PersonDetailPage extends StatefulWidget { + final String isMe; + final String tageId; + + const PersonDetailPage({Key? key, required this.isMe, required this.tageId}) + : super(key: key); + + @override + _PersonDetailPageState createState() => _PersonDetailPageState(); +} + +class _PersonDetailPageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + final List _pages = []; + final List _tabs = []; + ATUserIdentityRes? userIdentity; + + bool isFollow = false; + bool isLoading = true; + bool isBlacklistLoading = true; + bool isBlacklist = false; + + Map counterMap = {}; + + // 添加滚动控制器 + final ScrollController _scrollController = ScrollController(); + double _opacity = 0.0; + + @override + void initState() { + super.initState(); + + // _pages.add(PersonDynamicListPage(false, widget.tageId)); + _pages.add(ProfilePage(false, widget.tageId)); + _pages.add(GiftwallPage(widget.tageId)); + _pages.add(RelationShipPage(false, widget.tageId)); + _tabController = TabController( + initialIndex: 0, + length: _pages.length, + vsync: this, + ); + _tabController.addListener(() { + setState(() {}); // 刷新UI + }); // 监听切换 + + // 监听滚动 + _scrollController.addListener(() { + if (_scrollController.hasClients) { + final offset = _scrollController.offset; + // 当滚动到一定位置时,头像和昵称应该完全显示在AppBar上 + // 这里计算透明度,可以根据需要调整 + final newOpacity = (offset / 320).clamp(0.0, 1.0); + if (newOpacity != _opacity) { + setState(() { + _opacity = newOpacity; + }); + } + } + }); + + Provider.of(context, listen: false) + .userProfile = null; + Provider.of( + context, + listen: false, + ).getUserInfoById(widget.tageId); + AccountRepository().userIdentity(userId: widget.tageId).then((v) { + userIdentity = v; + setState(() {}); + }); + if (widget.isMe != "true") { + AccountRepository() + .followCheck(widget.tageId) + .then((v) { + isFollow = v; + isLoading = false; + setState(() {}); + }) + .catchError((e) { + setState(() { + isLoading = false; + }); + }); + AccountRepository() + .blacklistCheck(widget.tageId) + .then((v) { + isBlacklist = v; + isBlacklistLoading = false; + setState(() {}); + if (isBlacklist) { + ATTts.show( + ATAppLocalizations.of(context)!.thisUserHasBeenBlacklisted, + ); + } + }) + .catchError((e) { + setState(() { + isBlacklistLoading = false; + }); + }); + } else { + isBlacklist = false; + isBlacklistLoading = false; + } + userCounter(widget.tageId); + } + + void userCounter(String userId) async { + counterMap.clear(); + var userCounterList = await AccountRepository().userCounter(userId); + counterMap = Map.fromEntries( + userCounterList.map( + (counter) => MapEntry(counter.counterType ?? "", counter), + ), + ); + setState(() {}); + } + + Widget _buildTab(int index, String text) { + final isSelected = _tabController.index == index; + return Tab(text: text); + } + + @override + Widget build(BuildContext context) { + _tabs.clear(); + // _tabs.add(_buildTab(0, ATAppLocalizations.of(context)!.dynamicT)); + _tabs.add(_buildTab(0, ATAppLocalizations.of(context)!.aboutMe)); + _tabs.add(_buildTab(1, ATAppLocalizations.of(context)!.giftwall)); + _tabs.add(_buildTab(3, ATAppLocalizations.of(context)!.relationShip)); + return Consumer( + builder: (context, ref, child) { + List backgroundPhotos = []; + backgroundPhotos = + (ref.userProfile?.backgroundPhotos ?? []) + .where((t) => t.status == 1) + .toList(); + return Directionality( + textDirection: + window.locale.languageCode == "ar" + ? TextDirection.rtl + : TextDirection.ltr, + child: SafeArea( + top: false, + child: Stack( + children: [ + // Container( + // width: ScreenUtil().screenWidth, + // height: ScreenUtil().screenHeight, + // color: Color(0xffFDF7E5), + // ), + backgroundPhotos.isNotEmpty + ? (isBlacklistLoading || isBlacklist + ? Container() + : Column( + children: [ + SizedBox( + height: 300.w, + child: CarouselSlider( + options: CarouselOptions( + height: 300.w, + autoPlay: backgroundPhotos.length > 1, + // 启用自动播放 + enlargeCenterPage: false, + // 居中放大当前页面 + aspectRatio: 1 / 1, + // 宽高比 + enableInfiniteScroll: true, + // 启用无限循环 + autoPlayAnimationDuration: Duration( + milliseconds: 800, + ), + // 自动播放动画时长 + viewportFraction: 1, + // 视口分数 + onPageChanged: (index, reason) { + setState(() {}); + }, + ), + items: + backgroundPhotos.map((item) { + return GestureDetector( + child: netImage( + url: item.url ?? "", + width: ScreenUtil().screenWidth, + height: 300.w, + fit: BoxFit.cover, + ), + onTap: () {}, + ); + }).toList(), + ), + ), + Transform.translate( + offset: Offset(0, -6.w), + child: Container( + decoration: BoxDecoration( + color: Color(0xffFDF7E5), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(8.w), + topRight: Radius.circular(8.w), + ), + ), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + ), + ), + ], + )) + : Column( + children: [ + SizedBox( + height: 300.w, + child: Image.asset( + 'atu_images/person/at_icon_my_head_bg_defalt.png', + width: ScreenUtil().screenWidth, + height: 300.w, + fit: BoxFit.cover, + ), + ), + Transform.translate( + offset: Offset(0, -6.w), + child: Container( + decoration: BoxDecoration( + color: Color(0xffFDF7E5), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(8.w), + topRight: Radius.circular(8.w), + ), + ), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + ), + ), + ], + ), + Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + appBar: ChatVibeStandardAppBar( + backButtonColor: Colors.white, + title: "", + leading: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + Navigator.pop(context); + }, + child: Container( + width: width(50), + height: height(30), + alignment: AlignmentDirectional.centerStart, + padding: EdgeInsetsDirectional.only(start: width(15)), + child: Icon( + window.locale.languageCode == "ar" + ? Icons.keyboard_arrow_right + : Icons.keyboard_arrow_left, + size: 28.w, + color: Colors.white, + ), + ), + ), + actions: [ + // 在AppBar中显示头像和昵称 + if (_opacity > 0.5) ...[ + SizedBox(width: 45.w), + head( + url: ref.userProfile?.userAvatar ?? "", + width: 50.w, + height: 50.w, + headdress: ref.userProfile?.getHeaddress()?.sourceUrl, + ), + SizedBox(width: 8.w), + chatvibeNickNameText( + maxWidth: 135.w, + ref.userProfile?.userNickname ?? "", + fontSize: 16.sp, + textColor: Color(0xFF333333), + fontWeight: FontWeight.w600, + type: ref.userProfile?.getVIP()?.name ?? "", + needScroll: + (ref + .userProfile + ?.userNickname + ?.characters + .length ?? + 0) > + 13, + ), + SizedBox(width: 8.w), + ], + Spacer(), + Builder( + builder: (ct) { + return IconButton( + icon: + widget.isMe == "true" + ? Image.asset( + "atu_images/person/at_icon_edit_user_info2.png", + width: 22.w, + color: Colors.white, + ) + : Icon( + Icons.more_horiz, + size: 22.w, + color: Colors.white, + ), + onPressed: () { + if (widget.isMe == "true") { + ATNavigatorUtils.push( + context, + MainRoute.edit, + replace: false, + ); + return; + } + SmartDialog.showAttach( + tag: "showUserInfoOptMenu", + targetContext: ct, + alignment: Alignment.bottomCenter, + maskColor: Colors.transparent, + animationType: SmartAnimationType.fade, + scalePointBuilder: + (selfSize) => Offset(selfSize.width, 10), + builder: (_) { + return GestureDetector( + child: Transform.translate( + offset: Offset(0, -10), + child: buildOptUserMenu(context, ref), + ), + onTap: () { + SmartDialog.dismiss( + tag: "showUserInfoOptMenu", + ); + }, + ); + }, + ); + }, + ); + }, + ), + ], + ), + body: + isBlacklistLoading || isBlacklist + ? Container() + : ExtendedNestedScrollView( + controller: _scrollController, + onlyOneScrollInBody: true, + headerSliverBuilder: ( + BuildContext context, + bool innerBoxIsScrolled, + ) { + return [ + SliverToBoxAdapter( + child: Container( + color: Colors.transparent, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + SizedBox(height: 210.w), // 给AppBar留出空间 + Column( + children: [ + Row( + children: [ + SizedBox(width: 15.w), + (ref + .userProfile + ?.cpList + ?.isNotEmpty ?? + false) + ? SizedBox( + height: 100.w, + child: Stack( + alignment: + Alignment.center, + children: [ + Row( + mainAxisSize: + MainAxisSize + .min, + crossAxisAlignment: + CrossAxisAlignment + .center, + children: [ + GestureDetector( + child: head( + url: + ref + .userProfile + ?.cpList + ?.first + .meUserAvatar ?? + "", + width: 88.w, + headdress: + ref.userProfile + ?.getHeaddress() + ?.sourceUrl, + ), + onTap: () { + String + encodedUrls = Uri.encodeComponent( + jsonEncode([ + ref + .userProfile + ?.cpList + ?.first + .meUserAvatar, + ]), + ); + ATNavigatorUtils.push( + context, + "${MainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=0", + ); + }, + ), + SizedBox( + width: 25.w, + ), + GestureDetector( + onTap: () { + ATNavigatorUtils.push( + context, + replace: + true, + "${MainRoute.person}?isMe=${UserManager().getCurrentUser()?.userProfile?.id == ref.userProfile?.cpList?.first.cpUserId}&tageId=${ref.userProfile?.cpList?.first.cpUserId}", + ); + }, + child: head( + url: + ref + .userProfile + ?.cpList + ?.first + .cpUserAvatar ?? + "", + headdress: + ref + .userProfile + ?.cpList + ?.first + .cpUserAvatarFrame, + width: 88.w, + ), + ), + ], + ), + IgnorePointer( + child: netImage( + url: + ref + .userProfile + ?.cpList + ?.first + .selfRing + ?.sourceUrl ?? + "", + height: 75.w, + width: 75.w, + noDefaultImg: + false, + ), + ), + ], + ), + ) + : GestureDetector( + child: head( + url: + ref + .userProfile + ?.userAvatar ?? + "", + width: 88.w, + headdress: + ref.userProfile + ?.getHeaddress() + ?.sourceUrl, + ), + onTap: () { + String encodedUrls = + Uri.encodeComponent( + jsonEncode([ + ref + .userProfile + ?.userAvatar, + ]), + ); + ATNavigatorUtils.push( + context, + "${MainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=0", + ); + }, + ), + ], + ), + SizedBox(height: 8.w), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Row( + children: [ + SizedBox(width: 12.w), + chatvibeNickNameText( + maxWidth: 135.w, + ref + .userProfile + ?.userNickname ?? + "", + fontSize: 18.sp, + textColor: Color( + 0xFF333333, + ), + fontWeight: + FontWeight.w600, + type: + ref.userProfile + ?.getVIP() + ?.name ?? + "", + needScroll: + (ref + .userProfile + ?.userNickname + ?.characters + .length ?? + 0) > + 13, + ), + SizedBox(width: 3.w), + getVIPBadge( + ref.userProfile + ?.getVIP() + ?.name, + width: 65.w, + height: 28.w, + ), + SizedBox(width: 3.w), + Container( + width: + (ref.userProfile?.age ?? + 0) > + 999 + ? 56.w + : 46.w, + height: 23.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + ref.userProfile?.userSex == + 0 + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png", + ), + ), + ), + child: Row( + crossAxisAlignment: + CrossAxisAlignment + .center, + mainAxisAlignment: + MainAxisAlignment + .center, + children: [ + xb( + ref + .userProfile + ?.userSex, + ), + text( + "${ref.userProfile?.age ?? 0}", + textColor: + Colors.white, + fontSize: 13.sp, + fontWeight: + FontWeight.w600, + ), + SizedBox(width: 3.w), + ], + ), + ), + ], + ), + SingleChildScrollView( + scrollDirection: + Axis.horizontal, + child: Row( + children: [ + SizedBox(width: 12.w), + Row( + children: [ + Row( + children: [ + getIdIcon( + ref + .userProfile + ?.wealthLevel ?? + 0, + width: 32.w, + height: 32.w, + fontSize: 16.sp, + fontWeight: + FontWeight + .w600, + textColor: Color( + 0xFF333333, + ), + ), + chatvibeNickNameText( + maxWidth: 135.w, + ref.userProfile + ?.getID() ?? + "", + textColor: Color( + 0xFF333333, + ), + fontSize: 16.sp, + fontWeight: + FontWeight + .w600, + type: + ref.userProfile + ?.getVIP() + ?.name ?? + "", + needScroll: + (ref.userProfile + ?.getID() + .characters + .length ?? + 0) > + 13, + ), + ], + ), + ], + ), + SizedBox(width: 3.w), + getWealthLevel( + ref + .userProfile + ?.wealthLevel ?? + 0, + width: 58.w, + height: 30.w, + ), + SizedBox(width: 3.w), + getUserLevel( + ref + .userProfile + ?.charmLevel ?? + 0, + width: 58.w, + height: 30.w, + ), + SizedBox(width: 5.w), + ], + ), + ), + ], + ), + ref.userProfile?.wearHonor?.where(( + item, + ) { + return item.use ?? false; + }).isNotEmpty ?? + false + ? Row( + children: [ + SizedBox(width: 15.w), + Expanded( + child: Wrap( + runSpacing: 2.w, + spacing: 4.w, + direction: + Axis.horizontal, + children: + ref + .userProfile! + .wearHonor! + .where((item) { + return item + .use ?? + false; + }) + .map((item) { + return netImage( + url: + item.animationUrl ?? + "", + height: + 28.w, + ); + }) + .toList(), + ), + ), + SizedBox(width: 5.w), + ], + ) + : Container(), + (ref.userProfile?.wearHonor?.where(( + item, + ) { + return item.use ?? false; + }).isNotEmpty ?? + false) + ? SizedBox(height: 5.w) + : Container(), + Container( + margin: + EdgeInsetsDirectional.symmetric( + horizontal: 10.w, + ), + alignment: + AlignmentDirectional + .centerStart, + child: SingleChildScrollView( + scrollDirection: + Axis.horizontal, + child: Row( + mainAxisSize: + MainAxisSize.min, + children: [ + SizedBox(width: 5.w), + ref.userProfile?.wearBadge + ?.where((item) { + return item + .use ?? + false; + }) + .isNotEmpty ?? + false + ? SizedBox( + height: 35.w, + child: ListView.separated( + scrollDirection: + Axis.horizontal, + shrinkWrap: true, + itemCount: + ref + .userProfile + ?.wearBadge + ?.where(( + item, + ) { + return item + .use ?? + false; + }) + .length ?? + 0, + itemBuilder: ( + context, + index, + ) { + return netImage( + width: 35.w, + height: 35.w, + url: + ref + .userProfile + ?.wearBadge + ?.where(( + item, + ) { + return item.use ?? + false; + }) + .toList()[index] + .selectUrl ?? + "", + ); + }, + separatorBuilder: ( + BuildContext + context, + int index, + ) { + return SizedBox( + width: 5.w, + ); + }, + ), + ) + : Container(), + SizedBox(width: 5.w), + ], + ), + ), + ), + SizedBox(height: 5.w), + Consumer< + ChatVibeUserProfileManager + >( + builder: (context, ref, child) { + return Container( + height: 72.w, + alignment: + AlignmentDirectional + .center, + width: + ScreenUtil().screenWidth, + margin: EdgeInsets.symmetric( + horizontal: 18.w, + ), + padding: EdgeInsets.symmetric( + horizontal: 35.w, + ), + child: Row( + mainAxisSize: + MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + GestureDetector( + child: Column( + mainAxisAlignment: + MainAxisAlignment + .center, + children: [ + text( + "${counterMap["INTERVIEW"]?.quantity ?? 0}", + fontSize: 17.sp, + textColor: + Colors.black, + fontWeight: + FontWeight + .bold, + ), + text( + ATAppLocalizations.of( + context, + )!.vistors, + fontSize: 14.sp, + textColor: Color( + 0xffB1B1B1, + ), + ), + ], + ), + onTap: () { + if (widget.isMe == + "true") { + ATNavigatorUtils.push( + context, + "${MainRoute.vistors}?removePreviousPersonDetail=true", + ); + } + }, + ), + Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color( + 0xff333333, + ).withOpacity( + 0.0, + ), + Color(0xff333333), + Color( + 0xff333333, + ).withOpacity( + 0.0, + ), + ], + begin: + Alignment + .topCenter, + end: + Alignment + .bottomCenter, + ), + ), + height: 25.w, + width: 1.w, + ), + GestureDetector( + onTap: () { + if (widget.isMe == + "true") { + ATNavigatorUtils.push( + context, + "${MainRoute.follow}?removePreviousPersonDetail=true", + ); + } + }, + child: Column( + mainAxisAlignment: + MainAxisAlignment + .center, + children: [ + text( + "${counterMap["SUBSCRIPTION"]?.quantity ?? 0}", + fontSize: 17.sp, + textColor: + Colors.black, + fontWeight: + FontWeight + .bold, + ), + text( + ATAppLocalizations.of( + context, + )!.follow, + fontSize: 14.sp, + textColor: Color( + 0xffB1B1B1, + ), + ), + ], + ), + ), + Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color( + 0xff333333, + ).withOpacity( + 0.0, + ), + Color(0xff333333), + Color( + 0xff333333, + ).withOpacity( + 0.0, + ), + ], + begin: + Alignment + .topCenter, + end: + Alignment + .bottomCenter, + ), + ), + height: 25.w, + width: 1.w, + ), + GestureDetector( + child: Column( + mainAxisAlignment: + MainAxisAlignment + .center, + children: [ + text( + "${counterMap["FANS"]?.quantity ?? 0}", + fontSize: 17.sp, + textColor: + Colors.black, + fontWeight: + FontWeight + .bold, + ), + text( + ATAppLocalizations.of( + context, + )!.fans, + fontSize: 14.sp, + textColor: Color( + 0xffB1B1B1, + ), + ), + ], + ), + onTap: () { + if (widget.isMe == + "true") { + ATNavigatorUtils.push( + context, + "${MainRoute.fans}?removePreviousPersonDetail=true", + ); + } + }, + ), + ], + ), + ); + }, + ), + SizedBox(height: 6.w), + ], + ), + ], + ), + ), + ), + ]; + }, + body: Container( + padding: EdgeInsets.symmetric(horizontal: 3.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10.w), + topRight: Radius.circular(8.w), + ), + gradient: + ref.userProfile?.userSex == 0 + ? LinearGradient( + colors: [ + Color(0xFFFDF7E5), + Color(0xFFFDF7E5), + ], + begin: AlignmentDirectional.topStart, + end: AlignmentDirectional.bottomEnd, + ) + : LinearGradient( + colors: [ + Color(0xFFFDF7E5), + Color(0xFFFDF7E5), + ], + begin: AlignmentDirectional.topStart, + end: AlignmentDirectional.bottomEnd, + ), + ), + child: Column( + children: [ + Row( + children: [ + SizedBox(width: 6.w), + SizedBox( + height: 35.w, + child: TabBar( + tabAlignment: TabAlignment.start, + indicator: ATFixedWidthTabIndicator( + width: 20.w, + height: 4.w, + gradient: + ref.userProfile?.userSex == 0 + ? LinearGradient( + colors: [ + Color(0xffFFD800), + Color(0xffFF9500), + ], + ) + : LinearGradient( + colors: [ + Color(0xffFFD800), + Color(0xffFF9500), + ], + ), + ), + labelPadding: EdgeInsets.symmetric( + horizontal: 12.w, + ), + labelColor: Colors.black, + isScrollable: true, + unselectedLabelColor: Color( + 0xffB1B1B1, + ), + labelStyle: TextStyle( + fontSize: 15.sp, + fontWeight: FontWeight.bold, + ), + unselectedLabelStyle: TextStyle( + fontSize: 13.sp, + ), + indicatorColor: Colors.transparent, + dividerColor: Colors.transparent, + controller: _tabController, + tabs: _tabs, + ), + ), + SizedBox(width: 6.w), + ], + ), + Expanded( + child: TabBarView( + controller: _tabController, + children: _pages, + ), + ), + SizedBox(height: 75.w), + ], + ), + ), + ), + ), + // 底部按钮区域 + isBlacklistLoading || isBlacklist + ? Container() + : Positioned( + bottom: 0, + left: 0, + right: 0, + child: + widget.isMe == "true" + ? Container() + : (isLoading + ? Container() + : Container( + alignment: AlignmentDirectional.center, + width: ScreenUtil().screenWidth, + height: 75.w, + color: Color(0xffFDF7E5), + child: Column( + children: [ + Container( + height: 0.5.w, + color: Colors.black12, + ), + SizedBox(height: 10.w), + Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + ATDebounceWidget( + debounceTime: Duration( + milliseconds: 800, + ), + child: SizedBox( + width: 110.w, + child: Column( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Image.asset( + "atu_images/person/at_icon_person_in_room.png", + width: 23.w, + ), + SizedBox(height: 5.w), + Container( + margin: EdgeInsets.only( + bottom: 0.w, + ), + child: text( + ATAppLocalizations.of( + context, + )!.inRoom, + textColor: Color( + 0xff5C2C00, + ), + fontWeight: + FontWeight.w600, + fontSize: 14.sp, + ), + ), + ], + ), + ), + onTap: () { + if ((ref + .userProfile + ?.inRoomId ?? + "") + .isNotEmpty) { + ATRoomUtils.goRoom( + ref.userProfile?.inRoomId ?? + "", + context, + ); + } + }, + ), + SizedBox(width: 10.w), + ATDebounceWidget( + debounceTime: Duration( + milliseconds: 800, + ), + child: Container( + width: 110.w, + child: Column( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Image.asset( + "atu_images/person/at_icon_person_tochat.png", + width: 23.w, + ), + SizedBox(height: 5.w), + Container( + margin: EdgeInsets.only( + bottom: 0.w, + ), + child: text( + ATAppLocalizations.of( + context, + )!.message, + textColor: Color( + 0xff5C2C00, + ), + fontWeight: + FontWeight.w600, + fontSize: 14.sp, + ), + ), + ], + ), + ), + onTap: () async { + var conversation = + V2TimConversation( + type: + ConversationType + .V2TIM_C2C, + userID: widget.tageId, + conversationID: '', + ); + var bool = await Provider.of< + RtmProvider + >( + context, + listen: false, + ).startConversation( + conversation, + ); + if (!bool) return; + var json = jsonEncode( + conversation.toJson(), + ); + ATNavigatorUtils.push( + context, + "${ATChatRouter.chat}?conversation=${Uri.encodeComponent(json)}", + ); + }, + ), + SizedBox(width: 10.w), + ATDebounceWidget( + debounceTime: Duration( + milliseconds: 800, + ), + child: Container( + width: 110.w, + child: Column( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Image.asset( + "atu_images/person/at_icon_person_follow.png", + width: 23.w, + ), + SizedBox(height: 5.w), + Container( + margin: EdgeInsets.only( + bottom: 0.w, + ), + child: text( + isFollow + ? ATAppLocalizations.of( + context, + )!.following + : ATAppLocalizations.of( + context, + )!.follow, + textColor: Color( + 0xff5C2C00, + ), + fontWeight: + FontWeight.w600, + fontSize: 14.sp, + ), + ), + ], + ), + ), + onTap: () { + AccountRepository() + .followUser(widget.tageId) + .then((v) { + isFollow = !isFollow; + setState(() {}); + }); + }, + ), + ], + ), + ], + ), + )), + ), + ], + ), + ), + ); + }, + ); + } + + Container buildOptUserMenu( + BuildContext context, + ChatVibeUserProfileManager ref, + ) { + return Container( + width: 163.w, + margin: EdgeInsetsDirectional.only(end: 8.w), + decoration: BoxDecoration( + color: Colors.white30, + borderRadius: BorderRadius.circular(4), + ), + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 5.w), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ATDebounceWidget( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10.w), + child: text( + ATAppLocalizations.of(context)!.report, + textColor: Colors.white, + fontSize: 12.sp, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showUserInfoOptMenu"); + ATNavigatorUtils.push( + context, + "${MainRoute.report}?type=user&tageId=${ref.userProfile?.id}", + replace: false, + ); + }, + ), + ((!(userIdentity?.admin ?? false) && + !(userIdentity?.superAdmin ?? false)) && + ((Provider.of( + context, + listen: false, + ).userIdentity?.admin ?? + false))) + ? ATDebounceWidget( + child: Container( + margin: EdgeInsets.only(top: 5.w), + child: text( + ATAppLocalizations.of(context)!.userEditing, + letterSpacing: 0.1, + textColor: Colors.white, + fontSize: 12.sp, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showUserInfoOptMenu"); + ATNavigatorUtils.push( + context, + "${MainRoute.editingUserRoomAdmin}?type=User&profile=${Uri.encodeComponent(jsonEncode(ref.userProfile?.toJson()))}", + ); + }, + ) + : Container(), + ((ref.userIdentity?.admin ?? false) || + (ref.userIdentity?.superFreightAgent ?? false)) && + !((userIdentity?.admin ?? false) || + (userIdentity?.superFreightAgent ?? false)) + ? SizedBox(height: 5.w) + : Container(), + ((ref.userIdentity?.admin ?? false) || + (ref.userIdentity?.superFreightAgent ?? false)) && + !((userIdentity?.admin ?? false) || + (userIdentity?.superFreightAgent ?? false)) + ? GestureDetector( + child: text( + letterSpacing: 0.1, + ATAppLocalizations.of(context)!.becomeAgent, + textColor: Colors.white, + fontSize: 12.sp, + ), + onTap: () { + SmartDialog.dismiss(tag: "showUserInfoOptMenu"); + AccountRepository().teamCreate( + ref.userProfile?.getID() ?? "", + ); + }, + ) + : Container(), + (ref.userIdentity?.agent ?? false) + ? GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.only(top: 3.w), + child: text( + letterSpacing: 0.1, + ATAppLocalizations.of(context)!.inviteToBecomeAHost, + textColor: Colors.white, + fontSize: 12.sp, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showUserInfoOptMenu"); + ATLoadingManager.exhibitOperation(); + AccountRepository() + .teamInviteHost(ref.userProfile?.id ?? "") + .then((r) { + ATLoadingManager.veilRoutine(); + }) + .catchError((_) { + ATLoadingManager.veilRoutine(); + }); + }, + ) + : Container(), + (ref.userProfile?.isCpRelation ?? false) + ? GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.only(top: 3.w), + child: text( + letterSpacing: 0.1, + ATAppLocalizations.of(context)!.partWays, + textColor: Colors.white, + fontSize: 12.sp, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showUserInfoOptMenu"); + _showPartWaysDialog(ref); + }, + ) + : Container(), + isBlacklistLoading + ? Container() + : GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.only(top: 3.w), + child: text( + letterSpacing: 0.1, + isBlacklist + ? ATAppLocalizations.of(context)!.removeFromBlacklist + : ATAppLocalizations.of(context)!.moveToBlacklist, + textColor: Colors.white, + fontSize: 12.sp, + ), + ), + onTap: () { + if ((ref.userProfile?.isCpRelation ?? false)) { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.youAreCurrentlyCPRelationshipPleaseDissolve, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () {}, + ); + }, + ); + return; + } + ATAccountHelper.optBlacklist( + widget.tageId, + isBlacklist, + context, + (isOptBlacklist) { + isBlacklist = isOptBlacklist; + setState(() {}); + }, + ); + }, + ), + ], + ), + ); + } + + void _showPartWaysDialog(ChatVibeUserProfileManager ref) { + SmartDialog.show( + tag: "showPartWaysDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return Container( + height: 530.w, + width: ScreenUtil().screenWidth * 0.9, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_dialog_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + SizedBox(height: 58.w), + Container( + child: text( + ATAppLocalizations.of(context)!.partWays, + fontSize: 22.sp, + textColor: Color(0xffECB45C), + fontWeight: FontWeight.bold, + ), + ), + Transform.translate( + offset: Offset(0, -25), + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + PositionedDirectional( + top: 31.w, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + head( + url: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userAvatar ?? + "", + width: 95.w, + ), + SizedBox(width: 14.w), + head( + url: ref.userProfile?.userAvatar ?? "", + width: 95.w, + ), + ], + ), + ), + Image.asset( + "atu_images/person/at_icon_send_cp_requst_dialog_head2.png", + height: 120.w, + fit: BoxFit.fill, + ), + ], + ), + ), + Transform.translate( + offset: Offset(0, -23), + child: Container( + height: 25.w, + width: ScreenUtil().screenWidth * 0.6, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_username_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Row( + children: [ + SizedBox(width: 15.w), + Expanded( + child: Container( + alignment: Alignment.center, + height: 15.w, + child: + (AccountStorage().getCurrentUser()?.userProfile?.userNickname?.length ?? 0) > 8 + ? Marquee( + text: AccountStorage().getCurrentUser()?.userProfile?.userNickname?? "", + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.linear, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + AccountStorage().getCurrentUser()?.userProfile?.userNickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + ), + SizedBox(width: 20.w), + Expanded( + child: Container( + alignment: Alignment.center, + height: 15.w, + child: + (ref.userProfile?.userNickname?.length ?? 0) > 8 + ? Marquee( + text: ref.userProfile?.userNickname ?? "", + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.linear, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + ref.userProfile?.userNickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + ), + SizedBox(width: 15.w), + ], + ), + ), + ), + + Container( + padding: EdgeInsets.only(bottom: 12.w), + margin: EdgeInsets.symmetric(horizontal: 18.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_dialog_content.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + Container( + margin: EdgeInsets.symmetric( + horizontal: 35.w, + ).copyWith(top: 45.w), + child: text( + ATAppLocalizations.of( + context, + )!.areYouSureYouWantToPartWaysWithYourCP, + fontWeight: FontWeight.w500, + textColor: Color(0xffECB45C), + maxLines: 3, + fontSize: 14.sp, + ), + ), + Container( + margin: EdgeInsets.symmetric(horizontal: 20.w), + alignment: AlignmentDirectional.center, + child: text( + ATAppLocalizations.of(context)!.partWaysTips, + fontSize: 10.w, + textColor: Color(0xffECB45C), + maxLines: 6, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + SizedBox(height: 10.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + child: Container( + padding: EdgeInsets.only(top: 7.w), + alignment: AlignmentDirectional.center, + width: 130.w, + height: 48.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_ok_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: text( + ATAppLocalizations.of(context)!.cancel, + fontSize: 18.sp, + fontWeight: FontWeight.bold, + textColor: Color(0xffECB45C), + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showPartWaysDialog"); + }, + ), + SizedBox(width: 25.w), + GestureDetector( + child: Container( + padding: EdgeInsets.only(top: 7.w), + alignment: AlignmentDirectional.center, + width: 130.w, + height: 48.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_cancel_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: text( + ATAppLocalizations.of(context)!.confirm, + fontSize: 18.sp, + fontWeight: FontWeight.bold, + textColor: Color(0xffECB45C), + ), + ), + onTap: () { + ATLoadingManager.exhibitOperation(); + AccountRepository() + .cpRelationshipDismissApply(ref.userProfile?.id ?? "") + .then((result) { + SmartDialog.dismiss(tag: "showPartWaysDialog"); + ATTts.show( + ATAppLocalizations.of( + context, + )!.operationSuccessful, + ); + ATLoadingManager.veilRoutine(); + ATNavigatorUtils.popUntil( + context, + ModalRoute.withName(ATRoutes.home), + ); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + }, + ), + ], + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/chatvibe_features/user/profile/profile/profile_page.dart b/lib/chatvibe_features/user/profile/profile/profile_page.dart new file mode 100644 index 0000000..9497053 --- /dev/null +++ b/lib/chatvibe_features/user/profile/profile/profile_page.dart @@ -0,0 +1,442 @@ +import 'dart:convert'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_ninepatch_image/flutter_ninepatch_image.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/family_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/family_base_info_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_features/family/family_route.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +class ProfilePage extends StatefulWidget { + bool isFromMe = false; + String userId = ""; + + ProfilePage(this.isFromMe, this.userId); + + @override + _ProfilePageState createState() => _ProfilePageState(); +} + +class _ProfilePageState extends State { + ChatVibeFamilyBaseInfoRes? familyBaseInfo; + + @override + void initState() { + super.initState(); + _loadFamily(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + List items = []; + if (widget.isFromMe) { + items = + (AccountStorage().getCurrentUser()?.userProfile?.personalPhotos ?? + []) + .where((t) => t.status == 1) + .toList(); + } else { + items = ref.userProfile?.personalPhotos ?? []; + items = + (ref.userProfile?.personalPhotos ?? []) + .where((t) => t.status == 1) + .toList(); + } + return SingleChildScrollView( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 5.w), + GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, // 每行2个 + childAspectRatio: 1, // 宽高比 + mainAxisSpacing: 8, + crossAxisSpacing: 8, + ), + itemCount: items.length, + itemBuilder: (context, index) { + return GestureDetector( + child: netImage( + url: items[index].url ?? "", + width: double.infinity, + height: double.infinity, + defaultImg: "atu_images/general/at_icon_loading.webp", + borderRadius: BorderRadius.circular(12.w), + ), + onTap: () { + List ims = []; + for (var iv in items) { + if ((iv.url?.isNotEmpty ?? false)) { + ims.add(iv.url ?? ""); + } + } + String encodedUrls = Uri.encodeComponent( + jsonEncode(ims), + ); + ATNavigatorUtils.push( + context, + "${MainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=$index", + ); + }, + ); + }, + ), + SizedBox(height: 5.w), + text( + ATAppLocalizations.of(context)!.personal2, + fontSize: 14.sp, + fontWeight: FontWeight.bold, + textColor: Colors.black, + ), + SizedBox(height: 15.w), + _item2( + ATAppLocalizations.of(context)!.country, + widget.isFromMe + ? AccountStorage() + .getCurrentUser() + ?.userProfile + ?.countryName ?? + "" + : ref.userProfile?.countryName ?? "", + ), + SizedBox(height: 10.w), + _item(ATAppLocalizations.of(context)!.language, "English"), + SizedBox(height: 10.w), + _item( + ATAppLocalizations.of(context)!.dateOfBirth, + "${widget.isFromMe ? AccountStorage().getCurrentUser()?.userProfile?.bornYear ?? "" : ref.userProfile?.bornYear}-${widget.isFromMe ? AccountStorage().getCurrentUser()?.userProfile?.bornMonth ?? "" : ref.userProfile?.bornMonth}-${widget.isFromMe ? AccountStorage().getCurrentUser()?.userProfile?.bornDay ?? "" : ref.userProfile?.bornDay}", + ), + SizedBox(height: 10.w), + _item( + ATAppLocalizations.of(context)!.bio, + widget.isFromMe + ? AccountStorage() + .getCurrentUser() + ?.userProfile + ?.autograph ?? + "" + : ref.userProfile?.autograph ?? "", + ), + SizedBox(height: 10.w), + _item( + ATAppLocalizations.of(context)!.hobby, + widget.isFromMe + ? AccountStorage().getCurrentUser()?.userProfile?.hobby ?? + "" + : ref.userProfile?.hobby ?? "", + ), + SizedBox(height: 62.w), + ], + ), + ), + ); + }, + ); + } + + _item(String name, String value) { + return Row( + children: [ + SizedBox( + width: 100.w, + child: text(name, textColor: Color(0xffB1B1B1), fontSize: 13.sp), + ), + Expanded( + child: text( + value, + fontSize: 13.sp, + textColor: Colors.black, + maxLines: 2, + ), + ), + ], + ); + } + + _item2(String name, String value) { + return Row( + children: [ + SizedBox( + width: 100.w, + child: text(name, textColor: Color(0xffB1B1B1), fontSize: 13.sp), + ), + netImage( + url: + Provider.of( + context, + listen: false, + ).getCountryByName(value)?.nationalFlag ?? + "", + borderRadius: BorderRadius.all(Radius.circular(3.w)), + width: 19.w, + height: 14.w, + ), + SizedBox(width: 3.w), + Expanded( + child: text( + value, + fontSize: 13.sp, + textColor: Colors.black, + maxLines: 2, + ), + ), + ], + ); + } + + _buidFamilyInfo() { + String familyNotice = (familyBaseInfo?.familyNotice ?? "").replaceAll( + RegExp(r'\s+'), + ' ', + ); + num level = 0; + if ((familyBaseInfo?.familyLevel ?? 0) > 0 && + (familyBaseInfo?.familyLevel ?? 0) < 4) { + level = 1; + } else if ((familyBaseInfo?.familyLevel ?? 0) > 3 && + (familyBaseInfo?.familyLevel ?? 0) < 7) { + level = 2; + } else if ((familyBaseInfo?.familyLevel ?? 0) > 6 && + (familyBaseInfo?.familyLevel ?? 0) < 10) { + level = 3; + } else { + level = familyBaseInfo?.familyLevel ?? 0; + } + return SizedBox( + width: ScreenUtil().screenWidth, + child: NinePatchImage( + alignment: Alignment.center, + imageProvider: AssetImage( + "atu_images/family/at_icon_family_info_item_bg_$level.9.png", + ), + sliceCachedKey: "atu_images/family/at_icon_family_info_item_bg_$level.9.png", + child: Column( + children: [ + SizedBox(height: 5.w), + Row( + children: [ + SizedBox(width: 5.w), + ATDebounceWidget( + child: Container( + margin: EdgeInsetsDirectional.only(start: 8.w, top: 5.w), + child: netImage( + height: 80.w, + width: 80.w, + borderRadius: BorderRadius.circular(5.w), + url: familyBaseInfo?.familyAvatar ?? "", + ), + ), + onTap: () { + if ((familyBaseInfo?.familyId ?? "").isNotEmpty) { + ATNavigatorUtils.push( + context, + "${FamilyRoute.familyInfo}?familyId=${familyBaseInfo?.familyId}", + replace: false, + ); + } + }, + ), + SizedBox(width: 10.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + constraints: BoxConstraints( + maxWidth: 135.w, + maxHeight: 24.w, + ), + child: + (familyBaseInfo?.familyName?.length ?? 0) > 12 + ? Marquee( + text: familyBaseInfo?.familyName ?? "", + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 40.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + familyBaseInfo?.familyName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + SizedBox(height: 5.w), + Row( + children: [ + text( + "ID:${familyBaseInfo?.familyAccount ?? ""}", + fontWeight: FontWeight.w600, + fontSize: 13.sp, + textColor: Colors.white, + ), + SizedBox(width: 2.w), + Container( + alignment: AlignmentDirectional.center, + height: 25.w, + width: 66.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/family/at_icon_family_member_btn_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + height: 15.w, + "atu_images/family/at_icon_family_member_tag.png", + ), + SizedBox(width: 3.w), + text( + "${familyBaseInfo?.currentMember ?? 0}", + fontWeight: FontWeight.w600, + fontSize: 13.sp, + textColor: Colors.white, + ), + ], + ), + ), + ], + ), + ], + ), + ), + Image.asset( + "atu_images/family/at_icon_family_level_${familyBaseInfo?.familyLevel ?? 0}.png", + height: 80.w, + ), + // Image.asset( + // "images/family/at_icon_family_rank_tag.png", + // height: 80.w, + // ), + SizedBox(width: 5.w), + ], + ), + SizedBox(height: 8.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + padding: EdgeInsets.symmetric(vertical: 5.w, horizontal: 6.w), + // 外部容器用于实现渐变边框 + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + border: Border.all(color: Color(0xFFF5F5F5), width: 0.2.w), + ), + child: Row( + children: [ + Image.asset( + "atu_images/family/at_icon_announcement_tag2.png", + height: 22.w, + ), + SizedBox(width: 3.w), + Expanded( + child: SizedBox( + height: 22.w, + child: + (familyNotice.length ?? 0) > 20 + ? Marquee( + text: familyNotice ?? "", + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 40.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + (familyNotice ?? "").isEmpty + ? ATAppLocalizations.of( + context, + )!.welcomeToMyFamily + : familyNotice ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + ), + ], + ), // 背景色, + ), + SizedBox(height: 10.w), + ], + ), + ), + ); + } + + void _loadFamily() { + ATLoadingManager.exhibitOperation(); + GroupRepository() + .familyUserBaseInfo(widget.userId) + .then((result) { + familyBaseInfo = result; + setState(() { + ATLoadingManager.veilRoutine(); + }); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } +} diff --git a/lib/chatvibe_features/user/profile/props/giftwall_page.dart b/lib/chatvibe_features/user/profile/props/giftwall_page.dart new file mode 100644 index 0000000..2886173 --- /dev/null +++ b/lib/chatvibe_features/user/profile/props/giftwall_page.dart @@ -0,0 +1,84 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_domain/models/res/gift_res.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/gift_repository_imp.dart'; + +///礼物墙 +class GiftwallPage extends StatefulWidget { + String tageId = ""; + + GiftwallPage(this.tageId); + + @override + _GiftwallPageState createState() => _GiftwallPageState(); +} + +class _GiftwallPageState extends State { + List giftWallList = []; + + @override + void initState() { + super.initState(); + GiftRepositoryImp() + .giftWall(widget.tageId) + .then((result) { + giftWallList = result; + setState(() {}); + }) + .catchError((e) {}); + } + + @override + Widget build(BuildContext context) { + return giftWallList.isEmpty + ? mainEmpty( + msg: ATAppLocalizations.of(context)!.noData, + textColor: Colors.black54, + ) + : SingleChildScrollView( + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, // 每行2个 + childAspectRatio: 0.82, // 宽高比 + mainAxisSpacing: 10, + crossAxisSpacing: 10, + ), + padding: EdgeInsets.all(10.w), + itemCount: giftWallList.length, + itemBuilder: (context, index) { + return _buildItem(giftWallList[index]); + }, + ), + ); + ; + } + + Widget _buildItem(ChatVibeGiftRes gift) { + return Column( + children: [ + Container( + width: 68.w, + height: 68.w, + decoration: BoxDecoration( + color: Color(0xffD9D9D9), + borderRadius: BorderRadius.circular(10.w), + ), + alignment: Alignment.center, + child: netImage(url: gift.giftPhoto ?? "", width: 36.w), + ), + SizedBox(height: 9.w), + text( + "x ${gift.quantity}", + fontSize: 13.sp, + textColor: Colors.black38, + ) + ], + ); + } +} diff --git a/lib/chatvibe_features/user/profile/props/honor_page.dart b/lib/chatvibe_features/user/profile/props/honor_page.dart new file mode 100644 index 0000000..d054e7c --- /dev/null +++ b/lib/chatvibe_features/user/profile/props/honor_page.dart @@ -0,0 +1,277 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_path_utils.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/headdress/headdress_widget.dart'; + +///荣耀墙 +class HonorPage extends StatefulWidget { + String tageId = ""; + + HonorPage(this.tageId); + + @override + _HonorPageState createState() => _HonorPageState(); +} + +class _HonorPageState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + if (widget.tageId == AccountStorage().getCurrentUser()?.userProfile?.id) { + return (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.wearHonor + ?.isNotEmpty ?? + false) + ? Directionality( + textDirection: TextDirection.ltr, + child: SingleChildScrollView( + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, // 每行2个 + childAspectRatio: 1, // 宽高比 + mainAxisSpacing: 5, + crossAxisSpacing: 5, + ), + padding: EdgeInsets.all(10), + itemCount: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.wearHonor + ?.length ?? + 0, + itemBuilder: (context, index) { + return _buildItem( + AccountStorage() + .getCurrentUser()! + .userProfile! + .wearHonor![index], + ); + }, + ), + ), + ) + : mainEmpty( + msg: ATAppLocalizations.of(context)!.noData, + textColor: Colors.white, + ); + } else { + return (ref.userProfile?.wearHonor?.isNotEmpty ?? false) + ? Directionality( + textDirection: TextDirection.ltr, + child: SingleChildScrollView( + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, // 每行2个 + childAspectRatio: 1, // 宽高比 + mainAxisSpacing: 5, + crossAxisSpacing: 5, + ), + padding: EdgeInsets.all(10), + itemCount: ref.userProfile?.wearHonor?.length, + itemBuilder: (context, index) { + return _buildItem(ref.userProfile!.wearHonor![index]); + }, + ), + ), + ) + : mainEmpty( + msg: ATAppLocalizations.of(context)!.noData, + textColor: Colors.white, + ); + } + }, + ); + } + + Widget _buildItem(WearBadge wearBadge) { + return ATDebounceWidget( + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(getBadgeLevelLabBG(wearBadge.badgeLevel ?? 1)), + fit: BoxFit.fill, + ), + ), + alignment: Alignment.topCenter, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: 25.w), + Container( + margin: EdgeInsetsDirectional.only(start: 12.w, end: 5.w), + child: netImage( + url: wearBadge.selectUrl ?? "", + height: 35.w, + fit: BoxFit.contain, + ), + ), + SizedBox(height: 7.w), + Container( + margin: EdgeInsetsDirectional.only(start: 8.w), + constraints: BoxConstraints(maxWidth: 70.w, maxHeight: 16.w), + child: + (wearBadge.badgeName?.length ?? 0) > 8 + ? Marquee( + text: wearBadge.badgeName ?? "", + style: TextStyle( + fontSize: 10.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 40.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration(milliseconds: 550), + decelerationCurve: Curves.easeOut, + ) + : Text( + wearBadge.badgeName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Colors.white, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + ), + onTap: () { + SmartDialog.show( + tag: "showPropsDetail", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return Stack( + children: [ + Container( + width: 220.w, + height: 150.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/index/at_icon_honor_detail_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: + wearBadge.animationUrl != null && + ATPathUtils.fileTypeIsPicOperation( + wearBadge.animationUrl ?? "", + ) + ? netImage( + url: wearBadge.animationUrl ?? "", + height: 35.w, + fit: BoxFit.contain, + ) + : (ATPathUtils.fetchFileExtensionFunction( + wearBadge.animationUrl ?? "", + ).toLowerCase() == + ".svga" + ? SVGAHeadwearWidget( + height: 35.w, + resource: + wearBadge.animationUrl ?? "", + ) + : netImage( + url: wearBadge.animationUrl ?? "", + height: 35.w, + fit: BoxFit.contain, + )), + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 15.w), + Container( + margin: EdgeInsetsDirectional.symmetric( + horizontal: 8.w, + ), + alignment: AlignmentDirectional.center, + child: text( + textAlign: TextAlign.center, + maxLines: 2, + wearBadge.badgeName ?? "", + fontSize: 14.sp, + fontWeight: FontWeight.bold, + textColor: CupertinoColors.white, + ), + ), + ], + ), + ), + Positioned( + right: 15.w, + top: 10.w, + child: GestureDetector( + child: Icon( + Icons.close, + size: 20.w, + color: Color(0xFFF8E090), + ), + onTap: () { + SmartDialog.dismiss(tag: "showPropsDetail"); + }, + ), + ), + ], + ); + }, + ); + }, + ); + } + + String getBadgeLevelLabBG(num badgeLevel) { + if (badgeLevel == 1) { + return "atu_images/person/at_icon_honor_item_c_bg.png"; + } else if (badgeLevel == 2) { + return "atu_images/person/at_icon_honor_item_b_bg.png"; + } else if (badgeLevel == 3) { + return "atu_images/person/at_icon_honor_item_a_bg.png"; + } else if (badgeLevel == 4) { + return "atu_images/person/at_icon_honor_item_s_bg.png"; + } + return "atu_images/person/at_icon_honor_item_c_bg.png"; + } +} diff --git a/lib/chatvibe_features/user/profile/props/medal_page.dart b/lib/chatvibe_features/user/profile/props/medal_page.dart new file mode 100644 index 0000000..4e8d29f --- /dev/null +++ b/lib/chatvibe_features/user/profile/props/medal_page.dart @@ -0,0 +1,266 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_path_utils.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/headdress/headdress_widget.dart'; + +///勋章墙 +class MedalPage extends StatefulWidget { + String tageId = ""; + + MedalPage(this.tageId); + + @override + _MedalPageState createState() => _MedalPageState(); +} + +class _MedalPageState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + if (widget.tageId == AccountStorage().getCurrentUser()?.userProfile?.id) { + return (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.wearBadge + ?.isNotEmpty ?? + false) + ? Directionality( + textDirection: TextDirection.ltr, + child: SingleChildScrollView( + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, // 每行2个 + childAspectRatio: 0.8, // 宽高比 + mainAxisSpacing: 5, + crossAxisSpacing: 5, + ), + padding: EdgeInsets.all(10), + itemCount: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.wearBadge + ?.length ?? + 0, + itemBuilder: (context, index) { + return _buildItem( + AccountStorage() + .getCurrentUser()! + .userProfile! + .wearBadge![index], + ); + }, + ), + ), + ) + : mainEmpty( + msg: ATAppLocalizations.of(context)!.noData, + textColor: Colors.white, + ); + } else { + return (ref.userProfile?.wearBadge?.isNotEmpty ?? false) + ? Directionality( + textDirection: TextDirection.ltr, + child: SingleChildScrollView( + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, // 每行2个 + childAspectRatio: 0.81, // 宽高比 + mainAxisSpacing: 5, + crossAxisSpacing: 5, + ), + padding: EdgeInsets.all(10), + itemCount: ref.userProfile?.wearBadge?.length, + itemBuilder: (context, index) { + return _buildItem(ref.userProfile!.wearBadge![index]); + }, + ), + ), + ) + : mainEmpty( + msg: ATAppLocalizations.of(context)!.noData, + textColor: Colors.white, + ); + } + }, + ); + } + + Widget _buildItem(WearBadge wearBadge) { + return ATDebounceWidget( + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(getBadgeLevelLabBG(wearBadge.badgeLevel ?? 1)), + fit: BoxFit.fill, + ), + ), + alignment: Alignment.topCenter, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: 25.w), + Container( + margin: EdgeInsetsDirectional.only(start: 5.w), + child: netImage(url: wearBadge.selectUrl ?? "", width: 52.w), + ), + SizedBox(height: 9.w), + Container( + margin: EdgeInsetsDirectional.only(start: 8.w), + constraints: BoxConstraints(maxWidth: 70.w, maxHeight: 16.w), + child: + (wearBadge.badgeName?.length ?? 0) > 8 + ? Marquee( + text: wearBadge.badgeName ?? "", + style: TextStyle( + fontSize: 10.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 40.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration(milliseconds: 550), + decelerationCurve: Curves.easeOut, + ) + : Text( + wearBadge.badgeName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Colors.white, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + ), + onTap: () { + SmartDialog.show( + tag: "showPropsDetail", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return Stack( + children: [ + Container( + width: 220.w, + height: 220.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/index/at_icon_medal_detail_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + wearBadge.animationUrl != null && + ATPathUtils.fileTypeIsPicOperation( + wearBadge.animationUrl ?? "", + ) + ? netImage( + url: wearBadge.animationUrl ?? "", + width: 120, + height: 120, + ) + : (ATPathUtils.fetchFileExtensionFunction( + wearBadge.animationUrl ?? "", + ).toLowerCase() == + ".svga" + ? SVGAHeadwearWidget( + width: 120.w, + height: 120.w, + resource: wearBadge.animationUrl ?? "", + ) + : netImage( + url: wearBadge.animationUrl ?? "", + width: 120.w, + height: 120.w, + )), + + SizedBox(height: 5.w), + Container( + margin: EdgeInsetsDirectional.symmetric( + horizontal: 8.w, + ), + alignment: AlignmentDirectional.center, + child: text( + textAlign: TextAlign.center, + maxLines: 2, + wearBadge.badgeName ?? "", + fontSize: 14.sp, + fontWeight: FontWeight.bold, + textColor: CupertinoColors.white, + ), + ), + ], + ), + ), + Positioned( + right: 15.w, + top: 10.w, + child: GestureDetector( + child: Icon( + Icons.close, + size: 20.w, + color: Color(0xFFF8E090), + ), + onTap: () { + SmartDialog.dismiss(tag: "showPropsDetail"); + }, + ), + ), + ], + ); + }, + ); + }, + ); + } + + String getBadgeLevelLabBG(num badgeLevel) { + if (badgeLevel == 1) { + return "atu_images/person/at_icon_medal_item_c_bg.png"; + } else if (badgeLevel == 2) { + return "atu_images/person/at_icon_medal_item_b_bg.png"; + } else if (badgeLevel == 3) { + return "atu_images/person/at_icon_medal_item_a_bg.png"; + } else if (badgeLevel == 4) { + return "atu_images/person/at_icon_medal_item_s_bg.png"; + } + return "atu_images/person/at_icon_medal_item_c_bg.png"; + } +} diff --git a/lib/chatvibe_features/user/profile/props/props_page.dart b/lib/chatvibe_features/user/profile/props/props_page.dart new file mode 100644 index 0000000..889a766 --- /dev/null +++ b/lib/chatvibe_features/user/profile/props/props_page.dart @@ -0,0 +1,88 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_features/user/profile/props/giftwall_page.dart'; +import 'package:aslan/chatvibe_features/user/profile/props/honor_page.dart'; +import 'package:aslan/chatvibe_features/user/profile/props/medal_page.dart'; + +///道具 +class PropsPage extends StatefulWidget { + String tageId = ""; + bool isFromMe = false; + + PropsPage(this.isFromMe, this.tageId); + + @override + _PropsPageState createState() => _PropsPageState(); +} + +class _PropsPageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + final List _pages = []; + final List _tabs = []; + + @override + void initState() { + super.initState(); + _pages.add(MedalPage(widget.tageId)); + _pages.add(HonorPage(widget.tageId)); + _pages.add(GiftwallPage(widget.tageId)); + _tabController = TabController(length: _pages.length, vsync: this); + } + + @override + Widget build(BuildContext context) { + _tabs.clear(); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.medal)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.honor)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.giftwall)); + return Column( + children: [ + Row( + children: [ + SizedBox(width: 5.w), + SizedBox( + height: 35.w, + child: TabBar( + tabAlignment: TabAlignment.start, + labelPadding: EdgeInsets.symmetric(horizontal: 12.w), + isScrollable: true, + indicator: BoxDecoration(), + labelStyle: TextStyle( + fontSize: 15.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + ), + unselectedLabelStyle: TextStyle( + fontSize: 13.sp, + color: Colors.white54, + fontWeight: FontWeight.w500, + ), + // indicatorPadding: EdgeInsets.symmetric( + // vertical: 5.w, + // horizontal: 15.w, + // ), + indicatorColor: Colors.transparent, + dividerColor: Colors.transparent, + controller: _tabController, + tabs: _tabs, + ), + ), + ], + ), + Expanded( + child: TabBarView( + controller: _tabController, + physics: NeverScrollableScrollPhysics(), + children: _pages, + ), + ), + widget.tageId!=AccountStorage().getCurrentUser()?.userProfile?.id?SizedBox(height: 55.w,):Container() + ], + ); + } +} diff --git a/lib/chatvibe_features/user/profile/relation/cp/add_cp_page.dart b/lib/chatvibe_features/user/profile/relation/cp/add_cp_page.dart new file mode 100644 index 0000000..792edf3 --- /dev/null +++ b/lib/chatvibe_features/user/profile/relation/cp/add_cp_page.dart @@ -0,0 +1,85 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_features/user/profile/relation/cp/search_cp_following_tab_page.dart'; +import 'package:aslan/chatvibe_features/user/profile/relation/cp/search_cp_friend_tab_page.dart'; + +import 'package:aslan/app_localizations.dart'; + +class AddCpPage extends StatefulWidget { + @override + _AddCpPageState createState() => _AddCpPageState(); +} + +class _AddCpPageState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + final List _pages = []; + final List _tabs = []; + + @override + void initState() { + super.initState(); + _pages.add(SearchCpFriendTabPage()); + _pages.add(SearchCpFollowingTabPage()); + _tabController = TabController(length: _pages.length, vsync: this); + } + + @override + Widget build(BuildContext context) { + _tabs.clear(); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.friends)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.following)); + return SafeArea( + top: false, + child: Container( + height: ScreenUtil().screenHeight * 0.6, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TabBar( + tabAlignment: TabAlignment.center, + labelPadding: EdgeInsets.symmetric(horizontal: 35.w), + isScrollable: true, + indicator: BoxDecoration(), + labelStyle: TextStyle( + fontSize: 15.sp, + fontFamily: 'MyCustomFont', + color: Colors.black, + fontWeight: FontWeight.w600, + ), + unselectedLabelStyle: TextStyle( + fontSize: 13.sp, + fontFamily: 'MyCustomFont', + color: Colors.black38, + fontWeight: FontWeight.w500, + ), + // indicatorPadding: EdgeInsets.symmetric( + // vertical: 5.w, + // horizontal: 15.w, + // ), + indicatorColor: Colors.transparent, + dividerColor: Colors.transparent, + controller: _tabController, + tabs: _tabs, + ), + Expanded( + child: TabBarView( + controller: _tabController, + physics: NeverScrollableScrollPhysics(), + children: _pages, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/chatvibe_features/user/profile/relation/cp/cp_list_page.dart b/lib/chatvibe_features/user/profile/relation/cp/cp_list_page.dart new file mode 100644 index 0000000..f85fb72 --- /dev/null +++ b/lib/chatvibe_features/user/profile/relation/cp/cp_list_page.dart @@ -0,0 +1,1319 @@ +import 'dart:async'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:marquee/marquee.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_domain/models/res/get_cp_relationship_pair_list_res.dart'; +import 'package:aslan/chatvibe_features/user/profile/relation/cp/add_cp_page.dart'; +import 'package:provider/provider.dart'; +import '../../../../../chatvibe_core/at_lk_event_bus.dart'; +import '../../../../../chatvibe_core/constants/at_global_config.dart'; +import '../../../../../chatvibe_core/constants/at_screen.dart'; +import '../../../../../chatvibe_core/routes/at_fluro_navigator.dart'; +import '../../../../../chatvibe_data/models/enum/at_cp_status_type.dart'; +import '../../../../../chatvibe_managers/user_profile_manager.dart'; +import '../../../../../chatvibe_ui/components/at_debounce_widget.dart'; +import '../../../../../chatvibe_ui/components/at_tts.dart'; +import '../../../../index/main_route.dart'; + +class CpListPage extends StatefulWidget { + @override + _CpListPageState createState() => _CpListPageState(); +} + +class _CpListPageState extends State { + List cpList = []; + bool isLoading = true; + late StreamSubscription _refreshCPEventSubscription; + + @override + void initState() { + super.initState(); + _loadData(); + _refreshCPEventSubscription = eventBus.on().listen((event) { + if (!mounted) { + return; + } + _loadData(); + }); + } + + @override + void dispose() { + _refreshCPEventSubscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/room/at_icon_room_settig_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.cpList, + actions: [ + ATDebounceWidget( + child: Container( + margin: EdgeInsetsDirectional.only(end: 15.w), + child: Image.asset( + "atu_images/person/at_icon_cp_gift_record.png", + width: 22.w, + height: 22.w, + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.cpRewardUrl)}&showTitle=false", + replace: false, + ); + }, + ), + ], + ), + body: SafeArea( + top: false, + child: Column( + children: [ + Row( + children: [ + SizedBox(width: 20.w), + Expanded( + child: text( + ATAppLocalizations.of(context)!.numberOfMyCPs( + "" + "${cpList.length}", + "4", + ), + textColor: Colors.black, + fontSize: 14.sp, + ), + ), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + SmartDialog.show( + tag: "showCpHelpe", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return SafeArea( + top: false, + child: SingleChildScrollView( + child: GestureDetector( + child: Image.asset( + ATGlobalConfig.lang == "ar" + ? "atu_images/person/at_icon_cp_help_ar.webp" + : "atu_images/person/at_icon_cp_help_en.webp", + fit: BoxFit.fill, + ), + onTap: () { + SmartDialog.dismiss(tag: "showCpHelpe"); + }, + ), + ), + ); + }, + ); + }, + child: Image.asset( + "atu_images/person/at_icon_cp_helpe.png", + height: 18.w, + color: Colors.black, + ), + ), + SizedBox(width: 15.w), + ], + ), + SizedBox(height: 10.w), + isLoading + ? Container() + : (cpList.isNotEmpty + ? Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + cpList.length < 4 + ? _buidAddCpLine() + : Container(), + ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: cpList.length, + // 列表项数量 + padding: EdgeInsets.only( + bottom: 15.w, + left: 5.w, + right: 5.w, + ), + itemBuilder: (context, index) { + return _buildCpItem(cpList[index], index); + }, + separatorBuilder: ( + BuildContext context, + int index, + ) { + return SizedBox(height: 10.w); + }, + ), + ], + ), + ), + ) + : _buidAddCpLine()), + cpList.isEmpty ? Spacer() : Container(), + ATDebounceWidget( + child: Container( + width: 200.w, + decoration: BoxDecoration( + color: ChatVibeTheme.primaryLight, + borderRadius: BorderRadius.circular(35.w), + boxShadow: [ + BoxShadow( + color: ChatVibeTheme.primaryLight.withOpacity(0.5), + spreadRadius: 1, + blurRadius: 5, + offset: Offset(0, 3), // changes position of shadow + ), + ], + ), + height: 48.w, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.add_circle_outline, + color: Colors.white, + size: 28.w, + ), + SizedBox(width: 5.w), + text( + ATAppLocalizations.of(context)!.addCp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + fontSize: 18.sp, + ), + ], + ), + ), + onTap: () { + SmartDialog.show( + tag: "showAddCpPage", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return AddCpPage(); + }, + ); + }, + ), + SizedBox(height: 45.w), + ], + ), + ), + ), + ], + ); + } + + _buildCpItem(GetCpRelationshipPairListRes cp, int index) { + final num level = cp.cpLevel ?? 0; + num nextLevel = level + 1; + final int cpValue = cp.cpValue?.toInt() ?? 0; + final int nextLevelExp = cp.nextLevelExp?.toInt() ?? 0; + final int remain = (nextLevelExp - cpValue).clamp(0, 999999999); + final double progress = + nextLevelExp <= 0 ? 0 : (cpValue / nextLevelExp).clamp(0.0, 1.0); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + SizedBox(width: 15.w), + text( + ATAppLocalizations.of(context)!.couple("${index + 1}"), + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.black, + ), + Spacer(), + GestureDetector( + child: Container( + height: 28.w, + width: 80.w, + alignment: Alignment.center, + margin: EdgeInsetsDirectional.only(end: 8.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_cp_opt_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: text( + cp.status == ATCpStatusType.NORMAL.name + ? ATAppLocalizations.of(context)!.partWays + : ATAppLocalizations.of(context)!.reconcile, + fontWeight: FontWeight.w600, + fontSize: 10.sp, + textColor: Color(0xFFFFEE95), + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showCpOptMenu"); + if (cp.status == ATCpStatusType.NORMAL.name) { + _showPartWaysDialog(cp); + } else { + _showReconcileDialog(cp); + } + }, + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 5.w), + Stack( + alignment: AlignmentDirectional.center, + children: [ + Container( + width: ScreenUtil().screenWidth, + margin: EdgeInsets.symmetric(horizontal: 10.w), + height: 200.w, + decoration: + cp.status != ATCpStatusType.NORMAL.name + ? BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_no_cp_item_bg3.png", + ), + fit: BoxFit.fill, + ), + ) + : null, + child: Stack( + children: [ + cp.status == ATCpStatusType.NORMAL.name + ? (Image.asset( + "atu_images/person/at_icon_no_cp_item_bg2_lv_${(cp.profileCardLevel ?? 1) <= 3 ? (cp.profileCardLevel ?? 1) : 3}.png", + height: 188.w, + width: ScreenUtil().screenWidth, + fit: BoxFit.fill, + )) + : Container(), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 25.w), + Stack( + alignment: AlignmentGeometry.bottomCenter, + children: [ + cp.status == ATCpStatusType.NORMAL.name + ? netImage( + url: cp.selfRing?.sourceUrl ?? "", + width: 60.w, + noDefaultImg: false, + ) + : Container(height: 96.w), + Transform.translate( + offset: Offset( + cp.status == ATCpStatusType.NORMAL.name ? 0 : -2, + 15.w, + ), + child: Container( + width: 70.w, + height: + cp.status == ATCpStatusType.NORMAL.name + ? 32.w + : null, + alignment: AlignmentDirectional.center, + decoration: + cp.status == ATCpStatusType.NORMAL.name + ? BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_user_cp_level_value_bg.png", + ), + fit: BoxFit.fill, + ), + ) + : null, + child: Directionality( + textDirection: TextDirection.ltr, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + margin: EdgeInsets.only( + top: 5.w, + left: 15.w, + ), + child: text( + cp.status == ATCpStatusType.NORMAL.name + ? "LV.${cp.cpLevel ?? 1}" + : ATAppLocalizations.of( + context, + )!.separated, + fontWeight: FontWeight.w600, + fontSize: 12.sp, + textColor: + cp.status == + ATCpStatusType.NORMAL.name + ? Colors.white + : Color(0xFFB6B6B6), + ), + ), + ], + ), + ), + ), + ), + ], + ), + SizedBox( + height: + cp.status == ATCpStatusType.NORMAL.name + ? 25.w + : 22.w, + ), + cp.status == ATCpStatusType.NORMAL.name + ? Row( + children: [ + SizedBox(width: 40.w), + text( + "Lv$level", + fontSize: 11.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 6.w), + Expanded( + child: ClipRRect( + borderRadius: BorderRadius.circular(30.w), + child: Stack( + alignment: AlignmentDirectional.centerStart, + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + 30.w, + ), + color: const Color(0x7dffffff), + ), + height: 9.w, + ), + FractionallySizedBox( + widthFactor: progress, + child: Container( + height: 9.w, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xffFF1E37), + Color(0xffFE6A30), + Color(0xffFFCF67), + ], + ), + borderRadius: BorderRadius.circular( + 30.w, + ), + ), + ), + ), + + Container( + alignment: Alignment.center, + child: text( + "$cpValue/$nextLevelExp", + fontSize: 9.sp, + textColor: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + SizedBox(width: 6.w), + text( + "Lv${nextLevel > 10 ? " Max" : nextLevel}", + fontSize: 11.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 40.w), + ], + ) + : Container(), + Container( + alignment: AlignmentDirectional.bottomCenter, + child: text( + ATAppLocalizations.of( + context, + )!.timeSpentTogether(cp.days ?? ""), + fontWeight: FontWeight.w600, + fontSize: 12.sp, + textColor: + cp.status == ATCpStatusType.NORMAL.name + ? Color(0xFFFF79A1) + : Color(0xFF898989), + ), + ), + Container( + alignment: AlignmentDirectional.bottomCenter, + child: text( + ATAppLocalizations.of( + context, + )!.firstDay(cp.firstDay ?? ""), + fontWeight: FontWeight.w600, + lineHeight: 1.1, + fontSize: 11.sp, + textColor: + cp.status == ATCpStatusType.NORMAL.name + ? Colors.white + : Color(0xFFA3A3A3), + ), + ), + ], + ), + ], + ), + ), + PositionedDirectional( + top: cp.status == ATCpStatusType.NORMAL.name ? 45.w : 75.w, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + SizedBox( + height: 50.w, + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + netImage( + url: cp.meUserProfile?.userAvatar ?? "", + width: 48.w, + shape: BoxShape.circle, + defaultImg: + "atu_images/general/at_icon_avar_defalt.png", + ), + Image.asset( + cp.status == ATCpStatusType.NORMAL.name + ? "atu_images/person/at_icon_cp_head_ring.png" + : "atu_images/person/at_icon_cp_head_ring2.png", + ), + ], + ), + ), + Container( + alignment: Alignment.center, + width: 80.w, + height: 15.w, + child: + (cp.meUserProfile?.userNickname?.length ?? 0) > 8 + ? Marquee( + text: cp.meUserProfile?.userNickname ?? "", + style: TextStyle( + fontSize: 10.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.linear, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + cp.meUserProfile?.userNickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + SizedBox(width: 120.w), + Column( + children: [ + SizedBox( + height: 50.w, + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + netImage( + url: cp.cpUserProfile?.userAvatar ?? "", + width: 48.w, + defaultImg: + "atu_images/general/at_icon_avar_defalt.png", + shape: BoxShape.circle, + ), + Image.asset( + cp.status == ATCpStatusType.NORMAL.name + ? "atu_images/person/at_icon_cp_head_ring.png" + : "atu_images/person/at_icon_cp_head_ring2.png", + ), + ], + ), + ), + Container( + alignment: Alignment.center, + width: 80.w, + height: 15.w, + child: + (cp.cpUserProfile?.userNickname?.length ?? 0) > 8 + ? Marquee( + text: cp.cpUserProfile?.userNickname ?? "", + style: TextStyle( + fontSize: 10.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.linear, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + cp.cpUserProfile?.userNickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ], + ); + } + + String _cpVaFormat(double cpValue) { + int value = cpValue.toInt(); + if (value > 99999) { + return "${(value / 1000).toStringAsFixed(0)}k"; + } + return "$value"; + } + + void _loadData() { + AccountRepository() + .getCpRelationshipPairList() + .then((result) { + cpList = result; + isLoading = false; + ATLoadingManager.veilRoutine(); + setState(() {}); + }) + .catchError((e) { + isLoading = false; + ATLoadingManager.veilRoutine(); + setState(() {}); + }); + } + + _buidAddCpLine() { + return ATDebounceWidget( + child: Container( + width: ScreenUtil().screenWidth, + margin: EdgeInsets.symmetric(horizontal: 6.w), + height: 200.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/person/at_icon_no_cp_item_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Container( + margin: EdgeInsets.only(bottom: 48.w, left: 12.w), + alignment: AlignmentDirectional.bottomCenter, + child: text( + ATAppLocalizations.of(context)!.noMatchedCP, + fontWeight: FontWeight.w600, + fontSize: 10.sp, + textColor: Colors.white, + ), + ), + ), + onTap: () { + SmartDialog.show( + tag: "showAddCpPage", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return AddCpPage(); + }, + ); + }, + ); + } + + ///恢复CP弹框 + void _showReconcileDialog(GetCpRelationshipPairListRes cp) { + SmartDialog.show( + tag: "showReconcileDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return Container( + height: 530.w, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_dialog_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + SizedBox(height: 58.w), + Container( + child: text( + ATAppLocalizations.of(context)!.reconcileInvitation, + fontSize: 22.sp, + textColor: Color(0xffECB45C), + fontWeight: FontWeight.bold, + ), + ), + Transform.translate( + offset: Offset(0, -25), + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + PositionedDirectional( + top: 30.w, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + head( + url: cp.meUserProfile?.userAvatar ?? "", + width: 95.w, + ), + SizedBox(width: 14.w), + head( + url: cp.cpUserProfile?.userAvatar ?? "", + width: 95.w, + ), + ], + ), + ), + Image.asset( + "atu_images/person/at_icon_send_cp_requst_dialog_head.png", + height: 120.w, + fit: BoxFit.fill, + ), + ], + ), + ), + Transform.translate( + offset: Offset(0, -23), + child: Container( + height: 25.w, + width: ScreenUtil().screenWidth * 0.6, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_username_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Row( + children: [ + SizedBox(width: 15.w), + Expanded( + child: Container( + alignment: Alignment.center, + height: 15.w, + child: + (cp.meUserProfile?.userNickname?.length ?? 0) > 8 + ? Marquee( + text: cp.meUserProfile?.userNickname ?? "", + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.linear, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + cp.meUserProfile?.userNickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + ), + SizedBox(width: 20.w), + Expanded( + child: Container( + alignment: Alignment.center, + height: 15.w, + child: + (cp.cpUserProfile?.userNickname?.length ?? 0) > 8 + ? Marquee( + text: cp.cpUserProfile?.userNickname ?? "", + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.linear, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + cp.cpUserProfile?.userNickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + ), + SizedBox(width: 15.w), + ], + ), + ), + ), + + Container( + padding: EdgeInsets.only(bottom: 12.w), + margin: EdgeInsets.symmetric(horizontal: 18.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_dialog_content.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + Container( + margin: EdgeInsets.symmetric( + horizontal: 35.w, + ).copyWith(top: 45.w), + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: + ATAppLocalizations.of( + context, + )!.areYouSureYouWantToSpend, + style: TextStyle( + fontSize: sp(14), + color: Color(0xffECB45C), + fontWeight: FontWeight.w500, + ), + ), + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Image.asset( + "atu_images/general/at_icon_jb.png", + width: 20.w, + ), + ), + TextSpan( + text: "50000", + style: TextStyle( + fontSize: sp(14), + color: Color(0xffECB45C), + fontWeight: FontWeight.w500, + ), + ), + TextSpan( + text: " ", + style: TextStyle( + fontSize: sp(14), + color: Color(0xffECB45C), + fontWeight: FontWeight.w500, + ), + ), + TextSpan( + text: + ATAppLocalizations.of( + context, + )!.areYouSureYouWantToSpend4, + style: TextStyle( + fontSize: sp(14), + color: Color(0xffECB45C), + fontWeight: FontWeight.w500, + ), + ), + ], + ), + textAlign: TextAlign.left, + strutStyle: StrutStyle( + height: 1.2, // 行高倍数 + fontWeight: FontWeight.w500, + forceStrutHeight: true, // 强制应用行高 + ), + ), + ), + Container( + margin: EdgeInsets.symmetric(horizontal: 20.w), + alignment: AlignmentDirectional.center, + child: text( + ATAppLocalizations.of(context)!.reconcileInvitationTips, + fontSize: 10.w, + textColor: Color(0xffECB45C), + maxLines: 5, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + SizedBox(height: 10.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + child: Container( + padding: EdgeInsets.only(top: 7.w), + alignment: AlignmentDirectional.center, + width: 130.w, + height: 48.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_cancel_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: text( + ATAppLocalizations.of(context)!.cancel, + fontSize: 18.sp, + fontWeight: FontWeight.bold, + textColor: Color(0xffECB45C), + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showReconcileDialog"); + }, + ), + SizedBox(width: 25.w), + GestureDetector( + child: Container( + padding: EdgeInsets.only(top: 7.w), + alignment: AlignmentDirectional.center, + width: 130.w, + height: 48.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_ok_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: text( + ATAppLocalizations.of(context)!.confirm, + fontSize: 18.sp, + fontWeight: FontWeight.bold, + textColor: Color(0xffECB45C), + ), + ), + onTap: () { + ATLoadingManager.exhibitOperation(); + AccountRepository() + .cpRelationshipSendApply(cp.cpUserProfile?.id ?? "") + .then((result) { + ATLoadingManager.veilRoutine(); + SmartDialog.dismiss(tag: "showReconcileDialog"); + ATTts.show( + ATAppLocalizations.of( + context, + )!.operationSuccessful, + ); + Provider.of( + context, + listen: false, + ).getMyUserInfo(needLoadUserCountGuard: false); + _loadData(); + }) + .catchError((_) { + ATLoadingManager.veilRoutine(); + }); + }, + ), + ], + ), + ], + ), + ); + }, + ); + } + + ///解除CP弹框 + void _showPartWaysDialog(GetCpRelationshipPairListRes cp) { + SmartDialog.show( + tag: "showPartWaysDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return Container( + height: 530.w, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_dialog_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + SizedBox(height: 58.w), + Container( + child: text( + ATAppLocalizations.of(context)!.partWays, + fontSize: 22.sp, + textColor: Color(0xffECB45C), + fontWeight: FontWeight.bold, + ), + ), + Transform.translate( + offset: Offset(0, -25), + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + PositionedDirectional( + top: 31.w, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + head( + url: cp.meUserProfile?.userAvatar ?? "", + width: 95.w, + ), + SizedBox(width: 14.w), + head( + url: cp.cpUserProfile?.userAvatar ?? "", + width: 95.w, + ), + ], + ), + ), + Image.asset( + "atu_images/person/at_icon_send_cp_requst_dialog_head2.png", + height: 120.w, + fit: BoxFit.fill, + ), + ], + ), + ), + Transform.translate( + offset: Offset(0, -23), + child: Container( + height: 25.w, + width: ScreenUtil().screenWidth * 0.6, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_username_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Row( + children: [ + SizedBox(width: 15.w), + Expanded( + child: Container( + alignment: Alignment.center, + height: 15.w, + child: + (cp.meUserProfile?.userNickname?.length ?? 0) > 8 + ? Marquee( + text: cp.meUserProfile?.userNickname ?? "", + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.linear, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + cp.meUserProfile?.userNickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + ), + SizedBox(width: 20.w), + Expanded( + child: Container( + alignment: Alignment.center, + height: 15.w, + child: + (cp.cpUserProfile?.userNickname?.length ?? 0) > 8 + ? Marquee( + text: cp.cpUserProfile?.userNickname ?? "", + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.linear, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + cp.cpUserProfile?.userNickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + ), + SizedBox(width: 15.w), + ], + ), + ), + ), + + Container( + padding: EdgeInsets.only(bottom: 12.w), + margin: EdgeInsets.symmetric(horizontal: 18.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_dialog_content.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + Container( + margin: EdgeInsets.symmetric( + horizontal: 35.w, + ).copyWith(top: 45.w), + child: text( + ATAppLocalizations.of( + context, + )!.areYouSureYouWantToPartWaysWithYourCP, + fontWeight: FontWeight.w500, + textColor: Color(0xffECB45C), + maxLines: 3, + fontSize: 14.sp, + ), + ), + Container( + margin: EdgeInsets.symmetric(horizontal: 20.w), + alignment: AlignmentDirectional.center, + child: text( + ATAppLocalizations.of(context)!.partWaysTips, + fontSize: 10.w, + textColor: Color(0xffECB45C), + maxLines: 6, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + SizedBox(height: 10.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + child: Container( + padding: EdgeInsets.only(top: 7.w), + alignment: AlignmentDirectional.center, + width: 130.w, + height: 48.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_ok_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: text( + ATAppLocalizations.of(context)!.cancel, + fontSize: 18.sp, + fontWeight: FontWeight.bold, + textColor: Color(0xffECB45C), + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showPartWaysDialog"); + }, + ), + SizedBox(width: 25.w), + GestureDetector( + child: Container( + padding: EdgeInsets.only(top: 7.w), + alignment: AlignmentDirectional.center, + width: 130.w, + height: 48.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_cancel_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: text( + ATAppLocalizations.of(context)!.confirm, + fontSize: 18.sp, + fontWeight: FontWeight.bold, + textColor: Color(0xffECB45C), + ), + ), + onTap: () { + ATLoadingManager.exhibitOperation(); + AccountRepository() + .cpRelationshipDismissApply( + cp.cpUserProfile?.id ?? "", + ) + .then((result) { + ATLoadingManager.veilRoutine(); + SmartDialog.dismiss(tag: "showPartWaysDialog"); + ATTts.show( + ATAppLocalizations.of( + context, + )!.operationSuccessful, + ); + Provider.of( + context, + listen: false, + ).getMyUserInfo(needLoadUserCountGuard: false); + _loadData(); + }) + .catchError((e) { + ATLoadingManager.exhibitOperation(); + }); + }, + ), + ], + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/chatvibe_features/user/profile/relation/cp/search_cp_following_tab_page.dart b/lib/chatvibe_features/user/profile/relation/cp/search_cp_following_tab_page.dart new file mode 100644 index 0000000..a8e90ec --- /dev/null +++ b/lib/chatvibe_features/user/profile/relation/cp/search_cp_following_tab_page.dart @@ -0,0 +1,740 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/chatvibe_gradient_button.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/follow_user_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; + +import '../../../../../chatvibe_ui/theme/chatvibe_theme.dart'; + +class SearchCpFollowingTabPage extends ATPageList { + @override + _SearchCpFollowingTabPageState createState() => + _SearchCpFollowingTabPageState(); +} + +class _SearchCpFollowingTabPageState + extends ATPageListState { + late TextEditingController _textEditingController; + String? lastId; + Map selecteMap = {}; + + @override + void initState() { + super.initState(); + _textEditingController = TextEditingController(); + enablePullUp = true; + backgroundColor = Colors.transparent; + isShowDivider = true; + isShowFooter = false; + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: searchWidget( + hint: ATAppLocalizations.of(context)!.searchUserId, + controller: _textEditingController, + borderColor: Colors.black12, + textColor: Colors.grey, + ), + ), + chatvibeGradientButton( + text: ATAppLocalizations.of(context)!.search, + radius: 25, + textSize: 14.sp, + textColor: Colors.grey, + gradient: LinearGradient( + colors: [Colors.transparent, Colors.transparent], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + onPress: () { + if (_textEditingController.text.isNotEmpty) { + _search(); + } + }, + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 8.w), + Expanded(child: buildList(context)), + GestureDetector( + onTap: () { + if (selecteMap.isNotEmpty) { + var userProfile = selecteMap.values.first; + _showSendCpRequestDialog(userProfile); + } + }, + + child: Container( + alignment: AlignmentDirectional.center, + height: 45.w, + width: ScreenUtil().screenWidth * 0.75, + decoration: BoxDecoration( + gradient: + selecteMap.entries.isNotEmpty + ? LinearGradient( + colors: [ + ChatVibeTheme.primaryLight, + ChatVibeTheme.primaryLight, + ], + begin: AlignmentDirectional.topStart, + end: AlignmentDirectional.bottomEnd, + ) + : LinearGradient( + colors: [Color(0xffA2A2A2), Color(0xff646464)], + begin: AlignmentDirectional.topStart, + end: AlignmentDirectional.bottomEnd, + ), + borderRadius: BorderRadius.circular(30.w), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + text( + ATAppLocalizations.of(context)!.sendTheCpRequest, + fontSize: 16.sp, + textColor: Colors.white, + fontWeight: FontWeight.bold, + ), + ], + ), + ), + ), + SizedBox(height: 25.w), + ], + ); + } + + @override + Widget buildItem(FollowUserRes res) { + return Row( + children: [ + SizedBox(width: 10.w), + head(url: res?.userProfile?.userAvatar ?? "", width: 62.w), + SizedBox(width: 10.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + netImage( + url: + Provider.of(context, listen: false) + .getCountryByName( + res?.userProfile?.countryName ?? "", + ) + ?.nationalFlag ?? + "", + borderRadius: BorderRadius.all(Radius.circular(3.w)), + width: 19.w, + height: 14.w, + ), + SizedBox(width: 3.w), + Container( + constraints: BoxConstraints(maxWidth: 160.w), + child: text( + res?.userProfile?.userNickname ?? "", + fontSize: 14.sp, + textColor: Colors.black, + ), + ), + SizedBox(width: 3.w), + Container( + width: 47.w, + height: 24.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + res?.userProfile?.userSex == 0 + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png", + ), + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + xb(res?.userProfile?.userSex), + text( + "${res?.userProfile?.age}", + textColor: Colors.white, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + ], + ), + ), + ], + ), + SizedBox(height: 4.w), + Row( + children: [ + GestureDetector( + child: Row( + children: [ + getIdIcon( + res.userProfile?.wealthLevel ?? 0, + width: 32.w, + height: 32.w, + fontWeight: FontWeight.w600, + textColor: Color(0xFF333333), + ), + chatvibeNickNameText( + maxWidth: 135.w, + res.userProfile?.getID() ?? "", + textColor: Color(0xFF333333), + fontSize: 12.sp, + fontWeight: FontWeight.w600, + type: res.userProfile?.getVIP()?.name ?? "", + needScroll: + (res.userProfile?.getID().characters.length ?? + 0) > + 13, + ), + ], + ), + onTap: () { + Clipboard.setData( + ClipboardData(text: res?.userProfile?.getID() ?? ""), + ); + ATTts.show( + ATAppLocalizations.of(context)!.copiedToClipboard, + ); + }, + ), + SizedBox(width: 8.w), + ], + ), + ], + ), + ), + SizedBox(height: 4.w), + GestureDetector( + onTap: () { + var cc = selecteMap[res.userProfile?.id ?? ""]; + if (cc != null) { + selecteMap.remove(res.userProfile?.id); + } else { + if (res.userProfile?.userSex == + AccountStorage().getCurrentUser()?.userProfile?.userSex) { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return Container( + height: 180.w, + width: ScreenUtil().screenWidth * 0.8, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(10.w)), + ), + child: Column( + children: [ + SizedBox(height: 15.w), + Container( + alignment: Alignment.center, + child: text( + ATAppLocalizations.of(context)!.tips, + fontSize: 16.sp, + textColor: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 15.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + alignment: Alignment.center, + child: text( + ATAppLocalizations.of(context)!.cpSexTips, + maxLines: 20, + fontSize: 14.sp, + textColor: Colors.black54, + textAlign: TextAlign.start, + ), + ), + SizedBox(height: 15.w), + GestureDetector( + child: Container( + width: 120.w, + height: 45.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: ChatVibeTheme.primaryLight, + ), + alignment: Alignment.center, + child: text( + ATAppLocalizations.of(context)!.confirm, + fontSize: 14.sp, + textColor: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showConfirmDialog"); + }, + ), + ], + ), + ); + }, + ); + } else { + selecteMap.clear(); + selecteMap[res.userProfile?.id ?? ""] = res.userProfile; + } + } + setState(() {}); + }, + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.all(3.w), + child: + (selecteMap[res.userProfile?.id] != null) + ? Image.asset( + "atu_images/login/at_icon_login_ser_select.png", + width: 21.w, + ) + : Image.asset( + "atu_images/login/at_icon_login_ser_select_un.png", + width: 21.w, + ), + ), + ), + SizedBox(width: 15.w), + ], + ); + } + + void _search() { + AccountRepository() + .followMyList(account: _textEditingController.text) + .then((result) { + items = result; + selecteMap.clear(); + loadComplete(); + setState(() {}); + }) + .catchError((e) { + loadComplete(); + setState(() {}); + }); + } + + @override + builderDivider() { + return Divider( + height: 22.w, + color: Color(0xffF2F2F2), + indent: 15.w, + endIndent: 15.w, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + if (_textEditingController.text.isNotEmpty) { + _search(); + } else { + if (page == 1) { + lastId = null; + } + try { + var followList = await AccountRepository().followMyList(lastId: lastId); + if (followList.isNotEmpty) { + lastId = followList.last.id; + } + selecteMap.clear(); + onSuccess(followList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + } + + void _showSendCpRequestDialog(ChatVibeUserProfile? userProfile) { + SmartDialog.show( + tag: "showSendCpRequestDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return Container( + height: 520.w, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_dialog_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + SizedBox(height: 58.w), + Container( + child: text( + ATAppLocalizations.of(context)!.cpRequest, + fontSize: 22.sp, + textColor: Color(0xffECB45C), + fontWeight: FontWeight.bold, + ), + ), + Transform.translate( + offset: Offset(0, -25), + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + PositionedDirectional( + top: 30.w, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + head( + url: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userAvatar ?? + "", + width: 95.w, + ), + SizedBox(width: 18.w), + head(url: userProfile?.userAvatar ?? "", width: 95.w), + ], + ), + ), + Image.asset( + "atu_images/person/at_icon_send_cp_requst_dialog_head.png", + height: 120.w, + fit: BoxFit.fill, + ), + ], + ), + ), + Transform.translate( + offset: Offset(0, -23), + child: Container( + alignment: Alignment.center, + height: 25.w, + width: ScreenUtil().screenWidth * 0.7, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_username_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Row( + children: [ + SizedBox(width: 20.w), + Expanded( + child: Container( + alignment: Alignment.center, + height: 15.w, + child: + (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname + ?.length ?? + 0) > + 8 + ? Marquee( + text: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname ?? + "", + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname ?? + "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + ), + SizedBox(width: 20.w), + Expanded( + child: Container( + alignment: Alignment.center, + height: 15.w, + child: + (userProfile?.userNickname?.length ?? 0) > 8 + ? Marquee( + text: userProfile?.userNickname ?? "", + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + userProfile?.userNickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + ), + SizedBox(width: 20.w), + ], + ), + ), + ), + + Container( + padding: EdgeInsets.only(bottom: 12.w), + margin: EdgeInsets.symmetric(horizontal: 18.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_dialog_content.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + Container( + margin: EdgeInsets.symmetric( + horizontal: 35.w, + ).copyWith(top: 45.w), + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: + ATAppLocalizations.of( + context, + )!.areYouSureYouWantToSpend, + style: TextStyle( + fontSize: sp(14), + color: Color(0xffECB45C), + fontWeight: FontWeight.w500, + ), + ), + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Image.asset( + "atu_images/general/at_icon_jb.png", + width: 18.w, + ), + ), + TextSpan( + text: "50000", + style: TextStyle( + fontSize: sp(14), + color: Color(0xffECB45C), + fontWeight: FontWeight.w500, + ), + ), + TextSpan( + text: " ", + style: TextStyle( + fontSize: sp(14), + color: Color(0xffECB45C), + fontWeight: FontWeight.w500, + ), + ), + TextSpan( + text: + ATAppLocalizations.of( + context, + )!.areYouSureYouWantToSpend2, + style: TextStyle( + fontSize: sp(14), + color: Color(0xffECB45C), + fontWeight: FontWeight.w500, + ), + ), + ], + ), + textAlign: TextAlign.left, + strutStyle: StrutStyle( + height: 1.2, // 行高倍数 + fontWeight: FontWeight.w500, + forceStrutHeight: true, // 强制应用行高 + ), + ), + ), + Container( + margin: EdgeInsets.symmetric( + horizontal: 35.w, + ).copyWith(top: 10.w), + alignment: AlignmentDirectional.center, + child: text( + ATAppLocalizations.of( + context, + )!.areYouSureYouWantToSpend3, + fontSize: 10.w, + textColor: Color(0xffECB45C), + maxLines: 4, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + SizedBox(height: 10.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + child: Container( + padding: EdgeInsets.only(top: 7.w), + alignment: AlignmentDirectional.center, + width: 130.w, + height: 48.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_cancel_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: text( + ATAppLocalizations.of(context)!.cancel, + fontSize: 18.sp, + fontWeight: FontWeight.bold, + textColor: Color(0xffECB45C), + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showSendCpRequestDialog"); + }, + ), + SizedBox(width: 25.w), + GestureDetector( + child: Container( + padding: EdgeInsets.only(top: 7.w), + alignment: AlignmentDirectional.center, + width: 130.w, + height: 48.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_ok_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: text( + ATAppLocalizations.of(context)!.confirm, + fontSize: 18.sp, + fontWeight: FontWeight.bold, + textColor: Color(0xffECB45C), + ), + ), + onTap: () { + ATLoadingManager.exhibitOperation(); + AccountRepository() + .cpRelationshipSendApply(userProfile?.id ?? "") + .then((result) { + ATLoadingManager.veilRoutine(); + ATTts.show( + ATAppLocalizations.of( + context, + )!.operationSuccessful, + ); + SmartDialog.dismiss(tag: "showSendCpRequestDialog"); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + }, + ), + ], + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/chatvibe_features/user/profile/relation/cp/search_cp_friend_tab_page.dart b/lib/chatvibe_features/user/profile/relation/cp/search_cp_friend_tab_page.dart new file mode 100644 index 0000000..97daa95 --- /dev/null +++ b/lib/chatvibe_features/user/profile/relation/cp/search_cp_friend_tab_page.dart @@ -0,0 +1,737 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/chatvibe_gradient_button.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/message_friend_user_res.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; + +class SearchCpFriendTabPage extends ATPageList { + @override + _SearchCpFriendTabPageState createState() => _SearchCpFriendTabPageState(); +} + +class _SearchCpFriendTabPageState + extends ATPageListState { + late TextEditingController _textEditingController; + String? lastId; + Map selecteMap = {}; + + @override + void initState() { + super.initState(); + _textEditingController = TextEditingController(); + enablePullUp = true; + backgroundColor = Colors.transparent; + isShowDivider = true; + isShowFooter = false; + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: searchWidget( + hint: ATAppLocalizations.of(context)!.searchUserId, + controller: _textEditingController, + borderColor: Colors.black12, + textColor: Colors.grey, + ), + ), + chatvibeGradientButton( + text: ATAppLocalizations.of(context)!.search, + radius: 25, + textSize: 14.sp, + textColor: Colors.grey, + gradient: LinearGradient( + colors: [Colors.transparent, Colors.transparent], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + onPress: () { + if (_textEditingController.text.isNotEmpty) { + _search(); + } + }, + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 8.w), + Expanded(child: buildList(context)), + GestureDetector( + onTap: () { + if (selecteMap.isNotEmpty) { + var userProfile = selecteMap.values.first; + _showSendCpRequestDialog(userProfile); + } + }, + + child: Container( + alignment: AlignmentDirectional.center, + height: 45.w, + width: ScreenUtil().screenWidth * 0.75, + decoration: BoxDecoration( + gradient: + selecteMap.entries.isNotEmpty + ? LinearGradient( + colors: [ + ChatVibeTheme.primaryLight, + ChatVibeTheme.primaryLight, + ], + begin: AlignmentDirectional.topStart, + end: AlignmentDirectional.bottomEnd, + ) + : LinearGradient( + colors: [Color(0xffA2A2A2), Color(0xff646464)], + begin: AlignmentDirectional.topStart, + end: AlignmentDirectional.bottomEnd, + ), + borderRadius: BorderRadius.circular(30.w), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + text( + ATAppLocalizations.of(context)!.sendTheCpRequest, + fontSize: 16.sp, + textColor: Colors.white, + fontWeight: FontWeight.bold, + ), + ], + ), + ), + ), + SizedBox(height: 25.w), + ], + ); + } + + @override + Widget buildItem(MessageFriendUserRes res) { + return Row( + children: [ + SizedBox(width: 10.w), + head(url: res?.userProfile?.userAvatar ?? "", width: 62.w), + SizedBox(width: 10.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + netImage( + url: + Provider.of(context, listen: false) + .getCountryByName( + res?.userProfile?.countryName ?? "", + ) + ?.nationalFlag ?? + "", + borderRadius: BorderRadius.all(Radius.circular(3.w)), + width: 19.w, + height: 14.w, + ), + SizedBox(width: 3.w), + Container( + constraints: BoxConstraints(maxWidth: 160.w), + child: text( + res?.userProfile?.userNickname ?? "", + fontSize: 14.sp, + textColor: Colors.black, + ), + ), + SizedBox(width: 3.w), + Container( + width: 47.w, + height: 24.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + res?.userProfile?.userSex == 0 + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png", + ), + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + xb(res?.userProfile?.userSex), + text( + "${res?.userProfile?.age}", + textColor: Colors.white, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + ], + ), + ), + ], + ), + SizedBox(height: 4.w), + Row( + children: [ + GestureDetector( + child: Row( + children: [ + getIdIcon( + res.userProfile?.wealthLevel ?? 0, + width: 32.w, + height: 32.w, + fontWeight: FontWeight.w600, + textColor: Color(0xFF333333), + ), + chatvibeNickNameText( + maxWidth: 135.w, + res.userProfile?.getID() ?? "", + textColor: Color(0xFF333333), + fontSize: 12.sp, + fontWeight: FontWeight.w600, + type: res.userProfile?.getVIP()?.name ?? "", + needScroll: + (res.userProfile?.getID().characters.length ?? + 0) > + 13, + ), + ], + ), + onTap: () { + Clipboard.setData( + ClipboardData(text: res?.userProfile?.getID() ?? ""), + ); + ATTts.show( + ATAppLocalizations.of(context)!.copiedToClipboard, + ); + }, + ), + ], + ), + ], + ), + ), + SizedBox(height: 4.w), + GestureDetector( + onTap: () { + var cc = selecteMap[res.userProfile?.id ?? ""]; + if (cc != null) { + selecteMap.remove(res.userProfile?.id); + } else { + if (res.userProfile?.userSex == + AccountStorage().getCurrentUser()?.userProfile?.userSex) { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return Container( + height: 180.w, + width: ScreenUtil().screenWidth * 0.8, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(10.w)), + ), + child: Column( + children: [ + SizedBox(height: 15.w), + Container( + alignment: Alignment.center, + child: text( + ATAppLocalizations.of(context)!.tips, + fontSize: 16.sp, + textColor: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 15.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + alignment: Alignment.center, + child: text( + ATAppLocalizations.of(context)!.cpSexTips, + maxLines: 20, + fontSize: 14.sp, + textColor: Colors.black54, + textAlign: TextAlign.start, + ), + ), + SizedBox(height: 15.w), + GestureDetector( + child: Container( + width: 120.w, + height: 45.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: ChatVibeTheme.primaryLight, + ), + alignment: Alignment.center, + child: text( + ATAppLocalizations.of(context)!.confirm, + fontSize: 14.sp, + textColor: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showConfirmDialog"); + }, + ), + ], + ), + ); + }, + ); + } else { + selecteMap.clear(); + selecteMap[res.userProfile?.id ?? ""] = res.userProfile; + } + } + setState(() {}); + }, + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.all(3.w), + child: + (selecteMap[res.userProfile?.id] != null) + ? Image.asset( + "atu_images/login/at_icon_login_ser_select.png", + width: 21.w, + ) + : Image.asset( + "atu_images/login/at_icon_login_ser_select_un.png", + width: 21.w, + ), + ), + ), + SizedBox(width: 15.w), + ], + ); + } + + void _search() { + AccountRepository() + .friendSearch(_textEditingController.text) + .then((result) { + items = result; + selecteMap.clear(); + loadComplete(); + setState(() {}); + }) + .catchError((e) { + loadComplete(); + setState(() {}); + }); + } + + @override + builderDivider() { + return Divider( + height: 22.w, + color: Color(0xffF2F2F2), + indent: 15.w, + endIndent: 15.w, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + if (_textEditingController.text.isNotEmpty) { + _search(); + } else { + if (page == 1) { + lastId = null; + } + try { + var followList = await AccountRepository().friendList(lastId: lastId); + if (followList.isNotEmpty) { + lastId = followList.last.id; + } + selecteMap.clear(); + onSuccess(followList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + } + + void _showSendCpRequestDialog(ChatVibeUserProfile? userProfile) { + SmartDialog.show( + tag: "showSendCpRequestDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return Container( + height: 520.w, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_dialog_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + SizedBox(height: 58.w), + Container( + child: text( + ATAppLocalizations.of(context)!.cpRequest, + fontSize: 22.sp, + textColor: Color(0xffECB45C), + fontWeight: FontWeight.bold, + ), + ), + Transform.translate( + offset: Offset(0, -25), + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + PositionedDirectional( + top: 30.w, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + head( + url: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userAvatar ?? + "", + width: 95.w, + ), + SizedBox(width: 18.w), + head(url: userProfile?.userAvatar ?? "", width: 95.w), + ], + ), + ), + Image.asset( + "atu_images/person/at_icon_send_cp_requst_dialog_head.png", + height: 120.w, + fit: BoxFit.fill, + ), + ], + ), + ), + Transform.translate( + offset: Offset(0, -23), + child: Container( + alignment: Alignment.center, + height: 25.w, + width: ScreenUtil().screenWidth * 0.7, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_username_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Row( + children: [ + SizedBox(width: 20.w), + Expanded( + child: Container( + alignment: Alignment.center, + height: 15.w, + child: + (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname + ?.length ?? + 0) > + 8 + ? Marquee( + text: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname ?? + "", + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname ?? + "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + ), + SizedBox(width: 20.w), + Expanded( + child: Container( + alignment: Alignment.center, + height: 15.w, + child: + (userProfile?.userNickname?.length ?? 0) > 8 + ? Marquee( + text: userProfile?.userNickname ?? "", + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + userProfile?.userNickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffECB45C), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + ), + SizedBox(width: 20.w), + ], + ), + ), + ), + + Container( + padding: EdgeInsets.only(bottom: 12.w), + margin: EdgeInsets.symmetric(horizontal: 18.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_dialog_content.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + Container( + margin: EdgeInsets.symmetric( + horizontal: 35.w, + ).copyWith(top: 45.w), + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: + ATAppLocalizations.of( + context, + )!.areYouSureYouWantToSpend, + style: TextStyle( + fontSize: sp(14), + color: Color(0xffECB45C), + fontWeight: FontWeight.w500, + ), + ), + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Image.asset( + "atu_images/general/at_icon_jb.png", + width: 18.w, + ), + ), + TextSpan( + text: "50000", + style: TextStyle( + fontSize: sp(14), + color: Color(0xffECB45C), + fontWeight: FontWeight.w500, + ), + ), + TextSpan( + text: " ", + style: TextStyle( + fontSize: sp(14), + color: Color(0xffECB45C), + fontWeight: FontWeight.w500, + ), + ), + TextSpan( + text: + ATAppLocalizations.of( + context, + )!.areYouSureYouWantToSpend2, + style: TextStyle( + fontSize: sp(14), + color: Color(0xffECB45C), + fontWeight: FontWeight.w500, + ), + ), + ], + ), + textAlign: TextAlign.left, + strutStyle: StrutStyle( + height: 1.2, // 行高倍数 + fontWeight: FontWeight.w500, + forceStrutHeight: true, // 强制应用行高 + ), + ), + ), + Container( + margin: EdgeInsets.symmetric( + horizontal: 35.w, + ).copyWith(top: 10.w), + alignment: AlignmentDirectional.center, + child: text( + ATAppLocalizations.of( + context, + )!.areYouSureYouWantToSpend3, + fontSize: 10.w, + textColor: Color(0xffECB45C), + maxLines: 4, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + SizedBox(height: 10.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + child: Container( + padding: EdgeInsets.only(top: 7.w), + alignment: AlignmentDirectional.center, + width: 130.w, + height: 48.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_cancel_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: text( + ATAppLocalizations.of(context)!.cancel, + fontSize: 18.sp, + fontWeight: FontWeight.bold, + textColor: Color(0xffECB45C), + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showSendCpRequestDialog"); + }, + ), + SizedBox(width: 25.w), + GestureDetector( + child: Container( + padding: EdgeInsets.only(top: 7.w), + alignment: AlignmentDirectional.center, + width: 130.w, + height: 48.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_send_cp_requst_ok_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: text( + ATAppLocalizations.of(context)!.confirm, + fontSize: 18.sp, + fontWeight: FontWeight.bold, + textColor: Color(0xffECB45C), + ), + ), + onTap: () { + ATLoadingManager.exhibitOperation(); + AccountRepository() + .cpRelationshipSendApply(userProfile?.id ?? "") + .then((result) { + ATLoadingManager.veilRoutine(); + ATTts.show( + ATAppLocalizations.of( + context, + )!.operationSuccessful, + ); + SmartDialog.dismiss(tag: "showSendCpRequestDialog"); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + }, + ), + ], + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/chatvibe_features/user/profile/relation/relation_ship_page.dart b/lib/chatvibe_features/user/profile/relation/relation_ship_page.dart new file mode 100644 index 0000000..2901bac --- /dev/null +++ b/lib/chatvibe_features/user/profile/relation/relation_ship_page.dart @@ -0,0 +1,607 @@ +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/user_count_guard_res.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +///CP关系 +class RelationShipPage extends StatefulWidget { + String tageId = ""; + bool isFromMe = false; + + RelationShipPage(this.isFromMe, this.tageId); + + @override + _RelationShipPageState createState() => _RelationShipPageState(); +} + +class _RelationShipPageState extends State { + List? userCountGuardResList; + + @override + void initState() { + super.initState(); + ChatRoomRepository().userCountGuard(widget.tageId).then((result) { + userCountGuardResList = result; + setState(() {}); + }); + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Consumer( + builder: (context, ref, child) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + SizedBox(width: 12.w), + text( + ATAppLocalizations.of(context)!.couple2, + fontSize: 14.sp, + fontWeight: FontWeight.bold, + textColor: Colors.black, + ), + Spacer(), + widget.tageId == + UserManager().getCurrentUser()?.userProfile?.id + ? GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + ATNavigatorUtils.push(context, MainRoute.cpList); + }, + child: Row( + children: [ + text( + ATAppLocalizations.of(context)!.cpList, + fontSize: 12.sp, + fontWeight: FontWeight.bold, + textColor: Colors.black, + ), + Icon( + Icons.chevron_right, + color: Colors.black, + size: 18.w, + ), + ], + ), + ) + : Container(), + SizedBox(width: 12.w), + ], + ), + SizedBox(height: 10.w), + _buildCpList(ref), + // SizedBox(height: 10.w), + // Row( + // children: [ + // SizedBox(width: 12.w), + // text( + // ATAppLocalizations.of(context)!.superFans, + // fontSize: 14.sp, + // fontWeight: FontWeight.bold, + // textColor: Colors.white, + // ), + // ], + // ), + // SizedBox(height: 10.w), + // Row( + // children: [ + // Expanded( + // child: Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // userCountGuardResList != null && + // userCountGuardResList!.isNotEmpty + // ? GestureDetector( + // child: Stack( + // alignment: Alignment.center, + // children: [ + // Image.asset( + // "atu_images/room/at_icon_user_count_guard1_user.png", + // width: 100.w, + // height: 100.w, + // ), + // Positioned( + // bottom: 9.w, + // child: head( + // url: + // userCountGuardResList![0] + // .userProfile! + // .userAvatar ?? + // "", + // width: 70.w, + // ), + // ), + // ], + // ), + // onTap: () { + // ATNavigatorUtils.push( + // context, + // replace: !widget.isFromMe, + // "${MainRoute.person}?isMe=${UserManager().getCurrentUser()?.userProfile?.id == userCountGuardResList![0].userProfile?.id}&tageId=${userCountGuardResList![0].userProfile?.id}", + // ); + // }, + // ) + // : Image.asset( + // "atu_images/room/at_icon_user_count_guard1.png", + // width: 100.w, + // height: 100.w, + // ), + // ], + // ), + // ), + // Expanded( + // child: Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // userCountGuardResList != null && + // userCountGuardResList!.length > 1 + // ? GestureDetector( + // onTap: () { + // ATNavigatorUtils.push( + // context, + // replace: !widget.isFromMe, + // "${MainRoute.person}?isMe=${UserManager().getCurrentUser()?.userProfile?.id == userCountGuardResList![1].userProfile?.id}&tageId=${userCountGuardResList![1].userProfile?.id}", + // ); + // }, + // child: Stack( + // alignment: Alignment.center, + // children: [ + // Image.asset( + // "atu_images/room/at_icon_user_count_guard2_user.png", + // width: 100.w, + // height: 100.w, + // ), + // Positioned( + // bottom: 9.w, + // child: head( + // url: + // userCountGuardResList![1] + // .userProfile! + // .userAvatar ?? + // "", + // width: 70.w, + // ), + // ), + // ], + // ), + // ) + // : Image.asset( + // "atu_images/room/at_icon_user_count_guard2.png", + // width: 100.w, + // height: 100.w, + // ), + // ], + // ), + // ), + // Expanded( + // child: Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // userCountGuardResList != null && + // userCountGuardResList!.length > 2 + // ? GestureDetector( + // onTap: () { + // ATNavigatorUtils.push( + // context, + // replace: !widget.isFromMe, + // "${MainRoute.person}?isMe=${UserManager().getCurrentUser()?.userProfile?.id == userCountGuardResList![2].userProfile?.id}&tageId=${userCountGuardResList![2].userProfile?.id}", + // ); + // }, + // child: Stack( + // alignment: Alignment.center, + // children: [ + // Image.asset( + // "atu_images/room/at_icon_user_count_guard3_user.png", + // width: 100.w, + // height: 100.w, + // ), + // Positioned( + // bottom: 9.w, + // child: head( + // url: + // userCountGuardResList![2] + // .userProfile! + // .userAvatar ?? + // "", + // width: 70.w, + // ), + // ), + // ], + // ), + // ) + // : Image.asset( + // "atu_images/room/at_icon_user_count_guard3.png", + // width: 100.w, + // height: 100.w, + // ), + // ], + // ), + // ), + // ], + // ), + ], + ); + }, + ), + ); + } + + Widget _buildCpList(ChatVibeUserProfileManager ref) { + List cpList = []; + if (widget.tageId == UserManager().getCurrentUser()?.userProfile?.id) { + //自己 + if (widget.isFromMe) { + cpList = UserManager().getCurrentUser()?.userProfile?.cpList ?? []; + } else { + cpList = ref.userProfile?.cpList ?? []; + } + } else { + cpList = ref.userProfile?.cpList ?? []; + } + + if (widget.tageId == UserManager().getCurrentUser()?.userProfile?.id) { + return cpList.isNotEmpty + ? SizedBox( + height: 188.w, + child: CarouselSlider( + options: CarouselOptions( + height: 188.w, + autoPlay: (cpList.length ?? 0) > 1, + // 启用自动播放 + enlargeCenterPage: false, + // 居中放大当前页面 + aspectRatio: 1 / 1, + // 宽高比 + enableInfiniteScroll: true, + // 启用无限循环 + autoPlayAnimationDuration: Duration(milliseconds: 800), + // 自动播放动画时长 + viewportFraction: 1, + // 视口分数 + onPageChanged: (index, reason) { + setState(() {}); + }, + ), + items: + cpList.map((item) { + return GestureDetector( + child: _buildCpItem(item), + onTap: () {}, + ); + }).toList(), + ), + ) + : GestureDetector( + child: _buildEmptyCpItem(true), + onTap: () { + ATNavigatorUtils.push(context, MainRoute.cpList); + }, + ); + } else { + return cpList.isNotEmpty + ? SizedBox( + height: 188.w, + child: CarouselSlider( + options: CarouselOptions( + height: 188.w, + autoPlay: cpList.length > 1, + // 启用自动播放 + enlargeCenterPage: false, + // 居中放大当前页面 + aspectRatio: 1 / 1, + // 宽高比 + enableInfiniteScroll: true, + // 启用无限循环 + autoPlayAnimationDuration: Duration(milliseconds: 800), + // 自动播放动画时长 + viewportFraction: 1, + // 视口分数 + onPageChanged: (index, reason) { + setState(() {}); + }, + ), + items: + cpList.map((item) { + return GestureDetector( + child: _buildCpItem(item), + onTap: () {}, + ); + }).toList(), + ), + ) + : _buildEmptyCpItem(false); + } + } + + Widget _buildCpItem(CPRes item) { + return Stack( + alignment: AlignmentDirectional.center, + children: [ + Container( + width: ScreenUtil().screenWidth, + margin: EdgeInsets.symmetric(horizontal: 10.w), + child: Stack( + children: [ + Image.asset( + "atu_images/person/at_icon_no_cp_item_bg2_lv_${(item.profileCardLevel ?? 0) <= 3 ? (item.profileCardLevel ?? 1) : 3}.png", + width: ScreenUtil().screenWidth, + fit: BoxFit.fill, + ), + Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: 30.w), + Stack( + alignment: AlignmentGeometry.bottomCenter, + children: [ + Transform.scale( + scale: 1.2, + child: netImage( + url: item.selfRing?.sourceUrl ?? "", + width: 65.w, + ), + ), + Transform.translate( + offset: Offset(0, 15.w), + child: Container( + width: 70.w, + height: 32.w, + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_user_cp_level_value_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Directionality( + textDirection: TextDirection.ltr, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + margin: EdgeInsets.only(top: 5.w, left: 15.w), + child: text( + "LV.${item.cpLevel ?? 1}", + fontWeight: FontWeight.w600, + fontSize: 12.sp, + textColor: Colors.white, + ), + ), + ], + ), + ), + ), + ), + ], + ), + SizedBox(height: 18.w), + Container( + alignment: AlignmentDirectional.bottomCenter, + child: text( + ATAppLocalizations.of( + context, + )!.timeSpentTogether(item.days ?? ""), + fontWeight: FontWeight.w600, + fontSize: 12.sp, + textColor: Color(0xFFFF79A1), + ), + ), + Container( + alignment: AlignmentDirectional.bottomCenter, + child: text( + ATAppLocalizations.of( + context, + )!.firstDay(item.firstDay ?? ""), + fontWeight: FontWeight.w600, + fontSize: 11.sp, + textColor: Colors.white, + ), + ), + ], + ), + ], + ), + ), + PositionedDirectional( + top: 50.w, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + GestureDetector( + child: SizedBox( + height: 50.w, + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + netImage( + url: item.meUserAvatar ?? "", + width: 48.w, + shape: BoxShape.circle, + defaultImg: + "atu_images/general/at_icon_avar_defalt.png", + ), + Image.asset( + "atu_images/person/at_icon_cp_head_ring.png", + ), + ], + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + replace: !widget.isFromMe, + "${MainRoute.person}?isMe=${UserManager().getCurrentUser()?.userProfile?.id == item.meUserId}&tageId=${item.meUserId}", + ); + }, + ), + Container( + alignment: Alignment.center, + width: 80.w, + height: 15.w, + child: + (item.meUserNickname?.length ?? 0) > 8 + ? Marquee( + text: item.meUserNickname ?? "", + style: TextStyle( + fontSize: 10.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.linear, + decelerationDuration: Duration(milliseconds: 500), + decelerationCurve: Curves.easeOut, + ) + : Text( + item.meUserNickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + SizedBox(width: 120.w), + Column( + children: [ + GestureDetector( + child: SizedBox( + height: 50.w, + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + netImage( + url: item.cpUserAvatar ?? "", + width: 48.w, + shape: BoxShape.circle, + defaultImg: + "atu_images/general/at_icon_avar_defalt.png", + ), + Image.asset( + "atu_images/person/at_icon_cp_head_ring.png", + ), + ], + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + replace: !widget.isFromMe, + "${MainRoute.person}?isMe=${UserManager().getCurrentUser()?.userProfile?.id == item.cpUserId}&tageId=${item.cpUserId}", + ); + }, + ), + Container( + alignment: Alignment.center, + width: 80.w, + height: 15.w, + child: + (item.cpUserNickname?.length ?? 0) > 8 + ? Marquee( + text: item.cpUserNickname ?? "", + style: TextStyle( + fontSize: 10.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.linear, + decelerationDuration: Duration(milliseconds: 500), + decelerationCurve: Curves.easeOut, + ) + : Text( + item.cpUserNickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + ], + ), + ), + ], + ); + } + + String _cpVaFormat(num cpValue) { + int value = cpValue.toInt(); + if (value > 99999) { + return "${(value / 1000).toStringAsFixed(0)}k"; + } + return "$value"; + } + + _buildEmptyCpItem(bool canAdd) { + return Container( + width: ScreenUtil().screenWidth, + margin: EdgeInsets.symmetric(horizontal: 6.w), + height: 200.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + canAdd + ? "atu_images/person/at_icon_no_cp_item_bg.png" + : "atu_images/person/at_icon_no_cp_item_bg4.png", + ), + fit: BoxFit.fill, + ), + ), + child: Container( + margin: EdgeInsets.only(bottom: 48.w, left: 12.w), + alignment: AlignmentDirectional.bottomCenter, + child: text( + ATAppLocalizations.of(context)!.noMatchedCP, + fontWeight: FontWeight.w600, + fontSize: 10.sp, + textColor: Colors.white, + ), + ), + ); + } +} diff --git a/lib/chatvibe_features/user/settings/about/about_page.dart b/lib/chatvibe_features/user/settings/about/about_page.dart new file mode 100644 index 0000000..6f081b6 --- /dev/null +++ b/lib/chatvibe_features/user/settings/about/about_page.dart @@ -0,0 +1,135 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; + +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_ui/widgets/pop/pop_route.dart'; +import 'package:aslan/chatvibe_features/webview/webview_page.dart'; + +class AboutPage extends StatefulWidget { + @override + _AboutPageState createState() => _AboutPageState(); +} + +class _AboutPageState extends State { + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/room/at_icon_room_settig_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar(title: ATAppLocalizations.of(context)!.about, actions: []), + body: SafeArea(top: false,child: Column( + children: [ + SizedBox(height: 35.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + "atu_images/splash/at_icon_splash_icon.png", + width: 80.w, + height: 120.w, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text("v${ATGlobalConfig.version}(${ATGlobalConfig.build})", fontSize: 14.sp, textColor: Colors.black38), + ], + ), + SizedBox(height: 15.w,), + Container( + padding: EdgeInsets.symmetric(vertical: 12.w), + margin: EdgeInsets.symmetric(vertical: 8.w, horizontal: 35.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + border: Border.all(color: Color(0xffE6E6E6), width: 1.w), + ), + child: Column( + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Row( + children: [ + SizedBox(width: 18.w), + text( + ATAppLocalizations.of(context)!.termsofService, + textColor: Colors.black, + fontSize: 13.sp, + ), + Spacer(), + Icon( + Icons.keyboard_arrow_right, + size: 15.w, + color: Colors.black26, + ), + SizedBox(width: 18.w), + ], + ), + onTap: () { + Navigator.push( + context, + PopRoute( + child: WebViewPage( + title: ATAppLocalizations.of(context)!.termsofService, + url: '${ATGlobalConfig.userAgreementUrl}', + ), + ), + ); + }, + ), + Divider(color: Colors.black12, thickness: 0.5), + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Row( + children: [ + SizedBox(width: 18.w), + text( + ATAppLocalizations.of(context)!.privaceyPolicy, + textColor: Colors.black, + fontSize: 13.sp, + ), + Spacer(), + Icon( + Icons.keyboard_arrow_right, + size: 15.w, + color: Colors.black26, + ), + SizedBox(width: 18.w), + ], + ), + onTap: () { + Navigator.push( + context, + PopRoute( + child: WebViewPage( + title: ATAppLocalizations.of(context)!.privaceyPolicy, + url: + '${ATGlobalConfig.privacyAgreementUrl}', + ), + ), + ); + }, + ), + ], + ), + ) + ], + ),), + ), + ], + ); + } +} diff --git a/lib/chatvibe_features/user/settings/account/account_page.dart b/lib/chatvibe_features/user/settings/account/account_page.dart new file mode 100644 index 0000000..b8124b7 --- /dev/null +++ b/lib/chatvibe_features/user/settings/account/account_page.dart @@ -0,0 +1,122 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; + +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_features/user/settings/settings_route.dart'; + +class AccountPage extends StatefulWidget { + @override + _AccountPageState createState() => _AccountPageState(); +} + +class _AccountPageState extends State { + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + ATGlobalConfig.businessLogicStrategy.getAccountPageBackgroundImage(), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: ATGlobalConfig.businessLogicStrategy.getAccountPageScaffoldBackgroundColor(), + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.account, + actions: [], + ), + body: SafeArea( + top: false, + child: Column( + children: [ + Container( + alignment: Alignment.bottomLeft, + margin: EdgeInsets.only(left: 15.w), + child: text( + ATAppLocalizations.of(context)!.account, + textColor: ATGlobalConfig.businessLogicStrategy.getAccountPageSecondaryTextColor(), + fontSize: 12.sp, + ), + ), + Container( + padding: EdgeInsets.symmetric(vertical: 12.w), + margin: EdgeInsets.symmetric(vertical: 8.w, horizontal: 15.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: ATGlobalConfig.businessLogicStrategy.getAccountPageMainContainerBackgroundColor(), + border: Border.all(color: ATGlobalConfig.businessLogicStrategy.getAccountPageMainContainerBorderColor(), width: 1.w), + ), + child: Column( + children: [ + ATDebounceWidget( + child: Row( + children: [ + SizedBox(width: 18.w), + text( + ATAppLocalizations.of(context)!.setAccount, + textColor: ATGlobalConfig.businessLogicStrategy.getAccountPagePrimaryTextColor(), + fontSize: 13.sp, + ), + Spacer(), + Icon( + Icons.keyboard_arrow_right, + size: 15.w, + color: ATGlobalConfig.businessLogicStrategy.getAccountPageIconColor(), + ), + SizedBox(width: 18.w), + ], + ), + onTap: () { + ATNavigatorUtils.push( + context, + SettingsRoute.setAccount, + replace: false, + ); + }, + ), + Divider(color: ATGlobalConfig.businessLogicStrategy.getAccountPageDividerColor(), thickness: 0.5), + ATDebounceWidget( + child: Row( + children: [ + SizedBox(width: 18.w), + text( + ATAppLocalizations.of(context)!.deleteAccount, + textColor: ATGlobalConfig.businessLogicStrategy.getAccountPagePrimaryTextColor(), + fontSize: 13.sp, + ), + Spacer(), + Icon( + Icons.keyboard_arrow_right, + size: 15.w, + color: ATGlobalConfig.businessLogicStrategy.getAccountPageIconColor(), + ), + SizedBox(width: 18.w), + ], + ), + onTap: () { + ATNavigatorUtils.push( + context, + SettingsRoute.deleteAccount, + replace: false, + ); + }, + ), + ], + ), + ), + ], + ), + ), + ), + ], + ); + } +} diff --git a/lib/chatvibe_features/user/settings/account/delete/delete_account_page.dart b/lib/chatvibe_features/user/settings/account/delete/delete_account_page.dart new file mode 100644 index 0000000..705f9d1 --- /dev/null +++ b/lib/chatvibe_features/user/settings/account/delete/delete_account_page.dart @@ -0,0 +1,154 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; + +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; + +///删除账户 +class DeleteAccountPage extends StatefulWidget { + @override + _DeleteAccountPageState createState() => _DeleteAccountPageState(); +} + +class _DeleteAccountPageState extends State { + int delay = 30; + bool canClick = false; + String btnText = ""; + Timer? _timer; + BuildContext? ct; + + @override + void initState() { + super.initState(); + _timer = Timer.periodic(Duration(seconds: 1), (timer) { + if (delay == 0) { + canClick = true; + _timer?.cancel(); + btnText = ATAppLocalizations.of(ct!)!.deleteAccount; + } else { + delay--; + btnText = ATAppLocalizations.of(ct!)!.deleteAccount2("$delay"); + } + setState(() {}); + }); + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + ct = context; + return Scaffold( + backgroundColor: Color(0xffF3F3F3), + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.deleteAccount, + actions: [], + ), + body: SafeArea( + top: false, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + SizedBox(width: 10.w), + text( + ATAppLocalizations.of(context)!.accountDeletionNotice, + fontSize: 14.sp, + textColor: Colors.black38, + ), + ], + ), + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: text( + ATAppLocalizations.of(context)!.deleteAccountTips, + fontSize: 12.sp, + maxLines: 30, + textColor: Colors.black, + ), + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 10.w), + + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: text( + ATAppLocalizations.of(context)!.deleteAccountTips2, + fontSize: 12.sp, + maxLines: 30, + textColor: Colors.black38, + ), + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 35.w), + GestureDetector( + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(25), + color: !canClick ? Colors.black26 : Colors.red, + ), + width: 275.w, + height: 46.w, + child: text( + btnText.isEmpty + ? ATAppLocalizations.of(context)!.deleteAccount2("30") + : btnText, + fontSize: 15.sp, + textColor: Colors.white, + ), + ), + onTap: () { + if (canClick) { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.areYouSureYouWantToDeleteYourAccount, + btnText: ATAppLocalizations.of(context)!.delete, + onEnsure: () { + AccountStorage().logout(context); + }, + ); + }, + ); + } + }, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/chatvibe_features/user/settings/account/set/pwd/reset_pwd_page.dart b/lib/chatvibe_features/user/settings/account/set/pwd/reset_pwd_page.dart new file mode 100644 index 0000000..5ddc1a7 --- /dev/null +++ b/lib/chatvibe_features/user/settings/account/set/pwd/reset_pwd_page.dart @@ -0,0 +1,360 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; + +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; + +class ResetPwdPage extends StatefulWidget { + @override + _ResetPwdPageState createState() => _ResetPwdPageState(); +} + +class _ResetPwdPageState extends State { + ///显示密码 + bool showPass = false; + bool showPass3 = false; + bool showPass2 = false; + + ///账户控制器 + TextEditingController confirmController = TextEditingController(); + + ///密码控制器 + TextEditingController passController = TextEditingController(); + + ///旧密码 + TextEditingController oldPassController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/person/at_icon_edit_userinfo_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.resetLoginPassword, + actions: [], + ), + body: SafeArea(top: false,child: Column( + children: [ + Container( + width: ScreenUtil().screenWidth, + color: Colors.black12, + padding: EdgeInsets.symmetric(vertical: 8.w, horizontal: 8.w), + child: text( + ATAppLocalizations.of(context)!.resetLoginPasswordtTips1, + maxLines: 2, + textColor: Colors.black87, + fontSize: 10.sp, + ), + ), + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 15.w), + text( + ATAppLocalizations.of(context)!.account, + fontSize: 15.sp, + textColor: Colors.black, + ), + ], + ), + Container( + width: ScreenUtil().screenWidth, + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.w), + margin: EdgeInsets.symmetric(horizontal: 10.w, vertical: 8.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.black12, + ), + child: text( + AccountStorage().getCurrentUser()?.userProfile?.account ?? "", + textColor: Colors.black54, + fontSize: 14.sp, + ), + ), + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 15.w), + text( + ATAppLocalizations.of(context)!.inputYourOldPassword, + fontSize: 15.sp, + textColor: Colors.black, + ), + ], + ), + Container( + width: ScreenUtil().screenWidth, + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.w), + margin: EdgeInsets.symmetric(horizontal: 10.w, vertical: 8.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.black12, + ), + child: TextField( + controller: oldPassController, + maxLength: 30, + maxLines: 1, + inputFormatters: [ + FilteringTextInputFormatter.deny(RegExp(r'\s')) + ], + + obscureText: !showPass3, + textInputAction: TextInputAction.done, + onChanged: (s) { + setState(() {}); + }, + decoration: InputDecoration( + hintText: + ATAppLocalizations.of(context)!.enterYourOldPassword, + hintStyle: TextStyle( + color: Colors.black54, + fontSize: 14.sp, + ), + counterText: '', + contentPadding: EdgeInsets.only(top: 0.w), + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + suffix: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + setState(() { + showPass3 = !showPass3; + }); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Image.asset( + !showPass3 + ? "atu_images/login/at_icon_pass.png" + : "atu_images/login/at_icon_pass1.png", + gaplessPlayback: true, + width: 15.w, + ), + ), + ), + ), + style: TextStyle(fontSize: 12.sp, color: Colors.black), + ), + ), + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 15.w), + text( + ATAppLocalizations.of(context)!.setYourPassword, + fontSize: 15.sp, + textColor: Colors.black, + ), + ], + ), + Container( + width: ScreenUtil().screenWidth, + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.w), + margin: EdgeInsets.symmetric(horizontal: 10.w, vertical: 8.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.black12, + ), + child: TextField( + controller: passController, + maxLength: 30, + maxLines: 1, + inputFormatters: [ + FilteringTextInputFormatter.deny(RegExp(r'\s')) + ], + + obscureText: !showPass, + textInputAction: TextInputAction.done, + onChanged: (s) { + setState(() {}); + }, + decoration: InputDecoration( + hintText: + ATAppLocalizations.of(context)!.enterYourNewPassword, + hintStyle: TextStyle( + color: Colors.black54, + fontSize: 14.sp, + ), + counterText: '', + contentPadding: EdgeInsets.only(top: 0.w), + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + suffix: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + setState(() { + showPass = !showPass; + }); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Image.asset( + !showPass + ? "atu_images/login/at_icon_pass.png" + : "atu_images/login/at_icon_pass1.png", + gaplessPlayback: true, + width: 15.w, + ), + ), + ), + ), + style: TextStyle(fontSize: 12.sp, color: Colors.black), + ), + ), + Container( + width: ScreenUtil().screenWidth, + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.w), + margin: EdgeInsets.symmetric(horizontal: 10.w, vertical: 8.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.black12, + ), + child: TextField( + controller: confirmController, + maxLength: 30, + maxLines: 1, + inputFormatters: [ + FilteringTextInputFormatter.deny(RegExp(r'\s')) + ], + + obscureText: !showPass2, + textInputAction: TextInputAction.done, + onChanged: (s) { + setState(() {}); + }, + decoration: InputDecoration( + hintText: ATAppLocalizations.of(context)!.confirmYourPassword, + hintStyle: TextStyle( + color: Colors.black54, + fontSize: 14.sp, + ), + counterText: '', + contentPadding: EdgeInsets.only(top: 0.w), + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + suffix: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + setState(() { + showPass2 = !showPass2; + }); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Image.asset( + !showPass2 + ? "atu_images/login/at_icon_pass.png" + : "atu_images/login/at_icon_pass1.png", + gaplessPlayback: true, + width: 15.w, + ), + ), + ), + ), + style: TextStyle(fontSize: 12.sp, color: Colors.black), + ), + ), + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: text( + ATAppLocalizations.of(context)!.resetLoginPasswordtTips2, + fontSize: 10.sp, + textColor: Colors.black, + maxLines: 3, + ), + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 20.w), + GestureDetector( + child: Container( + height: 44.w, + width: 260.w, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: ChatVibeTheme.primaryLight, + ), + child: text( + ATAppLocalizations.of(context)!.submit, + fontSize: 16.sp, + textColor: Colors.white, + ), + ), + onTap: () { + resetPwd(); + }, + ), + ], + ),), + ), + ], + ); + } + + void resetPwd() { + if (confirmController.text.isNotEmpty && + passController.text.isNotEmpty && + oldPassController.text.isNotEmpty) { + if (confirmController.text == passController.text) { + ATLoadingManager.exhibitOperation(context: context); + AccountRepository() + .updatePwd(passController.text, oldPassController.text) + .then((d) { + ATLoadingManager.veilRoutine(); + ATTts.show(ATAppLocalizations.of(context)!.operationSuccessful); + ATNavigatorUtils.goBack(context); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } else { + ATTts.show(ATAppLocalizations.of(context)!.theTwoPasswordsDoNotMatch); + } + } + } +} diff --git a/lib/chatvibe_features/user/settings/account/set/pwd/set_pwd_page.dart b/lib/chatvibe_features/user/settings/account/set/pwd/set_pwd_page.dart new file mode 100644 index 0000000..d8f2492 --- /dev/null +++ b/lib/chatvibe_features/user/settings/account/set/pwd/set_pwd_page.dart @@ -0,0 +1,281 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; + +class SetPwdPage extends StatefulWidget { + @override + _SetPwdPageState createState() => _SetPwdPageState(); +} + +class _SetPwdPageState extends State { + ///显示密码 + bool showPass = false; + bool showPass2 = false; + + ///账户控制器 + TextEditingController confirmController = TextEditingController(); + + ///密码控制器 + TextEditingController passController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/person/at_icon_edit_userinfo_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.setLoginPassword, + actions: [], + ), + body:SafeArea(top: false,child: Column( + children: [ + Container( + width: ScreenUtil().screenWidth, + color: Colors.black12, + padding: EdgeInsets.symmetric(vertical: 8.w, horizontal: 8.w), + child: text( + ATAppLocalizations.of(context)!.resetLoginPasswordtTips1, + maxLines: 2, + textColor: Colors.black87, + fontSize: 10.sp, + ), + ), + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 15.w), + text( + ATAppLocalizations.of(context)!.account, + fontSize: 15.sp, + textColor: Colors.black, + ), + ], + ), + Container( + width: ScreenUtil().screenWidth, + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.w), + margin: EdgeInsets.symmetric(horizontal: 10.w, vertical: 8.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.black12, + ), + child: text( + AccountStorage().getCurrentUser()?.userProfile?.account ?? "", + textColor: Colors.black54, + fontSize: 14.sp, + ), + ), + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 15.w), + text( + ATAppLocalizations.of(context)!.setYourPassword, + fontSize: 15.sp, + textColor: Colors.black, + ), + ], + ), + Container( + width: ScreenUtil().screenWidth, + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.w), + margin: EdgeInsets.symmetric(horizontal: 10.w, vertical: 8.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.black12, + ), + child: TextField( + controller: passController, + maxLength: 30, + maxLines: 1, + inputFormatters: [ + FilteringTextInputFormatter.deny(RegExp(r'\s')), + ], + + obscureText: !showPass, + textInputAction: TextInputAction.done, + onChanged: (s) { + setState(() {}); + }, + decoration: InputDecoration( + hintText: + ATAppLocalizations.of(context)!.enterYourNewPassword, + hintStyle: TextStyle( + color: Colors.black54, + fontSize: 14.sp, + ), + counterText: '', + contentPadding: EdgeInsets.only(top: 0.w), + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + suffix: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + setState(() { + showPass = !showPass; + }); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Image.asset( + !showPass + ? "atu_images/login/at_icon_pass.png" + : "atu_images/login/at_icon_pass1.png", + gaplessPlayback: true, + width: 15.w, + ), + ), + ), + ), + style: TextStyle(fontSize: 12.sp, color: Colors.black), + ), + ), + Container( + width: ScreenUtil().screenWidth, + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.w), + margin: EdgeInsets.symmetric(horizontal: 10.w, vertical: 8.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.black12, + ), + child: TextField( + controller: confirmController, + maxLength: 30, + maxLines: 1, + inputFormatters: [ + FilteringTextInputFormatter.deny(RegExp(r'\s')), + ], + + obscureText: !showPass, + textInputAction: TextInputAction.done, + onChanged: (s) { + setState(() {}); + }, + decoration: InputDecoration( + hintText: ATAppLocalizations.of(context)!.confirmYourPassword, + hintStyle: TextStyle( + color: Colors.black54, + fontSize: 14.sp, + ), + counterText: '', + contentPadding: EdgeInsets.only(top: 0.w), + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + suffix: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + setState(() { + showPass2 = !showPass2; + }); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Image.asset( + !showPass2 + ? "atu_images/login/at_icon_pass.png" + : "atu_images/login/at_icon_pass1.png", + gaplessPlayback: true, + width: 15.w, + ), + ), + ), + ), + style: TextStyle(fontSize: 12.sp, color: Colors.black), + ), + ), + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: text( + ATAppLocalizations.of(context)!.resetLoginPasswordtTips2, + fontSize: 10.sp, + textColor: Colors.black, + maxLines: 3, + ), + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 20.w), + GestureDetector( + child: Container( + height: 44.w, + width: 260.w, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: ChatVibeTheme.primaryLight, + ), + child: text( + ATAppLocalizations.of(context)!.submit, + fontSize: 16.sp, + textColor: Colors.white, + ), + ), + onTap: () { + setPwd(); + }, + ), + ], + ),), + ), + ], + ); + } + + void setPwd() { + if (confirmController.text.isNotEmpty && passController.text.isNotEmpty) { + if (confirmController.text == passController.text) { + ATLoadingManager.exhibitOperation(context: context); + AccountRepository() + .bind(passController.text) + .then((f) { + ATLoadingManager.veilRoutine(); + ATTts.show(ATAppLocalizations.of(context)!.operationSuccessful); + ATNavigatorUtils.goBack(context); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } else { + ATTts.show(ATAppLocalizations.of(context)!.theTwoPasswordsDoNotMatch); + } + } + } +} diff --git a/lib/chatvibe_features/user/settings/account/set/set_account_page.dart b/lib/chatvibe_features/user/settings/account/set/set_account_page.dart new file mode 100644 index 0000000..8ff9788 --- /dev/null +++ b/lib/chatvibe_features/user/settings/account/set/set_account_page.dart @@ -0,0 +1,161 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; + +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_features/user/settings/settings_route.dart'; + +class SetAccountPage extends StatefulWidget { + @override + _SetAccountPageState createState() => _SetAccountPageState(); +} + +class _SetAccountPageState extends State { + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/person/at_icon_edit_userinfo_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar(title: ATAppLocalizations.of(context)!.setAccount, actions: []), + body: SafeArea(top: false,child:Column( + children: [ + Container( + alignment: Alignment.bottomLeft, + margin: EdgeInsets.only(left: 15.w), + child: text( + ATAppLocalizations.of(context)!.account, + textColor: Colors.black38, + fontSize: 12.sp, + ), + ), + Container( + padding: EdgeInsets.symmetric(vertical: 12.w), + margin: EdgeInsets.symmetric(vertical: 8.w, horizontal: 15.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + border: Border.all(color: Color(0xffE6E6E6), width: 1.w), + ), + child: Column( + children: [ + ATDebounceWidget( + onTap: () { + ATLoadingManager.exhibitOperation(); + AccountRepository().accountIsBind().then((value) { + ATLoadingManager.veilRoutine(); + if (value) { + ///修改密码 + ATNavigatorUtils.push( + context, + SettingsRoute.resetPwd, + replace: false, + ); + } else { + ///设置密码 + ATNavigatorUtils.push( + context, + SettingsRoute.setPwd, + replace: false, + ); + } + }).catchError((_){ + ATLoadingManager.veilRoutine(); + }); + }, + child: Row( + children: [ + SizedBox(width: 18.w), + text( + ATAppLocalizations.of(context)!.account, + textColor: Colors.black, + fontSize: 13.sp, + ), + Spacer(), + text( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.account ?? + "", + fontSize: 13.sp, + textColor: Colors.black38, + ), + SizedBox(width: 5.w), + Icon( + Icons.keyboard_arrow_right, + size: 15.w, + color: Colors.black26, + ), + SizedBox(width: 18.w), + ], + ), + ), + Divider(color: Colors.black12, thickness: 0.5), + Platform.isIOS + ? Row( + children: [ + SizedBox(width: 18.w), + text( + ATAppLocalizations.of(context)!.apple, + textColor: Colors.black, + fontSize: 13.sp, + ), + Spacer(), + Icon( + Icons.keyboard_arrow_right, + size: 15.w, + color: Colors.black26, + ), + SizedBox(width: 18.w), + ], + ) + : Container(), + Platform.isIOS + ? Divider(color: Colors.black12, thickness: 0.5) + : Container(), + ATDebounceWidget(child: Row( + children: [ + SizedBox(width: 18.w), + text( + ATAppLocalizations.of(context)!.google, + textColor: Colors.black, + fontSize: 13.sp, + ), + Spacer(), + Icon( + Icons.keyboard_arrow_right, + size: 15.w, + color: Colors.black26, + ), + SizedBox(width: 18.w), + ], + ),onTap: (){ + + },) , + ], + ), + ), + ], + ) ,), + ), + ], + ); + } +} diff --git a/lib/chatvibe_features/user/settings/blacklist/user_blacklist_page.dart b/lib/chatvibe_features/user/settings/blacklist/user_blacklist_page.dart new file mode 100644 index 0000000..656a85f --- /dev/null +++ b/lib/chatvibe_features/user/settings/blacklist/user_blacklist_page.dart @@ -0,0 +1,337 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/chatvibe_gradient_button.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/message_friend_user_res.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +class UserBlockedListPage extends ATPageList { + @override + _UserBlockedListPageState createState() => _UserBlockedListPageState(); +} + +class _UserBlockedListPageState + extends ATPageListState { + late TextEditingController _textEditingController; + String? lastId; + + @override + void initState() { + super.initState(); + _textEditingController = TextEditingController(); + enablePullUp = true; + backgroundColor = Colors.transparent; + isShowDivider = true; + isShowFooter = false; + padding = EdgeInsets.symmetric(horizontal: 10.w); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/person/at_icon_edit_userinfo_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.blockedList, + actions: [], + ), + body: SafeArea( + top: false, + child: Column( + children: [ + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: searchWidget( + hint: ATAppLocalizations.of(context)!.searchUserId, + controller: _textEditingController, + borderColor: Colors.black12, + textColor: Colors.grey, + ), + ), + chatvibeGradientButton( + text: ATAppLocalizations.of(context)!.search, + radius: 25, + textSize: 14.sp, + textColor: Colors.grey, + gradient: LinearGradient( + colors: [Colors.transparent, Colors.transparent], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + onPress: () { + if (_textEditingController.text.isNotEmpty) { + _search(); + } + }, + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 8.w), + Expanded(child: buildList(context)), + SizedBox(height: 8.w), + ], + ), + ), + ), + ], + ); + } + + @override + Widget buildItem(MessageFriendUserRes res) { + return Container( + padding: EdgeInsets.symmetric(vertical: 8.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + border: Border.all(color: Colors.black12, width: 1), + ), + child: Row( + children: [ + SizedBox(width: 10.w), + ATDebounceWidget( + child: head(url: res.userProfile?.userAvatar ?? "", width: 62.w), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == res.userProfile?.id}&tageId=${res.userProfile?.id}", + ); + }, + ), + SizedBox(width: 3.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + netImage( + url: + Provider.of(context, listen: false) + .getCountryByName( + res.userProfile?.countryName ?? "", + ) + ?.nationalFlag ?? + "", + borderRadius: BorderRadius.all(Radius.circular(3.w)), + width: 19.w, + height: 14.w, + ), + SizedBox(width: 3.w), + text( + maxWidth: 155.w, + textColor: Colors.black, + res.userProfile?.userNickname ?? "", + fontSize: 14.sp, + ), + SizedBox(width: 3.w), + Container( + width: (res.userProfile?.age ?? 0) > 999 ? 58.w : 48.w, + height: 24.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + res.userProfile?.userSex == 0 + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png", + ), + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + xb(res.userProfile?.userSex), + text( + "${res.userProfile?.age}", + textColor: Colors.white, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + ], + ), + ), + ], + ), + SizedBox(height: 4.w), + Row( + children: [ + GestureDetector( + child: Row( + textDirection: TextDirection.ltr, + children: [ + text( + "ID:${res.userProfile?.getID()}", + fontSize: 12.sp, + textColor: Colors.black38, + fontWeight: FontWeight.bold, + ), + SizedBox(width: 5.w), + Image.asset( + "atu_images/room/at_icon_user_card_copy_id.png", + width: 12.w, + height: 12.w, + ), + SizedBox(width: 8.w), + ], + ), + onTap: () { + Clipboard.setData( + ClipboardData(text: res.userProfile?.getID() ?? ""), + ); + ATTts.show( + ATAppLocalizations.of(context)!.copiedToClipboard, + ); + }, + ), + SizedBox(width: 8.w), + getWealthLevel( + res.userProfile?.wealthLevel ?? 0, + width: 58.w, + height: 30.w, + ), + SizedBox(width: 8.w), + getUserLevel( + res.userProfile?.charmLevel ?? 0, + width: 58.w, + height: 30.w, + ), + ], + ), + ], + ), + ), + ATDebounceWidget( + child: Image.asset( + "atu_images/room/at_icon_music_delete.png", + color: Colors.black, + height: 20.w, + ), + onTap: () { + _delete(res.userProfile); + }, + ), + SizedBox(width: 15.w), + ], + ), + ); + } + + void _search() { + AccountRepository() + .userBlacklistList(account: _textEditingController.text) + .then((result) { + items = result; + loadComplete(); + ATLoadingManager.veilRoutine(); + setState(() {}); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + loadComplete(); + setState(() {}); + }); + } + + @override + builderDivider() { + return Divider( + height: 8.w, + color: Color(0xffF2F2F2), + indent: 15.w, + endIndent: 15.w, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + if (_textEditingController.text.isNotEmpty) { + _search(); + } else { + if (page == 1) { + lastId = null; + } + try { + var userList = await AccountRepository().userBlacklistList( + lastId: lastId, + ); + if (userList.isNotEmpty) { + lastId = userList.last.id; + } + ATLoadingManager.veilRoutine(); + onSuccess(userList); + } catch (e) { + ATLoadingManager.veilRoutine(); + if (onErr != null) { + onErr(); + } + } + } + } + + void _delete(ChatVibeUserProfile? userProfile) { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context!)!.tips, + msg: ATAppLocalizations.of(context)!.areYouSureToCancelBlacklist, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + ATLoadingManager.exhibitOperation(); + AccountRepository() + .deleteUserBlacklist(userProfile?.id ?? "") + .then((result) { + ATTts.show( + ATAppLocalizations.of( + context, + )!.successfullyRemovedFromTheBlacklist, + ); + loadData(1); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + }, + ); + }, + ); + } +} diff --git a/lib/chatvibe_features/user/settings/settings_page.dart b/lib/chatvibe_features/user/settings/settings_page.dart new file mode 100644 index 0000000..bd2a79b --- /dev/null +++ b/lib/chatvibe_features/user/settings/settings_page.dart @@ -0,0 +1,296 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_features/user/settings/settings_route.dart'; + +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_app_utils.dart'; + +///设置页面 +class SettingsPage extends StatefulWidget { + @override + _SettingsPageState createState() => _SettingsPageState(); +} + +class _SettingsPageState extends State { + String totalSize = "0"; + + @override + void initState() { + super.initState(); + _loadCacheSize(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + ATGlobalConfig.businessLogicStrategy.getSettingsPageBackgroundImage(), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.settings, + actions: [], + ), + body: SafeArea( + top: false, + child: Column( + children: [ + Container( + alignment: AlignmentDirectional.bottomStart, + margin: EdgeInsetsDirectional.only(start: 15.w), + child: text( + ATAppLocalizations.of(context)!.account, + textColor: ATGlobalConfig.businessLogicStrategy.getSettingsPageSecondaryTextColor(), + fontSize: 12.sp, + ), + ), + Container( + padding: EdgeInsets.symmetric(vertical: 12.w), + margin: EdgeInsets.symmetric(vertical: 8.w, horizontal: 15.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: ATGlobalConfig.businessLogicStrategy.getSettingsPageMainContainerBackgroundColor(), + border: Border.all(color: ATGlobalConfig.businessLogicStrategy.getSettingsPageMainContainerBorderColor(), width: 1.w), + ), + child: Column( + children: [ + ATDebounceWidget( + child: Row( + children: [ + SizedBox(width: 18.w), + text( + ATAppLocalizations.of(context)!.account, + textColor: ATGlobalConfig.businessLogicStrategy.getSettingsPagePrimaryTextColor(), + fontSize: 13.sp, + fontWeight: FontWeight.w500, + ), + Spacer(), + Icon( + ATGlobalConfig.lang == "ar" + ? Icons.keyboard_arrow_left + : Icons.keyboard_arrow_right, + size: 15.w, + color: ATGlobalConfig.businessLogicStrategy.getSettingsPageIconColor(), + ), + SizedBox(width: 18.w), + ], + ), + onTap: () { + ATNavigatorUtils.push( + context, + SettingsRoute.account, + replace: false, + ); + }, + ), + ], + ), + ), + Container( + padding: EdgeInsets.symmetric(vertical: 12.w), + margin: EdgeInsets.symmetric(horizontal: 15.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: ATGlobalConfig.businessLogicStrategy.getSettingsPageContainerBackgroundColor(), + border: Border.all(color: ATGlobalConfig.businessLogicStrategy.getSettingsPageContainerBorderColor(), width: 1.w), + ), + child: Column( + children: [ + ATDebounceWidget( + child: Row( + children: [ + SizedBox(width: 18.w), + text( + ATAppLocalizations.of(context)!.blockedList, + textColor: ATGlobalConfig.businessLogicStrategy.getSettingsPagePrimaryTextColor(), + fontSize: 13.sp, + fontWeight: FontWeight.w500, + ), + Spacer(), + Icon( + ATGlobalConfig.lang == "ar" + ? Icons.keyboard_arrow_left + : Icons.keyboard_arrow_right, + size: 15.w, + color: ATGlobalConfig.businessLogicStrategy.getSettingsPageIconColor(), + ), + SizedBox(width: 18.w), + ], + ), + onTap: () { + ATNavigatorUtils.push( + context, + SettingsRoute.blockedList, + replace: false, + ); + }, + ), + ], + ), + ), + SizedBox(height: 8.w), + Container( + alignment: AlignmentDirectional.bottomStart, + margin: EdgeInsetsDirectional.only(start: 15.w), + child: text( + ATAppLocalizations.of(context)!.common, + textColor: ATGlobalConfig.businessLogicStrategy.getSettingsPageCommonTextColor(), + fontSize: 12.sp, + ), + ), + Container( + padding: EdgeInsets.symmetric(vertical: 12.w), + margin: EdgeInsets.symmetric(vertical: 8.w, horizontal: 15.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: ATGlobalConfig.businessLogicStrategy.getSettingsPageContainerBackgroundColor(), + border: Border.all(color: ATGlobalConfig.businessLogicStrategy.getSettingsPageContainerBorderColor(), width: 1.w), + ), + child: Column( + children: [ + ATDebounceWidget( + child: Row( + children: [ + SizedBox(width: 18.w), + text( + ATAppLocalizations.of(context)!.language, + textColor: ATGlobalConfig.businessLogicStrategy.getSettingsPagePrimaryTextColor(), + fontSize: 13.sp, + fontWeight: FontWeight.w500, + ), + Spacer(), + Icon( + ATGlobalConfig.lang == "ar" + ? Icons.keyboard_arrow_left + : Icons.keyboard_arrow_right, + size: 15.w, + color: ATGlobalConfig.businessLogicStrategy.getSettingsPageIconColor(), + ), + SizedBox(width: 18.w), + ], + ), + onTap: () { + ATNavigatorUtils.push( + context, + MainRoute.language, + replace: false, + ); + }, + ), + ], + ), + ), + SizedBox(height: 8.w), + Container( + alignment: AlignmentDirectional.bottomStart, + margin: EdgeInsetsDirectional.only(start: 15.w), + child: text( + ATAppLocalizations.of(context)!.about, + textColor: ATGlobalConfig.businessLogicStrategy.getSettingsPageAboutTextColor(), + fontSize: 12.sp, + ), + ), + Container( + padding: EdgeInsets.symmetric(vertical: 12.w), + margin: EdgeInsets.symmetric(vertical: 8.w, horizontal: 15.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: ATGlobalConfig.businessLogicStrategy.getSettingsPageContainerBackgroundColor(), + border: Border.all(color: ATGlobalConfig.businessLogicStrategy.getSettingsPageContainerBorderColor(), width: 1.w), + ), + child: Column( + children: [ + ATDebounceWidget( + onTap: () { + ATNavigatorUtils.push( + context, + SettingsRoute.about, + replace: false, + ); + }, + child: Row( + children: [ + SizedBox(width: 18.w), + text( + ATAppLocalizations.of(context)!.aboutUs, + textColor: ATGlobalConfig.businessLogicStrategy.getSettingsPagePrimaryTextColor(), + fontSize: 13.sp, + fontWeight: FontWeight.w500, + ), + Spacer(), + Icon( + ATGlobalConfig.lang == "ar" + ? Icons.keyboard_arrow_left + : Icons.keyboard_arrow_right, + size: 15.w, + color: ATGlobalConfig.businessLogicStrategy.getSettingsPageIconColor(), + ), + SizedBox(width: 18.w), + ], + ), + ), + ], + ), + ), + Row( + children: [ + Expanded( + child: ATDebounceWidget( + child: Container( + alignment: Alignment.center, + padding: EdgeInsets.symmetric(vertical: 10.w), + margin: EdgeInsets.symmetric( + horizontal: 55.w, + vertical: 35.w, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(25.w), + color: ATGlobalConfig.businessLogicStrategy.getSettingsPageButtonBackgroundColor(), + ), + child: text( + ATAppLocalizations.of(context)!.logout, + fontSize: 14.sp, + textColor: ATGlobalConfig.businessLogicStrategy.getSettingsPageButtonTextColor(), + ), + ), + onTap: () { + AccountStorage().logout(context); + }, + ), + ), + ], + ), + ], + ), + ), + ), + ], + ); + } + + void _loadCacheSize() { + ATAppUtils.collectCacheSize() + .then((res) { + totalSize = res; + setState(() {}); + }) + .catchError((_) {}); + } +} diff --git a/lib/chatvibe_features/user/settings/settings_route.dart b/lib/chatvibe_features/user/settings/settings_route.dart new file mode 100644 index 0000000..13caae2 --- /dev/null +++ b/lib/chatvibe_features/user/settings/settings_route.dart @@ -0,0 +1,68 @@ +import 'package:fluro/fluro.dart'; +import 'package:fluro/src/fluro_router.dart'; +import 'package:aslan/chatvibe_features/user/settings/account/set/pwd/reset_pwd_page.dart'; +import 'package:aslan/chatvibe_features/user/settings/account/set/pwd/set_pwd_page.dart'; +import 'package:aslan/chatvibe_features/user/settings/settings_page.dart'; +import 'package:aslan/chatvibe_features/user/settings/socialPrivilege/social_privilege_page.dart'; + +import 'package:aslan/chatvibe_core/routes/at_router_init.dart'; +import 'package:aslan/chatvibe_features/user/settings/about/about_page.dart'; +import 'package:aslan/chatvibe_features/user/settings/account/account_page.dart'; +import 'package:aslan/chatvibe_features/user/settings/account/delete/delete_account_page.dart'; +import 'package:aslan/chatvibe_features/user/settings/account/set/set_account_page.dart'; +import 'package:aslan/chatvibe_features/user/settings/blacklist/user_blacklist_page.dart'; + +class SettingsRoute implements ATIRouterProvider { + static String settings = '/me/settings'; + static String account = '/me/settings/account'; + static String blockedList = '/me/settings/blockedList'; + static String socialPrivilege = '/me/settings/socialPrivilege'; + static String setAccount = '/me/settings/account/setAccount'; + static String deleteAccount = '/me/settings/account/deleteAccount'; + static String resetPwd = '/me/settings/account/setAccount/resetPwd'; + static String setPwd = '/me/settings/account/setAccount/setPwd'; + static String about = '/me/settings/account/about'; + + @override + void initRouter(FluroRouter router) { + router.define( + settings, + handler: Handler(handlerFunc: (_, params) => SettingsPage()), + ); + router.define( + account, + handler: Handler(handlerFunc: (_, params) => AccountPage()), + ); + router.define( + setAccount, + handler: Handler(handlerFunc: (_, params) => SetAccountPage()), + ); + + router.define( + deleteAccount, + handler: Handler(handlerFunc: (_, params) => DeleteAccountPage()), + ); + router.define( + setPwd, + handler: Handler(handlerFunc: (_, params) => SetPwdPage()), + ); + router.define( + resetPwd, + handler: Handler(handlerFunc: (_, params) => ResetPwdPage()), + ); + router.define( + about, + handler: Handler(handlerFunc: (_, params) => AboutPage()), + ); + + router.define( + blockedList, + handler: Handler(handlerFunc: (_, params) => UserBlockedListPage()), + ); + + router.define( + socialPrivilege, + handler: Handler(handlerFunc: (_, params) => SocialPrivilegePage()), + ); + } +} diff --git a/lib/chatvibe_features/user/settings/socialPrivilege/social_privilege_page.dart b/lib/chatvibe_features/user/settings/socialPrivilege/social_privilege_page.dart new file mode 100644 index 0000000..a597a08 --- /dev/null +++ b/lib/chatvibe_features/user/settings/socialPrivilege/social_privilege_page.dart @@ -0,0 +1,538 @@ +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import '../../../../chatvibe_core/utilities/at_user_utils.dart'; +import '../../../../chatvibe_data/models/enum/at_vip_contact_mode_type.dart'; +import '../../../../chatvibe_data/models/enum/at_vip_type.dart'; +import '../../../../chatvibe_domain/models/res/at_user_vip_ability_res.dart'; + +class SocialPrivilegePage extends StatefulWidget { + @override + _SocialPrivilegePageState createState() => _SocialPrivilegePageState(); +} + +class _SocialPrivilegePageState extends State { + ATUserVipAbilityRes? res; + + @override + void initState() { + super.initState(); + _getUserVipAbility(); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [Color(0xff2D2B35), Color(0xff17161E)], + ), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12), + topRight: Radius.circular(12), + ), + ), + height: ScreenUtil().screenHeight * 0.6, + child: Column( + children: [ + SizedBox(height: 10.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + text( + ATAppLocalizations.of(context)!.permissionSettings, + textColor: ChatVibeTheme.primaryLight, + fontWeight: FontWeight.w600, + fontSize: 16.sp, + ), + AccountStorage().getVIP() != null + ? text( + "(${ATAccountHelper.getVipLevel(AccountStorage().getVIP()?.name??"")})", + textColor: ChatVibeTheme.primaryLight, + fontWeight: FontWeight.w600, + fontSize: 16.sp, + ) + : Container(), + ], + ), + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + Container( + alignment: AlignmentDirectional.bottomStart, + margin: EdgeInsetsDirectional.only(start: 15.w), + child: text( + ATAppLocalizations.of(context)!.contactUs, + textColor: Colors.white70, + fontSize: 15.sp, + ), + ), + Container( + padding: EdgeInsets.symmetric(vertical: 3.w), + margin: EdgeInsets.symmetric( + vertical: 4.w, + horizontal: 15.w, + ), + child: Column( + children: [ + ATDebounceWidget( + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text( + ATAppLocalizations.of(context)!.everyone, + textColor: Colors.white70, + fontSize: 13.sp, + ), + text( + ATAppLocalizations.of( + context, + )!.basicPermissions, + textColor: Colors.white38, + fontSize: 11.sp, + ), + ], + ), + Spacer(), + Image.asset( + res?.contactMode != + ATVipContactModeType + .VIP2_ABOVE + .name && + res?.contactMode != + ATVipContactModeType + .VIP3_ABOVE + .name && + res?.contactMode != + ATVipContactModeType + .VIP4_ABOVE + .name && + res?.contactMode != + ATVipContactModeType + .VIP5_ABOVE + .name + ? "atu_images/general/at_icon_social_privilege_select.png" + : "atu_images/general/at_icon_social_privilege_unselect.png", + width: 16.w, + height: 16.w, + ), + ], + ), + onTap: () { + if (res?.contactMode != + ATVipContactModeType.EVERYONE.name) { + _userVipAbilityUpdate( + contactMode: + ATVipContactModeType.EVERYONE.name, + ); + } + }, + ), + ], + ), + ), + Container( + padding: EdgeInsets.symmetric(vertical: 3.w), + margin: EdgeInsets.symmetric( + vertical: 4.w, + horizontal: 15.w, + ), + child: Column( + children: [ + ATDebounceWidget( + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text( + ATAppLocalizations.of( + context, + )!.andAboveUsers("VIP2"), + textColor: Colors.white70, + fontSize: 13.sp, + ), + text( + ATAppLocalizations.of( + context, + )!.privileges("VIP2"), + textColor: Colors.white38, + fontSize: 11.sp, + ), + ], + ), + Spacer(), + Image.asset( + res?.contactMode == + ATVipContactModeType.VIP2_ABOVE.name + ? "atu_images/general/at_icon_social_privilege_select.png" + : "atu_images/general/at_icon_social_privilege_unselect.png", + width: 16.w, + height: 16.w, + ), + ], + ), + onTap: () { + if (AccountStorage().getVIP()?.name == + ATVIPType.EARL.name || + AccountStorage().getVIP()?.name == + ATVIPType.MARQUIS.name || + AccountStorage().getVIP()?.name == + ATVIPType.DUKE.name || + AccountStorage().getVIP()?.name == + ATVIPType.KING.name || + AccountStorage().getVIP()?.name == + ATVIPType.EMPEROR.name) { + if (res?.contactMode != + ATVipContactModeType.VIP2_ABOVE.name) { + _userVipAbilityUpdate( + contactMode: + ATVipContactModeType.VIP2_ABOVE.name, + ); + } + } else { + ATTts.show( + ATAppLocalizations.of( + context, + )!.pleaseUpgradeYourVipLevel, + ); + } + }, + ), + ], + ), + ), + Container( + padding: EdgeInsets.symmetric(vertical: 3.w), + margin: EdgeInsets.symmetric( + vertical: 4.w, + horizontal: 15.w, + ), + child: Column( + children: [ + ATDebounceWidget( + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text( + ATAppLocalizations.of( + context, + )!.andAboveUsers("VIP3"), + textColor: Colors.white70, + fontSize: 13.sp, + ), + text( + ATAppLocalizations.of( + context, + )!.privileges("VIP3"), + textColor: Colors.white38, + fontSize: 11.sp, + ), + ], + ), + Spacer(), + Image.asset( + res?.contactMode == + ATVipContactModeType.VIP3_ABOVE.name + ? "atu_images/general/at_icon_social_privilege_select.png" + : "atu_images/general/at_icon_social_privilege_unselect.png", + width: 16.w, + height: 16.w, + ), + ], + ), + onTap: () { + if (AccountStorage().getVIP()?.name == + ATVIPType.MARQUIS.name || + AccountStorage().getVIP()?.name == + ATVIPType.DUKE.name || + AccountStorage().getVIP()?.name == + ATVIPType.KING.name || + AccountStorage().getVIP()?.name == + ATVIPType.EMPEROR.name) { + if (res?.contactMode != + ATVipContactModeType.VIP3_ABOVE.name) { + _userVipAbilityUpdate( + contactMode: + ATVipContactModeType.VIP3_ABOVE.name, + ); + } + } else { + ATTts.show( + ATAppLocalizations.of( + context, + )!.pleaseUpgradeYourVipLevel, + ); + } + }, + ), + ], + ), + ), + Container( + padding: EdgeInsets.symmetric(vertical: 3.w), + margin: EdgeInsets.symmetric( + vertical: 4.w, + horizontal: 15.w, + ), + child: Column( + children: [ + ATDebounceWidget( + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text( + ATAppLocalizations.of( + context, + )!.andAboveUsers("VIP4"), + textColor: Colors.white70, + fontSize: 13.sp, + ), + text( + ATAppLocalizations.of( + context, + )!.privileges("VIP4"), + textColor: Colors.white38, + fontSize: 11.sp, + ), + ], + ), + Spacer(), + Image.asset( + res?.contactMode == + ATVipContactModeType.VIP4_ABOVE.name + ? "atu_images/general/at_icon_social_privilege_select.png" + : "atu_images/general/at_icon_social_privilege_unselect.png", + width: 16.w, + height: 16.w, + ), + ], + ), + onTap: () { + if (AccountStorage().getVIP()?.name == + ATVIPType.DUKE.name || + AccountStorage().getVIP()?.name == + ATVIPType.KING.name || + AccountStorage().getVIP()?.name == + ATVIPType.EMPEROR.name) { + if (res?.contactMode != + ATVipContactModeType.VIP4_ABOVE.name) { + _userVipAbilityUpdate( + contactMode: + ATVipContactModeType.VIP4_ABOVE.name, + ); + } + } else { + ATTts.show( + ATAppLocalizations.of( + context, + )!.pleaseUpgradeYourVipLevel, + ); + } + }, + ), + ], + ), + ), + Container( + padding: EdgeInsets.symmetric(vertical: 3.w), + margin: EdgeInsets.symmetric( + vertical: 4.w, + horizontal: 15.w, + ), + child: Column( + children: [ + ATDebounceWidget( + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text( + ATAppLocalizations.of( + context, + )!.andAboveUsers("VIP5"), + textColor: Colors.white70, + fontSize: 13.sp, + ), + text( + ATAppLocalizations.of( + context, + )!.privileges("VIP5"), + textColor: Colors.white38, + fontSize: 11.sp, + ), + ], + ), + Spacer(), + Image.asset( + res?.contactMode == + ATVipContactModeType.VIP5_ABOVE.name + ? "atu_images/general/at_icon_social_privilege_select.png" + : "atu_images/general/at_icon_social_privilege_unselect.png", + width: 16.w, + height: 16.w, + ), + ], + ), + onTap: () { + if (AccountStorage().getVIP()?.name == + ATVIPType.KING.name || + AccountStorage().getVIP()?.name == + ATVIPType.EMPEROR.name) { + if (res?.contactMode != + ATVipContactModeType.VIP5_ABOVE.name) { + _userVipAbilityUpdate( + contactMode: + ATVipContactModeType.VIP5_ABOVE.name, + ); + } + } else { + ATTts.show( + ATAppLocalizations.of( + context, + )!.pleaseUpgradeYourVipLevel, + ); + } + }, + ), + ], + ), + ), + SizedBox(height: 8.w), + Container( + alignment: AlignmentDirectional.bottomStart, + margin: EdgeInsetsDirectional.only(start: 15.w), + child: text( + ATAppLocalizations.of(context)!.vipPrivilege, + textColor: Colors.white70, + fontSize: 15.sp, + ), + ), + SizedBox(height: 8.w), + Container( + padding: EdgeInsets.symmetric(vertical: 3.w), + margin: EdgeInsets.symmetric( + vertical: 4.w, + horizontal: 15.w, + ), + child: Column( + children: [ + ATDebounceWidget( + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text( + ATAppLocalizations.of( + context, + )!.avoidBeingKicked, + textColor: Colors.white70, + fontSize: 13.sp, + ), + text( + ATAppLocalizations.of( + context, + )!.privileges("VIP5"), + textColor: Colors.white38, + fontSize: 11.sp, + ), + ], + ), + Spacer(), + Image.asset( + res?.kickPrevention ?? false + ? "atu_images/general/at_icon_social_privilege_open.png" + : "atu_images/general/at_icon_social_privilege_close.png", + width: 32.w, + height: 18.w, + ), + ], + ), + onTap: () { + if (AccountStorage().getVIP()?.name == + ATVIPType.KING.name || + AccountStorage().getVIP()?.name == + ATVIPType.EMPEROR.name) { + _userVipAbilityUpdate( + kickPrevention: + !(res?.kickPrevention ?? false), + ); + } else { + ATTts.show( + ATAppLocalizations.of( + context, + )!.pleaseUpgradeYourVipLevel, + ); + } + }, + ), + ], + ), + ), + SizedBox(height: 12.w), + ], + ), + ), + ), + ], + ), + ), + ); + } + + void _getUserVipAbility() { + ATLoadingManager.exhibitOperation(); + AccountRepository() + .userVipAbility() + .then((value) { + ATLoadingManager.veilRoutine(); + res = value; + setState(() {}); + }) + .catchError((_) { + ATLoadingManager.veilRoutine(); + }); + } + + void _userVipAbilityUpdate({ + String? contactMode, + bool? kickPrevention, + bool? mysteriousInvisibility, + bool? antiBlock, + }) { + ATLoadingManager.exhibitOperation(); + AccountRepository() + .userVipAbilityUpdate( + contactMode: contactMode, + kickPrevention: kickPrevention, + mysteriousInvisibility: mysteriousInvisibility, + antiBlock: antiBlock, + ) + .then((res) { + _getUserVipAbility(); + }) + .catchError((_) { + ATLoadingManager.veilRoutine(); + }); + } +} diff --git a/lib/chatvibe_features/user/visitor/visitor_user_list_page.dart b/lib/chatvibe_features/user/visitor/visitor_user_list_page.dart new file mode 100644 index 0000000..9ca3ae1 --- /dev/null +++ b/lib/chatvibe_features/user/visitor/visitor_user_list_page.dart @@ -0,0 +1,223 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/follow_user_res.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +class VisitorUserListPage extends ATPageList { + final bool removePreviousPersonDetail; + + const VisitorUserListPage({ + super.key, + this.removePreviousPersonDetail = false, + }); + + @override + _VisitorUserListPageState createState() => _VisitorUserListPageState(); +} + +class _VisitorUserListPageState + extends ATPageListState { + bool _didRemovePreviousPersonDetail = false; + + @override + void initState() { + super.initState(); + enablePullUp = true; + backgroundColor = Colors.transparent; + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/person/at_icon_edit_userinfo_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.visitorList, + actions: [], + ), + body: buildList(context), + ), + ], + ); + } + + @override + Widget buildItem(FollowUserRes res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration(), + child: Row( + children: [ + SizedBox(width: 10.w), + head( + url: res.userProfile?.userAvatar ?? "", + width: 55.w, + headdress: res.userProfile?.getHeaddress()?.sourceUrl, + ), + SizedBox(width: 5.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + chatvibeNickNameText( + maxWidth: 160.w, + res.userProfile?.userNickname ?? "", + fontSize: 14.sp, + textColor: Colors.black, + type: res.userProfile?.getVIP()?.name ?? "", + needScroll: + (res.userProfile?.userNickname?.characters.length ?? + 0) > + 14, + ), + SizedBox(width: 3.w), + res.userProfile?.getVIP() != null + ? netImage( + url: res.userProfile?.getVIP()?.cover ?? "", + width: 25.w, + height: 25.w, + ) + : Container(), + SizedBox(width: 3.w), + Container( + width: (res.userProfile?.age ?? 0) > 999 ? 56.w : 46.w, + height: 23.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + res.userProfile?.userSex == 0 + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png", + ), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + xb(res.userProfile?.userSex), + text( + "${res.userProfile?.age}", + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 3.w), + ], + ), + ), + ], + ), + Row( + children: [ + GestureDetector( + child: SizedBox( + child: Row( + children: [ + getIdIcon( + res.userProfile?.wealthLevel ?? 0, + width: 28.w, + height: 28.w, + textColor: Color(0xFF333333), + fontSize: 12.sp, + fontWeight: FontWeight.w600, + ), + chatvibeNickNameText( + maxWidth: 135.w, + res.userProfile?.getID() ?? "", + textColor: Color(0xFF333333), + fontSize: 12.sp, + fontWeight: FontWeight.w600, + type: res.userProfile?.getVIP()?.name ?? "", + needScroll: + (res.userProfile + ?.getID() + .characters + .length ?? + 0) > + 13, + ), + ], + ), + ), + onTap: () { + Clipboard.setData( + ClipboardData(text: res.userProfile?.getID() ?? ""), + ); + ATTts.show( + ATAppLocalizations.of(context)!.copiedToClipboard, + ); + }, + ), + ], + ), + ], + ), + ), + SizedBox(width: 15.w), + ], + ), + ), + onTap: () { + if (!_didRemovePreviousPersonDetail && + (widget as VisitorUserListPage).removePreviousPersonDetail) { + _didRemovePreviousPersonDetail = true; + ATNavigatorUtils.removeRouteBelowCurrent(context); + } + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == res.userProfile?.id}&tageId=${res.userProfile?.id}", + ); + }, + ); + } + + @override + builderDivider() { + // return Divider( + // height: 1.w, + // color: Color(0xff3D3277).withOpacity(0.5), + // indent: 15.w, + // ); + return Container(height: 8.w); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var fansList = await AccountRepository().visitorList(page); + onSuccess(fansList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } +} diff --git a/lib/chatvibe_features/wallet/gold/gold_record_page.dart b/lib/chatvibe_features/wallet/gold/gold_record_page.dart new file mode 100644 index 0000000..14a1daa --- /dev/null +++ b/lib/chatvibe_features/wallet/gold/gold_record_page.dart @@ -0,0 +1,128 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; + +import '../../../chatvibe_domain/models/res/at_gold_record_res.dart'; +import '../../../chatvibe_ui/components/at_page_list.dart'; +import '../../../chatvibe_ui/components/text/at_text.dart'; + +class GoldRecordPage extends ATPageList { + @override + _GoldRecordPageState createState() => _GoldRecordPageState(); +} + +class _GoldRecordPageState + extends ATPageListState { + String? lastId; + + @override + void initState() { + super.initState(); + enablePullUp = true; + isShowDivider = false; + backgroundColor = ATGlobalConfig.businessLogicStrategy.getGoldRecordPageListBackgroundColor(); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + ATGlobalConfig.businessLogicStrategy.getGoldRecordPageBackgroundImage(), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: ATGlobalConfig.businessLogicStrategy.getGoldRecordPageScaffoldBackgroundColor(), + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.goldList, + actions: [], + ), + body: SafeArea(top: false,child: buildList(context),), + ), + ], + ); + } + + @override + Widget buildItem(ATGoldRecordRes res) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: ATGlobalConfig.businessLogicStrategy.getGoldRecordPageContainerBackgroundColor(), + border: Border.all(color: ATGlobalConfig.businessLogicStrategy.getGoldRecordPageBorderColor(), width: 1.w), + ), + margin: EdgeInsets.symmetric(horizontal: 10.w, vertical: 3.w), + padding: EdgeInsets.symmetric(vertical: 5.w), + child: Row( + children: [ + SizedBox(width: 10.w), + Container( + constraints: BoxConstraints(maxWidth: 220.w), + child: text( + res.title ?? "", + fontSize: 13.sp, + textColor: ATGlobalConfig.businessLogicStrategy.getGoldRecordPagePrimaryTextColor(), + fontWeight: FontWeight.w600, + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + text( + "${res.type == 0 ? "+" : "-"}${res.quantity} ${ATAppLocalizations.of(context)!.coins}", + fontSize: 13.sp, + textColor: ATGlobalConfig.businessLogicStrategy.getGoldRecordPagePrimaryTextColor(), + fontWeight: FontWeight.w600, + ), + SizedBox(height: 4.w), + text( + ATMDateUtils.formatDateTime2( + DateTime.fromMillisecondsSinceEpoch(res.createTime ?? 0), + ), + fontSize: 11.sp, + textColor: ATGlobalConfig.businessLogicStrategy.getGoldRecordPageSecondaryTextColor(), + ), + ], + ), + ), + SizedBox(width: 12.w), + ], + ), + ), + onTap: () {}, + ); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + if (page == 1) { + lastId = null; + } + try { + var goldList = await AccountRepository().goldRecord(lastId: lastId); + if (goldList.isNotEmpty) { + lastId = goldList.last.id; + } + onSuccess(goldList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } +} diff --git a/lib/chatvibe_features/wallet/recharge/agent_list/at_agent_list_page.dart b/lib/chatvibe_features/wallet/recharge/agent_list/at_agent_list_page.dart new file mode 100644 index 0000000..11189cc --- /dev/null +++ b/lib/chatvibe_features/wallet/recharge/agent_list/at_agent_list_page.dart @@ -0,0 +1,448 @@ +import 'dart:convert'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:provider/provider.dart'; +import 'package:tencent_cloud_chat_sdk/enum/conversation_type.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'; + +import '../../../../chatvibe_core/constants/at_global_config.dart'; +import '../../../../chatvibe_core/routes/at_fluro_navigator.dart'; +import '../../../../chatvibe_data/sources/local/user_manager.dart'; +import '../../../../chatvibe_data/sources/repositories/user_repository_impl.dart'; +import '../../../../chatvibe_domain/models/res/at_user_freight_balance_page_res.dart'; +import '../../../../chatvibe_managers/app_general_manager.dart'; +import '../../../../chatvibe_managers/rtm_manager.dart'; +import '../../../../chatvibe_ui/components/at_compontent.dart'; +import '../../../../chatvibe_ui/components/text/at_text.dart'; +import '../../../chat/at_chat_route.dart'; + +class ATAgentListPage extends ATPageList { + const ATAgentListPage({super.key}); + + @override + ATPageListState createState() => _ATAgentListPageState(); +} + +class _ATAgentListPageState extends ATPageListState { + @override + void initState() { + super.initState(); + backgroundColor = Colors.transparent; + padding = EdgeInsets.fromLTRB(12.w, 19.w, 12.w, 28.w); + loadData(1); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/index/at_icon_recharge_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.list, + backButtonColor: Colors.white, + actions: [], + ), + body: SafeArea(top: false, child: buildList(context)), + ), + ], + ); + } + + @override + Widget buildItem(Records item) { + return _buildAgentCard(context, item); + } + + @override + builderDivider() { + // return Divider( + // height: 1.w, + // color: Color(0xff3D3277).withOpacity(0.5), + // indent: 15.w, + // ); + return Container(height: 8.w); + } + + @override + void loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var result = await AccountRepository().userFreightBalancePage(); + onSuccess(result.records ?? []); + ATLoadingManager.veilRoutine(); + } catch (e) { + ATLoadingManager.veilRoutine(); + if (onErr != null) { + onErr(); + } + } + } + + Widget _buildAgentCard(BuildContext context, Records agent) { + return Container( + width: double.infinity, + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16.w), + border: Border.all(color: Colors.white, width: 2.75.w), + gradient: const RadialGradient( + center: Alignment.bottomCenter, + radius: 1.35, + colors: [Color(0xffffe660), Color(0xffffae50), Color(0xffff7759)], + stops: [0.0, 0.2, 0.69], + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.5), + offset: Offset(0, 1.75.w), + blurRadius: 1.5.w, + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + _buildAgentHeader(agent), + SizedBox(height: 8.w), + _buildBalanceLine(agent), + SizedBox(height: 3.w), + _buildInfoText( + ATAppLocalizations.of( + context, + )!.hasBeenACoinAgencyForDays("${agent.becomeDays ?? 0}"), + ), + SizedBox(height: 3.w), + _buildCountryLine(agent), + SizedBox(height: 3.w), + _buildInfoText( + ATAppLocalizations.of( + context, + )!.successfulTransaction("${agent.transactionCount ?? 0}"), + ), + SizedBox(height: 8.w), + _buildContactLine(agent), + SizedBox(height: 13.w), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildFollowingButton(context, agent), + _buildSendMessageButton(context, agent), + ], + ), + ], + ), + ); + } + + Widget _buildAgentHeader(Records agent) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + netImage( + url: agent.userBaseInfo?.userAvatar ?? "", + defaultImg: + ATGlobalConfig.businessLogicStrategy + .getMePageDefaultAvatarImage(), + width: 48.w, + height: 48.w, + shape: BoxShape.circle, + ), + SizedBox(width: 8.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 1.w), + Row( + children: [ + _buildFlag( + agent.ownNationalFlag ?? "", + width: 20.w, + height: 13.w, + ), + SizedBox(width: 4.w), + Expanded( + child: chatvibeNickNameText( + agent.userBaseInfo?.userNickname ?? "", + fontSize: 16.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + type: agent.userBaseInfo?.getVIP()?.name ?? "", + needScroll: + (agent + .userBaseInfo + ?.userNickname + ?.characters + .length ?? + 0) > + 20, + ), + ), + ], + ), + SizedBox(height: 4.w), + Row( + children: [ + Row( + children: [ + getIdIcon( + agent.userBaseInfo?.wealthLevel ?? 0, + width: 32.w, + height: 32.w, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + chatvibeNickNameText( + maxWidth: 135.w, + agent.userBaseInfo?.getID() ?? "", + textColor: Colors.white, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + type: agent.userBaseInfo?.getVIP()?.name ?? "", + needScroll: + (agent.userBaseInfo?.getID().characters.length ?? + 0) > + 13, + ), + ], + ), + SizedBox(width: 4.w), + _buildAgencyBadge(), + ], + ), + ], + ), + ), + ], + ); + } + + Widget _buildBalanceLine(Records agent) { + return Row( + children: [ + _buildLabelText(ATAppLocalizations.of(context)!.currentBalance), + SizedBox(width: 6.w), + _buildCoinIcon(), + SizedBox(width: 4.w), + Flexible( + child: _buildMaskedText( + "${(agent.balance ?? 0).toString().isNotEmpty ? (agent.balance ?? 0).toString()[0] : '*'}******", + ), + ), + ], + ); + } + + Widget _buildCountryLine(Records agent) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _buildInfoText(ATAppLocalizations.of(context)!.availableCountries), + SizedBox(width: 4.w), + Expanded( + child: Wrap( + spacing: 4.w, + runSpacing: 2.w, + children: + (agent.nationalFlag ?? []).map((flag) { + return _buildFlag(flag, width: 20.w, height: 13.w); + }).toList(), + ), + ), + ], + ); + } + + Widget _buildContactLine(Records agent) { + return Row( + children: [ + Image.asset( + 'atu_images/person/at_icon_agent_contact.png', + width: 24.w, + height: 24.w, + ), + SizedBox(width: 7.w), + Expanded( + child: text( + agent.contactInfo ?? "", + textColor: Colors.white, + fontSize: 10.sp, + fontWeight: FontWeight.w600, + ), + ), + ], + ); + } + + Widget _buildFollowingButton(BuildContext context, Records agent) { + return _buildActionButton( + tv: + agent.isFollow ?? false + ? ATAppLocalizations.of(context)!.following + : ATAppLocalizations.of(context)!.follow, + width: 137.w, + gradient: + agent.isFollow ?? false + ? const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0xffF5F5F5), Color(0xffD8D8D8)], + ) + : const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0xfffff55a), Color(0xffff870d)], + ), + onTap: () { + if (agent.userId == + AccountStorage().getCurrentUser()?.userProfile?.id) { + return; + } + ATLoadingManager.exhibitOperation(); + AccountRepository() + .followUser(agent.userId ?? "") + .then((result) { + loadData(1); + }) + .catchError((_) { + ATLoadingManager.veilRoutine(); + }); + }, + ); + } + + Widget _buildSendMessageButton(BuildContext context, Records agent) { + return _buildActionButton( + tv: ATAppLocalizations.of(context)!.sendMessage, + width: 137.w, + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0xff8BFF8F), Color(0xff03C83A)], + ), + onTap: () => _openChat(agent.userBaseInfo?.id ?? ""), + ); + } + + Widget _buildActionButton({ + required String tv, + required double width, + required Gradient gradient, + required VoidCallback onTap, + }) { + return ATDebounceWidget( + onTap: onTap, + child: Container( + width: width, + height: 38.w, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16.w), + gradient: gradient, + ), + child: text( + tv, + fontWeight: FontWeight.w500, + fontSize: 16.sp, + textColor: Colors.white, + ), + ), + ); + } + + Widget _buildAgencyBadge() { + return Image.asset( + "atu_images/person/at_icon_shipping_agent.png", + height: 23.w, + ); + } + + Widget _buildCoinIcon() { + return Image.asset( + "atu_images/general/at_icon_jb.png", + height: 18.w, + width: 18.w, + ); + } + + Widget _buildFlag( + String flag, { + required double width, + required double height, + }) { + return netImage( + url: flag, + width: 25.w, + height: 16.w, + borderRadius: BorderRadius.circular(2.w), + ); + } + + Widget _buildMaskedText(String value) { + return text( + textDirection: TextDirection.ltr, + value, + fontWeight: FontWeight.w600, + fontSize: 16.sp, + textColor: Colors.red, + ); + } + + Widget _buildLabelText(String value) { + return text( + value, + fontWeight: FontWeight.w700, + fontSize: 12.sp, + textColor: Colors.white, + ); + } + + Widget _buildInfoText(String value) { + return text( + value, + fontWeight: FontWeight.w600, + fontSize: 10.sp, + textColor: Colors.white, + ); + } + + Future _openChat(String userId) async { + if (userId == AccountStorage().getCurrentUser()?.userProfile?.id) { + return; + } + var conversation = V2TimConversation( + type: ConversationType.V2TIM_C2C, + userID: userId, + conversationID: '', + ); + var bool = await Provider.of( + context, + listen: false, + ).startConversation(conversation); + if (!bool) return; + var json = jsonEncode(conversation.toJson()); + ATNavigatorUtils.push( + context, + "${ATChatRouter.chat}?conversation=${Uri.encodeComponent(json)}", + ); + } +} diff --git a/lib/chatvibe_features/wallet/recharge/at_pay_custom_price_item_page.dart b/lib/chatvibe_features/wallet/recharge/at_pay_custom_price_item_page.dart new file mode 100644 index 0000000..e69fd57 --- /dev/null +++ b/lib/chatvibe_features/wallet/recharge/at_pay_custom_price_item_page.dart @@ -0,0 +1,171 @@ +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:provider/provider.dart'; + +import '../../../../../../app_localizations.dart'; +import '../../../chatvibe_core/routes/at_fluro_navigator.dart'; +import '../../../chatvibe_core/utilities/at_loading_manager.dart'; +import '../../../chatvibe_data/sources/repositories/user_repository_impl.dart'; +import '../../../chatvibe_domain/models/res/at_web_pay_commodity_res.dart'; +import '../../../chatvibe_ui/components/at_compontent.dart'; +import '../../../chatvibe_ui/components/text/at_text.dart'; +import '../../../main.dart'; +import '../../index/main_route.dart'; +import '../../webview/at_webview_navigation_handler.dart'; + +class ATPayCustomPriceItemPage extends StatefulWidget { + Channels channels; + List commodity; + + ATPayCustomPriceItemPage(this.channels, this.commodity); + + @override + _ATPayCustomPriceItemPageState createState() => + _ATPayCustomPriceItemPageState(); +} + +class _ATPayCustomPriceItemPageState extends State { + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: Container( + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10), + topRight: Radius.circular(10), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildHeadTag(), + widget.commodity.isEmpty + ? mainEmpty(textColor: Colors.grey) + : _buildPriceBody(), + ], + ), + ), + ); + } + + _buildHeadTag() { + return Row( + children: [ + netImage( + url: widget.channels.channel?.channelIcon ?? "", + width: 45.w, + height: 45.w, + fit: BoxFit.contain, + ), + SizedBox(width: 6.w), + text( + widget.channels.channel?.channelName ?? "", + textColor: Colors.black, + fontSize: 16.sp, + fontWeight: FontWeight.bold, + ), + ], + ); + } + + _buildPriceBody() { + return GridView.builder( + shrinkWrap: true, + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 5.w), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + childAspectRatio: 1, + mainAxisSpacing: 12.w, + crossAxisSpacing: 12.w, + ), + itemCount: widget.commodity.length, + itemBuilder: (BuildContext context, int index) { + return _buildCustomProductItem(widget.commodity[index], index); + ; + }, + ); + } + + Widget _buildCustomProductItem(Commodity productConfig, int index) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/general/at_icon_pay_price_item_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + SizedBox(height: 18.w), + Image.asset( + "atu_images/general/at_icon_jb.png", + width: 34.w, + height: 34.w, + ), + SizedBox(height: 8.w), + text( + productConfig.content ?? "0", + fontSize: 12.sp, + textColor: Color(0xffFFD328), + ), + SizedBox(height: 6.w), + text( + "\$${productConfig.amountUsd ?? 0}", + textColor: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 11.sp, + ), + ], + ), + ), + onTap: () { + _buy(productConfig); + }, + ); + } + + void _buy(Commodity productConfig) { + ATLoadingManager.exhibitOperation(); + AccountRepository() + .webPayRecharge( + productConfig.id ?? "", + productConfig.payCountryId ?? "", + AccountStorage().getCurrentUser()?.userProfile?.id ?? "", + widget.channels.channel?.channelCode ?? "", + ) + .then((res) async { + if ((res.requestUrl?.isNotEmpty) ?? false) { + SmartDialog.dismiss(tag: "showPayPriceItemDialog"); + if (res.requestUrl!.startsWith("http") || + res.requestUrl!.startsWith("https")) { + ATNavigatorUtils.push( + navigatorKey.currentState!.context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(res.requestUrl ?? "")}&showTitle=true", + replace: false, + ); + } else { + final uri = Uri.tryParse(res.requestUrl!); + final launched = await ATWebViewNavigationHandler.launchExternal( + uri!, + ); + if (!launched) { + ATWebViewNavigationHandler.showOpenAppFailedTip(uri); + } + } + } + ATLoadingManager.veilRoutine(); + }) + .catchError((_) { + ATLoadingManager.veilRoutine(); + }); + } +} diff --git a/lib/chatvibe_features/wallet/recharge/at_pay_google_apple_price_item_page.dart b/lib/chatvibe_features/wallet/recharge/at_pay_google_apple_price_item_page.dart new file mode 100644 index 0000000..25a02fc --- /dev/null +++ b/lib/chatvibe_features/wallet/recharge/at_pay_google_apple_price_item_page.dart @@ -0,0 +1,234 @@ +import 'package:aslan/chatvibe_features/wallet/recharge/recharge_page.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:provider/provider.dart'; + +import '../../../../../../app_localizations.dart'; +import '../../../chatvibe_managers/apple_payment_manager.dart'; +import '../../../chatvibe_managers/google_payment_manager.dart'; +import '../../../chatvibe_ui/components/at_compontent.dart'; +import '../../../chatvibe_ui/components/text/at_text.dart'; + +class ATPayGoogleApplePriceItemPage extends StatefulWidget { + String selectedChannelId; + + ATPayGoogleApplePriceItemPage({required this.selectedChannelId}); + + @override + _ATPayGoogleApplePriceItemPageState createState() => _ATPayGoogleApplePriceItemPageState(); +} + +class _ATPayGoogleApplePriceItemPageState extends State { + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: Container( + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10), + topRight: Radius.circular(10), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [_buildHeadTag(), _buildPriceBody()], + ), + ), + ); + } + + _buildHeadTag() { + if (widget.selectedChannelId == "-2") { + return Row( + children: [ + Image.asset( + "atu_images/general/at_icon_google_pay.png", + width: 45.w, + height: 45.w, + ), + SizedBox(width: 6.w), + text( + ATAppLocalizations.of(context)!.googlePay, + textColor: Colors.black, + fontSize: 16.sp, + fontWeight: FontWeight.bold, + ), + ], + ); + } else if (widget.selectedChannelId == "-3") { + return Row( + children: [ + Image.asset( + "atu_images/general/at_icon_apple_pay.png", + width: 45.w, + height: 45.w, + ), + SizedBox(width: 6.w), + text( + ATAppLocalizations.of(context)!.appStore, + textColor: Colors.black, + fontSize: 16.sp, + fontWeight: FontWeight.bold, + ), + ], + ); + } + return Container(); + } + + _buildPriceBody() { + if (widget.selectedChannelId == "-2") { + return Consumer( + builder: (context, ref, child) { + return ref.products.isNotEmpty + ? GridView.builder( + shrinkWrap: true, + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 5.w), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + childAspectRatio: 1, + mainAxisSpacing: 12.w, + crossAxisSpacing: 12.w, + ), + itemCount: ref.products.length, + itemBuilder: (BuildContext context, int index) { + return _buildGoogleProductItem( + ref.products[index], + ref, + index, + ); + ; + }, + ) + : mainEmpty(textColor: Colors.grey); + }, + ); + }else if (widget.selectedChannelId == "-3") { + return Consumer( + builder: (context, ref, child) { + return ref.products.isNotEmpty + ? GridView.builder( + shrinkWrap: true, + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 5.w), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + childAspectRatio: 1, + mainAxisSpacing: 12.w, + crossAxisSpacing: 12.w, + ), + itemCount: ref.products.length, + itemBuilder: (BuildContext context, int index) { + return _buildAppleProductItem( + ref.products[index], + ref, + index, + ); + ; + }, + ) + : mainEmpty(); + }, + ); + } + return Container(); + } + + Widget _buildGoogleProductItem( + SelecteProductConfig productConfig, + GooglePaymentManager ref, + int index, + ) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/general/at_icon_pay_price_item_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + SizedBox(height: 18.w), + Image.asset( + index == 0 + ? "atu_images/general/at_icon_jb.png" + : (index == 1 + ? "atu_images/general/at_icon_jb2.png" + : "atu_images/general/at_icon_jb3.png"), + width: 34.w, + height: 34.w, + ), + SizedBox(height: 8.w), + text( + "${ref.productMap[productConfig.produc.id]?.obtainCandy ?? 0}", + fontSize: 12.sp, + textColor: Color(0xffFFD328), + ), + SizedBox(height: 6.w), + text( + productConfig.produc.price, + textColor: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 11.sp, + ), + ], + ), + ), + onTap: () { + ref.seleceConfig(index, goBuy: true); + }, + ); + } + + Widget _buildAppleProductItem( + SelecteProductConfig productConfig, + ApplePaymentManager ref, + int index, + ) { + return GestureDetector( + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/general/at_icon_pay_price_item_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + SizedBox(height: 18.w), + Image.asset( + index == 0 + ? "atu_images/general/at_icon_jb.png" + : (index == 1 + ? "atu_images/general/at_icon_jb2.png" + : "atu_images/general/at_icon_jb3.png"), + width: 34.w, + height: 34.w, + ), + SizedBox(height: 8.w), + text( + "${ref.productMap[productConfig.produc.id]?.obtainCandy ?? 0}", + fontSize: 12.sp, + textColor: Color(0xffFFD328), + ), + SizedBox(height: 6.w), + text( + productConfig.produc.price, + textColor: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 11.sp, + ), + ], + ), + ), + onTap: () { + ref.seleceConfig(index, goBuy: true); + }, + ); + } + +} diff --git a/lib/chatvibe_features/wallet/recharge/at_recharge_page_v2.dart b/lib/chatvibe_features/wallet/recharge/at_recharge_page_v2.dart new file mode 100644 index 0000000..772007c --- /dev/null +++ b/lib/chatvibe_features/wallet/recharge/at_recharge_page_v2.dart @@ -0,0 +1,927 @@ +import 'dart:io'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:provider/provider.dart'; +import '../../../app_localizations.dart'; +import '../../../chatvibe_core/constants/at_global_config.dart'; +import '../../../chatvibe_core/constants/at_screen.dart'; +import '../../../chatvibe_core/routes/at_fluro_navigator.dart'; +import '../../../chatvibe_core/utilities/at_loading_manager.dart'; +import '../../../chatvibe_data/sources/repositories/user_repository_impl.dart'; +import '../../../chatvibe_domain/models/res/at_web_pay_commodity_res.dart'; +import '../../../chatvibe_domain/models/res/at_web_pay_user_profile_res.dart'; +import '../../../chatvibe_managers/apple_payment_manager.dart'; +import '../../../chatvibe_managers/google_payment_manager.dart'; +import '../../../chatvibe_managers/user_profile_manager.dart'; +import '../../../chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import '../../../chatvibe_ui/components/at_compontent.dart'; +import '../../../chatvibe_ui/components/at_debounce_widget.dart'; +import '../../../chatvibe_ui/components/text/at_text.dart'; +import '../../../chatvibe_ui/theme/chatvibe_theme.dart'; +import '../../../main.dart'; +import '../../index/main_route.dart'; +import '../wallet_route.dart'; +import 'at_pay_custom_price_item_page.dart'; +import 'at_pay_google_apple_price_item_page.dart'; +import 'contry/pay_select_country_page.dart'; + +class ATRechargePageV2 extends StatefulWidget { + @override + _ATRechargePageV2State createState() => _ATRechargePageV2State(); +} + +class _ATRechargePageV2State extends State with RouteAware { + bool _hasPersmission = false; + CountryList? _selectedCountry; + List _countryList = []; + List _channels = []; + List _commodity = []; + + String _selectedChannelId = ""; + String _regionId = ""; + + @override + void initState() { + super.initState(); + Provider.of(context, listen: false).balance(); + if (Platform.isAndroid) { + Provider.of(context, listen: false).init(context); + } else if (Platform.isIOS) { + Provider.of(context, listen: false).init(context); + } + if ((Provider.of( + context, + listen: false, + ).userIdentity?.agent ?? + false) || + (Provider.of( + context, + listen: false, + ).userIdentity?.anchor ?? + false) || + (Provider.of( + context, + listen: false, + ).userIdentity?.bd ?? + false) || + (Provider.of( + context, + listen: false, + ).userIdentity?.admin ?? + false) || + (Provider.of( + context, + listen: false, + ).userIdentity?.manager ?? + false) || + (Provider.of( + context, + listen: false, + ).userIdentity?.superAdmin ?? + false) || + (Provider.of( + context, + listen: false, + ).userIdentity?.bdLeader ?? + false) || + (Provider.of( + context, + listen: false, + ).userIdentity?.freightAgent ?? + false) || + (Provider.of( + context, + listen: false, + ).userIdentity?.superFreightAgent ?? + false)) { + _hasPersmission = true; + } + _loadData(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + // 注册路由观察 + final route = ModalRoute.of(context); + if (route is PageRoute) { + routeObserver.subscribe(this, route); + } + } + + @override + void didPush() { + // 路由被推入时调用 + _updateVisibility(true); + } + + @override + void didPopNext() { + // 当从下一个页面返回时调用 + _updateVisibility(true); + } + + @override + void didPushNext() { + // 当推入下一个页面时调用 + _updateVisibility(false); + } + + @override + void didPop() { + // 路由弹出时调用 + _updateVisibility(false); + } + + void _updateVisibility(bool isVisible) { + if (isVisible) { + Provider.of(context, listen: false).balance(); + } else {} + } + + @override + void dispose() { + routeObserver.unsubscribe(this); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + "atu_images/index/at_icon_recharge_bg.png", + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.recharge, + backButtonColor: Colors.white, + actions: [ + // GestureDetector( + // child: Container( + // margin: EdgeInsetsDirectional.only(end: 15.w), + // child: Image.asset( + // "atu_images/index/at_icon_recharge_recod.png", + // width: 24.w, + // height: 24.w, + // ), + // ), + // onTap: () { + // showGeneralDialog( + // context: context, + // barrierLabel: '', + // barrierDismissible: true, + // transitionDuration: Duration(milliseconds: 300), + // barrierColor: Colors.transparent, + // pageBuilder: ( + // BuildContext context, + // Animation animation, + // Animation secondaryAnimation, + // ) { + // return GestureDetector( + // behavior: HitTestBehavior.opaque, + // onTap: () { + // Navigator.pop(context); + // setState(() {}); + // }, + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.end, + // children: [ + // SizedBox(height: height(68)), + // Row( + // children: [ + // Spacer(), + // GestureDetector( + // child: Container( + // decoration: BoxDecoration( + // color: Colors.white30, + // borderRadius: BorderRadius.circular(4), + // ), + // padding: EdgeInsets.symmetric( + // horizontal: 8.w, + // vertical: 3.w, + // ), + // child: Column( + // crossAxisAlignment: + // CrossAxisAlignment.start, + // children: [ + // SizedBox(height: 5.w), + // GestureDetector( + // child: text( + // ATAppLocalizations.of( + // context, + // )!.goldListort, + // textColor: Colors.white70, + // fontSize: 14.sp, + // ), + // onTap: () { + // Navigator.of(context).pop(); + // ATNavigatorUtils.push( + // context, + // WalletRoute.goldRecord, + // replace: false, + // ); + // }, + // ), + // // SizedBox(height: 5.w), + // // Container( + // // color: Colors.black26, + // // width: 110.w, + // // height: 0.5.w, + // // ), + // // SizedBox(height: 5.w,), + // // text( + // // ATAppLocalizations.of( + // // context, + // // )!.rechargeList, + // // textColor: Colors.black38, + // // fontSize: 14.sp, + // // ), + // SizedBox(height: 5.w), + // ], + // ), + // ), + // onTap: () {}, + // ), + // SizedBox(width: 10.w), + // ], + // ), + // Expanded(child: SizedBox(width: width(1))), + // ], + // ), + // ); + // }, + // ); + // }, + // ), + ], + ), + body: SafeArea( + top: false, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: 25.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + height: 85.w, + width: ScreenUtil().screenWidth * 0.9, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/index/at_icon_wallet_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Stack( + alignment: AlignmentDirectional.centerEnd, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + SizedBox(width: 6.w), + Image.asset( + "atu_images/general/at_icon_jb.png", + width: 20.w, + height: 20.w, + ), + SizedBox(width: 5.w), + text( + ATAppLocalizations.of(context)!.balance, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + SizedBox(height: 5.w), + Consumer( + builder: (context, ref, child) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: 15.w), + text( + "${ref.myBalance}", + textColor: Colors.white, + fontSize: 18.sp, + ), + ], + ); + }, + ), + ], + ), + ATDebounceWidget( + child: Container( + margin: EdgeInsetsDirectional.only(end: 18.w), + padding: EdgeInsets.symmetric( + horizontal: 8.w, + vertical: 5.w, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + gradient: LinearGradient( + colors: [ + Color(0xffFFECD0), + Color(0xffEAB66B), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: text( + ATAppLocalizations.of(context)!.rechargeRecords, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + WalletRoute.goldRecord, + replace: false, + ); + }, + ), + ], + ), + ), + ], + ), + SizedBox(height: 25.w), + Expanded( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 8.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(8.w), + topRight: Radius.circular(8.w), + ), + ), + child: SingleChildScrollView( + child: Column( + children: [ + SizedBox(height: 8.w), + _hasPersmission + ? Row( + children: [ + SizedBox(width: 5.w), + text( + ATAppLocalizations.of( + context, + )!.selectACountry, + textColor: Colors.black, + fontSize: 13.sp, + ), + Spacer(), + GestureDetector( + child: Container( + alignment: Alignment.center, + width: 65.w, + height: 23.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/general/at_icon_pay_select_country_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + netImage( + url: + _selectedCountry + ?.country + ?.nationalFlag ?? + "", + borderRadius: BorderRadius.all( + Radius.circular(3.w), + ), + width: 19.w, + height: 14.w, + ), + SizedBox(width: 5.w), + Container( + constraints: BoxConstraints( + maxWidth: 32.w, + ), + child: text( + _selectedCountry + ?.country + ?.aliasName ?? + "", + textColor: Colors.white, + fontSize: 9.sp, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + onTap: () { + SmartDialog.show( + tag: "showPaySelectCountryDialog", + alignment: Alignment.bottomCenter, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return PaySelectCountryPage( + _countryList, + _selectedCountry, + (se) { + if (_selectedCountry?.id != + se.id) { + _selectedCountry = se; + setState(() {}); + refreshWebPayCommodity(); + } + }, + ); + }, + ); + }, + ), + SizedBox(width: 10.w), + ], + ) + : Container(), + SizedBox(height: 8.w), + Column( + spacing: 10.w, + children: [ + if (_hasPersmission) + ATDebounceWidget( + child: Stack( + children: [ + Container( + margin: EdgeInsets.symmetric( + horizontal: 8.w, + ), + height: 55.w, + padding: EdgeInsets.symmetric( + horizontal: 8.w, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + 8.w, + ), + color: + _selectedChannelId == "-1" + ? ChatVibeTheme.primaryLight + .withOpacity(0.1) + : Colors.white, + border: + _selectedChannelId == "-1" + ? Border.all( + color: + ChatVibeTheme + .primaryLight, + ) + : Border.all( + color: Colors.white, + ), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity( + 0.2, + ), + spreadRadius: 2, + blurRadius: 3, + offset: Offset( + 0, + 1, + ), // changes position of shadow + ), + ], + ), + child: Row( + children: [ + Image.asset( + "atu_images/general/at_icon_logo.png", + width: 35.w, + height: 35.w, + ), + SizedBox(width: 6.w), + text( + "Aslan", + textColor: Colors.black, + fontSize: 16.sp, + fontWeight: FontWeight.bold, + ), + SizedBox(width: 6.w), + text( + ATAppLocalizations.of( + context, + )!.officialRechargeAgent, + textColor: Colors.black, + fontSize: 13.sp, + ), + ], + ), + ), + PositionedDirectional( + end: 0, + child: Container( + margin: EdgeInsets.symmetric( + horizontal: 8.w, + ), + padding: EdgeInsets.symmetric( + horizontal: 6.w, + vertical: 2.w, + ), + decoration: BoxDecoration( + borderRadius: + BorderRadiusDirectional.only( + topEnd: Radius.circular(8.w), + bottomStart: Radius.circular( + 8.w, + ), + ), + gradient: LinearGradient( + colors: [ + Color(0xffFF3639), + Color(0xffFF787A), + ], + ), + ), + child: text( + ATAppLocalizations.of( + context, + )!.biggestDiscount, + textColor: Colors.white, + fontSize: 12.sp, + ), + ), + ), + ], + ), + onTap: () { + setState(() { + _selectedChannelId = "-1"; + }); + + ATNavigatorUtils.push( + context, + WalletRoute.agentList, + replace: false, + ); + }, + ), + if (_hasPersmission) + Column( + spacing: 10.w, + children: List.generate(_channels.length, ( + index, + ) { + return GestureDetector( + child: Container( + margin: EdgeInsets.symmetric( + horizontal: 8.w, + ), + height: 55.w, + padding: EdgeInsets.symmetric( + horizontal: 12.w, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + 8.w, + ), + color: + _selectedChannelId == + _channels[index] + .channel + ?.id + ? ChatVibeTheme.primaryLight + .withOpacity(0.1) + : Colors.white, + border: + _selectedChannelId == + _channels[index] + .channel + ?.id + ? Border.all( + color: + ChatVibeTheme + .primaryLight, + ) + : Border.all( + color: Colors.white, + ), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity( + 0.2, + ), + spreadRadius: 2, + blurRadius: 3, + offset: Offset( + 0, + 1, + ), // changes position of shadow + ), + ], + ), + child: Row( + children: [ + netImage( + url: + _channels[index] + .channel + ?.channelIcon ?? + "", + width: 35.w, + height: 35.w, + fit: BoxFit.contain, + ), + SizedBox(width: 6.w), + text( + _channels[index] + .channel + ?.channelName ?? + "", + textColor: Colors.black, + fontSize: 16.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + ), + onTap: () { + setState(() { + _selectedChannelId = + _channels[index].channel?.id ?? + ""; + }); + SmartDialog.show( + tag: "showPayPriceItemDialog", + alignment: Alignment.bottomCenter, + debounce: true, + animationType: + SmartAnimationType.fade, + builder: (_) { + return ATPayCustomPriceItemPage( + _channels[index], + _commodity, + ); + }, + ); + }, + ); + }), + ), + if (Platform.isAndroid) + GestureDetector( + child: Container( + margin: EdgeInsets.symmetric( + horizontal: 8.w, + ), + height: 55.w, + padding: EdgeInsets.symmetric( + horizontal: 12.w, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + color: + _selectedChannelId == "-2" + ? ChatVibeTheme.primaryLight + .withOpacity(0.1) + : Colors.white, + border: + _selectedChannelId == "-2" + ? Border.all( + color: + ChatVibeTheme.primaryLight, + ) + : Border.all(color: Colors.white), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.2), + spreadRadius: 2, + blurRadius: 3, + offset: Offset( + 0, + 1, + ), // changes position of shadow + ), + ], + ), + child: Row( + children: [ + Image.asset( + "atu_images/general/at_icon_google_pay.png", + width: 35.w, + height: 35.w, + fit: BoxFit.contain, + ), + SizedBox(width: 6.w), + text( + ATAppLocalizations.of( + context, + )!.googlePay, + textColor: Colors.black, + fontSize: 16.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + ), + onTap: () { + setState(() { + _selectedChannelId = "-2"; + }); + SmartDialog.show( + tag: "showPayPriceItemDialog", + alignment: Alignment.bottomCenter, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return ATPayGoogleApplePriceItemPage( + selectedChannelId: "-2", + ); + }, + ); + }, + ), + if (Platform.isIOS) + GestureDetector( + child: Container( + margin: EdgeInsets.symmetric( + horizontal: 8.w, + ), + height: 55.w, + padding: EdgeInsets.symmetric( + horizontal: 12.w, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + color: + _selectedChannelId == "-3" + ? ChatVibeTheme.primaryLight + .withOpacity(0.1) + : Colors.white, + border: + _selectedChannelId == "-3" + ? Border.all( + color: + ChatVibeTheme.primaryLight, + ) + : Border.all(color: Colors.white), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.2), + spreadRadius: 2, + blurRadius: 3, + offset: Offset( + 0, + 1, + ), // changes position of shadow + ), + ], + ), + child: Row( + children: [ + Image.asset( + "atu_images/general/at_icon_apple_pay.png", + width: 35.w, + height: 35.w, + fit: BoxFit.contain, + ), + SizedBox(width: 6.w), + text( + ATAppLocalizations.of( + context, + )!.appStore, + textColor: Colors.black, + fontSize: 16.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + ), + onTap: () { + setState(() { + _selectedChannelId = "-3"; + }); + SmartDialog.show( + tag: "showPayPriceItemDialog", + alignment: Alignment.bottomCenter, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return ATPayGoogleApplePriceItemPage( + selectedChannelId: "-3", + ); + }, + ); + }, + ), + SizedBox(height: 5.w), + ], + ), + ], + ), + ), + ), + ), + ], + ), + ), + ), + ], + ); + } + + void _loadData() async { + String type = "GOLD"; + if (Provider.of( + context, + listen: false, + ).userIdentity?.freightAgent ?? + false) { + type = "FREIGHT_GOLD"; + } else if (Provider.of( + context, + listen: false, + ).userIdentity?.superFreightAgent ?? + false) { + type = "FREIGHT_GOLD_SUPER"; + } + + try { + ATLoadingManager.exhibitOperation(); + var webPayUserProfileRes = await AccountRepository().webPayUserProfile( + userId: AccountStorage().getCurrentUser()?.userProfile?.id ?? "", + account: AccountStorage().getCurrentUser()?.userProfile?.account ?? "", + type: type, + ); + _countryList = webPayUserProfileRes.countryList ?? []; + _selectedCountry = webPayUserProfileRes.countryList?.first; + String payCountryId = _selectedCountry?.id ?? ""; + _regionId = webPayUserProfileRes.regionId ?? ""; + setState(() {}); + var webPayCommodityRes = await AccountRepository().webPayCommodity( + payCountryId: payCountryId, + type: type, + regionId: webPayUserProfileRes.regionId, + ); + _channels = webPayCommodityRes.channels ?? []; + _commodity = webPayCommodityRes.commodity ?? []; + setState(() {}); + ATLoadingManager.veilRoutine(); + } catch (e) { + ATLoadingManager.veilRoutine(); + } + } + + void refreshWebPayCommodity() async { + ATLoadingManager.exhibitOperation(); + String type = "GOLD"; + if (Provider.of( + context, + listen: false, + ).userIdentity?.freightAgent ?? + false) { + type = "FREIGHT_GOLD"; + } else if (Provider.of( + context, + listen: false, + ).userIdentity?.superFreightAgent ?? + false) { + type = "FREIGHT_GOLD_SUPER"; + } + try { + var webPayCommodityRes = await AccountRepository().webPayCommodity( + payCountryId: _selectedCountry?.id, + type: type, + regionId: _regionId, + ); + _selectedChannelId = ""; + _channels = webPayCommodityRes.channels ?? []; + _commodity = webPayCommodityRes.commodity ?? []; + setState(() {}); + ATLoadingManager.veilRoutine(); + } catch (_) { + ATLoadingManager.veilRoutine(); + } + } +} diff --git a/lib/chatvibe_features/wallet/recharge/contry/pay_select_country_page.dart b/lib/chatvibe_features/wallet/recharge/contry/pay_select_country_page.dart new file mode 100644 index 0000000..cec91d8 --- /dev/null +++ b/lib/chatvibe_features/wallet/recharge/contry/pay_select_country_page.dart @@ -0,0 +1,205 @@ +import 'dart:async'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import '../../../../../../../app_localizations.dart'; +import '../../../../chatvibe_domain/models/res/at_web_pay_user_profile_res.dart'; +import '../../../../chatvibe_ui/components/at_compontent.dart'; +import '../../../../chatvibe_ui/components/chatvibe_gradient_button.dart'; +import '../../../../chatvibe_ui/components/text/at_text.dart'; +import '../../../../chatvibe_ui/theme/chatvibe_theme.dart'; + +class PaySelectCountryPage extends StatefulWidget { + CountryList? selectedCountry; + List countryList = []; + Function(CountryList se)? sCallback; + + PaySelectCountryPage(this.countryList, this.selectedCountry, this.sCallback); + + @override + _PaySelectCountryPageState createState() => _PaySelectCountryPageState(); +} + +class _PaySelectCountryPageState extends State { + final TextEditingController _textEditingController = TextEditingController(); + List _filteredCountryList = []; + CountryList? _selectedCountry; + Timer? _timer; + + @override + void initState() { + super.initState(); + _selectedCountry = widget.selectedCountry; + _filteredCountryList = + widget.countryList + .map((item) => CountryList.fromJson(item.toJson())) + .toList(); + _textEditingController.addListener(() { + _timer?.cancel(); + _timer = Timer(Duration(milliseconds: 500), () { + setState(() { + String searchText = _textEditingController.text.trim().toLowerCase(); + if (searchText.isEmpty) { + _filteredCountryList = widget.countryList; + } else { + _filteredCountryList = + widget.countryList + .where((item) { + String countryName = + item.country?.countryName?.toLowerCase() ?? ''; + String enName = item.country?.enName?.toLowerCase() ?? ''; + String aliasName = + item.country?.aliasName?.toLowerCase() ?? ''; + return countryName.contains(searchText) || + enName.contains(searchText) || + aliasName.contains(searchText); + }) + .map((item) => CountryList.fromJson(item.toJson())) + .toList(); + } + }); + }); + }); + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: Container( + constraints: BoxConstraints( + maxHeight: ScreenUtil().screenHeight * 0.6, + minHeight: ScreenUtil().screenHeight * 0.3, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10), + topRight: Radius.circular(10), + ), + ), + child: Column( + children: [ + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: searchWidget( + hint: ATAppLocalizations.of(context)!.searchCountry, + controller: _textEditingController, + borderColor: Colors.black12, + textColor: Colors.grey, + ), + ), + chatvibeGradientButton( + text: ATAppLocalizations.of(context)!.cancel, + radius: 6.w, + textSize: 14.sp, + textColor: Colors.white, + gradient: LinearGradient( + colors: [Color(0xffFEB219), Color(0xffFF9326)], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + onPress: () { + if (_textEditingController.text.isNotEmpty) { + _textEditingController.clear(); + } else { + SmartDialog.dismiss(tag: "showPaySelectCountryDialog"); + } + }, + ), + SizedBox(width: 10.w), + ], + ), + Expanded( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 15.w, vertical: 10.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.2), + spreadRadius: 2, + blurRadius: 3, + offset: Offset(0, 1), // changes position of shadow + ), + ], + ), + child: + _filteredCountryList.isEmpty + ? mainEmpty(textColor: Colors.grey) + : ListView.separated( + padding: EdgeInsets.symmetric( + horizontal: 15.w, + vertical: 10.w, + ), + shrinkWrap: true, + itemCount: _filteredCountryList.length, + // 列表项数量 + itemBuilder: (context, index) { + return _buildCountryItem( + index, + _filteredCountryList[index], + ); + }, + separatorBuilder: (BuildContext context, int index) { + return SizedBox(height: 10.w); + }, + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildCountryItem(int subIndex, CountryList countryModeList) { + return GestureDetector( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w), + child: Row( + children: [ + netImage( + url: countryModeList.country?.nationalFlag ?? "", + width: 28.w, + height: 19.w, + borderRadius: BorderRadius.circular(2.w), + ), + SizedBox(width: 10.w), + Expanded( + child: text( + countryModeList.country?.countryName ?? "", + fontSize: 14.sp, + textColor: Colors.black, + ), + ), + Image.asset( + _selectedCountry?.country?.id == countryModeList.country?.id + ? "atu_images/general/at_icon_select2.png" + : "atu_images/general/at_icon_unselect2.png", + width: 16.w, + height: 16.w, + ), + ], + ), + ), + onTap: () { + _selectedCountry = countryModeList; + setState(() {}); + widget.sCallback?.call(_selectedCountry!); + SmartDialog.dismiss(tag: "showPaySelectCountryDialog"); + }, + ); + } +} diff --git a/lib/chatvibe_features/wallet/recharge/recharge_page.dart b/lib/chatvibe_features/wallet/recharge/recharge_page.dart new file mode 100644 index 0000000..6d4c8b2 --- /dev/null +++ b/lib/chatvibe_features/wallet/recharge/recharge_page.dart @@ -0,0 +1,511 @@ +import 'dart:io'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:in_app_purchase/in_app_purchase.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_ui/components/appbar/chatvibe_appbar.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_managers/apple_payment_manager.dart'; +import 'package:aslan/chatvibe_managers/google_payment_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_features/wallet/wallet_route.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; + +class RechargePage extends StatefulWidget { + @override + _RechargePageState createState() => _RechargePageState(); +} + +class _RechargePageState extends State { + @override + void initState() { + super.initState(); + Provider.of(context, listen: false).balance(); + if (Platform.isAndroid) { + Provider.of(context, listen: false).init(context); + } else if (Platform.isIOS) { + Provider.of(context, listen: false).init(context); + } + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Image.asset( + ATGlobalConfig.businessLogicStrategy.getRechargePageBackgroundImage(), + width: ScreenUtil().screenWidth, + fit: BoxFit.fill, + ), + Scaffold( + backgroundColor: + ATGlobalConfig.businessLogicStrategy + .getRechargePageScaffoldBackgroundColor(), + resizeToAvoidBottomInset: false, + appBar: ChatVibeStandardAppBar( + title: ATAppLocalizations.of(context)!.recharge, + actions: [ + GestureDetector( + child: Container( + margin: EdgeInsetsDirectional.only(end: 15.w), + child: Image.asset( + ATGlobalConfig.businessLogicStrategy + .getRechargePageRecordIcon(), + width: 24.w, + height: 24.w, + ), + ), + onTap: () { + showGeneralDialog( + context: context, + barrierLabel: '', + barrierDismissible: true, + transitionDuration: Duration(milliseconds: 350), + barrierColor: + ATGlobalConfig.businessLogicStrategy + .getRechargePageDialogBarrierColor(), + pageBuilder: ( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + Navigator.pop(context); + setState(() {}); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + SizedBox(height: height(68)), + Row( + children: [ + Spacer(), + GestureDetector( + child: Container( + decoration: BoxDecoration( + color: + ATGlobalConfig.businessLogicStrategy + .getRechargePageDialogContainerBackgroundColor(), + borderRadius: BorderRadius.circular(4), + ), + padding: EdgeInsets.symmetric( + horizontal: 8.w, + vertical: 3.w, + ), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + SizedBox(height: 5.w), + GestureDetector( + child: text( + ATAppLocalizations.of( + context, + )!.goldListort, + textColor: + ATGlobalConfig + .businessLogicStrategy + .getRechargePageDialogTextColor(), + fontSize: 14.sp, + ), + onTap: () { + Navigator.of(context).pop(); + ATNavigatorUtils.push( + context, + WalletRoute.goldRecord, + replace: false, + ); + }, + ), + // SizedBox(height: 5.w), + // Container( + // color: Colors.black26, + // width: 110.w, + // height: 0.5.w, + // ), + // SizedBox(height: 5.w,), + // text( + // ATAppLocalizations.of( + // context, + // )!.rechargeList, + // textColor: Colors.black38, + // fontSize: 14.sp, + // ), + SizedBox(height: 5.w), + ], + ), + ), + onTap: () {}, + ), + SizedBox(width: 10.w), + ], + ), + Expanded(child: SizedBox(width: width(1))), + ], + ), + ); + }, + ); + }, + ), + ], + ), + body: SafeArea( + top: false, + child: Column( + children: [ + _buildWalletSection(), + SizedBox(height: 20.w), + Expanded( + child: Container( + padding: EdgeInsets.symmetric( + vertical: 20.w, + horizontal: 12.w, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.0), + topRight: Radius.circular(12.0), + ), + color: + ATGlobalConfig.businessLogicStrategy + .getRechargePageMainContainerBackgroundColor(), + ), + child: Column( + children: [ + Expanded( + child: + Platform.isAndroid + ? Consumer( + builder: (context, ref, child) { + return ref.products.isNotEmpty + ? ListView.separated( + itemCount: + ref.products.length, // 列表项数量 + padding: EdgeInsets.only( + bottom: 15.w, + ), + itemBuilder: (context, index) { + return _buildGoogleProductItem( + ref.products[index], + ref, + index, + ); + }, + separatorBuilder: ( + BuildContext context, + int index, + ) { + return SizedBox(height: 10.w); + }, + ) + : mainEmpty(); + }, + ) + : Consumer( + builder: (context, ref, child) { + return ref.products.isNotEmpty + ? ListView.separated( + itemCount: + ref.products.length, // 列表项数量 + padding: EdgeInsets.only( + bottom: 15.w, + ), + itemBuilder: (context, index) { + return _buildAppleProductItem( + ref.products[index], + ref, + index, + ); + }, + separatorBuilder: ( + BuildContext context, + int index, + ) { + return SizedBox(height: 10.w); + }, + ) + : mainEmpty(); + }, + ), + ), + + ///针对ios的恢复购买 + Platform.isIOS + ? ATDebounceWidget( + child: Container( + alignment: Alignment.center, + margin: EdgeInsets.symmetric(horizontal: 60.w), + height: 42.w, + decoration: BoxDecoration( + color: + ATGlobalConfig.businessLogicStrategy + .getRechargePageButtonBackgroundColor(), + borderRadius: BorderRadius.all( + Radius.circular(height(8)), + ), + ), + child: text( + ATAppLocalizations.of( + context, + )!.restorePurchases, + textColor: + ATGlobalConfig.businessLogicStrategy + .getRechargePageButtonTextColor(), + fontSize: 16.sp, + ), + ), + onTap: () { + Provider.of( + context, + listen: false, + ).restorePurchases(); + }, + ) + : Container(), + SizedBox(height: 15.w), + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + margin: EdgeInsets.symmetric(horizontal: 60.w), + height: 42.w, + decoration: BoxDecoration( + color: + ATGlobalConfig.businessLogicStrategy + .getRechargePageButtonBackgroundColor(), + borderRadius: BorderRadius.all( + Radius.circular(height(8)), + ), + ), + child: text( + ATAppLocalizations.of(context)!.recharge, + textColor: + ATGlobalConfig.businessLogicStrategy + .getRechargePageButtonTextColor(), + fontSize: 16.sp, + ), + ), + onTap: () { + if (Platform.isAndroid) { + Provider.of( + context, + listen: false, + ).buyProduct(); + } else if (Platform.isIOS) { + Provider.of( + context, + listen: false, + ).buyProduct(); + } + }, + ), + SizedBox(height: 45.w), + ], + ), + ), + ), + ], + ), + ), + ), + ], + ); + } + + Widget _buildWalletSection() { + return Container( + padding: EdgeInsets.symmetric(vertical: 16.w, horizontal: 20.w), + margin: EdgeInsets.symmetric(horizontal: 15.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + ATGlobalConfig.businessLogicStrategy + .getRechargePageWalletBackgroundImage(), + ), + ), + ), + child: Row( + children: [ + // 左侧装饰图像 + Image.asset( + ATGlobalConfig.businessLogicStrategy.getRechargePageWalletIcon(), + width: 60.w, + height: 60.w, + ), + SizedBox(width: 12.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text( + ATAppLocalizations.of(context)!.wallet, + fontSize: 24.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getRechargePageWalletTextColor(), + fontWeight: FontWeight.w900, + fontStyle: FontStyle.italic, + ), + SizedBox(height: 4.w), + Row( + children: [ + Image.asset( + ATGlobalConfig.businessLogicStrategy + .getRechargePageGoldIcon(), + width: 24.w, + height: 24.w, + ), + SizedBox(width: 3.w), + Consumer( + builder: (context, ref, child) { + return text( + ref.myBalance > 1000 + ? "${(ref.myBalance / 1000).toStringAsFixed(2)}k" + : "${ref.myBalance}", + fontSize: 16.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getRechargePageWalletTextColor(), + fontWeight: FontWeight.bold, + ); + }, + ), + ], + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildGoogleProductItem( + SelecteProductConfig productConfig, + GooglePaymentManager ref, + int index, + ) { + return GestureDetector( + child: Container( + padding: EdgeInsets.symmetric(vertical: 5.w, horizontal: 10.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(99), + border: Border.all( + color: + productConfig.isSelecte + ? ATGlobalConfig.businessLogicStrategy + .getRechargePageSelectedItemBorderColor() + : ATGlobalConfig.businessLogicStrategy + .getRechargePageUnselectedItemBorderColor(), + width: 1.w, + ), + ), + child: Row( + children: [ + Image.asset( + ATGlobalConfig.businessLogicStrategy + .getRechargePageGoldIconByIndex(index), + width: 34.w, + height: 34.w, + ), + SizedBox(width: 5.w), + text( + "${ref.productMap[productConfig.produc.id]?.obtainCandy ?? 0}", + fontSize: 12.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getRechargePageItemTextColor(), + ), + Spacer(), + text( + productConfig.produc.price, + textColor: + ATGlobalConfig.businessLogicStrategy + .getRechargePageItemPriceTextColor(), + fontWeight: FontWeight.w600, + fontSize: 12.sp, + ), + ], + ), + ), + onTap: () { + ref.seleceConfig(index); + }, + ); + } + + Widget _buildAppleProductItem( + SelecteProductConfig productConfig, + ApplePaymentManager ref, + int index, + ) { + return GestureDetector( + child: Container( + padding: EdgeInsets.symmetric(vertical: 5.w, horizontal: 10.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(99), + border: Border.all( + color: + productConfig.isSelecte + ? ATGlobalConfig.businessLogicStrategy + .getRechargePageSelectedItemBorderColor() + : ATGlobalConfig.businessLogicStrategy + .getRechargePageUnselectedItemBorderColor(), + width: 1.w, + ), + ), + child: Row( + children: [ + Image.asset( + ATGlobalConfig.businessLogicStrategy + .getRechargePageGoldIconByIndex(index), + width: 34.w, + height: 34.w, + ), + SizedBox(width: 5.w), + text( + "${ref.productMap[productConfig.produc.id]?.obtainCandy ?? 0}", + fontSize: 12.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getRechargePageItemTextColor(), + ), + Spacer(), + text( + productConfig.produc.price, + textColor: + ATGlobalConfig.businessLogicStrategy + .getRechargePageItemPriceTextColor(), + fontWeight: FontWeight.w600, + fontSize: 12.sp, + ), + ], + ), + ), + onTap: () { + ref.seleceConfig(index); + }, + ); + } +} + +class SelecteProductConfig { + ProductDetails produc; + bool isSelecte = false; + + SelecteProductConfig(this.produc, this.isSelecte); +} diff --git a/lib/chatvibe_features/wallet/wallet_route.dart b/lib/chatvibe_features/wallet/wallet_route.dart new file mode 100644 index 0000000..82479b7 --- /dev/null +++ b/lib/chatvibe_features/wallet/wallet_route.dart @@ -0,0 +1,28 @@ +import 'package:aslan/chatvibe_features/wallet/recharge/agent_list/at_agent_list_page.dart'; +import 'package:aslan/chatvibe_features/wallet/recharge/at_recharge_page_v2.dart'; +import 'package:fluro/fluro.dart'; +import 'package:aslan/chatvibe_features/wallet/gold/gold_record_page.dart'; + +import 'package:aslan/chatvibe_core/routes/at_router_init.dart'; + +class WalletRoute implements ATIRouterProvider { + static String recharge = '/main/me/wallet/recharge'; + static String goldRecord = '/main/me/gold/goldRecord'; + static String agentList = '/main/me/gold/agentList'; + + @override + void initRouter(FluroRouter router) { + router.define( + recharge, + handler: Handler(handlerFunc: (_, params) => ATRechargePageV2()), + ); + router.define( + goldRecord, + handler: Handler(handlerFunc: (_, params) => GoldRecordPage()), + ); + router.define( + agentList, + handler: Handler(handlerFunc: (_, params) => ATAgentListPage()), + ); + } +} diff --git a/lib/chatvibe_features/webview/at_webview_navigation_handler.dart b/lib/chatvibe_features/webview/at_webview_navigation_handler.dart new file mode 100644 index 0000000..bb3c370 --- /dev/null +++ b/lib/chatvibe_features/webview/at_webview_navigation_handler.dart @@ -0,0 +1,203 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +class ATWebViewNavigationHandler { + static const MethodChannel _externalIntentChannel = MethodChannel( + 'com.chat.auu.webview/external_intent', + ); + + static const Set _webSchemes = { + 'about', + 'blob', + 'data', + 'file', + 'http', + 'https', + 'javascript', + }; + + static Future handleNavigationRequest( + NavigationRequest request, + ) async { + final uri = Uri.tryParse(request.url); + if (uri == null) return NavigationDecision.prevent; + + final scheme = uri.scheme.toLowerCase(); + if (scheme.isEmpty || _webSchemes.contains(scheme)) { + return NavigationDecision.navigate; + } + + final launched = await launchExternal(uri); + if (!launched) { + showOpenAppFailedTip(uri); + } + + return NavigationDecision.prevent; + } + + static Future handleWebResourceError(WebResourceError error) async { + final errorUrl = error.url; + if (errorUrl == null || errorUrl.isEmpty) return false; + + final uri = Uri.tryParse(errorUrl); + if (uri == null) return false; + + final scheme = uri.scheme.toLowerCase(); + if (scheme.isEmpty || _webSchemes.contains(scheme)) { + return false; + } + + final launched = await launchExternal(uri); + if (!launched) { + showOpenAppFailedTip(uri); + } + + return launched; + } + + static Future launchExternal(Uri uri) async { + for (final targetUri in [uri, ..._fallbackUris(uri)]) { + if (await _tryLaunch(targetUri)) { + return true; + } + } + + return false; + } + + static Future _tryLaunch(Uri uri) async { + if (await _tryLaunchNative(uri)) { + return true; + } + + if (defaultTargetPlatform == TargetPlatform.android && _isPaymentUri(uri)) { + return false; + } + + try { + return await launchUrl(uri, mode: LaunchMode.externalApplication); + } catch (e) { + debugPrint('Open external app failed: $uri, error: $e'); + return false; + } + } + + static Future _tryLaunchNative(Uri uri) async { + if (defaultTargetPlatform != TargetPlatform.android) { + return false; + } + + try { + final result = await _externalIntentChannel.invokeMethod( + 'openExternalUrl', + uri.toString(), + ); + return result ?? false; + } on MissingPluginException { + return false; + } catch (e) { + debugPrint('Open native external app failed: $uri, error: $e'); + return false; + } + } + + static List _fallbackUris(Uri uri) { + final fallbackUris = []; + final browserFallbackUrl = _intentFallbackUrl(uri); + if (browserFallbackUrl != null) { + fallbackUris.add(browserFallbackUrl); + } + + final upiFallbackUrl = _upiFallbackUrl(uri); + if (upiFallbackUrl != null) { + fallbackUris.add(upiFallbackUrl); + } + + fallbackUris.addAll(_paymentAppSchemeFallbackUrls(upiFallbackUrl ?? uri)); + fallbackUris.addAll(_upiIntentFallbackUrls(upiFallbackUrl ?? uri)); + return fallbackUris; + } + + static Uri? _intentFallbackUrl(Uri uri) { + if (uri.scheme.toLowerCase() != 'intent') return null; + + final fallback = RegExp( + r'S\.browser_fallback_url=([^;]+)', + ).firstMatch(uri.toString())?.group(1); + if (fallback == null || fallback.isEmpty) return null; + + return Uri.tryParse(Uri.decodeComponent(fallback)); + } + + static Uri? _upiFallbackUrl(Uri uri) { + final scheme = uri.scheme.toLowerCase(); + if (scheme != 'phonepe' && + scheme != 'paytmmp' && + scheme != 'bhim' && + scheme != 'bhimupi') { + return null; + } + + final path = uri.path.isNotEmpty ? uri.path : uri.host; + if (!path.split('/').contains('pay')) return null; + + return uri.replace(scheme: 'upi', host: 'pay', path: ''); + } + + static Iterable _paymentAppSchemeFallbackUrls(Uri uri) sync* { + if (uri.scheme.toLowerCase() != 'upi') return; + + final path = uri.path.isNotEmpty ? uri.path : uri.host; + if (path != 'pay') return; + + const schemes = ['phonepe', 'paytmmp', 'bhim']; + for (final scheme in schemes) { + yield uri.replace(scheme: scheme, host: 'pay', path: ''); + } + } + + static Iterable _upiIntentFallbackUrls(Uri uri) sync* { + if (!_isPaymentUri(uri)) return; + + final upiUri = + uri.scheme.toLowerCase() == 'upi' ? uri : _upiFallbackUrl(uri); + if (upiUri == null) return; + + final path = upiUri.path.isNotEmpty ? upiUri.path : upiUri.host; + if (path != 'pay') return; + + const packageNames = [ + 'com.phonepe.app', + 'net.one97.paytm', + 'com.google.android.apps.nbu.paisa.user', + 'in.org.npci.upiapp', + ]; + final query = upiUri.hasQuery ? '?${upiUri.query}' : ''; + for (final packageName in packageNames) { + yield Uri.parse( + 'intent://pay$query#Intent;scheme=upi;package=$packageName;end', + ); + } + } + + static bool _isPaymentUri(Uri uri) { + final scheme = uri.scheme.toLowerCase(); + return scheme == 'phonepe' || + scheme == 'paytmmp' || + scheme == 'bhim' || + scheme == 'bhimupi' || + scheme == 'upi' || + scheme == 'intent'; + } + + static void showOpenAppFailedTip(Uri uri) { + ATTts.show( + _isPaymentUri(uri) + ? 'Please install or set up the payment app first.' + : 'App is not installed.', + ); + } +} diff --git a/lib/chatvibe_features/webview/bridge/at_js_bridge.dart b/lib/chatvibe_features/webview/bridge/at_js_bridge.dart new file mode 100644 index 0000000..59ef06c --- /dev/null +++ b/lib/chatvibe_features/webview/bridge/at_js_bridge.dart @@ -0,0 +1,139 @@ +// js_bridge.dart +import 'dart:async'; +import 'dart:convert'; +import 'package:webview_flutter/webview_flutter.dart'; + +typedef JSBridgeHandler = Future Function(dynamic data); + +class ATJSBridge { + final Map _handlers = {}; + late WebViewController _controller; + final Map> _pendingCalls = {}; + int _callId = 0; + + // 初始化桥梁 + void initialize(WebViewController controller) { + _controller = controller; + } + + // 注册处理方法 + void registerHandler(String name, JSBridgeHandler handler) { + _handlers[name] = handler; + } + + // 移除处理方法 + void removeHandler(String name) { + _handlers.remove(name); + } + + // 调用JS方法 +// 调用JS方法 + Future callHandler(String handlerName, dynamic data) async { + final callId = 'flutter_${_callId++}_${DateTime.now().millisecondsSinceEpoch}'; + final completer = Completer(); + _pendingCalls[callId] = completer; + + final message = { + 'type': 'call', + 'handlerName': handlerName, + 'data': data, + 'callId': callId, + }; + + try { + await _controller.runJavaScript(''' + if (window.__flutterBridge && typeof window.__flutterBridge.receiveMessage === 'function') { + window.__flutterBridge.receiveMessage(${jsonEncode(message)}); + } else if (window.JSBridge) { + window.JSBridge.postMessage(${jsonEncode(message)}); + } else { + console.error('Flutter bridge not initialized'); + } + '''); + } catch (e) { + _pendingCalls.remove(callId); + completer.completeError(e); + } + + // 设置超时 + return completer.future.timeout(const Duration(seconds: 10), onTimeout: () { + _pendingCalls.remove(callId); + throw TimeoutException('JS call timeout'); + }); + } + + // 处理来自JS的消息 + Future handleMessage(String message) async { + try { + final Map data = jsonDecode(message); + final type = data['type'] as String?; + + switch (type) { + case 'call': + await _handleJSCall(data); + break; + case 'response': + await _handleJSResponse(data); + break; + default: + print('Unknown message type: $type'); + } + } catch (e) { + print('Error handling message: $e'); + } + } + + // 处理JS调用的方法 + Future _handleJSCall(Map data) async { + final handlerName = data['handlerName'] as String?; + final callId = data['callId'] as String?; + final messageData = data['data']; + + if (handlerName == null || callId == null) return; + + dynamic result; + String? error; + + try { + final handler = _handlers[handlerName]; + if (handler != null) { + result = await handler(messageData); + } else { + error = 'Handler "$handlerName" not found'; + } + } catch (e) { + error = e.toString(); + } + + // 发送响应回JS + final response = { + 'type': 'response', + 'callId': callId, + 'data': result, + 'error': error, + }; + + await _controller.runJavaScript(''' + if (window.__flutterBridge && window.__flutterBridge.receiveResponse) { + window.__flutterBridge.receiveResponse(${jsonEncode(response)}); + } + '''); + } + + // 处理JS的响应 + Future _handleJSResponse(Map data) async { + final callId = data['callId'] as String?; + if (callId == null) return; + + final completer = _pendingCalls[callId]; + if (completer == null) return; + + if (data['error'] != null) { + completer.completeError(Exception(data['error'])); + } else { + completer.complete(data['data']); + } + + _pendingCalls.remove(callId); + } +} diff --git a/lib/chatvibe_features/webview/game_bs_webview_page.dart b/lib/chatvibe_features/webview/game_bs_webview_page.dart new file mode 100644 index 0000000..9180682 --- /dev/null +++ b/lib/chatvibe_features/webview/game_bs_webview_page.dart @@ -0,0 +1,416 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_get_game_config_data.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_features/wallet/wallet_route.dart'; + +import '../../chatvibe_data/models/enum/at_game_mode_type.dart'; + +class GameBsWebviewPage extends StatefulWidget { + final String url; + + ///安全高度 + final double height; + final double width; + final String gameMode; + final String gameId; + + const GameBsWebviewPage({ + Key? key, + required this.url, + required this.height, + required this.width, + this.gameMode = "", + this.gameId = "", + }) : super(key: key); + + @override + _GameBsWebviewPageState createState() => _GameBsWebviewPageState(); +} + +class _GameBsWebviewPageState extends State { + late WebViewController _controller; + double _progress = 0.0; + bool _isLoading = true; + + final EventChannel _eventChannelPlugin = EventChannel('baishunChannel'); + + @override + void initState() { + super.initState(); + Provider.of(context, listen: false).closeFullGame = false; + // 初始化 WebViewController + _controller = + WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..addJavaScriptChannel( + "baishunChannel", + onMessageReceived: (sjMessage) {}, + ) + ..setNavigationDelegate( + NavigationDelegate( + onProgress: (int progress) { + setState(() { + _progress = progress / 100.0; + }); + }, + onPageStarted: (String url) { + setState(() { + _isLoading = true; + }); + }, + onPageFinished: (String url) { + _injectJavaScriptInterface(); + }, + onWebResourceError: (WebResourceError error) { + _onWebResourceError(error); + }, + ), + ) + ..loadRequest(Uri.parse(widget.url)); + bindChannel(); + _uploadGameRecord(); + } + + @override + void dispose() { + _controller.loadRequest(Uri.parse('about:blank')); + super.dispose(); + } + + void _injectJavaScriptInterface() async {} + + @override + Widget build(BuildContext context) { + return widget.height < 0 + ? SizedBox( + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + child: buidMainContent(), + ) + : Column( + children: [ + widget.gameMode == ATGameModeType.CHAT_ROOM.name + ? Container() + : Row( + children: [ + Spacer(), + ATDebounceWidget( + child: Icon( + Icons.close, + color: + ATGlobalConfig.businessLogicStrategy + .getGameWebViewCloseIconColor(), + size: 25.w, + ), + onTap: () { + Navigator.of(context).pop(); + }, + ), + SizedBox(width: 3.w), + ], + ), + SizedBox(height: 6.w), + AspectRatio( + aspectRatio: (750 / widget.height), + child: buidMainContent(), + ), + ], + ); + } + + Widget buidMainContent() { + return Scaffold( + backgroundColor: + ATGlobalConfig.businessLogicStrategy + .getGameWebViewScaffoldBackgroundColor(), + resizeToAvoidBottomInset: false, + body: SafeArea( + top: false, + child: Stack( + children: [ + Selector( + selector: (c, p) => p.closeFullGame, + shouldRebuild: (prev, next) => prev != next, + builder: (_, closeFullGame, __) { + if (closeFullGame) { + Navigator.of(context).removeRoute(ModalRoute.of(context)!); + } + return Container(); + }, + ), + WebViewWidget(controller: _controller), + + // 加载指示器 + if (_isLoading) + Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + ATGlobalConfig.businessLogicStrategy + .getWebViewPageProgressBarActiveColor(), + ), + ), + ), + ], + ), + ), + ); + } + + Widget _progressBar(double progress, BuildContext context) { + return progress < 1.0 + ? LinearProgressIndicator( + value: progress, + backgroundColor: + ATGlobalConfig.businessLogicStrategy + .getGameWebViewProgressBarBackgroundColor(), + minHeight: 1.0, + valueColor: AlwaysStoppedAnimation( + ATGlobalConfig.businessLogicStrategy + .getWebViewPageProgressBarActiveColor(), + ), + ) + : const SizedBox.shrink(); + } + + void _onWebResourceError(WebResourceError error) { + print('_onWebResourceError:${error.description}'); + setState(() { + _isLoading = false; + }); + // 可以在这里添加错误处理逻辑,比如显示错误页面 + } + + void bindChannel() { + if (Platform.isAndroid) { + _eventChannelPlugin.receiveBroadcastStream().listen((event) { + final obj = json.decode(event); + String jsFunName = obj['jsCallback']; + if (jsFunName.contains('getConfig')) { + print("BSGAME游戏调⽤getConfig main.dart"); + ATGetGameConfigData _configData = ATGetGameConfigData( + appChannel: ATGlobalConfig.gameAppChannel, + appId: ATGlobalConfig.gameAppid, + userId: AccountStorage().getCurrentUser()?.userProfile?.id ?? "", + gameMode: + widget.height < 0 + ? "3" + : (widget.gameMode == ATGameModeType.CHAT_ROOM.name + ? "3" + : "2"), + language: ATGlobalConfig.lang == "ar" ? "7" : "2", + gsp: 101, + roomId: + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.id ?? + "", + gameRoomId: "", + code: AccountStorage().getToken(), + role: + Provider.of(context, listen: false).isFz() + ? "2" + : "0", + gameConfig: GameConfig( + sceneMode: 0, + currencyIcon: + "https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/avatar/b1bc4425-0037-433b-bf00-6977620ff57e.png", + ), + ); + String jsUrl = jsFunName + "(${jsonEncode(_configData.toJson())})"; + _controller!.runJavaScript(jsUrl); + } else if (jsFunName.contains('destroy')) { + print("BSGAME游戏调⽤destroy main.dart"); + //关闭游戏TODO客⼾端 + _controller.loadRequest(Uri.parse('about:blank')); + if (widget.gameMode == ATGameModeType.CHAT_ROOM) { + Provider.of( + context, + listen: false, + ).roomMannyGameClose(); + } else { + Navigator.of(context).pop(); + } + super.dispose(); + } else if (jsFunName.contains('gameRecharge')) { + print("BSGAME游戏调⽤gameRecharge main.dart"); + //拉起⽀付商城TODO客⼾端 + _showGameRecharge(); + } else if (jsFunName.contains('gameLoaded')) { + print("BSGAME游戏调⽤gameLoaded main.dart"); + setState(() { + _isLoading = false; + }); + //游戏加载完毕TODO客⼾端 + } else if (jsFunName.contains('sendGameAction')) { + print("BSGAME游戏调⽤sendGameAction main.dart--${obj}"); + String str = sendGameActionUtil(obj); + if (str.isNotEmpty) { + _controller.runJavaScript(str); + } + } + }); + } else { + _controller.addJavaScriptChannel( + "getConfig", + onMessageReceived: (JavaScriptMessage jsonMessage) { + print("BSGAME 游戏调⽤getConfig main.dart ios"); + final obj = json.decode(jsonMessage.message); + String jsFunName = obj['jsCallback']; + ATGetGameConfigData _configData = ATGetGameConfigData( + appChannel: ATGlobalConfig.gameAppChannel, + appId: ATGlobalConfig.gameAppid, + userId: AccountStorage().getCurrentUser()?.userProfile?.id ?? "", + gameMode: widget.height < 0 ? "3" : "2", + language: ATGlobalConfig.lang == "ar" ? "7" : "2", + gsp: 101, + roomId: + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.id ?? + "", + code: AccountStorage().getToken(), + gameConfig: GameConfig( + sceneMode: 0, + currencyIcon: + "https://tkm-atu.oss-ap-southeast-1.aliyuncs.com/avatar/b1bc4425-0037-433b-bf00-6977620ff57e.png", + ), + ); + String jsUrl = jsFunName + "(${jsonEncode(_configData.toJson())})"; + _controller!.runJavaScript(jsUrl); + }, + ); + _controller.addJavaScriptChannel( + "destroy", + onMessageReceived: (JavaScriptMessage message) { + print("BSGAME 游戏调⽤destroy main.dart ios"); + _controller.loadRequest(Uri.parse('about:blank')); + if (widget.gameMode == ATGameModeType.CHAT_ROOM) { + Provider.of( + context, + listen: false, + ).roomMannyGameClose(); + } else { + Navigator.of(context).pop(); + } + super.dispose(); + }, + ); + _controller.addJavaScriptChannel( + "gameRecharge", + onMessageReceived: (JavaScriptMessage message) { + print("BSGAME 游戏调⽤gameRecharge main.dart ios"); + _showGameRecharge(); + }, + ); + _controller.addJavaScriptChannel( + "gameLoaded", + onMessageReceived: (JavaScriptMessage message) { + setState(() { + _isLoading = false; + }); + print("BSGAME 游戏调⽤gameLoaded main.dart ios"); + }, + ); + _controller.addJavaScriptChannel( + "sendGameAction", + onMessageReceived: (JavaScriptMessage message) { + String str = sendGameActionUtil(message.message); + if (str.isNotEmpty) { + _controller.runJavaScript(str); + } + }, + ); + } + } + + void _showGameRecharge() { + SmartDialog.dismiss(tag: "showConfirmDialog"); + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.balanceNotEnough, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + SmartDialog.dismiss(tag: "showConfirmDialog"); + ATNavigatorUtils.push( + context, + WalletRoute.recharge, + replace: false, + ); + }, + ); + }, + ); + } + + String sendGameActionUtil(message) { + String result = ''; + int type = message['type']; + Map params = message['params']; + switch (type) { + case 13: //游戏开始 + break; + case 14: //游戏结束 + break; + case 15: //上下座 + int optType = params['optType']; //0 上座,1 下座 + String userId = params['userId']; //用户 id + int seat = params['seat']; ////座位位置 + //如果不鉴权就直接把下面的消息返回 + final Map map = {}; + map["optType"] = optType; + map["userId"] = userId; + map["seat"] = seat; + final Map parentMap = {}; + parentMap["type"] = 4; // 使用下标操作符添加数据 + parentMap["params"] = map; + result = "gameActionUpdate(${json.encode(parentMap)})"; + break; + case 17: //踢人 + String userId = params['userId']; + int seat = params['seat']; + + //如果不鉴权就直接把下面的消息返回 + final Map map = {}; + map["optResult"] = 0; + map["optUserId"] = AccountStorage().getCurrentUser()?.userProfile?.id; + map["userId"] = userId; + map["reason"] = seat; + final Map parentMap = {}; + parentMap["type"] = 6; // 使用下标操作符添加数据 + parentMap["params"] = map; + result = "gameActionUpdate(${json.encode(parentMap)})"; + break; + } + return result; + } + + void _uploadGameRecord() { + ChatRoomRepository().ludoHistoryRecord( + widget.gameId, + widget.height > 0 ? "IN_ROOM" : "OUT_ROOM", + ); + } +} diff --git a/lib/chatvibe_features/webview/game_lx_webview_page.dart b/lib/chatvibe_features/webview/game_lx_webview_page.dart new file mode 100644 index 0000000..c5dd2c1 --- /dev/null +++ b/lib/chatvibe_features/webview/game_lx_webview_page.dart @@ -0,0 +1,188 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_features/wallet/wallet_route.dart'; + +class GameLxWebviewPage extends StatefulWidget { + final String url; + + ///安全高度 + final double height; + final double width; + final String gameId; + + const GameLxWebviewPage({ + Key? key, + required this.url, + required this.height, + required this.width, + this.gameId = "", + }) : super(key: key); + + @override + _GameLxWebviewPageState createState() => _GameLxWebviewPageState(); +} + +class _GameLxWebviewPageState extends State { + late WebViewController _controller; + bool _isLoading = true; + String gameLink = ""; + String gameUrl = ""; + + @override + void initState() { + super.initState(); + Provider.of(context, listen: false).closeFullGame = false; + if (widget.url.split("?").length > 1) { + gameLink = "${widget.url}&"; + } else { + gameLink = "${widget.url}?"; + } + gameUrl = + "${gameLink}uid=${AccountStorage().getCurrentUser()?.userProfile?.id}&token=${AccountStorage().token}&lang=${ATGlobalConfig.lang}&time=${DateTime.now().millisecondsSinceEpoch}"; + _initializeWebViewController(); + _uploadGameRecord(); + } + + @override + void dispose() { + _controller.loadRequest(Uri.parse('about:blank')); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return widget.height < 0 + ? SizedBox( + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + child: buidMainContent(), + ) + : Column( + children: [ + Row( + children: [ + Spacer(), + ATDebounceWidget( + child: Icon(Icons.close, color: ATGlobalConfig.businessLogicStrategy.getGameWebViewCloseIconColor(), size: 25.w), + onTap: () { + Navigator.of(context).pop(); + }, + ), + SizedBox(width: 3.w), + ], + ), + SizedBox(height: 6.w), + AspectRatio( + aspectRatio: (750 / widget.height), + child: buidMainContent(), + ), + ], + ); + } + + Widget buidMainContent() { + return Scaffold( + backgroundColor: ATGlobalConfig.businessLogicStrategy.getGameWebViewScaffoldBackgroundColor(), + body: SafeArea( + top: false, + child: Stack( + children: [ + Selector( + selector: (c, p) => p.closeFullGame, + shouldRebuild: (prev, next) => prev != next, + builder: (_, closeFullGame, __) { + if (closeFullGame) { + Navigator.of(context).removeRoute(ModalRoute.of(context)!); + } + return Container(); + }, + ), + WebViewWidget(controller: _controller), + // 加载指示器 + if (_isLoading) + Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(ATGlobalConfig.businessLogicStrategy.getGameWebViewLoadingIndicatorColor()), + ), + ), + ], + ), + ), + ); + } + + void _onWebResourceError(WebResourceError error) { + print('_onWebResourceError:${error.description}'); + // 可以在这里添加错误处理逻辑,比如显示错误页面 + } + + void _initializeWebViewController() { + _controller = + WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..loadRequest(Uri.parse(gameUrl)) // 游戏网址 + ..addJavaScriptChannel( + 'pay', + onMessageReceived: (message) { + debugPrint('pay callback: ${message.message}'); + // 实际支付处理逻辑 + ATNavigatorUtils.push( + context, + WalletRoute.recharge, + replace: false, + ); + }, + ) + ..addJavaScriptChannel( + 'closeGame', + onMessageReceived: (message) { + debugPrint('closeGame callback'); + Navigator.of(context).pop(); + }, + ) + ..addJavaScriptChannel( + 'loadComplete', + onMessageReceived: (message) { + debugPrint('game load complete'); + setState(() { + _isLoading = false; + }); + }, + ) + ..setNavigationDelegate( + NavigationDelegate( + onPageFinished: (url) { + setState(() { + _isLoading = false; + }); + }, + onWebResourceError: (WebResourceError error) { + _onWebResourceError(error); + }, + ), + ); + } + + /// 调用游戏币更新 + void updateCoin() { + _controller.runJavaScript('updateCoin();'); + } + + void _uploadGameRecord() { + ChatRoomRepository().ludoHistoryRecord( + widget.gameId, + widget.height > 0 ? "IN_ROOM" : "OUT_ROOM", + ); + } +} diff --git a/lib/chatvibe_features/webview/game_ry_webview_page.dart b/lib/chatvibe_features/webview/game_ry_webview_page.dart new file mode 100644 index 0000000..bd2c335 --- /dev/null +++ b/lib/chatvibe_features/webview/game_ry_webview_page.dart @@ -0,0 +1,228 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_features/wallet/wallet_route.dart'; +import 'package:aslan/chatvibe_features/webview/bridge/at_js_bridge.dart'; + +class GameRyWebviewPage extends StatefulWidget { + final String url; + + ///安全高度 + final double height; + final double width; + final String gameId; + + const GameRyWebviewPage({ + Key? key, + required this.url, + required this.height, + required this.width, + this.gameId = "", + }) : super(key: key); + + @override + _GameRyWebviewPageState createState() => _GameRyWebviewPageState(); +} + +class _GameRyWebviewPageState extends State { + late WebViewController _controller; + bool _isLoading = true; + String gameLink = ""; + String gameUrl = ""; + late final ATJSBridge _bridge; + + @override + void initState() { + super.initState(); + Provider.of(context, listen: false).closeFullGame = false; + _bridge = ATJSBridge(); + if (widget.url.split("?").length > 1) { + gameLink = "${widget.url}&"; + } else { + gameLink = "${widget.url}?"; + } + + gameUrl = + "${gameLink}sdk=atu&uid=${AccountStorage().getCurrentUser()?.userProfile?.id}&token=${AccountStorage().token}&lang=${ATGlobalConfig.lang}"; + _initializeWebViewController(); + _bridge.initialize(_controller); + _uploadGameRecord(); + } + + @override + void dispose() { + _controller.loadRequest(Uri.parse('about:blank')); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return widget.height < 0 + ? SizedBox( + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + child: buidMainContent(), + ) + : Column( + children: [ + Row( + children: [ + Spacer(), + ATDebounceWidget( + child: Icon(Icons.close, color: ATGlobalConfig.businessLogicStrategy.getGameWebViewCloseIconColor(), size: 25.w), + onTap: () { + Navigator.of(context).pop(); + }, + ), + SizedBox(width: 3.w), + ], + ), + SizedBox(height: 6.w), + AspectRatio( + aspectRatio: (750 / widget.height), + child: buidMainContent(), + ), + ], + ); + } + + Widget buidMainContent() { + return Scaffold( + backgroundColor: ATGlobalConfig.businessLogicStrategy.getGameWebViewScaffoldBackgroundColor(), + body: SafeArea( + top: false, + child: Stack( + children: [ + Selector( + selector: (c, p) => p.closeFullGame, + shouldRebuild: (prev, next) => prev != next, + builder: (_, closeFullGame, __) { + if (closeFullGame) { + Navigator.of(context).removeRoute(ModalRoute.of(context)!); + } + return Container(); + }, + ), + WebViewWidget(controller: _controller), + // 加载指示器 + if (_isLoading) + Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(ATGlobalConfig.businessLogicStrategy.getGameWebViewLoadingIndicatorColor()), + ), + ), + ], + ), + ), + ); + } + + void _onWebResourceError(WebResourceError error) { + print('_onWebResourceError:${error.description}'); + // 可以在这里添加错误处理逻辑,比如显示错误页面 + } + + void _initializeWebViewController() { + _controller = + WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate( + onPageStarted: (url) { + setState(() => _isLoading = true); + }, + onPageFinished: (url) { + setState(() => _isLoading = false); + _injectBridgeScript(); + }, + onWebResourceError: (error) { + print('Web资源错误: ${error.description}'); + _onWebResourceError(error); + }, + ), + ) + ..addJavaScriptChannel( + 'ATJSBridge', + onMessageReceived: (JavaScriptMessage message) { + _bridge.handleMessage(message.message); + }, + ); + + _bridge.initialize(_controller); + + // 注册quit和recharge方法 + _registerHandlers(); + + // 加载URL + _controller.loadRequest(Uri.parse(gameUrl)); + } + + void _registerHandlers() { + // 注册quit方法 + _bridge.registerHandler('quit', (data) async { + Navigator.of(context).pop(); + }); + + // 注册recharge方法 + _bridge.registerHandler('recharge', (data) async { + ATNavigatorUtils.push(context, WalletRoute.recharge, replace: false); + }); + } + + void _injectBridgeScript() { + _controller.runJavaScript(''' + // 确保window.quit是一个对象,并有postMessage方法 + window.quit = window.quit || {}; + + window.quit.postMessage = function(message) { + // 构造发送给Flutter的消息 + const payload = { + type: 'call', + handlerName: 'quit', + data: message, + callId: 'web_' + Date.now() + }; + + // 通过JavaScriptChannel发送消息 + if (window.JSBridge) { + window.JSBridge.postMessage(JSON.stringify(payload)); + } + }; + + window.recharge = window.recharge || {}; + + window.recharge.postMessage = function(message) { + // 构造发送给Flutter的消息 + const payload = { + type: 'call', + handlerName: 'recharge', + data: message, + callId: 'web_' + Date.now() + }; + + // 通过JavaScriptChannel发送消息 + if (window.JSBridge) { + window.JSBridge.postMessage(JSON.stringify(payload)); + } + }; + + console.log('Flutter JS Bridge 初始化完成 - 支持window.quit.postMessage'); + '''); + } + + void _uploadGameRecord() { + ChatRoomRepository().ludoHistoryRecord( + widget.gameId, + widget.height > 0 ? "IN_ROOM" : "OUT_ROOM", + ); + } +} diff --git a/lib/chatvibe_features/webview/game_ym_webview_page.dart b/lib/chatvibe_features/webview/game_ym_webview_page.dart new file mode 100644 index 0000000..6a188bf --- /dev/null +++ b/lib/chatvibe_features/webview/game_ym_webview_page.dart @@ -0,0 +1,180 @@ +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_features/wallet/wallet_route.dart'; + +class GameYmWebviewPage extends StatefulWidget { + final String url; + + ///安全高度 + final double height; + final double width; + final String gameId; + + const GameYmWebviewPage({ + Key? key, + required this.url, + required this.height, + required this.width, + this.gameId = "", + }) : super(key: key); + + @override + _GameYmWebviewPageState createState() => _GameYmWebviewPageState(); +} + +class _GameYmWebviewPageState extends State { + late WebViewController _controller; + bool _isLoading = true; + String gameLink = ""; + String gameUrl = ""; + + @override + void initState() { + super.initState(); + Provider.of(context, listen: false).closeFullGame = false; + if (widget.url.split("?").length > 1) { + gameLink = "${widget.url}&"; + } else { + gameLink = "${widget.url}?"; + } + gameUrl = + "${gameLink}platUserId=${AccountStorage().getCurrentUser()?.userProfile?.id}&platAuthCode=${AccountStorage().token}&lang=${ATGlobalConfig.lang}&time=${DateTime.now().millisecondsSinceEpoch}"; + _initializeWebViewController(); + _uploadGameRecord(); + } + + @override + void dispose() { + _controller.loadRequest(Uri.parse('about:blank')); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return widget.height < 0 + ? SizedBox( + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + child: buidMainContent(), + ) + : Column( + children: [ + Row( + children: [ + Spacer(), + ATDebounceWidget( + child: Icon(Icons.close, color: ATGlobalConfig.businessLogicStrategy.getGameWebViewCloseIconColor(), size: 25.w), + onTap: () { + Navigator.of(context).pop(); + }, + ), + SizedBox(width: 3.w), + ], + ), + SizedBox(height: 6.w), + AspectRatio( + aspectRatio: (750 / widget.height), + child: buidMainContent(), + ), + ], + ); + } + + Widget buidMainContent() { + return Scaffold( + backgroundColor: ATGlobalConfig.businessLogicStrategy.getGameWebViewScaffoldBackgroundColor(), + body: SafeArea( + top: false, + child: Stack( + children: [ + Selector( + selector: (c, p) => p.closeFullGame, + shouldRebuild: (prev, next) => prev != next, + builder: (_, closeFullGame, __) { + if (closeFullGame) { + Navigator.of(context).removeRoute(ModalRoute.of(context)!); + } + return Container(); + }, + ), + WebViewWidget(controller: _controller), + // 加载指示器 + if (_isLoading) + Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(ATGlobalConfig.businessLogicStrategy.getGameWebViewLoadingIndicatorColor()), + ), + ), + ], + ), + ), + ); + } + + void _onWebResourceError(WebResourceError error) { + print('_onWebResourceError:${error.description}'); + // 可以在这里添加错误处理逻辑,比如显示错误页面 + } + + void _initializeWebViewController() { + _controller = + WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..loadRequest(Uri.parse(gameUrl)) // 游戏网址 + ..addJavaScriptChannel( + 'yomi', + onMessageReceived: (message) { + var json = jsonDecode(message.message); + String type = json["type"] ?? ""; + if (type == 'closeGame') { + Navigator.of(context).pop(); + }else if (type == 'hideSplash') { + + } else if (type == 'insufficient') { + ATNavigatorUtils.push( + context, + WalletRoute.recharge, + replace: false, + ); + } + }, + ) + ..setNavigationDelegate( + NavigationDelegate( + onPageFinished: (url) { + setState(() { + _isLoading = false; + }); + }, + onWebResourceError: (WebResourceError error) { + _onWebResourceError(error); + }, + ), + ); + } + + /// 调用游戏币更新 + void updateCoin() { + _controller.runJavaScript('updateCoin();'); + } + + void _uploadGameRecord() { + ChatRoomRepository().ludoHistoryRecord( + widget.gameId, + widget.height > 0 ? "IN_ROOM" : "OUT_ROOM", + ); + } +} diff --git a/lib/chatvibe_features/webview/webview_page.dart b/lib/chatvibe_features/webview/webview_page.dart new file mode 100644 index 0000000..5c2aaa1 --- /dev/null +++ b/lib/chatvibe_features/webview/webview_page.dart @@ -0,0 +1,331 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_core/utilities/at_pick_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:tencent_cloud_chat_sdk/enum/conversation_type.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_deviceId_utils.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/main.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/share/share_page.dart'; +import 'package:aslan/chatvibe_features/chat/at_chat_route.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_features/webview/at_webview_navigation_handler.dart'; + +class WebViewPage extends StatefulWidget { + final String title; + final String url; + final String showTitle; + + const WebViewPage({ + Key? key, + this.title = "", + this.showTitle = "true", + required this.url, + }) : super(key: key); + + @override + _WebViewPageState createState() => _WebViewPageState(); +} + +class _WebViewPageState extends State { + late WebViewController _controller; + String _title = ""; + double _progress = 0.0; + bool _isLoading = true; + String urlLink = ""; + + @override + void initState() { + super.initState(); + if (widget.url.split("?").length > 1) { + urlLink = "${widget.url}&"; + } else { + urlLink = "${widget.url}?"; + } + urlLink = "${urlLink}lang=${ATGlobalConfig.lang}"; + // 初始化 WebViewController + _controller = + WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..addJavaScriptChannel( + "FlutterPageControl", + onMessageReceived: (sjMessage) async { + String msg = sjMessage.message; + if (sjMessage.message == "close_page") { + ATNavigatorUtils.goBack(context); + } else if (msg.startsWith("view_user_info")) { + //跳转个人信息 + if (msg.contains(":")) { + var sli = msg.split(":"); + if (sli.length > 1) { + String userId = sli[1]; + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == userId}&tageId=$userId", + ); + } + } + } else if (msg.startsWith("go_to_room")) { + //跳转房间 + if (msg.contains(":")) { + var sli = msg.split(":"); + if (sli.length > 1) { + String roomId = sli[1]; + ATRoomUtils.goRoom( + roomId, + navigatorKey.currentState!.context, + ); + } + } + } else if (msg.startsWith("private_chat")) { + //打开私聊 + if (msg.contains(":")) { + var sli = msg.split(":"); + if (sli.length > 1) { + String userId = sli[1]; + var conversation = V2TimConversation( + type: ConversationType.V2TIM_C2C, + userID: userId, + conversationID: '', + ); + var bool = await Provider.of( + context, + listen: false, + ).startConversation(conversation); + if (!bool) return; + var json = jsonEncode(conversation.toJson()); + ATNavigatorUtils.push( + context, + "${ATChatRouter.chat}?conversation=${Uri.encodeComponent(json)}", + ); + } + } + } else if (msg.startsWith("uploadImgFile")) { + picImage(); + } else if (msg.startsWith("editingRoom")) { + ATNavigatorUtils.push(context, MainRoute.editRoomSearchAdmin); + } else if (msg.startsWith("editingUser")) { + ATNavigatorUtils.push(context, MainRoute.editUserSearchAdmin); + } else if (msg.startsWith("myCoupleList")) { + ATNavigatorUtils.push(context, MainRoute.cpList); + } else if (msg.startsWith("shareDrawer")) { + if (msg.contains(":")) { + var sli = msg.split(":"); + if (sli.length > 1) { + String shareMsg = sli[1]; + _showShareDialog(shareMsg); + } + } + } + }, + ) + ..setNavigationDelegate( + NavigationDelegate( + onProgress: (int progress) { + setState(() { + _progress = progress / 100.0; + }); + }, + onNavigationRequest: + ATWebViewNavigationHandler.handleNavigationRequest, + onPageStarted: (String url) { + setState(() { + _isLoading = true; + }); + }, + onPageFinished: (String url) { + // 延迟500毫秒注入,等待Vue3初始化 + Future.delayed(Duration(milliseconds: 550), () { + _injectJavaScriptInterface(); + }); + setState(() { + _isLoading = false; + _getPageTitle(); + }); + }, + onWebResourceError: (WebResourceError error) { + _onWebResourceError(error); + }, + ), + ) + ..loadRequest(Uri.parse(urlLink)); + } + + @override + void dispose() { + _controller.loadRequest(Uri.parse('about:blank')); + super.dispose(); + } + + void _injectJavaScriptInterface() async { + String imei = await ATDeviceIdUtils.collectDeviceId(); + // 注入JavaScript对象和方法 + _controller.runJavaScript(''' + window.app = window.app || {}; + // 注入获取访问信息的方法 + window.app.getAccessOrigin = function() { + // 获取认证信息(这里可以从Flutter传递) + return JSON.stringify({ + 'Authorization': 'Bearer ${AccountStorage().token}', + 'Req-Lang': '${ATGlobalConfig.lang}', + 'Req-App-Intel': 'build=${ATGlobalConfig.build};version=${ATGlobalConfig.version};model=${ATGlobalConfig.model};channel=${ATGlobalConfig.channel};Req-Imei=$imei', + 'Req-Sys-Origin': 'origin=${ATGlobalConfig.origin};originChild=${ATGlobalConfig.originChild}' + }); + }; + + // 注入其他可能需要的方法 + window.app.getAuth = function() { + return window.app.getAccessOrigin(); + }; + + // 可以通过这个方法向Flutter发送消息 + window.app.sendToFlutter = function(data) { + FlutterApp.postMessage(data); + }; + '''); + } + + void picImage() async { + ATPickUtils.pickImageProcess(context, (b, path) { + _controller.runJavaScript(''' + window.app = window.app || {}; + // 注入获取访问信息的方法 + window.app.getImagePath = function() { + // 获取认证信息(这里可以从Flutter传递) + return JSON.stringify({ + 'path': '$path', + }); + }; + + // 注入其他可能需要的方法 + window.app.getAuth = function() { + return window.app.getImagePath(); + }; + + // 可以通过这个方法向Flutter发送消息 + window.app.sendToFlutter = function(data) { + FlutterApp.postMessage(data); + }; + '''); + }, neeCrop: false); + } + + // 获取页面标题 + Future _getPageTitle() async { + try { + final title = + await _controller.runJavaScriptReturningResult("document.title") + as String?; + if (title != null) { + setState(() { + _title = title.replaceAll("\"", ""); + }); + } + } catch (e) { + print("获取标题失败: $e"); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: + widget.showTitle == "true" + ? AppBar( + elevation: 0, + backgroundColor: + ATGlobalConfig.businessLogicStrategy + .getWebViewPageAppBarBackgroundColor(), + centerTitle: true, + title: Text( + _title.isNotEmpty ? _title : widget.title, + style: TextStyle( + fontSize: sp(18), + color: + ATGlobalConfig.businessLogicStrategy + .getWebViewPageTitleTextColor(), + ), + ), + leading: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + ATNavigatorUtils.goBack(context); + }, + child: Container( + padding: EdgeInsets.only(left: width(15), right: width(15)), + child: Center( + child: Icon( + Icons.arrow_back_ios, + size: 20.w, + color: + ATGlobalConfig.businessLogicStrategy + .getWebViewPageBackArrowColor(), + ), + ), + ), + ), + systemOverlayStyle: SystemUiOverlayStyle.dark, + ) + : null, + body: SafeArea( + top: false, + child: Stack( + children: [ + WebViewWidget(controller: _controller), + + // 加载指示器 + // if (_isLoading) + // Center( + // child: CircularProgressIndicator( + // valueColor: AlwaysStoppedAnimation(Color(0xffFF6000)), + // ), + // ), + ], + ), + ), + ); + } + + Widget _progressBar(double progress, BuildContext context) { + return progress < 1.0 + ? LinearProgressIndicator( + value: progress, + backgroundColor: + ATGlobalConfig.businessLogicStrategy + .getWebViewPageProgressBarBackgroundColor(), + minHeight: 1.0, + valueColor: AlwaysStoppedAnimation( + ATGlobalConfig.businessLogicStrategy + .getWebViewPageProgressBarActiveColor(), + ), + ) + : const SizedBox.shrink(); + } + + void _onWebResourceError(WebResourceError error) { + print('_onWebResourceError:${error.description}, url:${error.url}'); + ATWebViewNavigationHandler.handleWebResourceError(error); + } + + void _showShareDialog(String shareMsg) { + SmartDialog.show( + tag: "showShareDialog", + alignment: Alignment.bottomCenter, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return SharePage(shareMsg); + }, + ); + } +} diff --git a/lib/chatvibe_managers/app_general_manager.dart b/lib/chatvibe_managers/app_general_manager.dart new file mode 100644 index 0000000..820b117 --- /dev/null +++ b/lib/chatvibe_managers/app_general_manager.dart @@ -0,0 +1,277 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svga/flutter_svga.dart'; +import 'package:aslan/chatvibe_data/sources/local/file_cache_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/config_repository_imp.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_core/utilities/at_path_utils.dart'; +import 'package:aslan/chatvibe_core/utilities/at_gift_vap_svga_manager.dart'; +import 'package:aslan/chatvibe_data/models/country_mode.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_banner_leaderboard_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/country_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/gift_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_index_banner_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_emoji_res.dart'; + +import '../chatvibe_data/models/enum/at_banner_type.dart'; +import '../chatvibe_domain/models/res/room_res.dart'; + +class AppGeneralManager extends ChangeNotifier { + List countryModeList = []; + Map> _countryMap = {}; + Map _countryByNameMap = {}; + + ///礼物 + List giftResList = []; + Map _giftByIdMap = {}; + + Map> giftByTab = {}; + Map> activityGiftByTab = {}; + + ///选中的国家 + Country? selectCountryInfo; + + List activityList = []; + + AppGeneralManager() { + loadCountry(); + giftList(); // 使用默认参数 + giftActivityList(); + discoveryRoom(); + // giftBackpack(); + // configLevel(); + } + + ///加载国家列表 + void loadCountry() async { + if (countryModeList.isNotEmpty) { + resetCountryState(); + notifyListeners(); + return; + } + while (countryModeList.isEmpty) { + await Future.delayed(Duration(milliseconds: 550)); + var result = await ConfigRepositoryImp().loadCountry(); + if (result.openCountry!.isNotEmpty) { + result.openCountry?.forEach((c) { + String prefix = c.aliasName?.substring(0, 1) ?? ""; + if (prefix.isNotEmpty) { + List countryList = _countryMap[prefix] ?? []; + countryList.add(c.copyWith()); + _countryMap[prefix] = countryList; + } + _countryByNameMap[c.countryName!] = c; + }); + } + _countryMap.forEach((k, v) { + countryModeList.add(CountryMode(k, v)); + }); + + ///排序 + countryModeList.sort((a, b) => a.prefix.compareTo(b.prefix)); + notifyListeners(); + } + } + + ///国家国家名称获取国家 + Country? getCountryByName(String countryName) { + return _countryByNameMap[countryName]; + } + + /// 只暴露国旗地址给 Selector 使用,避免 AppGeneralManager 其它数据更新时刷新整块 UI。 + String getCountryFlagByName(String? countryName) { + if (countryName == null || countryName.isEmpty) return ""; + return _countryByNameMap[countryName]?.nationalFlag ?? ""; + } + + ///选择国家 + Country? selectCountry(int subIndex, int cIndex) { + var selectCm = countryModeList[subIndex].prefixCountrys[cIndex]; + if (selectCountryInfo != null) { + selectCountryInfo = null; + } else { + selectCountryInfo = selectCm; + } + return selectCountryInfo; + } + + void setCurrentCountry(Country? countryInfo) { + selectCountryInfo = countryInfo; + notifyListeners(); + } + + void resetCountryState() { + selectCountryInfo = null; + } + + void openDrawer(BuildContext context) { + Scaffold.of(context).openDrawer(); + } + + void closeDrawer(BuildContext context) { + Scaffold.of(context).closeDrawer(); + } + + void giftList({bool includeCustomized = true}) async { + try { + giftResList = await ChatRoomRepository().giftList(); + giftByTab.clear(); + for (var gift in giftResList) { + giftByTab[gift.giftTab]; + var gmap = giftByTab[gift.giftTab]; + gmap ??= []; + gmap.add(gift); + giftByTab[gift.giftTab!] = gmap; + var gAllMap = giftByTab["ALL"]; + gAllMap ??= []; + if (gift.giftTab != "NATIONAL_FLAG" && + gift.giftTab != "ACTIVITY" && + gift.giftTab != "LUCKY_GIFT" && + gift.giftTab != "CP" && + gift.giftTab != "CUSTOMIZED" && + gift.giftTab != "MAGIC") { + gAllMap.add(gift); + } + giftByTab["ALL"] = gAllMap; + _giftByIdMap[gift.id!] = gift; + } + + if (includeCustomized) { + giftByTab["CUSTOMIZED"] ??= []; + + ///定制礼物需要一个占位符,显示规则的 + giftByTab["CUSTOMIZED"]?.insert(0, ChatVibeGiftRes(id: "-1000")); + } else { + giftByTab.remove("CUSTOMIZED"); + } + + notifyListeners(); + + ///先去预下载 + downLoad(giftResList); + } catch (e) {} + } + + void giftActivityList() async { + try { + activityList.clear(); + activityList = await ChatRoomRepository().giftActivityList(); + activityGiftByTab.clear(); + for (var gift in activityList) { + var gmap = activityGiftByTab["${gift.activityId}"]; + gmap ??= []; + gmap.add(gift); + activityGiftByTab["${gift.activityId}"] = gmap; + } + notifyListeners(); + } catch (e) {} + } + + ///礼物背包 + void giftBackpack() async { + try { + await ChatRoomRepository().giftBackpack(); + } catch (e) {} + } + + ///礼物id获取礼物 + ChatVibeGiftRes? getGiftById(String id) { + return _giftByIdMap[id]; + } + + void downLoad(List giftResList) { + for (var gift in giftResList) { + if (gift.giftSourceUrl != null) { + if (ATPathUtils.fetchFileExtensionFunction( + gift.giftSourceUrl!, + ).toLowerCase() == + ".svga") { + ///预解析 + if (!ATGiftVapSvgaManager().videoItemCache.containsKey( + gift.giftSourceUrl, + )) { + SVGAParser.shared.decodeFromURL(gift.giftSourceUrl!).then((entity) { + entity.autorelease = false; + ATGiftVapSvgaManager().videoItemCache[gift.giftSourceUrl!] = + entity; + }); + } + } else { + if ((gift.giftSourceUrl ?? "").isNotEmpty) { + FileCacheManager.getInstance().getFile(url: gift.giftSourceUrl!); + } + } + } + } + } + + List homeBanners = []; + List exploreBanners = []; + List roomBanners = []; + List gameBanners = []; + + ///首页banner + void loadMainBanner() async { + try { + var banners = await ConfigRepositoryImp().getBanner(); + homeBanners.clear(); + roomBanners.clear(); + gameBanners.clear(); + exploreBanners.clear(); + for (var v in banners) { + if ((v.displayPosition ?? "").contains( + ATBannerType.EXPLORE_PAGE.name, + )) { + exploreBanners.add(v); + } + if ((v.displayPosition ?? "").contains(ATBannerType.ROOM.name)) { + roomBanners.add(v); + } + + if ((v.displayPosition ?? "").contains(ATBannerType.HOME_ALERT.name)) { + homeBanners.add(v); + } + if ((v.displayPosition ?? "").contains(ATBannerType.GAME.name)) { + gameBanners.add(v); + } + } + notifyListeners(); + } catch (e) {} + } + + Map> emojiByTab = {}; + + ///emoji表情 + void emojiAll() async { + var roomEmojis = await ChatRoomRepository().emojiAll(); + for (var value in roomEmojis) { + emojiByTab[value.groupName!] = value.emojis ?? []; + } + notifyListeners(); + } + + ///加载等级资源 + void configLevel() { + ConfigRepositoryImp().configLevel(); + } + + ATBannerLeaderboardRes? appLeaderResult; + + ///查询榜单前三名 + void appLeaderboard() async { + try { + appLeaderResult = await ChatRoomRepository().appLeaderboard(); + notifyListeners(); + } catch (e) {} + } + + ChatVibeRoomRes? hotRoom; + + void discoveryRoom() { + ChatRoomRepository() + .discovery(allRegion: true) + .then((values) { + hotRoom = values.first; + }) + .catchError((e) {}); + } +} diff --git a/lib/chatvibe_managers/apple_payment_manager.dart b/lib/chatvibe_managers/apple_payment_manager.dart new file mode 100644 index 0000000..f7213bf --- /dev/null +++ b/lib/chatvibe_managers/apple_payment_manager.dart @@ -0,0 +1,342 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:in_app_purchase/in_app_purchase.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/config_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_product_config_res.dart'; +import 'package:aslan/chatvibe_features/wallet/recharge/recharge_page.dart'; + +import '../chatvibe_data/models/enum/at_erro_code.dart'; + +class ApplePaymentManager extends ChangeNotifier { + StreamSubscription>? _subscription; + final InAppPurchase iap = InAppPurchase.instance; + + // 只复制必要的状态变量 + bool _isAvailable = false; + String _errorMessage = ''; + List _products = []; + + late BuildContext context; + Map productMap = {}; + + // 公开访问器 + bool get isAvailable => _isAvailable; + + String get errorMessage => _errorMessage; + + List get products => _products; + + // 初始化 - 只处理iOS逻辑 + Future init(BuildContext context) async { + this.context = context; + + // 如果不是iOS,直接返回 + if (!Platform.isIOS) { + _errorMessage = 'This provider is for iOS only'; + return; + } + + try { + ATLoadingManager.exhibitOperation(context: context); + // 检查支付是否可用 + _isAvailable = await iap.isAvailable(); + if (!_isAvailable) { + _errorMessage = 'App Store payment service is unavailable'; + ATTts.show(_errorMessage); + return; + } + _subscription?.cancel(); + // 设置购买流监听 + _subscription = iap.purchaseStream.listen( + _handlePurchase, + onError: (error, stackTrace) => _handleError(error, stackTrace), + ); + + // 获取iOS商品配置 + await _loadIOSProductConfig(); + + // 获取苹果商品信息 + await _fetchIOSProducts(); + } catch (e) { + ATTts.show("Apple Pay init fail: $e"); + _errorMessage = 'Apple Pay init fail: ${e.toString()}'; + } finally { + ATLoadingManager.veilRoutine(); + notifyListeners(); + } + } + + /// 加载iOS商品配置 + Future _loadIOSProductConfig() async { + productMap.clear(); + // 假设你的接口支持按平台获取配置 + var productConfigs = await ConfigRepositoryImp().productConfig(); + + for (var value in productConfigs) { + productMap[value.productPackage!] = value; + } + notifyListeners(); + } + + // 获取iOS商品信息 + Future _fetchIOSProducts() async { + try { + ATLoadingManager.exhibitOperation(context: context); + notifyListeners(); + + Set productIds = productMap.keys.toSet(); + + ProductDetailsResponse response = await iap.queryProductDetails( + productIds, + ); + + if (response.notFoundIDs.isNotEmpty) { + _errorMessage = '未找到商品: ${response.notFoundIDs.join(', ')}'; + debugPrint(_errorMessage); + } + + var details = response.productDetails; + // 保持你的排序逻辑 + if (details.isNotEmpty) { + // 这里可以添加iOS特定的排序逻辑 + } + + _products.clear(); + for (var v in details) { + _products.add(SelecteProductConfig(v, _products.isEmpty)); // 第一个选中 + } + } catch (e) { + ATTts.show("Failed to retrieve iOS products: $e"); + _errorMessage = 'Failed to get iOS goods: ${e.toString()}'; + debugPrint(_errorMessage); + } finally { + ATLoadingManager.veilRoutine(); + notifyListeners(); + } + } + + // 发起iOS购买 + Future buyProduct() async { + ProductDetails? product; + _products.forEach((d) { + if (d.isSelecte) { + product = d.produc; + } + }); + + if (product == null) { + ATTts.show(ATAppLocalizations.of(context)!.pleaseSelectaItem); + return; + } + + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.areYouRureRoRecharge, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + _goIOSBuy(product!); + }, + ); + }, + ); + } + + // iOS购买流程 + void _goIOSBuy(ProductDetails product) async { + try { + ATLoadingManager.exhibitOperation(); + notifyListeners(); + + final PurchaseParam purchaseParam = PurchaseParam( + productDetails: product, + applicationUserName: null, + ); + + // iOS统一使用buyNonConsumable,但实际类型由App Store Connect配置决定 + bool success = await iap.buyConsumable(purchaseParam: purchaseParam); + + if (!success) { + _errorMessage = + 'If the purchase fails to start, check your network connection'; + ATTts.show(_errorMessage); + } + } catch (e) { + ATTts.show("iOS Purchase failed: $e"); + _errorMessage = 'iOS purchase failed: ${e.toString()}'; + debugPrint(_errorMessage); + } finally { + ATLoadingManager.veilRoutine(); + notifyListeners(); + } + } + + // 处理iOS购买结果 + void _handlePurchase(List purchases) { + for (var purchase in purchases) { + switch (purchase.status) { + case PurchaseStatus.purchased: + _verifyIOSPayment(purchase); + break; + + case PurchaseStatus.error: + if (purchase.error != null) { + _handleIOSError(purchase.error!); + } + break; + + case PurchaseStatus.pending: + debugPrint('iOS支付处理中: ${purchase.productID}'); + ATTts.show('Payment is being processed, please wait...'); + break; + case PurchaseStatus.restored: + // SmartDialog.show( + // tag: "showConfirmDialog", + // alignment: Alignment.center, + // animationType: SmartAnimationType.fade, + // builder: (_) { + // return MsgDialog( + // title: ATAppLocalizations.of(context)!.tips, + // msg: ATAppLocalizations.of(context)!.restorePurchasesTips, + // btnText: ATAppLocalizations.of(context)!.confirm, + // onEnsure: () { + // + // }, + // ); + // }, + // ); + _verifyIOSPayment(purchase); // 恢复的购买也需要验证 + break; + case PurchaseStatus.canceled: + break; + } + } + notifyListeners(); + } + + // 验证iOS支付 + Future _verifyIOSPayment(PurchaseDetails purchase) async { + ATLoadingManager.exhibitOperation(); + try { + String receiptData = purchase.verificationData.serverVerificationData; + if (receiptData.isEmpty) { + receiptData = purchase.verificationData.localVerificationData; + } + // iOS验证 - 使用服务器收据验证 + try { + await ConfigRepositoryImp().applePay( + purchase.productID, + receiptData, + purchase.purchaseID ?? "", + ); + // 🔥 iOS关键:必须完成交易 + await iap.completePurchase(purchase); + // 更新用户信息 + Provider.of(context, listen: false).getMyUserInfo(); + Provider.of(context, listen: false).balance(); + ATTts.show('Purchase is successful!'); + debugPrint('iOS购买成功: ${purchase.productID}'); + } catch (e) { + if (e.toString().endsWith("${ATErroCode.orderExistsCreated.code}")) { + ATTts.show('The order already exists!'); + await iap.completePurchase(purchase); + } + } + } catch (e) { + ATTts.show("ios verification fails $e"); + _errorMessage = 'iOS verification failed: ${e.toString()}'; + debugPrint(_errorMessage); + // 即使验证失败,也要完成交易,否则会卡住 + if (purchase.pendingCompletePurchase) { + await iap.completePurchase(purchase); + } + } finally { + ATLoadingManager.veilRoutine(); + notifyListeners(); + } + } + + // 交付商品(与安卓相同) + Future _deliverProduct(String productId) async { + debugPrint('交付iOS商品: $productId'); + // 使用相同的业务逻辑 + } + + // iOS错误处理 + void _handleIOSError(IAPError error) { + _errorMessage = 'iOS payment error: ${error.message} (${error.code})'; + + switch (error.code) { + case 'SKErrorPaymentCancelled': + ATTts.show('the purchase has been canceled'); + break; + case 'SKErrorPaymentNotAllowed': + ATTts.show('the device does not support in app purchases'); + break; + default: + ATTts.show('ios payment failed: ${error.message}'); + break; + } + + debugPrint(_errorMessage); + ATLoadingManager.veilRoutine(); + notifyListeners(); + } + + void _handleError(Object error, StackTrace stackTrace) { + if (error is IAPError) { + _handleIOSError(error); + } else { + _errorMessage = 'iOS Unknown Error: ${error.toString()}'; + ATTts.show(_errorMessage); + } + } + + // iOS恢复购买 + Future restorePurchases() async { + try { + ATLoadingManager.exhibitOperation(context: context); + notifyListeners(); + + ATTts.show('recovering ios purchases...'); + await iap.restorePurchases(); + } catch (e) { + _errorMessage = 'Failed to restore iOS purchases: ${e.toString()}'; + ATTts.show(_errorMessage); + } finally { + ATLoadingManager.veilRoutine(); + notifyListeners(); + } + } + + void seleceConfig(int index, {bool goBuy = false}) { + for (var v in _products) { + v.isSelecte = false; + } + _products[index].isSelecte = true; + notifyListeners(); + if (goBuy) { + buyProduct(); + } + } + + @override + void dispose() { + _subscription?.cancel(); + super.dispose(); + } +} diff --git a/lib/chatvibe_managers/audio_manager.dart b/lib/chatvibe_managers/audio_manager.dart new file mode 100644 index 0000000..33efe49 --- /dev/null +++ b/lib/chatvibe_managers/audio_manager.dart @@ -0,0 +1,352 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:math'; + +import 'package:agora_rtc_engine/agora_rtc_engine.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:on_audio_query/on_audio_query.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; +import 'package:aslan/chatvibe_data/models/at_music_mode.dart'; + +import '../chatvibe_features/room/music/control/at_room_music_control_page.dart'; + +typedef MixingCurrentPositionChange = Function(int currentValue, int maxValue); + +class AudioManager with ChangeNotifier { + ATMusicMode? currentMusicMode; + int playingIndex = -1; + List localSongs = []; + List addedSongs = []; + Map addedMusicMap = {}; + AudioMixingStateType state = AudioMixingStateType.audioMixingStateStopped; + int playModel = 0; + OnAudioQuery? _audioQuery; + Timer? _timer; + + MixingCurrentPositionChange? changeCall; + + + void startMixingCurrentPositionTimer(BuildContext context) { + _timer?.cancel(); + _timer = Timer.periodic(Duration(milliseconds: 550), (timer) async { + int currentPos = + await Provider.of( + context, + listen: false, + ).engine!.getAudioMixingCurrentPosition(); + // 获取音乐总时长 + int duration = + await Provider.of( + context, + listen: false, + ).engine!.getAudioMixingDuration(); + changeCall?.call(currentPos, duration); + if (currentPos == duration) { + if (currentPos > 0) { + playFinished(context); + } + } + }); + } + + void stopTimer() { + _timer?.cancel(); + } + + Future getAtSongs() async { + try { + localSongs.clear(); + _audioQuery ??= OnAudioQuery(); + // 检查并请求存储权限 + bool hasPermission = await _audioQuery!.checkAndRequest(); + // 确保权限已授予后再进行查询 + if (hasPermission) { + // 查询设备上的所有歌曲 + var ss = await _audioQuery!.querySongs(); + for (var s in ss) { + if ((s.duration ?? 0) > 10000) { + localSongs.add(s); + } + } + loadAtRoomMusics(); + } else { + // 用户拒绝了权限,需要进行处理(例如显示提示) + ATTts.show("Failed to obtain permission"); + return []; + } + } catch (e) {} + } + + void addRoomMusic(ATMusicMode music) { + try { + List musics = []; + String musicJson = DataPersistence.getUserRoomMusic(); + if (musicJson.isNotEmpty) { + musics = + (jsonDecode(musicJson) as List) + .map((e) => ATMusicMode.fromJson(e)) + .toList(); + } + bool isHas = false; + if (musics.isNotEmpty) { + for (var m in musics) { + if (m.title == music.title) { + isHas = true; + break; + } + } + } + if (!isHas) { + musics.add(music); + } + String newMusicJson = jsonEncode(musics.map((v) => v.toJson()).toList()); + DataPersistence.setUserRoomMusic(newMusicJson); + addedMusicMap[music.title ?? ""] = music; + notifyListeners(); + } catch (e) {} + } + + void deleteRoomMusic(BuildContext context, ATMusicMode music) { + try { + addedMusicMap.remove(music.title); + List musics = []; + String musicJson = DataPersistence.getUserRoomMusic(); + if (musicJson.isNotEmpty) { + musics = + (jsonDecode(musicJson) as List) + .map((e) => ATMusicMode.fromJson(e)) + .toList(); + } + if (musics.isNotEmpty) { + musics.removeWhere((m) => m.title == music.title); + } + String newMusicJson = jsonEncode(musics.map((v) => v.toJson()).toList()); + DataPersistence.setUserRoomMusic(newMusicJson); + if (music.title == currentMusicMode?.title) { + currentMusicMode = null; + Provider.of(context, listen: false).stopMusic(); + SmartDialog.dismiss(tag: "showRoomMusicControl"); + reset(); + } + } catch (e) {} + notifyListeners(); + } + + void loadAtRoomMusics() { + try { + addedSongs.clear(); + String musicJson = DataPersistence.getUserRoomMusic(); + if (musicJson.isNotEmpty) { + addedSongs = + (jsonDecode(musicJson) as List) + .map((e) => ATMusicMode.fromJson(e)) + .toList(); + } + } catch (e) {} + if (addedSongs.isNotEmpty) { + addedMusicMap = {for (var music in addedSongs) music.title ?? "": music}; + } + notifyListeners(); + } + + void playMusic(BuildContext context, ATMusicMode music, int playingIndex) { + if (this.playingIndex == playingIndex) { + SmartDialog.show( + tag: "showRoomMusicControl", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + clickMaskDismiss: true, + builder: (_) { + return ATRoomMusicControlPage(); + }, + ); + return; + } + currentMusicMode = music; + this.playingIndex = playingIndex; + Provider.of( + context, + listen: false, + ).playMusic(music.localPath ?? "", playModel == 0 ? -1 : 1); + notifyListeners(); + SmartDialog.dismiss(tag: "showRoomMusic"); + SmartDialog.show( + tag: "showRoomMusicControl", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + clickMaskDismiss: true, + builder: (_) { + return ATRoomMusicControlPage(); + }, + ); + } + + void setPlayState(AudioMixingStateType state, BuildContext context) { + if (state == AudioMixingStateType.audioMixingStatePlaying) { + startMixingCurrentPositionTimer(context); + } + this.state = state; + notifyListeners(); + } + + void reset() { + playingIndex = -1; + playModel = 0; + currentMusicMode = null; + state = AudioMixingStateType.audioMixingStateStopped; + stopTimer(); + notifyListeners(); + } + + ///暂停或者播放 + void playOrPause(BuildContext context) { + if (state == AudioMixingStateType.audioMixingStatePlaying) { + Provider.of(context, listen: false).pauseMusic(); + } else if (state == AudioMixingStateType.audioMixingStatePaused) { + Provider.of(context, listen: false).resumeMusic(); + } else { + if (currentMusicMode != null) { + Provider.of( + context, + listen: false, + ).playMusic(currentMusicMode!.localPath ?? "", playModel == 0 ? -1 : 1); + } + } + } + + void switchModel() { + if (playModel == 0) { + playModel = 1; + } else if (playModel == 1) { + playModel = 2; + } else { + playModel = 0; + } + notifyListeners(); + } + + ///上一首 + void previous(BuildContext context) { + if (playingIndex == 0) { + return; + } else { + playingIndex = playingIndex - 1; + currentMusicMode = addedSongs[playingIndex]; + Provider.of( + context, + listen: false, + ).playMusic(currentMusicMode?.localPath ?? "", playModel == 0 ? -1 : 1); + notifyListeners(); + } + } + + ///下一首 + void playNext(BuildContext context) { + if (playingIndex == addedSongs.length - 1) { + return; + } else { + playingIndex = playingIndex + 1; + currentMusicMode = addedSongs[playingIndex]; + Provider.of( + context, + listen: false, + ).playMusic(currentMusicMode?.localPath ?? "", playModel == 0 ? -1 : 1); + notifyListeners(); + } + } + + ///随机播放一首 + void playRandom(BuildContext context) { + playingIndex = Random().nextInt(addedSongs.length + 1); + currentMusicMode = addedSongs[playingIndex]; + Provider.of( + context, + listen: false, + ).playMusic(currentMusicMode?.localPath ?? "", playModel == 0 ? -1 : 1); + notifyListeners(); + } + + int sTime = 0; + + ///音乐播放完成 + void playFinished(BuildContext context) { + int nTime = DateTime.now().millisecondsSinceEpoch; + if (nTime - sTime > 2000) { + sTime = nTime; + if (playModel == 1) { + playRandom(context); + } else { + if (playingIndex == addedSongs.length - 1) { + playingIndex = -1; + } + playNext(context); + } + } + } + + ///置顶 + void toUp(ATMusicMode s, int i) { + if (addedSongs.contains(s)) { + addedSongs.remove(s); + addedSongs.insert(0, s); + } + int index = 0; + for (var ms in addedSongs) { + if (currentMusicMode?.title == ms.title) { + playingIndex = index; + break; + } + index = index + 1; + } + + String newMusicJson = jsonEncode( + addedSongs.map((v) => v.toJson()).toList(), + ); + DataPersistence.setUserRoomMusic(newMusicJson); + notifyListeners(); + } + + void selectAll() { + try { + Future(() { + List musics = []; + String musicJson = DataPersistence.getUserRoomMusic(); + if (musicJson.isNotEmpty) { + musics = + (jsonDecode(musicJson) as List) + .map((e) => ATMusicMode.fromJson(e)) + .toList(); + } + + List result = + localSongs + .where((p1) => musics.any((p2) => p2.title == p1.title)) + .toList(); + for (var song in localSongs) { + if (!result.contains(song)) { + var mode = ATMusicMode( + title: song.title, + artist: song.artist, + duration: song.duration, + localPath: song.uri, + ); + musics.add(mode); + addedMusicMap[song.title] = mode; + } + } + String newMusicJson = jsonEncode( + musics.map((v) => v.toJson()).toList(), + ); + DataPersistence.setUserRoomMusic(newMusicJson); + notifyListeners(); + }); + } catch (e) {} + } +} diff --git a/lib/chatvibe_managers/authentication_manager.dart b/lib/chatvibe_managers/authentication_manager.dart new file mode 100644 index 0000000..54fc4b9 --- /dev/null +++ b/lib/chatvibe_managers/authentication_manager.dart @@ -0,0 +1,98 @@ + +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:sign_in_with_apple/sign_in_with_apple.dart'; +import 'package:aslan/chatvibe_core/routes/at_routes.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_google_auth_utils.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_core/utilities/at_version_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +import '../chatvibe_data/models/enum/at_auth_type.dart'; + +class ChatVibeAuthenticationManager extends ChangeNotifier { + String authType = ""; + String uid = ""; + User? _user; + + User? get user => _user; + + bool get isSignedIn => _user != null; + + ChatVibeAuthenticationManager() { + _init(); + } + + Future _init() async { + await ATGoogleAuthService.prepareFunction(); + await _checkExistingSession(); + } + + Future _checkExistingSession() async { + final user = await ATGoogleAuthService.signInSilently(); + _user = user; + notifyListeners(); + } + + Future signIn(BuildContext context, String authType) async { + if (authType == ATAuthType.GOOGLE.name) { + this.authType = authType; + try { + _user ??= await ATGoogleAuthService.signInWithGoogleFunction(); + uid = _user!.uid; + ATLoadingManager.exhibitOperation(); + if (uid.isNotEmpty) { + Future.wait([ + AccountRepository().loginForChannel(authType, uid), + ATVersionUtils.checkReview(), + ]).then((res) { + var user = (res[0] as ChatVibeLoginRes); + AccountStorage().setCurrentUser(user); + ATLoadingManager.veilRoutine(); + notifyListeners(); + ATNavigatorUtils.push(context, ATRoutes.home, replace: true); + }); + } + } catch (e) { + ATLoadingManager.veilRoutine(); + rethrow; + } + } else if (authType == ATAuthType.APPLE.name) { + this.authType = authType; + try { + final appleCredential = await SignInWithApple.getAppleIDCredential( + scopes: [ + AppleIDAuthorizationScopes.fullName, // 请求姓名权限 + ], + ); + uid = appleCredential.userIdentifier ?? ""; + ATLoadingManager.exhibitOperation(); + if (uid.isNotEmpty) { + Future.wait([ + AccountRepository().loginForChannel(authType, uid), + ATVersionUtils.checkReview(), + ]).then((res) { + var user = (res[0] as ChatVibeLoginRes); + AccountStorage().setCurrentUser(user); + ATLoadingManager.veilRoutine(); + notifyListeners(); + ATNavigatorUtils.push(context, ATRoutes.home, replace: true); + }); + } + } catch (e) { + ATLoadingManager.veilRoutine(); + rethrow; + } + } + } + + Future signOut() async { + await ATGoogleAuthService.signOutMethod(); + _user = null; + notifyListeners(); + } +} diff --git a/lib/chatvibe_managers/dynamic_content_manager.dart b/lib/chatvibe_managers/dynamic_content_manager.dart new file mode 100644 index 0000000..365ecd9 --- /dev/null +++ b/lib/chatvibe_managers/dynamic_content_manager.dart @@ -0,0 +1,233 @@ +import 'package:flutter/cupertino.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/dynamic_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_comment_list_res.dart'; + +class ChatVibeDynamicContentManager extends ChangeNotifier { + bool showEmoji = false; + bool showGift = false; + + Map pageMap = {}; + + ///主评论 + List subCommentList = []; + + ///子评论 + Map> childCommentListMap = {}; + bool commentIsLoading = true; + + ///主评论列表 + void dynamicListComment( + String dynamicId, + int pageNumber, + Function(bool hasMoer) loadSuccCallBack, + Function loadFailCallBack, + ) { + commentIsLoading = true; + if (pageNumber < 2) { + reset(); + } + DynamicRepositoryImp() + .dynamicListComment(dynamicId, pageNumber, 10) + .then((res) { + if (pageNumber < 2) { + subCommentList = res.records ?? []; + } else { + subCommentList.addAll(res.records ?? []); + } + commentIsLoading = false; + if ((res.records?.length ?? 0) < 10) { + loadSuccCallBack(true); + } else { + loadSuccCallBack(false); + } + + res.records?.forEach((element) { + if ((element.childCommentCount ?? 0) > 0) { + ///表示有子评论 + DynamicRepositoryImp() + .dynamicListCommentChildren(element.rootCommentId ?? 0, 1) + .then((value) { + var ls = + childCommentListMap[element.rootCommentId ?? 0] ?? []; + ls.addAll(value.records ?? []); + childCommentListMap[element.rootCommentId ?? 0] = ls; + pageMap[element.rootCommentId ?? 0] = 1; + loadSuccCallBack(false); + }); + } + }); + }) + .catchError((e) { + commentIsLoading = false; + loadFailCallBack(); + }); + } + + void dynamicListCommentChildren(num rootCommentId, Function callBack) { + int pageNumber = (pageMap[rootCommentId] ?? 1) + 1; + ATLoadingManager.exhibitOperation(); + + ///表示有子评论 + DynamicRepositoryImp() + .dynamicListCommentChildren(rootCommentId, pageNumber) + .then((value) { + var ls = childCommentListMap[rootCommentId] ?? []; + ls.addAll(value.records ?? []); + childCommentListMap[rootCommentId] = ls; + pageMap[rootCommentId] = pageNumber; + callBack.call(); + ATLoadingManager.veilRoutine(); + }) + .catchError((_) { + ATLoadingManager.veilRoutine(); + }); + } + + ///评论更新一级评论 + void updateRootComment(CommentListRecords cm, Function callBack) { + subCommentList.insert(0, cm); + callBack.call(); + } + + ///更新子评论 + void updateChildComment( + CommentListRecords cm, + num rootCommentId, + String toUserId, + Function callBack, + ) { + var cList = subCommentList; + for (var element in cList) { + if (element.rootCommentId == cm.rootCommentId) { + element.setChildCommentCount((element.childCommentCount ?? 0) + 1); + break; + } + } + List childCommentList = + childCommentListMap[rootCommentId] ?? []; + childCommentList.insert(0, cm); + childCommentListMap[rootCommentId] = childCommentList; + callBack.call(); + } + + void updateShowEmojiState(bool state, Function callBack) { + showEmoji = state; + callBack.call(); + } + + void updateShowGiftState(bool state, Function callBack) { + showGift = state; + callBack.call(); + } + + void withdraw(num rootCommentId, Function callBack) { + childCommentListMap[rootCommentId] = []; + pageMap[rootCommentId] = 0; + callBack.call(); + } + + void reset() { + subCommentList.clear(); + childCommentListMap.clear(); + pageMap.clear(); + } + + ///点赞评论 + void likeComment( + BuildContext context, + String dynamicId, + String id, + num rootCommentId, + String toUserId, + bool like, + bool isRootComment, + Function callBack, + ) { + DynamicRepositoryImp() + .likeComment(dynamicId, id, rootCommentId, toUserId, like) + .then((res) { + if (isRootComment) { + var cList = subCommentList; + for (var element in cList) { + if (element.id == id) { + element.setLike(like); + String likeStrQuantity = ""; + if (like) { + likeStrQuantity = + "${int.parse(element.likeStrQuantity ?? "0") + 1}"; + } else { + likeStrQuantity = + "${int.parse(element.likeStrQuantity ?? "0") - 1}"; + } + + element.setLikeStrQuantity(likeStrQuantity); + break; + } + } + } else { + var cList = childCommentListMap[rootCommentId] ?? []; + for (var element in cList) { + if (element.id == id) { + element.setLike(like); + String likeStrQuantity = ""; + if (like) { + likeStrQuantity = + "${int.parse(element.likeStrQuantity ?? "0") + 1}"; + } else { + likeStrQuantity = + "${int.parse(element.likeStrQuantity ?? "0") - 1}"; + } + element.setLikeStrQuantity(likeStrQuantity); + break; + } + } + } + + ATTts.show(ATAppLocalizations.of(context)!.operationSuccessful); + callBack.call(); + }) + .catchError((_) {}); + } + + void deleteComment( + BuildContext context, + String dynamicId, + String id, + num commentQuantity, + bool isRootComment, + Function callBack, { + Function(num count)? deleteCall, + }) { + if (isRootComment) { + DynamicRepositoryImp() + .deleteComment(id) + .then((res) { + int index = -1; + for (var element in subCommentList) { + index++; + if (element.id == id) { + break; + } + } + if (index > -1) { + num count = subCommentList[index].childCommentCount ?? 0; + subCommentList.removeAt(index); + deleteCall?.call(count + 1); + ATTts.show(ATAppLocalizations.of(context)!.deleteSuccessful); + callBack.call(); + } + }) + .catchError((_) {}); + } else { + DynamicRepositoryImp().deleteComment(id).then((resCount) { + num deleteCount = commentQuantity - resCount; + ATTts.show(ATAppLocalizations.of(context)!.deleteSuccessful); + deleteCall?.call(deleteCount); + callBack.call(); + }); + } + } +} diff --git a/lib/chatvibe_managers/gift_animation_manager.dart b/lib/chatvibe_managers/gift_animation_manager.dart new file mode 100644 index 0000000..e5b31d0 --- /dev/null +++ b/lib/chatvibe_managers/gift_animation_manager.dart @@ -0,0 +1,66 @@ +import 'dart:collection'; + +import 'package:flutter/cupertino.dart'; + +import 'package:aslan/chatvibe_ui/widgets/room/anim/l_gift_animal_view.dart'; + +class GiftAnimationManager extends ChangeNotifier { + Queue gifts = Queue(); + List aninControllers = []; + + //每个控件正在播放的动画 + Map giftMap = {0: null, 1: null, 2: null, 3: null}; + + void addGift(LGiftModel giftModel) { + gifts.add(giftModel); + playNext(); + } + + ///开始播放 + playNext() { + if (gifts.isEmpty) { + return; + } + var playGift = gifts.first; + for (var key in giftMap.keys) { + var value = giftMap[key]; + if (value == null) { + giftMap[key] = playGift; + gifts.removeFirst(); + notifyListeners(); + aninControllers[key].controller.forward(from: 0); + break; + } else { + if (value.labelId == playGift.labelId) { + playGift.giftCount = value.giftCount + playGift.giftCount; + giftMap[key] = playGift; + gifts.removeFirst(); + notifyListeners(); + aninControllers[key].controller.forward(from: 0.45); + break; + } + } + } + } + + void bindController(List anins) { + aninControllers = anins; + } + + void disposeAnim() { + gifts.clear(); + giftMap[0] = null; + giftMap[1] = null; + giftMap[2] = null; + giftMap[3] = null; + for (var element in aninControllers) { + element.controller.dispose(); + } + } + + void animCompleted(int index) { + giftMap[index] = null; + notifyListeners(); + playNext(); + } +} diff --git a/lib/chatvibe_managers/gift_system_manager.dart b/lib/chatvibe_managers/gift_system_manager.dart new file mode 100644 index 0000000..5bbcb2c --- /dev/null +++ b/lib/chatvibe_managers/gift_system_manager.dart @@ -0,0 +1,179 @@ +import 'package:flutter/cupertino.dart'; +import 'package:aslan/chatvibe_domain/models/res/mic_res.dart'; + +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/utilities/at_gift_vap_svga_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/gift_res.dart'; + +typedef GiftProvider = ChatVibeGiftSystemManager; + +class ChatVibeGiftSystemManager extends ChangeNotifier { + ///幸运礼物就不显示这个特效 + bool hideLGiftAnimal = false; + + ///挡位是否已经播放了 + Map isPlayed = { + 10: false, + 20: false, + 30: false, + 50: false, + 66: false, + 88: false, + 100: false, + 200: false, + 300: false, + 400: false, + 500: false, + 666: false, + 777: false, + 888: false, + 1000: false, + 1500: false, + 2000: false, + 3000: false, + 5000: false, + 10000: false, + 15000: false, + 20000: false, + 25000: false, + 30000: false, + 35000: false, + 40000: false, + 45000: false, + 50000: false, + 55000: false, + 60000: false, + 65000: false, + 70000: false, + 75000: false, + 80000: false, + 85000: false, + 90000: false, + 95000: false, + 100000: false, + }; + + ///幸运礼物获得的金币总额 + num luckGiftObtainCoins = 0; + + ///单次中奖的金币 + num awardAmount = 0; + + ///赠送的数量 + num number = 0; + + bool isManyPeople = false; + + MicRes? toUser; + + ChatVibeGiftRes? gift; + + double giftAnimSize = 1; + double obtainCoinsAnimSize = 1; + + double awardAmountAnimSize = 1; + + void updateHideLGiftAnimal(bool isHide) { + hideLGiftAnimal = isHide; + notifyListeners(); + } + + void reset() { + gift = null; + luckGiftObtainCoins = 0; + number = 0; + isManyPeople = false; + toUser = null; + + isPlayed = { + 10: false, + 20: false, + 30: false, + 50: false, + 66: false, + 88: false, + 100: false, + 200: false, + 300: false, + 400: false, + 500: false, + 666: false, + 777: false, + 888: false, + 1000: false, + 1500: false, + 2000: false, + 3000: false, + 5000: false, + 10000: false, + 15000: false, + 20000: false, + 25000: false, + 30000: false, + 35000: false, + 40000: false, + 45000: false, + 50000: false, + 60000: false, + 65000: false, + 70000: false, + 75000: false, + 80000: false, + 85000: false, + 90000: false, + 95000: false, + 100000: false, + }; + notifyListeners(); + } + + void updateLuckGiftNumber( + num n, + bool isManyPeople, + MicRes first, + ChatVibeGiftRes? checkedGift, + ) { + this.number = number + n; + this.isManyPeople = isManyPeople; + this.toUser = first; + this.gift = checkedGift; + giftAnimSize = 1.4; + notifyListeners(); + playAnim(); + } + + void updateLuckGiftObtainCoins(num n) { + this.awardAmount = n; + this.luckGiftObtainCoins = luckGiftObtainCoins + n; + this.obtainCoinsAnimSize = 1.4; + this.awardAmountAnimSize = 1.4; + notifyListeners(); + } + + void playAnim() { + isPlayed.forEach((k, v) { + if (k < number + 1 && !v) { + playVap(k); + } + }); + } + + void playVap(num n) { + if (!(isPlayed[n] ?? false)) { + if (ATGlobalConfig.isLuckGiftSpecialEffects) { + if (n > 9999) { + ATGiftVapSvgaManager().play( + "atu_images/room/anim/luck_gift_count_5000_mor.mp4", + priority: 200, + ); + } else { + ATGiftVapSvgaManager().play( + "atu_images/room/anim/luck_gift_count_$n.mp4", + priority: 200, + ); + } + } + } + isPlayed[n] = true; + } +} diff --git a/lib/chatvibe_managers/google_payment_manager.dart b/lib/chatvibe_managers/google_payment_manager.dart new file mode 100644 index 0000000..f2dbd8a --- /dev/null +++ b/lib/chatvibe_managers/google_payment_manager.dart @@ -0,0 +1,499 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:in_app_purchase/in_app_purchase.dart'; +import 'package:in_app_purchase_android/in_app_purchase_android.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_core/utilities/at_string_utils.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; + +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/config_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_product_config_res.dart'; +import 'package:aslan/chatvibe_features/wallet/recharge/recharge_page.dart'; + +class GooglePaymentManager extends ChangeNotifier { + StreamSubscription>? _subscription; + final InAppPurchase iap = InAppPurchase.instance; + + // 状态管理变量 + bool _isAvailable = false; + String _errorMessage = ''; + List _products = []; + List _purchases = []; + + ///商品列表 + List productConfigs = []; + + // 维护商品类型映射 + final Map _productTypeMap = {}; + + // 公开访问器 + bool get isAvailable => _isAvailable; + + String get errorMessage => _errorMessage; + + List get products => _products; + + List get purchases => _purchases; + late BuildContext context; + Map productMap = {}; + + Map productTypes = {}; + + // 初始化支付系统 + Future init(BuildContext context) async { + this.context = context; + try { + ATLoadingManager.exhibitOperation(context: context); + + // 检查支付是否可用 + _isAvailable = await iap.isAvailable(); + if (!_isAvailable) { + _errorMessage = 'Google Play payment service is unavailable'; + ATTts.show("Google Play payment service is unavailable"); + return; + } + _subscription?.cancel(); + // 设置购买流监听 + _subscription = iap.purchaseStream.listen( + _handlePurchase, + onError: (error, stackTrace) => _handleError(error, stackTrace), + ); + + // 先获取商品配置 + await loadProductConfig(); + + // 然后获取谷歌商品信息 + await _fetchProducts(); + + // 最后恢复购买,处理未完成交易 + await restorePurchases(); + } catch (e) { + ATTts.show("init fail: $e"); + _errorMessage = 'init fail: ${e.toString()}'; + } finally { + ATLoadingManager.veilRoutine(); + notifyListeners(); + } + } + + ///用户获取商店配置的商品列表 + Future loadProductConfig() async { + productMap.clear(); + productConfigs = await ConfigRepositoryImp().productConfig(); + int index = 0; + for (var value in productConfigs) { + productMap[value.productPackage!] = value; + productTypes[value.productPackage!] = "consumable"; + index = index + 1; + } + notifyListeners(); + } + + // 获取商品信息 + Future _fetchProducts() async { + try { + ATLoadingManager.exhibitOperation(context: context); + notifyListeners(); + + Set productIds = productTypes.keys.toSet(); + + ProductDetailsResponse response = await iap.queryProductDetails( + productIds, + ); + if (response.notFoundIDs.isNotEmpty) { + _errorMessage = '未找到商品: ${response.notFoundIDs.join(', ')}'; + debugPrint(_errorMessage); + } + var dtails = response.productDetails; + if (dtails.isNotEmpty) { + dtails.sort((a, b) { + int ia = 0; + int ib = 0; + if (a.id.contains(".")) { + ia = ATStringUtils.toIntFunction(a.id.split(".").last); + } + if (b.id.contains(".")) { + ib = ATStringUtils.toIntFunction(b.id.split(".").last); + } + return ia.compareTo(ib); + }); + } + int index = 0; + _products.clear(); + for (var v in dtails) { + if (index == 0) { + _products.add(SelecteProductConfig(v, true)); + } else { + _products.add(SelecteProductConfig(v, false)); + } + index++; + } + + // 初始化商品类型映射 + for (var product in _products) { + if (productTypes.containsKey(product.produc.id)) { + _productTypeMap[product.produc.id] = productTypes[product.produc.id]!; + } else { + _productTypeMap[product.produc.id] = 'nonConsumable'; // 默认类型 + } + } + + // 打印商品信息用于调试 + for (var product in _products) { + debugPrint( + '商品: ${product.produc.id}, 类型: ${_productTypeMap[product.produc.id]}, 价格: ${product.produc.price}', + ); + } + } catch (e) { + ATTts.show("Failed to retrieve the product: $e"); + _errorMessage = '获取商品失败: ${e.toString()}'; + debugPrint(_errorMessage); + } finally { + ATLoadingManager.veilRoutine(); + notifyListeners(); + } + } + + // 获取商品类型 + String _getProductType(String productId) { + return _productTypeMap[productId] ?? 'nonConsumable'; + } + + // 发起购买 + Future buyProduct() async { + ProductDetails? product; + _products.forEach((d) { + if (d.isSelecte) { + product = d.produc; + } + }); + if (product == null) { + ATTts.show(ATAppLocalizations.of(context)!.pleaseSelectaItem); + return; + } + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.areYouRureRoRecharge, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + _goBuy(product!); + }, + ); + }, + ); + } + + // 处理购买结果 + void _handlePurchase(List purchases) { + _purchases = purchases; + + for (var purchase in purchases) { + switch (purchase.status) { + case PurchaseStatus.purchased: + _verifyPayment(purchase); + break; + + case PurchaseStatus.error: + if (purchase.error != null) { + _handleIAPError(purchase.error!); + // 特别处理 "item-already-owned" 错误 + if (purchase.error!.code == 'item-already-owned') { + _handleAlreadyOwnedItem(purchase); + } + } + break; + + case PurchaseStatus.pending: + debugPrint('支付处理中: ${purchase.productID}'); + break; + + case PurchaseStatus.restored: + // 处理恢复的购买 + _verifyPayment(purchase); + break; + default: + break; + } + } + + notifyListeners(); + } + + // 新增:处理已拥有商品的情况 + Future _handleAlreadyOwnedItem(PurchaseDetails purchase) async { + try { + debugPrint('检测到已拥有商品: ${purchase.productID},尝试消耗'); + + // 如果是消耗型商品,尝试消耗 + if (_getProductType(purchase.productID) == 'consumable') { + await consumePurchase(purchase); + // 消耗后重新获取余额 + Provider.of(context, listen: false).balance(); + ATTts.show('outstanding purchases have been reinstated'); + } + } catch (e) { + debugPrint('处理已拥有商品失败: $e'); + } + } + + // 验证支付凭证 + Future _verifyPayment(PurchaseDetails purchase) async { + try { + // 1. 基本验证 + if (purchase.verificationData.serverVerificationData.isEmpty) { + _errorMessage = '无效的支付凭证'; + ATTts.show("Invalid payment voucher"); + return; + } + + // 2. 检查是否已经处理过这个购买 + if (!purchase.pendingCompletePurchase) { + debugPrint('购买已处理过: ${purchase.productID}'); + return; + } + + // 3. 获取购买数据 + String purchaseData = purchase.verificationData.localVerificationData; + String signature = ""; + + if (purchase is GooglePlayPurchaseDetails) { + signature = purchase.billingClientPurchase.signature; + } + + // 4. 服务器验证 + await ConfigRepositoryImp().googlePay( + purchase.productID, + signature, + purchaseData, + ); + + // 5. 交付商品 + await _deliverProduct(purchase.productID); + + // 6. 完成购买 + await iap.completePurchase(purchase); + + // 7. 如果是消耗型商品,确保消耗 + if (_getProductType(purchase.productID) == 'consumable') { + await _ensurePurchaseConsumed(purchase); + } + + // 8. 更新用户余额 + Provider.of(context, listen: false).getMyUserInfo(); + Provider.of(context, listen: false).balance(); + + ATTts.show('purchase successful'); + debugPrint('购买成功: ${purchase.productID}'); + } catch (e) { + ATTts.show("verification failed: $e"); + _errorMessage = '验证失败: ${e.toString()}'; + debugPrint(_errorMessage); + } finally { + ATLoadingManager.veilRoutine(); + notifyListeners(); + } + } + + // 交付商品 + Future _deliverProduct(String productId) async { + debugPrint('交付商品: $productId'); + // 实现您的业务逻辑 + // 例如:增加用户余额、解锁功能等 + } + + // 新增:确保购买被消耗 + Future _ensurePurchaseConsumed(PurchaseDetails purchase) async { + try { + // 等待一段时间确保购买完成 + await Future.delayed(Duration(seconds: 1)); + + // 尝试消耗购买 + await consumePurchase(purchase); + + debugPrint('商品已成功消耗: ${purchase.productID}'); + } catch (e) { + debugPrint('消耗商品失败,可能已自动消耗: $e'); + } + } + + void _handleError(Object error, StackTrace stackTrace) { + ATTts.show("Payment error: $error"); + if (error is IAPError) { + // 处理IAP特定错误 + _handleIAPError(error); + } else { + // 处理其他类型的错误 + _errorMessage = '未知错误: ${error.toString()}'; + ATTts.show(_errorMessage); + } + } + + // 添加了专门的IAP错误处理方法 + void _handleIAPError(IAPError error) { + _errorMessage = '支付错误: ${error.message} (${error.code})'; + + // 处理特定错误代码 + switch (error.code) { + case 'payment-invalid': + debugPrint('支付无效: 商品ID可能配置错误'); + ATTts.show( + 'Payment configuration error, please contact customer service', + ); + break; + case 'item-already-owned': + debugPrint('商品已拥有,尝试消耗商品'); + ATTts.show('Unfinished purchase detected, processing...'); + break; + case 'user-cancelled': + debugPrint('用户取消购买'); + ATTts.show('Purchase cancelled'); + break; + case 'service-timeout': + case 'service-unavailable': + debugPrint('Google Play服务不可用'); + ATTts.show( + 'Google Play services are temporarily unavailable, please try again later', + ); + break; + default: + ATTts.show('Payment failed: ${error.message}'); + break; + } + + debugPrint(_errorMessage); + ATLoadingManager.veilRoutine(); + notifyListeners(); + } + + // 恢复购买 + Future restorePurchases() async { + try { + ATLoadingManager.exhibitOperation(context: context); + notifyListeners(); + + await iap.restorePurchases(); + + // 给恢复操作一些时间 + await Future.delayed(Duration(milliseconds: 1000)); + } catch (e) { + _errorMessage = '恢复购买失败: ${e.toString()}'; + debugPrint(_errorMessage); + ATTts.show("Failed to restore purchase: ${e.toString()}"); + } finally { + ATLoadingManager.veilRoutine(); + notifyListeners(); + } + } + + // 消耗型商品手动消耗 + Future consumePurchase(PurchaseDetails purchase) async { + try { + if (Platform.isAndroid) { + final InAppPurchaseAndroidPlatformAddition androidAddition = + iap.getPlatformAddition(); + await androidAddition.consumePurchase(purchase); + debugPrint('已消耗商品: ${purchase.productID}'); + } + } catch (e) { + _errorMessage = '消耗商品失败: ${e.toString()}'; + debugPrint(_errorMessage); + } + } + + // 购买流程 + void _goBuy(ProductDetails product) async { + try { + ATLoadingManager.exhibitOperation(context: context); + notifyListeners(); + + // 购买前检查是否有未完成的购买 + await _checkPendingPurchases(); + + final PurchaseParam purchaseParam = PurchaseParam( + productDetails: product, + applicationUserName: null, + ); + + String productType = _getProductType(product.id); + + bool success; + if (productType == 'consumable') { + success = await iap.buyConsumable( + purchaseParam: purchaseParam, + autoConsume: kReleaseMode, // 生产环境自动消耗,调试时手动控制 + ); + } else { + success = await iap.buyNonConsumable(purchaseParam: purchaseParam); + } + + if (!success) { + _errorMessage = '购买启动失败,请检查网络连接'; + ATTts.show(_errorMessage); + } + } catch (e) { + ATTts.show("Purchase failed: $e"); + _errorMessage = '购买失败: ${e.toString()}'; + debugPrint(_errorMessage); + } finally { + ATLoadingManager.veilRoutine(); + notifyListeners(); + } + } + + // 新增:检查未完成的购买 + Future _checkPendingPurchases() async { + try { + // 恢复购买以获取所有未完成交易 + await iap.restorePurchases(); + + // 给一点时间处理恢复的购买 + await Future.delayed(Duration(milliseconds: 550)); + } catch (e) { + debugPrint('检查未完成购买时出错: $e'); + } + } + + // 添加调试方法 + void debugPurchaseState() { + debugPrint('=== 当前购买状态 ==='); + debugPrint('可用商品数量: ${_products.length}'); + debugPrint('未完成购买数量: ${_purchases.length}'); + + for (var purchase in _purchases) { + debugPrint('商品: ${purchase.productID}, 状态: ${purchase.status}'); + debugPrint('待完成: ${purchase.pendingCompletePurchase}'); + } + debugPrint('=================='); + } + + // 释放资源 + @override + void dispose() { + _subscription?.cancel(); + super.dispose(); + } + + void seleceConfig(int index,{bool goBuy = false}) { + for (var v in _products) { + v.isSelecte = false; + } + _products[index].isSelecte = true; + notifyListeners(); + if (goBuy) { + buyProduct(); + } + } +} diff --git a/lib/chatvibe_managers/localization_manager.dart b/lib/chatvibe_managers/localization_manager.dart new file mode 100644 index 0000000..236932e --- /dev/null +++ b/lib/chatvibe_managers/localization_manager.dart @@ -0,0 +1,54 @@ +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; + +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; + +class LocalizationManager with ChangeNotifier { + Locale? _locale; + + Locale? get locale => _locale; + + LocalizationManager() { + init(); + } + + init() { + String language = DataPersistence.getLang(); + if (language.isEmpty) { + Locale systemLocale = ui.window.locale; + if (systemLocale.languageCode.startsWith("ar")) { + _locale = Locale('ar', ''); + } else if (systemLocale.languageCode == "en") { + _locale = Locale('en', ''); + } else if (systemLocale.languageCode == "tr") { + _locale = Locale('tr', ''); + } else if (systemLocale.languageCode == "bn") { + _locale = Locale('bn', ''); + } else { + _locale = Locale('en', ''); + } + } else { + if (language.startsWith("ar")) { + _locale = Locale('ar', ''); + } else if (language == "tr") { + _locale = Locale('tr', ''); + } else if (language == "bn") { + _locale = Locale('bn', ''); + } else { + _locale = Locale('en', ''); + } + } + if (_locale != null) { + setLocale(_locale!); + } + } + + void setLocale(Locale locale) { + _locale = locale; + ATGlobalConfig.lang = locale.languageCode; + DataPersistence.setLang(locale.languageCode); + notifyListeners(); + } +} diff --git a/lib/chatvibe_managers/room_manager.dart b/lib/chatvibe_managers/room_manager.dart new file mode 100644 index 0000000..4beb60a --- /dev/null +++ b/lib/chatvibe_managers/room_manager.dart @@ -0,0 +1,46 @@ +import 'package:flutter/cupertino.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_edit_room_info_res.dart'; + +import 'package:aslan/chatvibe_domain/models/res/my_room_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_contribute_level_res.dart'; +class ChatVibeRoomManager extends ChangeNotifier { + MyRoomRes? myRoom; + ATRoomContributeLevelRes? roomContributeLevelRes; + + void getMyRoom() async { + myRoom = null; + myRoom = await AccountRepository().myProfile(); + notifyListeners(); + } + + void updateMyRoomInfo(ATEditRoomInfoRes roomInfo) { + if (myRoom != null && myRoom!.id == roomInfo.id) { + myRoom?.setRoomName = roomInfo.roomName ?? ""; + myRoom?.setRoomDesc = roomInfo.roomDesc ?? ""; + notifyListeners(); + } + } + + void createRoom(BuildContext context) async { + ATLoadingManager.exhibitOperation(context: context); + await AccountRepository().createRoom(); + myRoom = await AccountRepository().myProfile(); + ATTts.show(ATAppLocalizations.of(context)!.createRoomSuccsess); + notifyListeners(); + ATLoadingManager.veilRoutine(); + } + + void contributionLevel(String roomId) { + roomContributeLevelRes = null; + ChatRoomRepository().roomContributionActivity(roomId).then((value){ + roomContributeLevelRes = value; + notifyListeners(); + }); + + } +} diff --git a/lib/chatvibe_managers/rtc_manager.dart b/lib/chatvibe_managers/rtc_manager.dart new file mode 100644 index 0000000..286ccde --- /dev/null +++ b/lib/chatvibe_managers/rtc_manager.dart @@ -0,0 +1,1720 @@ +import 'dart:async'; + +import 'package:agora_rtc_engine/agora_rtc_engine.dart'; +import 'package:fluro/fluro.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/constants/at_room_msg_type.dart'; +import 'package:aslan/chatvibe_core/utilities/at_heartbeat_utils.dart'; +import 'package:aslan/chatvibe_core/utilities/at_permission_utils.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/family_repository_impl.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_managers/room_manager.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/redpack/room_redenvelope_list_page.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_msg_item.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/at_float_ichart.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/routes/at_routes.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_lk_dialog_util.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_core/utilities/at_gift_vap_svga_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/family_base_info_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/game_ludo_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/join_room_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/mic_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_res.dart'; +import 'package:aslan/chatvibe_features/room/voice_room_route.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/empty_mai_select.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/join_room_member_page.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/at_room_user_info_card.dart'; +import 'package:aslan/chatvibe_managers/audio_manager.dart'; + +import '../chatvibe_data/models/enum/at_heartbeat_status.dart'; +import '../chatvibe_data/models/enum/at_room_info_event_type.dart'; +import '../chatvibe_data/models/enum/at_room_roles_type.dart'; +import '../chatvibe_data/models/enum/at_vip_type.dart'; +import '../chatvibe_data/sources/local/at_foreground_service_helper.dart'; +import '../chatvibe_domain/models/res/at_get_list_game_config_res.dart'; +import '../chatvibe_domain/models/res/at_is_follow_room_res.dart'; +import '../chatvibe_domain/models/res/at_room_red_packet_list_res.dart'; +import '../chatvibe_domain/models/res/at_room_rocket_status_res.dart'; +import '../chatvibe_domain/models/res/at_room_theme_list_res.dart'; +import '../chatvibe_features/gift/gift_page.dart'; +import '../chatvibe_ui/components/at_tts.dart'; +import '../chatvibe_ui/widgets/room/game/room_game_list_page.dart'; + +typedef OnSoundVoiceChange = Function(num index, int volum); +typedef RtcProvider = RealTimeCommunicationManager; + +class RealTimeCommunicationManager extends ChangeNotifier { + bool needUpDataUserInfo = false; + + ///当前所在房间 + JoinRoomRes? currenRoom; + + /// 声音音量变化监听 + List _onSoundVoiceChangeList = []; + + ///麦位 + Map roomWheatMap = {}; + final Set _remoteRtcUidsInChannel = {}; + Timer? _roomStateResyncTimer; + Timer? _pendingMicResyncTimer; + bool _isRefreshingMicList = false; + static const Duration _roomStateResyncInterval = Duration(seconds: 15); + static const Duration _micChangeResyncDelay = Duration(milliseconds: 800); + BigInt? _lastMicChangeTimeId; + + RtcEngine? engine; + BuildContext? context; + RtmProvider? rtmProvider; + + ATIsFollowRoomRes? isFollowRoomRes; + ATRoomRocketStatusRes? roomRocketStatus; + ChatVibeFamilyBaseInfoRes? familyBaseInfoRes; + + ///正在玩的ludo游戏 + GameLudoRes? playingLudoGame; + + ///是否是最小化ludo游戏 + bool isMinLudoGame = false; + + ///房间是否静音 + bool roomIsMute = false; + + bool closeFullGame = false; + + ///禁音开关 默认关闭 + bool isMic = true; + + ///在线用户列表 + List onlineUsers = []; + + ///房间管理员 + List managerUsers = []; + + ///音乐是否正在播放 + bool isMusicPlaying = false; + + ///房间红包列表 + List redPacketList = []; + + num roomTaskClaimableCount = 0; + bool _isJoiningRoom = false; + bool _isExitingRoom = false; + int _roomSessionId = 0; + Future? _exitRoomFuture; + + init(BuildContext context) { + this.context = context; + } + + int _nextRoomSessionId() { + _roomSessionId++; + return _roomSessionId; + } + + bool _isRoomSessionActive(int roomSessionId, String roomId) { + return !_isExitingRoom && + _roomSessionId == roomSessionId && + currenRoom?.roomProfile?.roomProfile?.id == roomId; + } + + Future joinAgoraChannel({ + required int roomSessionId, + required String roomId, + }) async { + try { + final RtcEngine rtcEngine = await _initAgoraRtcEngine(); + if (!_isRoomSessionActive(roomSessionId, roomId)) { + await _shutdownRtcEngine(rtcEngine); + return; + } + engine = rtcEngine; + _remoteRtcUidsInChannel.clear(); + rtcEngine.setAudioProfile( + profile: AudioProfileType.audioProfileIot, + scenario: AudioScenarioType.audioScenarioMeeting, + ); + rtcEngine.enableAudioVolumeIndication( + interval: 500, + smooth: 3, + reportVad: true, + ); + await rtcEngine.disableVideo(); + await rtcEngine.setLocalPublishFallbackOption( + StreamFallbackOptions.streamFallbackOptionAudioOnly, + ); + rtcEngine.registerEventHandler( + RtcEngineEventHandler( + onError: (ErrorCodeType err, String msg) { + if (!_isRoomSessionActive(roomSessionId, roomId)) { + return; + } + print('rtc错误${err}'); + }, + onLocalAudioStateChanged: + ( + RtcConnection connection, + LocalAudioStreamState state, + LocalAudioStreamReason reason, + ) {}, + onAudioRoutingChanged: (routing) {}, + onAudioMixingStateChanged: ( + AudioMixingStateType state, + AudioMixingReasonType reason, + ) { + if (!_isRoomSessionActive(roomSessionId, roomId)) { + return; + } + Provider.of( + context!, + listen: false, + ).setPlayState(state, context!); + switch (state) { + case AudioMixingStateType.audioMixingStatePlaying: + isMusicPlaying = true; + break; + case AudioMixingStateType.audioMixingStateStopped: + case AudioMixingStateType.audioMixingStateFailed: + isMusicPlaying = false; + break; + default: + break; + } + }, + onRemoteAudioStateChanged: ( + RtcConnection connection, + int remoteUid, + RemoteAudioState state, + RemoteAudioStateReason reason, + int elapsed, + ) { + // print('用户 $remoteUid 音频状态: $state, 原因: $reason'); + }, + onJoinChannelSuccess: (RtcConnection connection, int elapsed) { + if (!_isRoomSessionActive(roomSessionId, roomId)) { + return; + } + print('rtc 自己加入 ${connection.channelId} ${connection.localUid}'); + }, + onUserJoined: (connection, remoteUid, elapsed) { + if (!_isRoomSessionActive(roomSessionId, roomId)) { + return; + } + print('rtc用户 $remoteUid 加入了频道'); + _remoteRtcUidsInChannel.add(remoteUid); + _applyRemoteAudioPolicy(); + }, + // 监听远端用户离开 + onUserOffline: (connection, remoteUid, reason) { + if (!_isRoomSessionActive(roomSessionId, roomId)) { + return; + } + print('rtc用户 $remoteUid 离开了频道 (原因: ${reason})'); + _remoteRtcUidsInChannel.remove(remoteUid); + _applyRemoteAudioPolicy(); + }, + onTokenPrivilegeWillExpire: ( + RtcConnection connection, + String token, + ) async { + if (!_isRoomSessionActive(roomSessionId, roomId)) { + return; + } + var rtcToken = await AccountRepository().getRtcToken( + roomId, + AccountStorage().getCurrentUser()?.userProfile?.id ?? "", + ); + if (_isRoomSessionActive(roomSessionId, roomId) && + identical(engine, rtcEngine)) { + rtcEngine.renewToken(rtcToken.rtcToken ?? ""); + } + }, + onAudioVolumeIndication: ( + RtcConnection connection, + List speakers, + int speakerNumber, + int totalVolume, + ) { + if (!_isRoomSessionActive(roomSessionId, roomId)) { + return; + } + initOnAudioVolumeIndication( + connection, + speakers, + speakerNumber, + totalVolume, + ); + }, + ), + ); + var rtcToken = await AccountRepository().getRtcToken( + roomId, + AccountStorage().getCurrentUser()?.userProfile?.id ?? "", + ); + if (!_isRoomSessionActive(roomSessionId, roomId)) { + if (identical(engine, rtcEngine)) { + engine = null; + } + await _shutdownRtcEngine(rtcEngine); + return; + } + await rtcEngine.joinChannel( + token: rtcToken.rtcToken ?? "", + channelId: roomId, + uid: int.parse( + AccountStorage().getCurrentUser()?.userProfile?.account ?? "0", + ), + options: ChannelMediaOptions( + // 自动订阅所有视频流 + autoSubscribeVideo: false, + // 自动订阅所有音频流 + autoSubscribeAudio: true, + // 发布摄像头采集的视频 + publishCameraTrack: false, + // 发布麦克风采集的音频 + publishMicrophoneTrack: true, + // 设置用户角色为 clientRoleBroadcaster(主播)或 clientRoleAudience(观众) + clientRoleType: ClientRoleType.clientRoleAudience, + channelProfile: ChannelProfileType.channelProfileLiveBroadcasting, + ), + ); + if (!_isRoomSessionActive(roomSessionId, roomId)) { + if (identical(engine, rtcEngine)) { + engine = null; + } + await _shutdownRtcEngine(rtcEngine); + return; + } + rtcEngine.muteAllRemoteAudioStreams(roomIsMute); + await syncLocalPublishStateFromMicSnapshot(); + await _applyRemoteAudioPolicy(); + } catch (e) { + if (_isRoomSessionActive(roomSessionId, roomId)) { + ATTts.show("Join room fail"); + extRoom(false); + } + print('加入失败:${e.runtimeType},${e.toString()}'); + } + } + + Future _initAgoraRtcEngine() async { + RtcEngine? engine; + while (engine == null) { + engine = createAgoraRtcEngine(); + await engine.initialize( + RtcEngineContext(appId: ATGlobalConfig.agoraRtcAppid), + ); + } + return engine; + } + + int? _parseRtcUid(String? account) { + if (account == null || account.isEmpty) { + return null; + } + return int.tryParse(account); + } + + Set _buildAllowedSpeakerUids() { + final Set allowed = {}; + for (final mic in roomWheatMap.values) { + if (mic.user == null) { + continue; + } + if (mic.micMute == true) { + continue; + } + final int? uid = _parseRtcUid(mic.user?.account); + if (uid != null) { + allowed.add(uid); + } + } + return allowed; + } + + Future _applyRemoteAudioPolicy() async { + if (engine == null) { + return; + } + final Set allowedSpeakerUids = _buildAllowedSpeakerUids(); + for (final int uid in _remoteRtcUidsInChannel) { + final bool shouldMute = roomIsMute || !allowedSpeakerUids.contains(uid); + try { + await engine?.muteRemoteAudioStream(uid: uid, mute: shouldMute); + } catch (_) { + // 忽略单个用户静音失败,避免中断整体策略应用 + } + } + } + + void _startRoomStateResyncTimer() { + _stopRoomStateResyncTimer(); + _roomStateResyncTimer = Timer.periodic(_roomStateResyncInterval, (_) { + if (currenRoom == null) { + return; + } + getMicList(); + getOnlineUsers(); + }); + } + + void _stopRoomStateResyncTimer() { + _roomStateResyncTimer?.cancel(); + _roomStateResyncTimer = null; + } + + void _scheduleMicStateResync() { + _pendingMicResyncTimer?.cancel(); + _pendingMicResyncTimer = Timer(_micChangeResyncDelay, () { + if (currenRoom != null && !_isRefreshingMicList && !_isExitingRoom) { + getMicList(); + } + }); + } + + BigInt? _parseMicChangeTimeId(String? timeId) { + if (timeId == null || timeId.isEmpty) { + return null; + } + return BigInt.tryParse(timeId); + } + + bool _shouldAcceptMicChange(String? timeId) { + final BigInt? nextTimeId = _parseMicChangeTimeId(timeId); + if (nextTimeId == null) { + return true; + } + if (_lastMicChangeTimeId != null && nextTimeId < _lastMicChangeTimeId!) { + return false; + } + _lastMicChangeTimeId = nextTimeId; + return true; + } + + MicRes _cloneMicWithUser(MicRes mic, ChatVibeUserProfile? user) { + return MicRes( + roomId: mic.roomId, + micIndex: mic.micIndex, + micLock: mic.micLock, + micMute: mic.micMute, + user: user, + roomToken: mic.roomToken, + emojiPath: mic.emojiPath, + type: mic.type, + number: mic.number, + ); + } + + List _normalizeMicSnapshot(List mics) { + final Set seenUserIds = {}; + final List normalized = []; + for (final mic in mics) { + final String? userId = mic.user?.id; + if (userId != null && userId.isNotEmpty) { + if (seenUserIds.contains(userId)) { + normalized.add(_cloneMicWithUser(mic, null)); + continue; + } + seenUserIds.add(userId); + } + normalized.add(mic); + } + return normalized; + } + + Future _shutdownRtcEngine(RtcEngine currentEngine) async { + try { + await currentEngine.muteLocalAudioStream(true); + } catch (_) {} + try { + await currentEngine.muteAllRemoteAudioStreams(true); + } catch (_) {} + try { + await currentEngine.stopAudioMixing(); + } catch (_) {} + try { + await currentEngine.setClientRole( + role: ClientRoleType.clientRoleAudience, + ); + } catch (_) {} + try { + await currentEngine.leaveChannel(); + } catch (_) {} + try { + await Future.delayed(Duration(milliseconds: 100)); + await currentEngine.release(); + } catch (_) {} + } + + Future _disposeRtcEngine() async { + final currentEngine = engine; + engine = null; + if (currentEngine == null) { + _remoteRtcUidsInChannel.clear(); + return; + } + await _shutdownRtcEngine(currentEngine); + _remoteRtcUidsInChannel.clear(); + } + + Future syncLocalPublishStateFromMicSnapshot() async { + if (engine == null) { + return; + } + final String? myUserId = AccountStorage().getCurrentUser()?.userProfile?.id; + if (myUserId == null || myUserId.isEmpty) { + return; + } + + final num myMicIndex = userOnMaiInIndex(myUserId); + final bool isOnMic = myMicIndex > -1; + final bool isMicMutedByRoom = + isOnMic ? (roomWheatMap[myMicIndex]?.micMute ?? true) : true; + final bool shouldPublish = isOnMic && !isMicMutedByRoom && !isMic; + final bool shouldKeepMusicPublishing = + isOnMic && !isMicMutedByRoom && isMic && isMusicPlaying; + + if (shouldPublish) { + await engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster); + adjustRecordingSignalVolume(100); + await engine?.muteLocalAudioStream(false); + ATHeartbeatUtils.scheduleAnchorHeartbeat( + currenRoom?.roomProfile?.roomProfile?.id ?? "", + ); + return; + } + + if (shouldKeepMusicPublishing) { + await engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster); + adjustRecordingSignalVolume(0); + await engine?.muteLocalAudioStream(false); + ATHeartbeatUtils.scheduleAnchorHeartbeat( + currenRoom?.roomProfile?.roomProfile?.id ?? "", + ); + return; + } + + await engine?.setClientRole(role: ClientRoleType.clientRoleAudience); + await engine?.muteLocalAudioStream(true); + if (!isOnMic) { + isMic = true; + stopMusic(); + } + } + + void initOnAudioVolumeIndication( + RtcConnection connection, + List speakers, + int speakerNumber, + int totalVolume, + ) { + // if (user == null) { + // throw "本地User尚未初始化"; + // } + // print('初始化声音监听器:${user.toJson()}'); + + ///声音全部清零 + roomWheatMap.forEach((k, v) { + roomWheatMap[k]!.setVolume = 0; + }); + for (var info in speakers) { + num? uid = info.uid; + int voloum = info.volume!; + print('${info.uid}在说话---${voloum}'); + + if (voloum > 0) { + ///是本地声音 + if (uid == 0) { + uid = num.parse( + AccountStorage().getCurrentUser()!.userProfile!.account!, + ); + } + + ///在哪个麦 + roomWheatMap.forEach((k, v) { + if (v.user != null) { + if (num.parse(v.user!.account!) == uid) { + roomWheatMap[k]!.setVolume = voloum; + for (var element in _onSoundVoiceChangeList) { + element.call(k, voloum); + } + } + } + }); + } + } + } + + ///关注/取消房间 + void followRoom() async { + var result = await AccountRepository().followRoom( + currenRoom?.roomProfile?.roomProfile?.id ?? "", + ); + if (isFollowRoomRes != null) { + isFollowRoomRes = isFollowRoomRes!.copyWith( + followRoom: !isFollowRoomRes!.followRoom!, + ); + } else { + isFollowRoomRes = ATIsFollowRoomRes(followRoom: result); + } + SmartDialog.dismiss(tag: "unFollowDialog"); + notifyListeners(); + } + + int startTime = 0; + + void joinRoom( + BuildContext context, + String roomId, { + String? pwd, + bool clearRoomData = false, + bool needOpenRedenvelope = false, + String redPackId = "", + bool openGameDialog = false, + bool openGiftDialogToLuckyGift = false, + bool openGiftDialogToGift = false, + }) async { + if (_isJoiningRoom || _isExitingRoom) { + return; + } + _isJoiningRoom = true; + int nextTime = DateTime.now().millisecondsSinceEpoch; + if (nextTime - startTime < 1000) { + //频繁点击 + _isJoiningRoom = false; + return; + } + startTime = nextTime; + try { + if (clearRoomData) { + if (currenRoom != null || engine != null) { + await extRoom(false, navigateHome: false); + } else { + _nextRoomSessionId(); + _clearData(); + } + } + bool hasPermission = await ATPermissionUtils.checkMicrophonePermission(); + if (!hasPermission) { + ATTts.show('Microphone permission is denied.'); + return; + } + if (roomId == currenRoom?.roomProfile?.roomProfile?.id) { + ///最小化进入房间,或者进入的是同一个房间 + loadRoomInfo(currenRoom?.roomProfile?.roomProfile?.id ?? ""); + Provider.of( + context, + listen: false, + ).getMyUserInfo(); + getMicList(); + ATFloatIchart().remove(); + ATNavigatorUtils.push( + context, + '${VoiceRoomRoute.voiceRoom}?id=$roomId', + transition: TransitionType.custom, + transitionDuration: kOpenRoomTransitionDuration, + transitionBuilder: ( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) { + return kOpenRoomTransitionBuilder( + context, + animation, + secondaryAnimation, + child, + ); + }, + ); + } else { + ATFloatIchart().remove(); + if (currenRoom != null) { + // 切房时先退出当前房间,但不跳回 Home,避免 context 失效导致后续 join 偶发失败 + await extRoom(false, navigateHome: false); + } + rtmProvider = Provider.of(context, listen: false); + ATLoadingManager.exhibitOperation(context: context); + currenRoom = await AccountRepository().entryRoom( + roomId, + pwd: pwd, + redPackId: redPackId, + needOpenRedenvelope: needOpenRedenvelope, + ); + final int roomSessionId = _nextRoomSessionId(); + if (currenRoom?.entrants?.roles == ATRoomRolesType.TOURIST.name) { + SmartDialog.show( + tag: "showJoinRoomMember", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + clickMaskDismiss: false, + backType: SmartBackType.block, + builder: (_) { + return JoinRoomMemberPage( + currenRoom?.roomProfile?.roomProfile?.id ?? "", + () { + ATLoadingManager.exhibitOperation(context: context); + //加入会员 + ChatRoomRepository() + .changeRoomRole( + roomId, + ATRoomRolesType.ADMIN.name, + AccountStorage().getCurrentUser()?.userProfile?.id ?? + "", + AccountStorage().getCurrentUser()?.userProfile?.id ?? + "", + "ONESELF_JOIN", + ) + .then( + (result) async { + currenRoom = currenRoom?.copyWith( + entrants: currenRoom?.entrants?.copyWith( + roles: ATRoomRolesType.MEMBER.name, + ), + ); + await initRoon( + roomSessionId: roomSessionId, + needOpenRedenvelope: needOpenRedenvelope, + redPackId: redPackId, + ); + ATLoadingManager.veilRoutine(); + notifyListeners(); + }, + onError: (e) { + currenRoom = null; + ATLoadingManager.veilRoutine(); + }, + ); + }, + () { + _nextRoomSessionId(); + currenRoom = null; + }, + () async { + await initRoon( + roomSessionId: roomSessionId, + needOpenRedenvelope: needOpenRedenvelope, + redPackId: redPackId, + ); + ATLoadingManager.veilRoutine(); + notifyListeners(); + }, + ); + }, + ); + ATLoadingManager.veilRoutine(); + } else { + await initRoon( + roomSessionId: roomSessionId, + needOpenRedenvelope: needOpenRedenvelope, + redPackId: redPackId, + ); + ATLoadingManager.veilRoutine(); + notifyListeners(); + } + } + } catch (e) { + ATLoadingManager.veilRoutine(); + print('joinRoom fail: $e'); + } finally { + _isJoiningRoom = false; + } + + if (openGameDialog) { + showBottomInBottomDialog(context, RoomGameListPage()); + } + if (openGiftDialogToLuckyGift) { + SmartDialog.show( + tag: "showGiftControl", + alignment: Alignment.bottomCenter, + maskColor: Colors.transparent, + animationType: SmartAnimationType.fade, + clickMaskDismiss: true, + builder: (_) { + return GiftPage(initialIndex: 1); + }, + ); + } + if (openGiftDialogToGift) { + SmartDialog.show( + tag: "showGiftControl", + alignment: Alignment.bottomCenter, + maskColor: Colors.transparent, + animationType: SmartAnimationType.fade, + clickMaskDismiss: true, + builder: (_) { + return GiftPage(); + }, + ); + } + } + + ///初始化房间相关 + Future initRoon({ + required int roomSessionId, + bool needOpenRedenvelope = false, + String? redPackId, + }) async { + final String roomId = currenRoom?.roomProfile?.roomProfile?.id ?? ""; + if (roomId.isEmpty || !_isRoomSessionActive(roomSessionId, roomId)) { + return; + } + if ((currenRoom?.roomProfile?.roomProfile?.event == + ATRoomInfoEventType.WAITING_CONFIRMED.name || + currenRoom?.roomProfile?.roomProfile?.event == + ATRoomInfoEventType.ID_CHANGE.name) && + currenRoom?.entrants?.roles == ATRoomRolesType.HOMEOWNER.name) { + ///需要去修改房间信息,创建群聊 + ATNavigatorUtils.push( + context!, + "${VoiceRoomRoute.roomEdit}?need=true", + replace: false, + ); + return Future; + } + ATRoomUtils.roomAtGlobalConfig( + currenRoom?.roomProfile?.roomProfile?.id ?? "", + ); + if (currenRoom?.roomProfile?.roomProfile?.id != + AccountStorage().getCurrentUser()?.userProfile?.id) { + ChatRoomRepository() + .isFollowRoom(currenRoom?.roomProfile?.roomProfile?.id ?? "") + .then((value) { + isFollowRoomRes = value; + notifyListeners(); + }); + } + ATNavigatorUtils.push(context!, VoiceRoomRoute.voiceRoom, replace: false); + + ///暂时让他每次都走一次创建群组,新的腾讯云IM数据迁移 + await Provider.of(context!, listen: false).createRoomGroup( + currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "", + currenRoom?.roomProfile?.roomProfile?.roomName ?? "", + ); + var joinResult = await rtmProvider?.joinRoomGroup( + currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "", + "", + ); + if (!_isRoomSessionActive(roomSessionId, roomId)) { + return; + } + ChatRoomRepository() + .rocketClaim(currenRoom?.roomProfile?.roomProfile?.id ?? "") + .catchError((e) => false); + rtmProvider?.addMsg( + Msg( + groupId: currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "", + msg: ATAppLocalizations.of(context!)!.systemRoomTips, + type: ATRoomMsgType.systemTips, + ), + ); + rtmProvider?.addMsg( + Msg( + groupId: currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "", + msg: currenRoom?.roomProfile?.roomProfile?.roomDesc, + type: ATRoomMsgType.systemTips, + ), + ); + + ///获取麦位 + getMicList(); + Provider.of( + context!, + listen: false, + ).contributionLevel(currenRoom?.roomProfile?.roomProfile?.id ?? ""); + await joinAgoraChannel(roomSessionId: roomSessionId, roomId: roomId); + if (_isRoomSessionActive(roomSessionId, roomId)) { + _startRoomStateResyncTimer(); + } else { + return; + } + if (joinResult?.code == 0) { + Msg joinMsg = Msg( + groupId: currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "", + user: AccountStorage().getCurrentUser()?.userProfile, + msg: "", + role: currenRoom?.entrants?.roles ?? "", + type: ATRoomMsgType.joinRoom, + ); + rtmProvider?.sendMsg(joinMsg, addLocal: true); + if (ATGlobalConfig.isEntryVehicleAnimation) { + if (AccountStorage().getCurrentUser()?.userProfile?.getMountains() != + null) { + Future.delayed(Duration(milliseconds: 500), () { + if (!_isRoomSessionActive(roomSessionId, roomId)) { + return; + } + ATGiftVapSvgaManager().play( + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.getMountains() + ?.sourceUrl ?? + "", + priority: 100, + type: 1, + ); + }); + } + } + if (rtmProvider?.msgUserJoinListener != null) { + rtmProvider?.msgUserJoinListener!(joinMsg); + } + } + ChatRoomRepository() + .rocketStatus(currenRoom?.roomProfile?.roomProfile?.id ?? "") + .then((res) { + if (!_isRoomSessionActive(roomSessionId, roomId)) { + return; + } + roomRocketStatus = res; + notifyListeners(); + }) + .catchError((e) {}); + ATForegroundServiceHelper().startService(); + loadRoomRedPacketList(1); + if (needOpenRedenvelope) { + openRedEnvelope(redPackId ?? ""); + } + GroupRepository() + .familyUserBaseInfo(currenRoom?.roomProfile?.userProfile?.id ?? "") + .then((res) { + if (!_isRoomSessionActive(roomSessionId, roomId)) { + return; + } + familyBaseInfoRes = res; + notifyListeners(); + }); + + getRoomTaskClaimableCount(); + getLudoGameInfo(); + } + + ///更新房间火箭信息 + void updateRoomRocketStatus(ATRoomRocketStatusRes res) { + roomRocketStatus = res; + notifyListeners(); + } + + ///获取在线用户 + Future getOnlineUsers() async { + final int roomSessionId = _roomSessionId; + final String roomId = currenRoom?.roomProfile?.roomProfile?.id ?? ""; + if (roomId.isEmpty) { + return; + } + final users = await ChatRoomRepository().roomOnlineUsers(roomId); + if (!_isRoomSessionActive(roomSessionId, roomId)) { + return; + } + onlineUsers = users; + managerUsers.clear(); + for (var user in onlineUsers) { + if (user.roles == ATRoomRolesType.ADMIN.name) { + managerUsers.add(user); + } + } + notifyListeners(); + } + + Future getMicList() async { + if (currenRoom == null || _isRefreshingMicList) { + return; + } + final int roomSessionId = _roomSessionId; + final String roomId = currenRoom!.roomProfile?.roomProfile?.id ?? ""; + if (roomId.isEmpty) { + return; + } + _isRefreshingMicList = true; + bool isOnMic = false; + try { + final roomWheatList = await ChatRoomRepository().micList(roomId); + if (!_isRoomSessionActive(roomSessionId, roomId)) { + return; + } + roomWheatMap.clear(); + for (final roomWheat in _normalizeMicSnapshot(roomWheatList)) { + roomWheatMap[roomWheat.micIndex!] = roomWheat; + if (roomWheat.user != null && + roomWheat.user!.id == + AccountStorage().getCurrentUser()?.userProfile?.id) { + isOnMic = true; + } + } + notifyListeners(); + await syncLocalPublishStateFromMicSnapshot(); + await _applyRemoteAudioPolicy(); + ATHeartbeatUtils.scheduleHeartbeat( + ATHeartbeatStatus.VOICE_LIVE.name, + isOnMic, + roomId: roomId, + ); + Future.delayed(Duration(milliseconds: 1500), () { + if (_isRoomSessionActive(roomSessionId, roomId)) { + getOnlineUsers(); + } + }); + } finally { + _isRefreshingMicList = false; + } + } + + void getRoomTaskClaimableCount() { + ChatRoomRepository() + .roomTaskClaimableCount() + .then((res) { + roomTaskClaimableCount = res.claimableCount ?? 0; + notifyListeners(); + }) + .catchError((e) {}); + } + + Future> loadRoomRedPacketList( + int current, + ) async { + var result = await ChatRoomRepository().roomRedPacketList( + currenRoom?.roomProfile?.roomProfile?.id ?? "", + current, + ); + if (current == 1) { + redPacketList = result; + notifyListeners(); + } + return result; + } + + Future extRoom(bool isLogout, {bool navigateHome = true}) async { + if (_isExitingRoom) { + return _exitRoomFuture ?? Future.value(); + } + _nextRoomSessionId(); + _isExitingRoom = true; + final JoinRoomRes? leavingRoom = currenRoom; + _exitRoomFuture = _extRoomInternal( + leavingRoom: leavingRoom, + isLogout: isLogout, + navigateHome: navigateHome, + ); + try { + await _exitRoomFuture; + } finally { + _isExitingRoom = false; + _exitRoomFuture = null; + } + } + + Future _extRoomInternal({ + required JoinRoomRes? leavingRoom, + required bool isLogout, + required bool navigateHome, + }) async { + try { + _stopRoomStateResyncTimer(); + _pendingMicResyncTimer?.cancel(); + _pendingMicResyncTimer = null; + rtmProvider?.msgAllListener = null; + rtmProvider?.msgChatListener = null; + rtmProvider?.msgGiftListener = null; + ATLoadingManager.exhibitOperation(context: context); + ATGiftVapSvgaManager().dispose(); + ATHeartbeatUtils.scheduleHeartbeat(ATHeartbeatStatus.ONLINE.name, false); + ATHeartbeatUtils.cancelAnchorTimer(); + rtmProvider?.cleanRoomData(); + playingLudoGame = null; + await _disposeRtcEngine(); + if (leavingRoom != null) { + await rtmProvider?.quitGroup( + leavingRoom.roomProfile?.roomProfile?.roomAccount ?? "", + ); + await AccountRepository().quitRoom( + leavingRoom.roomProfile?.roomProfile?.id ?? "", + ); + } + if (currenRoom == null || + currenRoom?.roomProfile?.roomProfile?.id == + leavingRoom?.roomProfile?.roomProfile?.id) { + _clearData(); + } + ATLoadingManager.veilRoutine(); + if (!isLogout && navigateHome) { + ATNavigatorUtils.popUntil(context!, ModalRoute.withName(ATRoutes.home)); + } + } catch (e) { + print('rtc退出房间出错: $e'); + _clearData(); + ATLoadingManager.veilRoutine(); + if (!isLogout && navigateHome) { + ATNavigatorUtils.popUntil(context!, ModalRoute.withName(ATRoutes.home)); + } + } + } + + ///清空列表数据 + void _clearData() { + _stopRoomStateResyncTimer(); + _pendingMicResyncTimer?.cancel(); + _pendingMicResyncTimer = null; + _remoteRtcUidsInChannel.clear(); + _lastMicChangeTimeId = null; + roomRocketStatus = null; + familyBaseInfoRes = null; + rtmProvider + ?.onNewMessageListenerGroupMap["${currenRoom?.roomProfile?.roomProfile?.roomAccount}"] = + null; + roomWheatMap.clear(); + onlineUsers.clear(); + needUpDataUserInfo = false; + ATRoomUtils.roomUsersMap.clear(); + MsgItem.clearRoomUserCaches(); + roomIsMute = false; + playingLudoGame = null; + isMinLudoGame = false; + rtmProvider?.roomAllMsgList.clear(); + rtmProvider?.roomChatMsgList.clear(); + redPacketList.clear(); + currenRoom = null; + isMic = true; + isMusicPlaying = false; + engine = null; + Provider.of(context!, listen: false).reset(); + ATGiftVapSvgaManager().clearTasks(); + ATRoomUtils.closeAllDialogs(); + ATForegroundServiceHelper().stopService(); + ATGlobalConfig.isEntryVehicleAnimation = true; + ATGlobalConfig.isGiftSpecialEffects = true; + ATGlobalConfig.isFloatingAnimationInGlobal = true; + ATGlobalConfig.isLuckGiftSpecialEffects = true; + } + + void muteAllRemote() { + roomIsMute = !roomIsMute; + engine?.muteAllRemoteAudioStreams(roomIsMute); + _applyRemoteAudioPolicy(); + notifyListeners(); + } + + ///点击的位置 + void clickSite(num index, {ChatVibeUserProfile? clickUser}) { + if (index == -1) { + if (clickUser != null) { + if (clickUser.id == + AccountStorage().getCurrentUser()?.userProfile?.id) { + ///是自己,直接打开资料卡 + showBottomInBottomDialog( + context!, + ATRoomUserInfoCard(userId: clickUser.id), + ); + } else { + showBottomInBottomDialog( + context!, + EmptyMaiSelect(index: index, clickUser: clickUser), + ); + } + } + } else { + ChatVibeUserProfile? roomWheatUser = roomWheatMap[index]?.user; + showBottomInBottomDialog( + context!, + EmptyMaiSelect(index: index, clickUser: roomWheatUser), + ); + // if (roomWheatUser != null) { + // ///麦上有人 + // if (roomWheatUser.id == + // AccountStorage().getCurrentUser()?.ChatVibeUserProfile?.id) { + // ///是自己 + // showBottomInBottomDialog( + // context!, + // EmptyMaiSelect(index: index, clickUser: roomWheatUser), + // ); + // } else { + // showBottomInBottomDialog( + // context!, + // RoomUserInfoCard(userId: roomWheatUser.id), + // ); + // } + // } else { + // showBottomInBottomDialog( + // context!, + // EmptyMaiSelect(index: index, clickUser: roomWheatUser), + // ); + // } + } + } + + addSoundVoiceChangeListener(OnSoundVoiceChange onSoundVoiceChange) { + _onSoundVoiceChangeList.add(onSoundVoiceChange); + print('添加监听:${_onSoundVoiceChangeList.length}'); + } + + removeSoundVoiceChangeListener(OnSoundVoiceChange onSoundVoiceChange) { + _onSoundVoiceChangeList.remove(onSoundVoiceChange); + print('删除监听:${_onSoundVoiceChangeList.length}'); + } + + void shangMai(num index, {String? eventType, String? inviterId}) async { + var myUser = AccountStorage().getCurrentUser()?.userProfile; + if (AccountStorage().getVIP()?.name == ATVIPType.KING.name) { + ///vip5无视所有规则 + } else { + if (currenRoom?.roomProfile?.roomSetting?.touristMike ?? false) { + } else { + if (isTourists()) { + ATTts.show( + ATAppLocalizations.of(context!)!.touristsAreNotAllowedToGoOnTheMic, + ); + return; + } + } + } + + try { + var micGoUpRes = await ChatRoomRepository().micGoUp( + currenRoom?.roomProfile?.roomProfile?.id ?? "", + index, + eventType: eventType, + inviterId: inviterId, + ); + + /// 设置成主播角色 + engine?.renewToken(micGoUpRes.roomToken ?? ""); + if (!micGoUpRes.micMute!) { + if (!isMic) { + engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster); + adjustRecordingSignalVolume(100); + await engine?.muteLocalAudioStream(false); + } + } else { + engine?.setClientRole(role: ClientRoleType.clientRoleAudience); + await engine?.muteLocalAudioStream(true); + } + ATHeartbeatUtils.scheduleAnchorHeartbeat( + currenRoom?.roomProfile?.roomProfile?.id ?? "", + ); + final us = roomWheatMap[index]; + if (us != null) { + roomWheatMap[index] = us.copyWith( + user: ChatVibeUserProfile( + id: myUser?.id, + account: myUser?.account, + userAvatar: myUser?.userAvatar, + userNickname: myUser?.userNickname, + userSex: myUser?.userSex, + ), + ); + } + if ((roomWheatMap[index]?.micMute ?? false)) { + ///房主上麦自动解禁麦位 + if (isFz()) { + jieJinMai(index); + } + } + await syncLocalPublishStateFromMicSnapshot(); + await _applyRemoteAudioPolicy(); + + notifyListeners(); + } catch (ex) { + ATTts.show('Failed to put on the microphone, $ex'); + } + } + + Future xiaMai(num index) async { + try { + await ChatRoomRepository().micGoDown( + currenRoom?.roomProfile?.roomProfile?.id ?? "", + index, + ); + } finally { + //自己 + if (roomWheatMap[index]?.user?.id == + UserManager().getCurrentUser()?.userProfile?.id) { + isMic = true; + } + ATHeartbeatUtils.cancelAnchorTimer(); + + /// 设置成主持人角色 + await engine?.renewToken(""); + roomWheatMap[index]?.setUser = null; + await syncLocalPublishStateFromMicSnapshot(); + await _applyRemoteAudioPolicy(); + notifyListeners(); + } + } + + ///踢人下麦 + Future killXiaMai(String userId) async { + try { + for (final entry in roomWheatMap.entries.toList()) { + final k = entry.key; + final v = entry.value; + if (v.user?.id == userId) { + ///是否可以踢VIP有防踢 + final canKill = await ChatRoomRepository().kickOffMicrophone( + currenRoom?.roomProfile?.roomProfile?.id ?? "", + userId, + ); + if (canKill) { + await ChatRoomRepository().micKill( + currenRoom?.roomProfile?.roomProfile?.id ?? "", + k, + ); + Provider.of(context!, listen: false).sendMsg( + Msg( + groupId: currenRoom?.roomProfile?.roomProfile?.roomAccount, + msg: userId, + type: ATRoomMsgType.killXiaMai, + ), + addLocal: false, + ); + roomWheatMap[k]?.setUser = null; + if (userId == AccountStorage().getCurrentUser()?.userProfile?.id) { + isMic = true; + } + await syncLocalPublishStateFromMicSnapshot(); + await _applyRemoteAudioPolicy(); + notifyListeners(); + } else { + ATTts.show("cant kill the user"); + } + } + } + } catch (e) { + ATTts.show("kill the user fail"); + print('踢人下麦失败'); + } + } + + ///自己是否在麦上 + bool isOnMai() { + return roomWheatMap.values + .map((userWheat) => userWheat.user?.id) + .toList() + .contains(AccountStorage().getCurrentUser()?.userProfile?.id); + } + + ///自己是否在指定的麦上 + bool isOnMaiInIndex(num index) { + return roomWheatMap[index]?.user?.id == + AccountStorage().getCurrentUser()?.userProfile?.id; + } + + ///点击的用户在哪个麦上 + num userOnMaiInIndex(String userId) { + num index = -1; + roomWheatMap.forEach((k, value) { + if (value.user?.id == userId) { + index = k; + } + }); + return index; + } + + ///座位上的用户是否开麦 + bool userOnMaiInIndexOfMute(String userId) { + bool micMute = false; + roomWheatMap.forEach((k, value) { + if (value.user?.id == userId) { + value.micMute; + } + }); + return micMute; + } + + ///是否是房主 + bool isFz() { + return currenRoom?.entrants?.roles == ATRoomRolesType.HOMEOWNER.name; + } + + ///是否是管理 + bool isGL() { + return currenRoom?.entrants?.roles == ATRoomRolesType.ADMIN.name; + } + + ///是否是会员 + bool isHY() { + return currenRoom?.entrants?.roles == ATRoomRolesType.MEMBER.name; + } + + ///是否是游客 + bool isTourists() { + return currenRoom?.entrants?.roles == ATRoomRolesType.TOURIST.name; + } + + ///麦位变动 + Future micChange(List? mics, {String? timeId}) async { + if (mics == null || mics.isEmpty) { + return; + } + if (currenRoom == null || _isExitingRoom) { + return; + } + if (!_shouldAcceptMicChange(timeId)) { + _scheduleMicStateResync(); + return; + } + roomWheatMap.clear(); + for (final mic in _normalizeMicSnapshot(mics)) { + roomWheatMap[mic.micIndex!] = mic; + } + notifyListeners(); + await syncLocalPublishStateFromMicSnapshot(); + await _applyRemoteAudioPolicy(); + _scheduleMicStateResync(); + } + + ///找一个空麦位 -1表示没有空位 + num findWheat() { + for (var entry in roomWheatMap.entries) { + if (entry.value.user == null && !entry.value.micLock!) { + return entry.key; + } + } + return -1; + } + + void loadRoomInfo(String roomId) { + final int roomSessionId = _roomSessionId; + ChatRoomRepository().specific(roomId).then((value) { + if (!_isRoomSessionActive(roomSessionId, roomId)) { + return; + } + currenRoom = currenRoom?.copyWith( + roomProfile: currenRoom?.roomProfile?.copyWith( + roomCounter: value.counter, + roomSetting: value.setting, + roomProfile: currenRoom?.roomProfile?.roomProfile?.copyWith( + roomCover: value.roomCover, + roomName: value.roomName, + roomDesc: value.roomDesc, + ), + ), + ); + + ///如果是游客禁止上麦,已经在麦上的游客需要下麦 + bool touristMike = + currenRoom?.roomProfile?.roomSetting?.touristMike ?? false; + if (!touristMike && isTourists()) { + num index = userOnMaiInIndex( + AccountStorage().getCurrentUser()?.userProfile?.id ?? "", + ); + if (index > -1) { + xiaMai(index); + } + } + notifyListeners(); + }); + } + + ///解锁麦位 + void jieFeng(num index) async { + await ChatRoomRepository().micLock( + currenRoom?.roomProfile?.roomProfile?.id ?? "", + index, + false, + ); + } + + ///锁麦 + void fengMai(num index) async { + await ChatRoomRepository().micLock( + currenRoom?.roomProfile?.roomProfile?.id ?? "", + index, + true, + ); + } + + ///静音麦克风 + void jinMai(num index) async { + await ChatRoomRepository().micMute( + currenRoom?.roomProfile?.roomProfile?.id ?? "", + index, + true, + ); + if (isOnMaiInIndex(index)) { + Provider.of( + context!, + listen: false, + ).engine?.setClientRole(role: ClientRoleType.clientRoleAudience); + } + var mic = roomWheatMap[index]; + if (mic != null) { + roomWheatMap[index] = mic.copyWith(micMute: true); + notifyListeners(); + } + } + + ///解除静音麦克风 + void jieJinMai(num index) async { + await ChatRoomRepository().micMute( + currenRoom?.roomProfile?.roomProfile?.id ?? "", + index, + false, + ); + if (isOnMaiInIndex(index)) { + if (!Provider.of(context!, listen: false).isMic) { + if (roomWheatMap[index]?.user != null) { + adjustRecordingSignalVolume(100); + Provider.of( + context!, + listen: false, + ).engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster); + } + } + } + + var mic = roomWheatMap[index]; + if (mic != null) { + roomWheatMap[index] = mic.copyWith(micMute: false); + notifyListeners(); + } + } + + void addOnlineUser(String groupId, ChatVibeUserProfile user) { + if (groupId != currenRoom?.roomProfile?.roomProfile?.roomAccount) { + return; + } + bool isExtOnlineList = false; + for (var us in onlineUsers) { + if (us.id == user.id) { + isExtOnlineList = true; + break; + } + } + if (!isExtOnlineList) { + Provider.of(context!, listen: false).onlineUsers.add(user); + notifyListeners(); + } + } + + void removOnlineUser(String groupId, String userId) { + if (groupId != currenRoom?.roomProfile?.roomProfile?.roomAccount) { + return; + } + ChatVibeUserProfile? isExtOnlineUser; + for (var us in onlineUsers) { + if (us.id == userId) { + isExtOnlineUser = us; + break; + } + } + if (isExtOnlineUser != null) { + Provider.of( + context!, + listen: false, + ).onlineUsers.remove(isExtOnlineUser); + notifyListeners(); + } + } + + void starPlayEmoji(Msg msg) { + if (msg.number! > -1) { + var mic = roomWheatMap[msg.number]; + if (mic != null && mic.user != null) { + roomWheatMap[msg.number!] = mic.copyWith( + emojiPath: msg.msg, + type: msg.type, + number: msg.msg, + ); + notifyListeners(); + } + } + } + + ///更新房间背景 + void updateRoomBG(ATRoomThemeListRes res) { + var roomTheme = RoomTheme( + expireTime: res.expireTime, + id: res.id, + themeStatus: res.themeStatus, + themeBack: res.themeBack, + ); + currenRoom?.roomProps?.setRoomTheme(roomTheme); + notifyListeners(); + } + + void updateRoomSetting(RoomSetting roomSetting) { + currenRoom?.roomProfile?.setRoomSetting(roomSetting); + notifyListeners(); + } + + Map>> seatGlobalKeyMap = {}; + + void bindTargetKey(num index, GlobalKey> targetKey) { + seatGlobalKeyMap[index] = targetKey; + } + + GlobalKey>? getSeatGlobalKeyByIndex(String userId) { + ///需要有人的座位 + num index = userOnMaiInIndex(userId); + if (index > -1) { + return seatGlobalKeyMap[index]; + } + } + + ///播放音乐 + void playMusic(String localPath, int cycle) { + if (localPath.isEmpty) { + return; + } + engine?.startAudioMixing( + filePath: localPath, + loopback: false, + cycle: cycle, + ); + } + + ///停止播放音乐 + void stopMusic() { + Provider.of(context!, listen: false).reset(); + engine?.stopAudioMixing(); + } + + ///暂停播放音乐 + void pauseMusic() { + engine?.pauseAudioMixing(); + } + + ///恢复播放音乐 + void resumeMusic() { + engine?.resumeAudioMixing(); + } + + void adjustRecordingSignalVolume(int volume) { + engine?.adjustRecordingSignalVolume(volume); + } + + ///打开红包列表页面 + void openRedEnvelope(String redPackId) { + // if (redPackId.isEmpty) { + // return; + // } + SmartDialog.dismiss(tag: "showRoomRedenvelopeList"); + SmartDialog.show( + tag: "showRoomRedenvelopeList", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return RoomRedenvelopeListPage(); + }, + ); + } + + void roomMannyGameClose() { + playingLudoGame = null; + notifyListeners(); + } + + void gameLudoCreate(ATGetListGameConfigRes item) { + if (playingLudoGame == null) { + ChatRoomRepository() + .gameLudoCreate( + currenRoom?.roomProfile?.roomProfile?.id ?? "", + 0, + "1", + currenRoom?.roomProfile?.roomProfile?.userId ?? "", + item.id ?? "", + ) + .then((res) { + playingLudoGame = res; + Provider.of(context!, listen: false).sendMsg( + Msg( + groupId: + currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "", + msg: "${playingLudoGame?.gameInfo?.id}", + type: ATRoomMsgType.roomGameCreate, + ), + addLocal: false, + ); + notifyListeners(); + }); + } + } + + Future gameLudoDisband() async { + if (playingLudoGame != null) { + try { + ATLoadingManager.exhibitOperation(); + await ChatRoomRepository().gameLudoDisband( + currenRoom?.roomProfile?.roomProfile?.id ?? "", + ); + ATLoadingManager.veilRoutine(); + playingLudoGame = null; + Provider.of(context!, listen: false).sendMsg( + Msg( + groupId: currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "", + msg: "${playingLudoGame?.gameInfo?.id}", + type: ATRoomMsgType.roomGameClose, + ), + addLocal: false, + ); + notifyListeners(); + } catch (_) { + ATLoadingManager.veilRoutine(); + } + } + } + + void getLudoGameInfo() { + ChatRoomRepository() + .gameLudo(currenRoom?.roomProfile?.roomProfile?.id ?? "") + .then((res) { + playingLudoGame = res; + notifyListeners(); + }); + } + + void minOrShowLudoGame(bool ms) { + isMinLudoGame = ms; + notifyListeners(); + } + + @override + void dispose() { + _stopRoomStateResyncTimer(); + _pendingMicResyncTimer?.cancel(); + _remoteRtcUidsInChannel.clear(); + super.dispose(); + } + + void openGameDialog() { + showBottomInBottomDialog(context!, RoomGameListPage()); + } + + void openGiftDialogToLuckyGift() { + SmartDialog.show( + tag: "showGiftControl", + alignment: Alignment.bottomCenter, + maskColor: Colors.transparent, + animationType: SmartAnimationType.fade, + clickMaskDismiss: true, + builder: (_) { + return GiftPage(initialIndex: 1); + }, + ); + } + + void openGiftDialogToGift() { + SmartDialog.show( + tag: "showGiftControl", + alignment: Alignment.bottomCenter, + maskColor: Colors.transparent, + animationType: SmartAnimationType.fade, + clickMaskDismiss: true, + builder: (_) { + return GiftPage(); + }, + ); + } +} diff --git a/lib/chatvibe_managers/rtm_manager.dart b/lib/chatvibe_managers/rtm_manager.dart new file mode 100644 index 0000000..46ebc4d --- /dev/null +++ b/lib/chatvibe_managers/rtm_manager.dart @@ -0,0 +1,1480 @@ +import 'dart:collection'; +import 'dart:convert'; +import 'dart:io'; +import 'package:agora_rtc_engine/agora_rtc_engine.dart'; +import 'package:extended_image/extended_image.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_debouncer/flutter_debouncer.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/utilities/at_message_utils.dart'; +import 'package:aslan/chatvibe_core/utilities/at_path_utils.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/floating_screen_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:tencent_cloud_chat_sdk/enum/V2TimAdvancedMsgListener.dart'; +import 'package:tencent_cloud_chat_sdk/enum/V2TimConversationListener.dart'; +import 'package:tencent_cloud_chat_sdk/enum/V2TimGroupListener.dart'; +import 'package:tencent_cloud_chat_sdk/enum/V2TimSDKListener.dart'; +import 'package:tencent_cloud_chat_sdk/enum/group_type.dart'; +import 'package:tencent_cloud_chat_sdk/enum/log_level_enum.dart'; +import 'package:tencent_cloud_chat_sdk/enum/message_status.dart'; +import 'package:tencent_cloud_chat_sdk/manager/v2_tim_group_manager.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation_result.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_info.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_image_elem.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_receipt.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_msg_create_info_result.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_status.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'; +import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_core/constants/at_room_msg_type.dart'; +import 'package:aslan/chatvibe_core/at_lk_event_bus.dart'; +import 'package:aslan/chatvibe_core/utilities/at_gift_vap_svga_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/file_cache_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/config_repository_imp.dart'; +import 'package:aslan/chatvibe_data/models/message/big_broadcast_group_message.dart'; +import 'package:aslan/chatvibe_data/models/message/at_floating_message.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_broad_cast_luck_gift_push.dart'; +import 'package:aslan/chatvibe_domain/models/res/broad_cast_mic_change_push.dart'; +import 'package:aslan/chatvibe_domain/models/res/gift_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_public_message_page_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_rocket_status_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_theme_list_res.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/invite/invite_room_dialog.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_msg_item.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_managers/gift_system_manager.dart'; + +import '../chatvibe_core/utilities/at_heartbeat_utils.dart'; +import '../chatvibe_data/models/enum/at_gift_type.dart'; +import '../chatvibe_data/models/enum/at_room_roles_type.dart'; + +typedef RoomNewMsgListener = Function(Msg msg); +typedef OnNewMessageListener = Function(V2TimMessage? message, {String? msgId}); +typedef OnNewANMessageListener = Function(Records? message); +typedef OnRevokeMessageListener = Function(String msgId); +typedef OnNewGroupMessageListener = + Function(String groupID, V2TimMessage message); +typedef OnMessageRecvC2CReadListener = Function(List messageIDList); +typedef RtmProvider = RealTimeMessagingManager; + +class RealTimeMessagingManager extends ChangeNotifier { + BuildContext? context; + + ///消息列表 + List roomAllMsgList = []; + List roomChatMsgList = []; + List roomGiftMsgList = []; + RoomNewMsgListener? msgAllListener; + RoomNewMsgListener? msgChatListener; + RoomNewMsgListener? msgGiftListener; + + RoomNewMsgListener? msgFloatingGiftListener; + RoomNewMsgListener? msgUserJoinListener; + + /// 当前会话 + V2TimConversation? currentConversation; + + /// 会话列表缓存 + Map conversationMap = {}; + + ///消息已读监听 + OnMessageRecvC2CReadListener? onMessageRecvC2CReadListener; + + ///消息被撤回监听 + OnRevokeMessageListener? onRevokeMessageListener; + + ///新消息监听 单聊 + OnNewMessageListener? onNewMessageCurrentConversationListener; + + OnNewANMessageListener? onNewActivityMessageCurrentConversationListener; + OnNewANMessageListener? onNewNotifcationMessageCurrentConversationListener; + + ///新消息监听 群聊 + Map onNewMessageListenerGroupMap = {}; + int allUnReadCount = 0; + int messageUnReadCount = 0; + int systemUnReadCount = 0; + int customerUnReadCount = 0; + int activityUnReadCount = 0; + int notifcationUnReadCount = 0; + ATBroadCastLuckGiftPush? currentPlayingLuckGift; + final Queue _luckGiftPushQueue = Queue(); + bool _isFinishingLuckGift = false; + Debouncer debouncer = Debouncer(); + List conversationList = []; + + ///客服 + ChatVibeUserProfile? customerInfo; + + void getConversationList() { + List list = conversationMap?.values?.toList() ?? []; + list.removeWhere((element) { + if (element.conversationID == ATGlobalConfig.imAdmin) { + return true; + } + if (element.conversationID == "c2c_${customerInfo?.id}") { + return true; + } + if (element.conversationID == "c2c_atyou-newsletter") { + ///删除这个会话,后台乱发的联系人。。防止无效未读数 + clearC2CHistoryMessage(element.conversationID, false); + return true; + } + if (element.lastMessage == null) { + return true; + } + return false; + }); + list?.sort((e1, e2) { + int time1 = e1.lastMessage?.timestamp ?? 0; + int time2 = e2.lastMessage?.timestamp ?? 0; + return time2.compareTo(time1); + }); + conversationList = list; + systemUnReadCount = + conversationMap[ATGlobalConfig.imAdmin]?.unreadCount ?? 0; + customerUnReadCount = + conversationMap["c2c_${customerInfo?.id}"]?.unreadCount ?? 0; + + notifyListeners(); + } + + init(BuildContext context) async { + this.context = context; + V2TimSDKListener sdkListener = V2TimSDKListener( + onConnectFailed: (int code, String error) { + // 连接失败的回调函数 + // code 错误码 + // error 错误信息 + }, + onConnectSuccess: () { + // SDK 已经成功连接到腾讯云服务器 + }, + onConnecting: () { + // SDK 正在连接到腾讯云服务器 + }, + onKickedOffline: () { + // 当前用户被踢下线,此时可以 UI 提示用户,并再次调用 V2TIMManager 的 login() 函数重新登录。 + }, + onSelfInfoUpdated: (V2TimUserFullInfo info) { + // 登录用户的资料发生了更新 + // info登录用户的资料 + }, + onUserSigExpired: () { + // 在线时票据过期:此时您需要生成新的 userSig 并再次调用 V2TIMManager 的 login() 函数重新登录。 + }, + onUserStatusChanged: (List userStatusList) { + //用户状态变更通知 + //userStatusList 用户状态变化的用户列表 + //收到通知的情况:订阅过的用户发生了状态变更(包括在线状态和自定义状态),会触发该回调 + //在 IM 控制台打开了好友状态通知开关,即使未主动订阅,当好友状态发生变更时,也会触发该回调 + //同一个账号多设备登录,当其中一台设备修改了自定义状态,所有设备都会收到该回调 + }, + ); + V2TimValueCallback initSDKRes = await TencentImSDKPlugin.v2TIMManager + .initSDK( + sdkAppID: int.parse(ATGlobalConfig.tencentImAppid), // SDKAppID + loglevel: LogLevelEnum.V2TIM_LOG_ALL, // 日志登记等级 + listener: sdkListener, // 事件监听器 + ); + if (initSDKRes.code == 0) {} + try { + customerInfo = await ConfigRepositoryImp().customerService(); + } catch (e) {} + + /// 登录 + await loginTencetRtm(context); + + /// 初始化会话列表 + // _onRefreshConversationSub = FTIM.getContactManager().addRefreshConversationListener(_onRefreshConversation); + TencentImSDKPlugin.v2TIMManager + .getConversationManager() + .addConversationListener( + listener: V2TimConversationListener( + onNewConversation: (conversationList) { + // _onRefreshConversation(conversationList); + initConversation(); + }, + onTotalUnreadMessageCountChanged: (int totalUnreadCount) { + messageUnReadCount = totalUnreadCount; + systemUnReadCount = + conversationMap[ATGlobalConfig.imAdmin]?.unreadCount ?? 0; + customerUnReadCount = + conversationMap["c2c_${customerInfo?.id}"]?.unreadCount ?? 0; + allUnReadCount = + messageUnReadCount + + notifcationUnReadCount + + activityUnReadCount; + notifyListeners(); + }, + ), + ); + TencentImSDKPlugin.v2TIMManager.addGroupListener( + listener: V2TimGroupListener( + onMemberEnter: + (String groupID, List memberList) {}, + onMemberLeave: (String groupID, V2TimGroupMemberInfo member) { + Provider.of( + context, + listen: false, + ).removOnlineUser(groupID, member.userID!); + }, + onMemberKicked: ( + String groupID, + V2TimGroupMemberInfo opUser, + List memberList, + ) { + ///踢出房间 + if (memberList.isNotEmpty) { + if (memberList.first.userID == + AccountStorage().getCurrentUser()?.userProfile?.id) { + Provider.of( + context!, + listen: false, + ).engine?.setClientRole(role: ClientRoleType.clientRoleAudience); + + ///退出房间 + Provider.of( + context!, + listen: false, + ).extRoom(false).whenComplete(() { + ATRoomUtils.closeAllDialogs(); + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.kickRoomTips, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () {}, + ); + }, + ); + }); + } + } + }, + ), + ); + + /// 新消息监听 + // FTIM.getMessageManager().addNewMessagesListener(_onNewMessage); + TencentImSDKPlugin.v2TIMManager.getMessageManager().addAdvancedMsgListener( + listener: V2TimAdvancedMsgListener( + onRecvC2CReadReceipt: (List receiptList) { + //会话已读回调 + }, + onRecvMessageModified: (V2TimMessage message) { + // msg 为被修改之后的消息对象 + }, + onRecvMessageReadReceipts: (List receiptList) { + //群聊/单聊已读回调 + List messageIDList = []; + for (var element in receiptList) { + messageIDList.add(element.msgID!); + } + onMessageRecvC2CReadListener?.call(messageIDList); + }, + onRecvMessageRevoked: (String messageId) { + // 在本地维护的消息中处理被对方撤回的消息 + TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .deleteMessages(msgIDs: [messageId]) + .then((result) { + onRevokeMessageListener?.call(messageId); + for (var ms in conversationList) { + if (ms.lastMessage?.msgID == messageId) { + ms.lastMessage?.status = + MessageStatus.V2TIM_MSG_STATUS_LOCAL_REVOKED; + break; + } + } + notifyListeners(); + }); + }, + onRecvNewMessage: (V2TimMessage message) async { + _onNewMessage(message); + }, + onSendMessageProgress: (V2TimMessage message, int progress) { + //文件上传进度回调 + }, + ), + ); + getAllUnReadCount(); + joinBigBroadcastGroup(); + } + + /// 初始化会话 + /// 打开聊天界面的时候调用 + int sTime = 0; + + Future startConversation(V2TimConversation conversation) async { + assert(conversation != null); + int eTime = DateTime.now().millisecondsSinceEpoch; + if (eTime - sTime > 5000) { + sTime = eTime; + currentConversation = conversation; + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .markC2CMessageAsRead(userID: conversation.userID!); + notifyListeners(); + return true; + } else { + return false; + } + } + + ///初始话会话 + Future initConversation() async { + V2TimValueCallback convList = + await TencentImSDKPlugin.v2TIMManager + .getConversationManager() + .getConversationList(nextSeq: '0', count: 100); + List? conversationList = + convList.data?.conversationList; + print('conversationList:${conversationList?.length}'); + conversationMap.clear(); + if (conversationList != null) { + for (V2TimConversation? conversation in conversationList) { + conversationMap[conversation!.conversationID] = conversation; + } + } + getConversationList(); + } + + // /// 所有消息未读数 + // int getAllUnReadCount() { + // allUnReadCount = 0; + // for (var value in conversationList) { + // allUnReadCount += value.unreadCount ?? 0; + // } + // // for (var value in systemConversationList) { + // // i += value.unreadMessageNum; + // // } + // notifyListeners(); + // return allUnReadCount; + // } + + /// 所有消息未读数 + void getAllUnReadCount() async { + V2TimValueCallback res = + await TencentImSDKPlugin.v2TIMManager + .getConversationManager() + .getTotalUnreadMessageCount(); + if (res.code == 0) { + print('初始未读总数: ${res.data}'); + messageUnReadCount = res.data ?? 0; + allUnReadCount = + messageUnReadCount + activityUnReadCount + notifcationUnReadCount; + notifyListeners(); + // 这里可以先用初始值更新UI + } + } + + ///登录IM + Future loginTencetRtm(BuildContext context) async { + ChatVibeLoginRes? userModel = AccountStorage().getCurrentUser(); + bool logined = false; + + while (!logined && userModel != null) { + await Future.delayed(Duration(milliseconds: 550)); + try { + if (userModel.userSig != null) { + V2TimCallback res = await TencentImSDKPlugin.v2TIMManager.login( + userID: userModel.userProfile?.id ?? "", + userSig: userModel.userSig ?? "", + ); + print( + 'tim voLogin:${res.code},${userModel.userProfile?.id},${userModel.userSig}', + ); + if (res.code == 0) { + isLogout = false; + // 登录成功逻辑 + logined = true; + print('tim 登录成功'); + await initConversation(); + } else { + // 登录失败逻辑 + //print('timm 需要重新登录2'); + print('tim 登录失败${res.code}'); + ATTts.show('tim login fail'); + } + } else { + //print('timm 需要重新登录sign'); + ATTts.show('tim login fail'); + } + } catch (e) { + //print('timm 登录异常:${e.toString()}'); + ATTts.show('timm login fail:${e.toString()}'); + } + userModel = AccountStorage().getCurrentUser(); + } + } + + _onNewMessage(V2TimMessage message) { + if (message.groupID != null) { + ///全服通知 + if (message.groupID == ATGlobalConfig.bigBroadcastGroup) { + _newBroadCastMsgRecv(message.groupID!, message); + } + + ///群消息 + for (var element in onNewMessageListenerGroupMap.values) { + element?.call(message.groupID!, message); + } + } else { + ///单聊消息 + _onNew1v1Message(message); + } + } + + _onNew1v1Message(V2TimMessage? message, {String? msgId}) async { + for (var element in conversationList) { + if (message?.userID == element?.userID) { + element.lastMessage = message; + if (onNewMessageCurrentConversationListener == null) { + element.unreadCount = element.unreadCount! + 1; + } + } + } + if (message?.userID == customerInfo?.id) { + if (onNewMessageCurrentConversationListener == null) { + conversationMap["c2c_${customerInfo?.id}"]?.unreadCount = + (conversationMap["c2c_${customerInfo?.id}"]?.unreadCount ?? 0) + 1; + } + } + if (message?.userID == "atyou-admin") { + if (onNewMessageCurrentConversationListener == null) { + conversationMap[ATGlobalConfig.imAdmin]?.unreadCount = + (conversationMap[ATGlobalConfig.imAdmin]?.unreadCount ?? 0) + 1; + } + } + notifyListeners(); + onNewMessageCurrentConversationListener?.call(message, msgId: msgId); + } + + void _onRefreshConversation(List conversations) { + for (V2TimConversation conversation in conversations) { + conversationMap[conversation.conversationID] = conversation; + } + getConversationList(); + } + + ///创建房间im群聊 + Future> createRoomGroup( + String groupID, + String groupName, + ) { + return V2TIMGroupManager().createGroup( + groupID: groupID, + groupType: GroupType.AVChatRoom, + groupName: groupName, + ); + } + + ///加入房间的im群聊 + Future joinRoomGroup(String groupID, String message) async { + _luckGiftPushQueue.clear(); + currentPlayingLuckGift = null; + var joinResult = await TencentImSDKPlugin.v2TIMManager.joinGroup( + groupID: groupID, + message: message, + ); + if (joinResult.code == 0) { + onNewMessageListenerGroupMap[groupID] = _newGroupMsg; + } + return joinResult; + } + + ///发送文本消息 + Future sendMsg( + Msg msg, { + bool showEmoticons = true, + bool addLocal = true, + }) async { + if (addLocal) { + addMsg(msg); + } + // 发送消息到腾讯云IM + if (msg.groupId != null) { + await _sendTencentMessage(msg); + } + notifyListeners(); + } + + // 发送腾讯云消息 + Future _sendTencentMessage(Msg msg) async { + try { + switch (msg.type) { + case ATRoomMsgType.text: + case ATRoomMsgType.shangMai: + case ATRoomMsgType.emoticons: + case ATRoomMsgType.xiaMai: + case ATRoomMsgType.killXiaMai: + case ATRoomMsgType.roomRoleChange: + case ATRoomMsgType.roomSettingUpdate: + case ATRoomMsgType.roomBGUpdate: + case ATRoomMsgType.qcfj: + case ATRoomMsgType.fengMai: + case ATRoomMsgType.jieFeng: + case ATRoomMsgType.joinRoom: + case ATRoomMsgType.gift: + case ATRoomMsgType.bsm: + case ATRoomMsgType.roomDice: + case ATRoomMsgType.roomRPS: + case ATRoomMsgType.roomLuckNumber: + case ATRoomMsgType.image: + case ATRoomMsgType.roomGameClose: + case ATRoomMsgType.roomGameCreate: + case ATRoomMsgType.luckGiftAnimOther: + _sendRoomMessage(msg); + break; + default: + break; + } + } catch (e) { + print('发送消息失败: $e'); + // 处理发送失败的情况 + } + } + + ///发送单聊文本消息 + Future sendC2CTextMsg( + String msg, + V2TimConversation toConversation, + ) async { + // 创建文本消息 + V2TimValueCallback createTextMessageRes = + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .createTextMessage( + text: msg, // 文本信息 + ); + if (createTextMessageRes.code == 0) { + // 文本信息创建成功 + String id = createTextMessageRes.data!.id!; + // 发送文本消息 + // 在sendMessage时,若只填写receiver则发个人用户单聊消息 + // 若只填写groupID则发群组消息 + // 若填写了receiver与groupID则发群内的个人用户,消息在群聊中显示,只有指定receiver能看见 + String receiveId = toConversation.userID!; + V2TimValueCallback sendMessageRes = await TencentImSDKPlugin + .v2TIMManager + .getMessageManager() + .sendMessage( + id: id, // 创建的messageid + receiver: toConversation.userID!, // 接收人id + needReadReceipt: true, + groupID: '', // 是否需要已读回执 + ); + if (sendMessageRes.code == 0) { + // 发送成功 + _onNew1v1Message(sendMessageRes.data); + } else { + ATTts.show( + 'create fail,code:${sendMessageRes.code},${sendMessageRes.desc}', + ); + } + } + } + + ///发送单聊自定义消息 + Future sendC2CCustomMsg( + String msg, + V2TimConversation toConversation, + String extension, + ) async { + // 创建文本消息 + V2TimValueCallback createCustomMessageRes = + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .createCustomMessage( + data: msg, // 文本信息 + extension: extension, + ); + if (createCustomMessageRes.code == 0) { + // 文本信息创建成功 + String id = createCustomMessageRes.data!.id!; + // 发送文本消息 + // 在sendMessage时,若只填写receiver则发个人用户单聊消息 + // 若只填写groupID则发群组消息 + // 若填写了receiver与groupID则发群内的个人用户,消息在群聊中显示,只有指定receiver能看见 + String receiveId = toConversation.userID!; + V2TimValueCallback sendMessageRes = await TencentImSDKPlugin + .v2TIMManager + .getMessageManager() + .sendMessage( + id: id, + // 创建的messageid + receiver: toConversation.userID!, + // 接收人id + needReadReceipt: true, + isSupportMessageExtension: true, + groupID: '', // 是否需要已读回执 + ); + if (sendMessageRes.code == 0) { + // 发送成功 + _onNew1v1Message(sendMessageRes.data); + } else { + ATTts.show( + 'create fail,code:${sendMessageRes.code},${sendMessageRes.desc}', + ); + } + } + } + + ///发送单聊图片消息 + Future sendImageMsg({ + List? selectedList, + File? file, + required V2TimConversation conversation, + }) async { + if (file != null) { + File newFile = await ATMessageUtils.buildImageElem(file); + V2TimValueCallback createImageMessageRes = + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .createImageMessage(imagePath: newFile.path); + if (createImageMessageRes.code == 0) { + String id = createImageMessageRes.data!.id!; + V2TimValueCallback sendMessageRes = + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .sendMessage( + id: id, + receiver: conversation.userID!, + needReadReceipt: true, + groupID: '', + ); + if (sendMessageRes.code == 0) { + // 发送成功 + _onNew1v1Message(sendMessageRes.data); + } else { + ATTts.show( + 'create fail,code:${sendMessageRes.code},${sendMessageRes.desc}', + ); + } + } + } else { + if (selectedList != null) { + for (File entity in selectedList) { + String id = ""; + V2TimMessage? message; + //判断是视频或者图片消息 + + if (ATPathUtils.receiveFileType(entity.path) == "image") { + V2TimValueCallback createImageMessageRes = + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .createImageMessage(imagePath: entity.path); + if (createImageMessageRes.code == 0) { + message = createImageMessageRes.data?.messageInfo; + id = createImageMessageRes.data!.id!; + // 创建图片 + File newFile = await ATMessageUtils.buildImageElem(entity); + //发送 + V2TimImageElem elem = V2TimImageElem(path: newFile.path); + message?.imageElem = elem; + } + } else if (ATPathUtils.receiveFileType(entity.path) == "video_pic") { + if (entity.lengthSync() > 50000000) { + ATTts.show(ATAppLocalizations.of(context!)!.theVideoSizeCannotExceed); + return; + } + // 复制一份视频 + String md5Str1 = keyToMd5(entity.path); + File newFile = File( + "${FileCacheManager.videoCachePath}/$md5Str1.mp4", + ); + if (!newFile.existsSync()) { + await entity.copy(newFile.path); + } + // 创建缩略图 + String? thumbImagePath = await ATMessageUtils.produceFileThumbnailProcess( + newFile.path, + 128, + ); + V2TimValueCallback createVideoMessageRes = + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .createVideoMessage( + videoFilePath: entity.path, + type: "mp4", + duration: 0, + snapshotPath: thumbImagePath ?? "", + ); + + if (createVideoMessageRes.code == 0) { + message = createVideoMessageRes.data?.messageInfo; + id = createVideoMessageRes.data!.id!; + } + } + // 消息设置 + message?.isSelf = true; + // message.conversation = conversation; + message?.status = MessageStatus.V2TIM_MSG_STATUS_SENDING; + print('组装完成,准备发送:${message?.toJson()}'); + // String id = await FTIM.getMessageManager().sendMessage(message); + // message.msgId = id; + V2TimValueCallback sendMessageRes = + await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .sendMessage( + id: id, + receiver: conversation.userID!, + needReadReceipt: true, + groupID: '', + ); + if (sendMessageRes.code == 0) { + // 发送成功 + _onNew1v1Message(sendMessageRes.data); + } else { + ATTts.show( + 'create fail,code:${sendMessageRes.code},${sendMessageRes.desc}', + ); + } + } + } + } + notifyListeners(); + } + + // 发送消息(房间) + Future _sendRoomMessage(Msg msg) async { + try { + var user = msg.user?.copyWith(); + var toUser = msg.toUser?.copyWith(); + final rtcManager = Provider.of( + context!, + listen: false, + ); + final bool shouldRefreshUserInfo = rtcManager.needUpDataUserInfo; + user?.cleanWearHonor(); + user?.cleanWearBadge(); + user?.cleanPhotos(); + toUser?.cleanWearHonor(); + toUser?.cleanWearBadge(); + toUser?.cleanUseProps(); + toUser?.cleanPhotos(); + msg.needUpDataUserInfo = shouldRefreshUserInfo; + msg.user = user; + msg.toUser = toUser; + final textMsg = await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .createCustomMessage(data: jsonEncode(msg.toJson())); + + if (textMsg.code != 0) return; + + final sendResult = await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .sendMessage( + id: textMsg.data!.id!, + groupID: msg.groupId!, + receiver: '', + ); + if (sendResult.code == 0 && shouldRefreshUserInfo) { + rtcManager.needUpDataUserInfo = false; + } + } catch (e) { + throw Exception("create fail: $e"); + } + } + + /// 添加消息 + addMsg(Msg msg) { + roomAllMsgList.insert(0, msg); + if (roomAllMsgList.length > 250) { + print('大于200条消息'); + roomAllMsgList.removeAt(roomAllMsgList.length - 1); + } + msgAllListener?.call(msg); + + if (msg.type == ATRoomMsgType.text) { + roomChatMsgList.insert(0, msg); + if (roomChatMsgList.length > 250) { + print('大于200条消息'); + roomChatMsgList.removeAt(roomChatMsgList.length - 1); + } + msgChatListener?.call(msg); + } else if (msg.type == ATRoomMsgType.image) { + roomChatMsgList.insert(0, msg); + if (roomChatMsgList.length > 250) { + print('大于200条消息'); + roomChatMsgList.removeAt(roomChatMsgList.length - 1); + } + msgChatListener?.call(msg); + } else if (msg.type == ATRoomMsgType.gift) { + roomGiftMsgList.insert(0, msg); + if (roomGiftMsgList.length > 250) { + print('大于200条消息'); + roomGiftMsgList.removeAt(roomGiftMsgList.length - 1); + } + msgGiftListener?.call(msg); + msgFloatingGiftListener?.call(msg); + } + } + + bool isLogout = false; + + logout() async { + V2TimCallback logoutRes = await TencentImSDKPlugin.v2TIMManager.logout(); + TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .removeAdvancedMsgListener(); + if (logoutRes.code == 0) { + isLogout = true; + } + } + + ///全服广播消息 + _newBroadCastMsgRecv(String groupID, V2TimMessage message) { + try { + String? customData = message.customElem?.data; + if (customData != null && customData.isNotEmpty) { + final data = json.decode(customData); + var type = data["type"]; + if (type == "SYS_ACTIVITY") { + if (onNewActivityMessageCurrentConversationListener != null) { + var recode = Records.fromJson(data["data"]); + onNewActivityMessageCurrentConversationListener?.call(recode); + } else { + activityUnReadCount = activityUnReadCount + 1; + allUnReadCount = + messageUnReadCount + + notifcationUnReadCount + + activityUnReadCount; + notifyListeners(); + } + } else if (type == "SYS_ANNOUNCEMENT") { + if (onNewNotifcationMessageCurrentConversationListener != null) { + var recode = Records.fromJson(data["data"]); + onNewNotifcationMessageCurrentConversationListener?.call(recode); + } else { + notifcationUnReadCount = notifcationUnReadCount + 1; + allUnReadCount = + messageUnReadCount + + notifcationUnReadCount + + activityUnReadCount; + notifyListeners(); + } + } else if (type == "GAME_BAISHUN_WIN") { + if (ATGlobalConfig.isReview) { + ///审核状态不播放动画 + return; + } + var fdata = data["data"]; + var winCoins = fdata["currencyDiff"]; + if (winCoins > 14999) { + ///达到5000才飘屏 + ATFloatingMessage msg = ATFloatingMessage( + type: 2, + userId: fdata["account"], + userAvatarUrl: fdata["userAvatar"], + userName: fdata["userNickname"], + giftUrl: fdata["gameUrl"], + roomId: fdata["roomId"], + coins: fdata["currencyDiff"], + ); + OverlayManager().addMessage(msg); + } + } else if (type == "GAME_LUCKY_GIFT") { + } else if (type == "ROCKET_ENERGY_LAUNCH") { + ///火箭触发飘屏 + var fdata = data["data"]; + ATFloatingMessage msg = ATFloatingMessage( + type: 3, + roomId: fdata["roomId"], + rocketLevel: fdata["fromLevel"], + userAvatarUrl: fdata["userAvatar"], + userName: fdata["nickname"], + userId: fdata["actualAccount"], + priority: 1000, + ); + OverlayManager().addMessage(msg); + } else if (type == "CP_GIFT_WINDOW") { + ///CP礼物触发飘屏 + var fdata = data["data"]; + ATFloatingMessage msg = ATFloatingMessage( + type: 5, + userAvatarUrl: fdata["senderAvatar"], + userName: fdata["senderNickname"], + toUserName: fdata["receiverNickname"], + giftUrl: fdata["giftIcon"], + roomId: fdata["roomId"], + number: fdata["giftCount"], + rocketLevel: fdata["giftWindowLevel"], + ); + OverlayManager().addMessage(msg); + } else if (type == ATRoomMsgType.roomRedPacket) { + ///红包触发飘屏 + var fData = data["data"]; + ATFloatingMessage msg = ATFloatingMessage( + type: 4, + roomId: fData["roomId"], + userAvatarUrl: fData["userAvatar"], + userName: fData["userNickname"], + userId: fData["actualAccount"], + toUserId: fData["packetId"], + priority: 1000, + ); + if (msg.roomId == + Provider.of( + context!, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.id) { + Provider.of( + context!, + listen: false, + ).loadRoomRedPacketList(1); + } + OverlayManager().addMessage(msg); + } else if (type == ATRoomMsgType.inviteRoom) { + ///邀请进入房间 + var fdata = data["data"]; + ATFloatingMessage msg = ATFloatingMessage.fromJson(fdata); + if (msg.toUserId == AccountStorage().getCurrentUser()?.userProfile?.id && + msg.roomId != + Provider.of( + context!, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.id) { + SmartDialog.dismiss(tag: "showInviteRoom"); + SmartDialog.show( + tag: "showInviteRoom", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return InviteRoomDialog(msg); + }, + ); + } + } + } + } catch (e) {} + } + + _newGroupMsg(String groupID, V2TimMessage message) { + if (groupID != + Provider.of( + context!, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.roomAccount) { + return; + } + try { + String? customData = message.customElem?.data; + if (customData != null && customData.isNotEmpty) { + // 直接处理字符串格式的自定义数据 + final data = json.decode(customData); + debugPrint(">>>>>>>>>>>>>>>>>>>消息类型${data["type"]}"); + + if (data["type"] == ATRoomMsgType.rocketEnergyUpdate) { + ///房间火箭进度条更新 + var rocketData = ATRoomRocketStatusRes.fromJson(data["data"]); + Provider.of( + context!, + listen: false, + ).updateRoomRocketStatus(rocketData); + return; + } + if (data["type"] == ATRoomMsgType.roomRedPacket) { + ///房间红包 + var fData = data["data"]; + ATFloatingMessage msg = ATFloatingMessage( + type: 4, + roomId: fData["roomId"], + userAvatarUrl: fData["userAvatar"], + userName: fData["userNickname"], + userId: fData["actualAccount"], + toUserId: fData["packetId"], + priority: 1000, + ); + Provider.of( + context!, + listen: false, + ).loadRoomRedPacketList(1); + OverlayManager().addMessage(msg); + return; + } + if (data["type"] == ATRoomMsgType.rocketRewardUser) { + ///房间火箭用户中奖 + var fData = data["data"]; + String cover = fData["cover"] ?? ""; + String rewardType = fData["rewardType"] ?? ""; + String userNickname = fData["userNickname"] ?? ""; + int rocketLevel = fData["rocketLevel"] ?? 1; + String userId = fData["userId"] ?? 0; + if (AccountStorage().getCurrentUser()?.userProfile?.id == userId) { + ATRoomUtils.playRoomRocketAnim(cover, rewardType, rocketLevel); + } + + ///在房间里发送一条消息 + addMsg( + Msg( + groupId: "", + msg: cover, + type: ATRoomMsgType.rocketRewardUser, + role: rewardType, + user: ChatVibeUserProfile(userNickname: userNickname), + number: rocketLevel, + ), + ); + return; + } + + Msg msg = Msg.fromJson(data); + + if (msg.type == ATRoomMsgType.sendGift || + msg.type == ATRoomMsgType.gameBurstCrystalSprint || + msg.type == ATRoomMsgType.gameBurstCrystalBox) { + ///这个消息暂时不监听 + return; + } + if (msg.type == ATRoomMsgType.bsm) { + if (msg.toUser?.id == + AccountStorage().getCurrentUser()?.userProfile?.id) { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context!)!.tips, + msg: ATAppLocalizations.of( + context!, + )!.invitesYouToTheMicrophone(msg.msg ?? ""), + btnText: ATAppLocalizations.of(context!)!.confirm, + onEnsure: () { + ///上麦 + num index = + Provider.of( + context!, + listen: false, + ).findWheat(); + if (index > -1) { + Provider.of( + context!, + listen: false, + ).shangMai( + index, + eventType: "INVITE", + inviterId: msg.role, + ); + } + }, + ); + }, + ); + } + return; + } + if (msg.type == ATRoomMsgType.killXiaMai) { + ///踢下麦 + if (msg.msg == UserManager().getCurrentUser()?.userProfile?.id) { + final rtc = Provider.of(context!, listen: false); + rtc.isMic = true; + rtc.engine?.setClientRole(role: ClientRoleType.clientRoleAudience); + rtc.engine?.muteLocalAudioStream(true); + ATHeartbeatUtils.cancelAnchorTimer(); + rtc.getMicList(); + } + return; + } + + if (msg.type == ATRoomMsgType.roomSettingUpdate) { + Provider.of( + context!, + listen: false, + ).loadRoomInfo(msg.msg ?? ""); + return; + } + if (msg.type == ATRoomMsgType.roomBGUpdate) { + ATRoomThemeListRes res; + if ((msg.msg ?? "").isNotEmpty) { + res = ATRoomThemeListRes.fromJson(jsonDecode(msg.msg!)); + } else { + res = ATRoomThemeListRes(); + } + Provider.of(context!, listen: false).updateRoomBG(res); + return; + } + if (msg.type == ATRoomMsgType.emoticons) { + Provider.of(context!, listen: false).starPlayEmoji(msg); + return; + } + if (msg.type == ATRoomMsgType.micChange) { + final micChangePush = BroadCastMicChangePush.fromJson(data); + Provider.of( + context!, + listen: false, + ).micChange( + micChangePush.data?.mics, + timeId: micChangePush.data?.timeId, + ); + } else if (msg.type == ATRoomMsgType.refreshOnlineUser) { + Provider.of(context!, listen: false).getOnlineUsers(); + } else if (msg.type == ATRoomMsgType.gameLuckyGift) { + var broadCastRes = ATBroadCastLuckGiftPush.fromJson(data); + msg.gift = ChatVibeGiftRes(giftPhoto: broadCastRes.data?.giftCover); + msg.awardAmount = broadCastRes.data?.awardAmount; + msg.user = ChatVibeUserProfile( + id: broadCastRes.data?.sendUserId, + userNickname: broadCastRes.data?.nickname, + ); + msg.toUser = ChatVibeUserProfile( + id: broadCastRes.data?.acceptUserId, + userNickname: broadCastRes.data?.acceptNickname, + ); + addMsg(msg); + if ((broadCastRes.data?.multiple ?? 0) > 0) { + Msg msg2 = Msg( + groupId: '', + msg: '${broadCastRes.data?.multiple}', + type: ATRoomMsgType.gameLuckyGift_5, + ); + + ///5倍率以上聊天页面需要发个消息 + msg2.awardAmount = broadCastRes.data?.awardAmount; + msg2.user = ChatVibeUserProfile( + id: broadCastRes.data?.sendUserId, + userNickname: broadCastRes.data?.nickname, + ); + addMsg(msg2); + + if ((broadCastRes.data?.multiple ?? 0) > 2) { + ///3倍率 + ATFloatingMessage msg = ATFloatingMessage( + type: 0, + userId: broadCastRes.data?.sendUserId, + roomId: broadCastRes.data?.roomId, + toUserId: broadCastRes.data?.acceptUserId, + userAvatarUrl: broadCastRes.data?.userAvatar, + userName: broadCastRes.data?.nickname, + toUserName: broadCastRes.data?.acceptNickname, + giftUrl: broadCastRes.data?.giftCover, + number: broadCastRes.data?.giftQuantity, + coins: broadCastRes.data?.awardAmount, + multiple: broadCastRes.data?.multiple, + ); + OverlayManager().addMessage(msg); + addluckGiftPushQueue(broadCastRes); + } + } + + if (broadCastRes.data?.sendUserId == + AccountStorage().getCurrentUser()?.userProfile?.id) { + Provider.of( + context!, + listen: false, + ).updateLuckGiftObtainCoins(msg.awardAmount ?? 0); + } + } else { + if (msg.type == ATRoomMsgType.joinRoom) { + if (msg.user != null) { + Provider.of( + context!, + listen: false, + ).addOnlineUser(msg.groupId ?? "", msg.user!); + } + if (msgUserJoinListener != null) { + msgUserJoinListener!(msg); + } + + ///坐骑 + if (msg.user?.getMountains() != null) { + if (ATGlobalConfig.isEntryVehicleAnimation) { + ATGiftVapSvgaManager().play( + msg.user?.getMountains()?.sourceUrl ?? "", + priority: 100, + type: 1, + ); + } + } + } else if (msg.type == ATRoomMsgType.gift) { + if (msg.gift!.giftSourceUrl != null && msg.gift!.special != null) { + if (msg.gift!.special!.contains(ATGiftType.ANIMATION.name) || + msg.gift!.special!.contains(ATGiftType.GLOBAL_GIFT.name)) { + if (ATGlobalConfig.isGiftSpecialEffects) { + ATGiftVapSvgaManager().play(msg.gift!.giftSourceUrl!); + } + } + } + if (Provider.of( + context!, + listen: false, + ).currenRoom?.roomProfile?.roomSetting?.showHeartbeat ?? + false) { + debouncer.debounce( + duration: Duration(milliseconds: 350), + onDebounce: () { + Provider.of( + context!, + listen: false, + ).getMicList(); + }, + ); + } + num coins = msg.number! * msg.gift!.giftCandy!; + if (coins > 9999) { + OverlayManager().addMessage( + ATFloatingMessage( + type: 1, + userAvatarUrl: msg.user?.userAvatar ?? "", + userName: msg.user?.userNickname ?? "", + toUserName: msg.toUser?.userNickname ?? "", + toUserAvatarUrl: msg.toUser?.userAvatar ?? "", + giftUrl: msg.gift!.giftPhoto, + number: msg.number, + coins: coins, + roomId: msg.msg, + ), + ); + } + } else if (msg.type == ATRoomMsgType.luckGiftAnimOther) { + if (Provider.of( + context!, + listen: false, + ).hideLGiftAnimal) { + return; + } + eventBus.fire( + GiveRoomLuckWithOtherEvent( + msg.gift?.giftPhoto ?? "", + (jsonDecode(msg.msg ?? "") as List) + .map((e) => e as String) + .toList(), + ), + ); + return; + } else if (msg.type == ATRoomMsgType.roomRoleChange) { + ///房间身份变动 + Provider.of(context!, listen: false).getMicList(); + if (msg.toUser?.id == + AccountStorage().getCurrentUser()?.userProfile?.id) { + Provider.of( + context!, + listen: false, + ).currenRoom?.entrants?.setRoles(msg.msg); + if (msg.msg == ATRoomRolesType.TOURIST.name && + !(Provider.of( + context!, + listen: false, + ).currenRoom?.roomProfile?.roomSetting?.touristMike ?? + false)) { + ///如果变成了游客,房间又是禁止游客上麦,需要下麦 + num index = Provider.of( + context!, + listen: false, + ).userOnMaiInIndex( + AccountStorage().getCurrentUser()?.userProfile?.id ?? "", + ); + if (index > -1) { + Provider.of( + context!, + listen: false, + ).xiaMai(index); + } + } + } + } else if (msg.type == ATRoomMsgType.roomDice) { + if ((msg.number ?? -1) > -1) { + Provider.of( + context!, + listen: false, + ).starPlayEmoji(msg); + } + } else if (msg.type == ATRoomMsgType.roomRPS) { + if ((msg.number ?? -1) > -1) { + Provider.of( + context!, + listen: false, + ).starPlayEmoji(msg); + } + } else if (msg.type == ATRoomMsgType.roomGameClose) { + Provider.of( + context!, + listen: false, + ).roomMannyGameClose(); + return; + } else if (msg.type == ATRoomMsgType.roomGameCreate) { + Provider.of(context!, listen: false).getLudoGameInfo(); + return; + } + addMsg(msg); + } + } + } catch (e) { + throw Exception("message parser fail: $e"); + } + } + + ///加入全服广播群 + joinBigBroadcastGroup() async { + bool joined = false; + while (!isLogout && !joined) { + await Future.delayed(Duration(milliseconds: 550)); + try { + var joinResult = await TencentImSDKPlugin.v2TIMManager.joinGroup( + groupID: ATGlobalConfig.bigBroadcastGroup, + message: "", + ); + if (joinResult.code == 0) { + joined = true; + } + } catch (e) { + //print('timm 登录异常:${e.toString()}'); + ATTts.show('broadcastGroup join fail:${e.toString()}'); + } + } + } + + ///发送全服消息 + sendBigBroadcastGroup(BigBroadcastGroupMessage msg) async { + try { + final textMsg = await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .createCustomMessage(data: jsonEncode(msg.toJson())); + + if (textMsg.code != 0) return; + + final sendResult = await TencentImSDKPlugin.v2TIMManager + .getMessageManager() + .sendMessage( + id: textMsg.data!.id!, + groupID: ATGlobalConfig.bigBroadcastGroup, + receiver: '', + ); + if (sendResult.code == 0) {} + } catch (e) { + throw Exception("create fail: $e"); + } + } + + Future quitGroup(String groupID) async { + await TencentImSDKPlugin.v2TIMManager.quitGroup(groupID: groupID); + } + + ///清屏 + void clearMessage() { + roomAllMsgList.clear(); + roomChatMsgList.clear(); + roomGiftMsgList.clear(); + msgChatListener?.call(Msg(groupId: "-1000", msg: "", type: "")); + msgAllListener?.call(Msg(groupId: "-1000", msg: "", type: "")); + msgGiftListener?.call(Msg(groupId: "-1000", msg: "", type: "")); + notifyListeners(); + } + + cleanRoomData() { + roomAllMsgList.clear(); + roomGiftMsgList.clear(); + roomChatMsgList.clear(); + _luckGiftPushQueue.clear(); + currentPlayingLuckGift = null; + _isFinishingLuckGift = false; + onNewMessageListenerGroupMap.forEach((k, v) { + v = null; + }); + onNewMessageListenerGroupMap.clear(); + } + + void addluckGiftPushQueue(ATBroadCastLuckGiftPush broadCastRes) { + if (ATGlobalConfig.isLuckGiftSpecialEffects) { + _luckGiftPushQueue.add(broadCastRes); + playLuckGiftBackCoins(); + } + } + + void cleanLuckGiftBackCoins() { + _luckGiftPushQueue.clear(); + currentPlayingLuckGift = null; + _isFinishingLuckGift = false; + showLuckGiftBigHead = true; + notifyListeners(); + } + + bool showLuckGiftBigHead = true; + + void playLuckGiftBackCoins() { + if (currentPlayingLuckGift != null || _luckGiftPushQueue.isEmpty) { + return; + } + currentPlayingLuckGift = _luckGiftPushQueue.removeFirst(); + showLuckGiftBigHead = true; + notifyListeners(); + } + + void finishCurrentLuckGiftBackCoins() { + if (_isFinishingLuckGift || currentPlayingLuckGift == null) { + return; + } + _isFinishingLuckGift = true; + currentPlayingLuckGift = null; + showLuckGiftBigHead = true; + notifyListeners(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _isFinishingLuckGift = false; + playLuckGiftBackCoins(); + }); + } + + void updateNotificationCount(int count) { + notifcationUnReadCount = 0; + allUnReadCount = + messageUnReadCount + notifcationUnReadCount + activityUnReadCount; + notifyListeners(); + } + + void updateActivityCount(int count) { + activityUnReadCount = 0; + allUnReadCount = + messageUnReadCount + notifcationUnReadCount + activityUnReadCount; + notifyListeners(); + } + + void updateSystemCount(int count) { + conversationMap[ATGlobalConfig.imAdmin]?.unreadCount = 0; + systemUnReadCount = 0; + notifyListeners(); + } + + void updateCustomerCount(int count) { + conversationMap["c2c_${customerInfo?.id}"]?.unreadCount = 0; + customerUnReadCount = 0; + notifyListeners(); + } + + void clearC2CHistoryMessage(String conversationID, bool needShowToast) async { + // 清空单聊本地及云端的消息(不删除会话) + + V2TimCallback clearC2CHistoryMessageRes = await TencentImSDKPlugin + .v2TIMManager + .getConversationManager() + .deleteConversation(conversationID: conversationID); // 需要清空记录的用户id + if (clearC2CHistoryMessageRes.code == 0) { + // 清除成功 + if (needShowToast) { + ATTts.show(ATAppLocalizations.of(context!)!.operationSuccessful); + } + initConversation(); + } else { + // 清除失败,可以查看 clearC2CHistoryMessageRes.desc 获取错误描述 + } + } +} diff --git a/lib/chatvibe_managers/shop_manager.dart b/lib/chatvibe_managers/shop_manager.dart new file mode 100644 index 0000000..8bf284e --- /dev/null +++ b/lib/chatvibe_managers/shop_manager.dart @@ -0,0 +1,26 @@ +import 'package:flutter/cupertino.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/store_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/store_list_res.dart'; +import '../chatvibe_data/models/enum/at_currency_type.dart'; +import '../chatvibe_data/models/enum/at_props_type.dart'; + +class ShopManager extends ChangeNotifier { + List headdressList = []; + List mountainsList = []; + + ///头饰 + void loadHeaddress() async { + headdressList = await StoreRepositoryImp().storeList( + ATCurrencyType.GOLD.name, + ATPropsType.AVATAR_FRAME.name, + ); + } + + ///坐骑 + void loadMountains() async { + mountainsList = await StoreRepositoryImp().storeList( + ATCurrencyType.GOLD.name, + ATPropsType.RIDE.name, + ); + } +} diff --git a/lib/chatvibe_managers/theme_manager.dart b/lib/chatvibe_managers/theme_manager.dart new file mode 100644 index 0000000..0e617db --- /dev/null +++ b/lib/chatvibe_managers/theme_manager.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; + +class ThemeManager with ChangeNotifier { + ThemeMode _themeMode = ThemeMode.system; + + ThemeMode get themeMode => _themeMode; + + ThemeData get currentTheme { + switch (_themeMode) { + case ThemeMode.dark: + return ChatVibeTheme.darkTheme; + case ThemeMode.light: + case ThemeMode.system: + default: + return ChatVibeTheme.lightTheme; + } + } + + void setThemeMode(ThemeMode mode) { + _themeMode = mode; + notifyListeners(); + } +} \ No newline at end of file diff --git a/lib/chatvibe_managers/user_profile_manager.dart b/lib/chatvibe_managers/user_profile_manager.dart new file mode 100644 index 0000000..b51f1d5 --- /dev/null +++ b/lib/chatvibe_managers/user_profile_manager.dart @@ -0,0 +1,196 @@ +import 'package:flutter/cupertino.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/req/at_user_profile_cmd.dart'; +import 'package:aslan/chatvibe_domain/models/res/country_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_user_card_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_user_identity_res.dart'; + +class ChatVibeUserProfileManager extends ChangeNotifier { + ATUserProfileCmd? editUser; + Function? _familyUpdateCall; + + void bindFamilyUpdate(Function? familyUpdate) { + _familyUpdateCall = familyUpdate; + } + + void setUserAvatar(String avatar) { + editUser ??= ATUserProfileCmd(); + editUser!.setUserAvatar = avatar; + notifyListeners(); + } + + void setUserSex(num sex) { + editUser ??= ATUserProfileCmd(); + editUser!.setUserSex = sex; + } + + void setUserNickname(String nickname) { + editUser ??= ATUserProfileCmd(); + editUser!.setUserNickname = nickname; + } + + void setBornYear(num bornYear) { + editUser ??= ATUserProfileCmd(); + editUser!.setBornYear = bornYear; + } + + void setBornMonth(num bornMonth) { + editUser ??= ATUserProfileCmd(); + editUser!.setBornMonth = bornMonth; + } + + void setBornDay(num bornDay) { + editUser ??= ATUserProfileCmd(); + editUser!.setBornDay = bornDay; + } + + void setAge(num age) { + editUser ??= ATUserProfileCmd(); + editUser!.setAge = age; + } + + void setCountry(Country country) { + editUser ??= ATUserProfileCmd(); + editUser!.setCountryCode = country.alphaTwo!; + editUser!.setCountryId = country.id!; + editUser!.setCountryName = country.countryName!; + } + + void clearEditUserUser() { + editUser = null; + notifyListeners(); + } + + Future getMyUserInfo({ + bool needLoadUserCountGuard = true, + bool needRefreshFamily = false, + }) async { + var us = AccountStorage().getCurrentUser(); + String userId = us?.userProfile?.id ?? ""; + var userInfo = await AccountRepository().loadUserInfo(userId); + userProfile = userInfo; + if (needRefreshFamily) { + updateFamily(userInfo.familyId); + } + us?.setUserProfile(userInfo); + AccountStorage().setCurrentUser(us!); + notifyListeners(); + } + + ATUserIdentityRes? userIdentity; + + void getUserIdentity() async { + userIdentity = await AccountRepository().userIdentity(); + notifyListeners(); + } + + ///用户信息详情个人页 + ChatVibeUserProfile? userProfile; + + void getUserInfoById(String userId) async { + userProfile = null; + userProfile = await AccountRepository().loadUserInfo(userId); + + notifyListeners(); + } + + ///房间资料卡 + RoomUserCardRes? userCardInfo; + + // List? userCountGuardResList; + + void roomUserCard(String roomId, String userId) async { + userCardInfo = null; + userCardInfo = await ChatRoomRepository().roomUserCard(roomId, userId); + notifyListeners(); + } + + void followUser(String userId) async { + var result = await AccountRepository().followUser(userId); + if (result) { + if (userCardInfo != null) { + userCardInfo = userCardInfo?.copyWith(follow: !userCardInfo!.follow!); + notifyListeners(); + } + } + } + + num myBalance = 0.0; + + void balance() async { + myBalance = await AccountRepository().balance(); + myBalance = myBalance.toInt(); + notifyListeners(); + } + + updateBalance(double m) { + myBalance = m; + myBalance = myBalance.toInt(); + notifyListeners(); + } + + updateFamily(String? familyId) { + if (AccountStorage().getCurrentUser()?.userProfile?.familyId != familyId) { + AccountStorage().getCurrentUser()?.userProfile?.setFamilyId(familyId); + _familyUpdateCall?.call(); + notifyListeners(); + } + } + + void inviteHostOpt(BuildContext ct, String id, String status) async { + try { + await AccountRepository().inviteHost(id, status); + getUserIdentity(); + ATTts.show(ATAppLocalizations.of(ct)!.operationSuccessful); + } catch (e) {} + } + + void inviteAgentOpt(BuildContext ct, String id, String status) async { + try { + await AccountRepository().inviteAgent(id, status); + getUserIdentity(); + ATTts.show(ATAppLocalizations.of(ct)!.operationSuccessful); + } catch (e) {} + } + + void inviteBDOpt(BuildContext ct, String id, String status) async { + try { + await AccountRepository().inviteBD(id, status); + getUserIdentity(); + ATTts.show(ATAppLocalizations.of(ct)!.operationSuccessful); + } catch (e) {} + } + + void inviteBDLeader(BuildContext ct, String id, String status) async { + try { + await AccountRepository().inviteBDLeader(id, status); + getUserIdentity(); + ATTts.show(ATAppLocalizations.of(ct)!.operationSuccessful); + } catch (e) {} + } + + void inviteRechargeAgent(BuildContext ct, String id, String status) async { + try { + await AccountRepository().inviteRechargeAgent(id, status); + getUserIdentity(); + ATTts.show(ATAppLocalizations.of(ct)!.operationSuccessful); + } catch (e) {} + } + + void cpRlationshipProcessApply( + BuildContext ct, + String id, + bool status, + ) async { + try { + await AccountRepository().cpRelationshipProcessApply(id, status); + getMyUserInfo(needLoadUserCountGuard: false); + ATTts.show(ATAppLocalizations.of(ct)!.operationSuccessful); + } catch (e) {} + } +} diff --git a/lib/chatvibe_ui/components/anim/at_scaled_button.dart b/lib/chatvibe_ui/components/anim/at_scaled_button.dart new file mode 100644 index 0000000..db40199 --- /dev/null +++ b/lib/chatvibe_ui/components/anim/at_scaled_button.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +/// 按钮按下缩小,离开还原 +class ATScaledButton extends StatefulWidget { + + final Widget child; + final VoidCallback? onClick; + + const ATScaledButton({Key? key, required this.child, this.onClick}) : super(key: key); + + + @override + _ScaledButtonState createState() => _ScaledButtonState(); +} + +class _ScaledButtonState extends State with TickerProviderStateMixin{ + + late AnimationController controller; + late Animation tween; + @override + void initState() { + super.initState(); + controller = AnimationController(duration: const Duration(milliseconds: 100), vsync: this); + tween = Tween(begin:1.0, end:1.5).animate(controller); + } + + @override + void dispose() { + super.dispose(); + controller?.dispose(); + } + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: ScaleTransition( + scale: tween, + alignment: Alignment.center, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: widget.onClick, + + onTapDown: (details){ + controller.forward(); + }, + onTapUp: (details){ + controller.reset(); + }, + onTapCancel: (){ + controller.reset(); + }, + child: widget.child, + ), + ), + ); + } +} diff --git a/lib/chatvibe_ui/components/appbar/chatvibe_appbar.dart b/lib/chatvibe_ui/components/appbar/chatvibe_appbar.dart new file mode 100644 index 0000000..f8bf369 --- /dev/null +++ b/lib/chatvibe_ui/components/appbar/chatvibe_appbar.dart @@ -0,0 +1,173 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; + +class ChatVibeAppBar extends StatefulWidget implements PreferredSizeWidget { + final Widget child; + final double height; + final backgroundColor; + + //LinearGradient gradient; + + @override + State createState() { + return ChatVibeAppBarState(); + } + + ChatVibeAppBar({ + required this.child, + this.height = kToolbarHeight, + this.backgroundColor = Colors.white, + //this.gradient = const LinearGradient(colors: [Colors.white, Colors.white] , begin: Alignment.topLeft, end: Alignment.bottomRight) + }); + + @override + Size get preferredSize => Size.fromHeight(height); +} + +class ChatVibeAppBarState extends State { + @override + Widget build(BuildContext context) { + // SystemUiOverlayStyle style = SystemUiOverlayStyle( + // statusBarColor: Colors.white, + // ///这是设置状态栏的图标和字体的颜色 + // ///Brightness.light 一般都是显示为白色 + // ///Brightness.dark 一般都是显示为黑色 + // statusBarIconBrightness: Brightness.dark + // ); + // SystemChrome.setSystemUIOverlayStyle(style); + return Container( + color: widget.backgroundColor, + //decoration: BoxDecoration(gradient: widget.gradient), + child: SafeArea( + top: true, + child: Container(height: widget.height, child: widget.child), + ), + ); + } +} + +class ChatVibeStandardAppBar extends StatelessWidget + implements PreferredSizeWidget { + final String title; + final Widget? titleWidget; + final Widget? leading; + final List actions; + final Color backgroundColor; + final Color? backButtonColor; + final Gradient gradient; + final Function? onTag; + final bool showBackButton; + + ChatVibeStandardAppBar({ + this.title = "", + required this.actions, + this.backgroundColor = Colors.white, + this.backButtonColor, + this.titleWidget, + this.showBackButton = true, + this.leading, + this.onTag, + this.gradient = const LinearGradient( + colors: [Colors.transparent, Colors.transparent], + ), + }); + + @override + Widget build(BuildContext context) { + var leadingWidget = Container( + width: titleWidget == null ? 38 : null, + alignment: AlignmentDirectional.centerStart, + child: + !showBackButton + ? Container() + : leading ?? + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (onTag != null) { + onTag!(); + } else { + Navigator.pop(context); + } + }, + child: Container( + width: width(50), + height: height(30), + alignment: AlignmentDirectional.centerStart, + padding: EdgeInsetsDirectional.only(start: width(15)), + child: Icon( + ATGlobalConfig.lang == "ar" + ? Icons.keyboard_arrow_right + : Icons.keyboard_arrow_left, + size: 28.w, + color: backButtonColor, + ), + ), + ), + ); + var titleBody = + titleWidget ?? + Align( + alignment: Alignment.center, + child: Text( + title, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: ScreenUtil().setSp(18), + color: backButtonColor ?? Color(0xff000000), + fontWeight: FontWeight.w500, + ), + ), + ); + var body; + if (titleWidget != null) { + body = Container( + margin: EdgeInsets.only(top: ScreenUtil().statusBarHeight), + child: Row( + children: [ + leadingWidget, + Expanded(child: titleBody), + if (actions != null) + Row(mainAxisAlignment: MainAxisAlignment.end, children: actions), + ], + ), + ); + } + return Container( + decoration: BoxDecoration( + gradient: + gradient ?? + LinearGradient(colors: [Color(0xffffffff), Color(0xffffffff)]), + ), + child: + body ?? + Container( + margin: EdgeInsets.only(top: ScreenUtil().statusBarHeight), + child: Stack( + alignment: AlignmentDirectional.centerStart, + children: [ + leadingWidget, + titleBody, + if (actions != null) + Align( + alignment: AlignmentDirectional.centerEnd, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: actions, + ), + ), + ], + ), + ), + ); + } + + @override + // TODO: implement preferredSize + Size get preferredSize => Size.fromHeight(kToolbarHeight); +} diff --git a/lib/chatvibe_ui/components/at_compontent.dart b/lib/chatvibe_ui/components/at_compontent.dart new file mode 100644 index 0000000..cd8ee9b --- /dev/null +++ b/lib/chatvibe_ui/components/at_compontent.dart @@ -0,0 +1,775 @@ +import 'package:extended_image/extended_image.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:tancent_vap/utils/constant.dart'; +import 'package:tancent_vap/widgets/vap_view.dart'; +import 'package:aslan/chatvibe_data/sources/local/file_cache_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/headdress/headdress_widget.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/utilities/at_path_utils.dart'; + +import '../../chatvibe_data/models/enum/at_room_roles_type.dart'; +import '../../chatvibe_data/models/enum/at_vip_type.dart'; + +Widget head({ + required String url, + required double width, + double? height, + Border? border, + String? headdress, + BoxShape shape = BoxShape.circle, + BoxFit fit = BoxFit.cover, + BorderRadius? borderRadius, + bool showDefault = true, + bool isRoom = false, +}) { + final double outerHeight = height ?? width; + final double avatarWidth = width * 0.76; + final double avatarHeight = outerHeight * 0.76; + final String imagePathForExt = Uri.tryParse(url)?.path ?? url; + final bool isGifAvatar = + ATPathUtils.fetchFileExtensionFunction(imagePathForExt) == ".gif"; + + Widget buildAvatarPlaceholder(String assetPath) { + return Image.asset( + assetPath, + width: avatarWidth, + height: avatarHeight, + fit: BoxFit.cover, + ); + } + + Widget buildStaticAvatar() { + return ExtendedImage.network( + url, + width: avatarWidth, + height: avatarHeight, + fit: fit, + cache: true, + clearMemoryCacheWhenDispose: false, + clearMemoryCacheIfFailed: true, + loadStateChanged: (ExtendedImageState state) { + if (state.extendedImageLoadState == LoadState.completed) { + return ExtendedRawImage( + image: state.extendedImageInfo?.image, + width: avatarWidth, + height: avatarHeight, + fit: fit, + ); + } else if (state.extendedImageLoadState == LoadState.loading) { + return showDefault + ? buildAvatarPlaceholder("atu_images/general/at_icon_loading.png") + : Container(); + } else { + return buildAvatarPlaceholder( + "atu_images/general/at_icon_avar_defalt.png", + ); + } + }, + ); + } + + Widget buildGifAvatar() { + return Image.network( + url, + width: avatarWidth, + height: avatarHeight, + fit: fit, + gaplessPlayback: true, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) { + return child; + } + return showDefault + ? buildAvatarPlaceholder("atu_images/general/at_icon_loading.png") + : Container(); + }, + errorBuilder: (context, error, stackTrace) { + return buildAvatarPlaceholder( + "atu_images/general/at_icon_avar_defalt.png", + ); + }, + ); + } + + Widget avatarChild = isGifAvatar ? buildGifAvatar() : buildStaticAvatar(); + + Widget clippedAvatar; + if (shape == BoxShape.circle) { + clippedAvatar = ClipOval(child: avatarChild); + } else if (borderRadius != null) { + clippedAvatar = ClipRRect(borderRadius: borderRadius, child: avatarChild); + } else { + clippedAvatar = ClipRect(child: avatarChild); + } + + return RepaintBoundary( + child: Stack( + alignment: Alignment.center, + children: [ + SizedBox( + width: width, + height: outerHeight, + child: Center( + child: Container( + width: avatarWidth, + height: avatarHeight, + decoration: BoxDecoration( + shape: shape, + border: border, + borderRadius: shape == BoxShape.rectangle ? borderRadius : null, + ), + child: clippedAvatar, + ), + ), + ), + headdress != null && ATPathUtils.fileTypeIsPicOperation(headdress) + ? netImage(url: headdress, width: width, height: width) + : Container(), + headdress != null && + ATPathUtils.fetchFileExtensionFunction( + headdress, + ).toLowerCase() == + ".svga" && + ATGlobalConfig.sdkInt > ATGlobalConfig.maxSdkNoAnim + ? IgnorePointer( + child: SVGAHeadwearWidget( + width: width, + height: width, + resource: headdress, + ), + ) + : Container(), + headdress != null && + ATPathUtils.fetchFileExtensionFunction( + headdress, + ).toLowerCase() == + ".mp4" && + ATGlobalConfig.sdkInt > ATGlobalConfig.maxSdkNoAnim + ? SizedBox( + width: width, + height: width, + child: IgnorePointer( + child: VapView( + scaleType: ScaleType.centerCrop, + repeat: 90000000, + onViewCreated: (controller) { + FileCacheManager.getInstance().getFile(url: headdress).then( + (file) { + controller.playFile(file.path); + }, + ); + }, + ), + ), + ) + : Container(), + ], + ), + ); +} + +Widget netImage({ + required String url, + String? defaultImg, + bool noDefaultImg = false, + double? width, + double? height, + BorderRadius? borderRadius, + BoxFit? fit, + BoxShape? shape, + Border? border, +}) { + // print('${ATGlobalConfig.imgHost}$image?imageslim'); + if (ATPathUtils.fetchFileExtensionFunction(url).toLowerCase() == ".svga") { + return SVGAHeadwearWidget( + width: width, + height: height ?? width, + resource: url, + ); + } + return ExtendedImage.network( + url, + width: width, + height: height ?? width, + fit: fit ?? BoxFit.cover, + cache: true, + shape: shape ?? BoxShape.rectangle, + borderRadius: borderRadius, + clearMemoryCacheWhenDispose: false, + clearMemoryCacheIfFailed: true, + border: border, + gaplessPlayback: true, + loadStateChanged: (ExtendedImageState state) { + if (state.extendedImageLoadState == LoadState.completed) { + return ExtendedRawImage( + image: state.extendedImageInfo?.image, + fit: fit ?? BoxFit.cover, + ); + } else if (state.extendedImageLoadState == LoadState.failed) { + return noDefaultImg + ? Container() + : Image.asset( + defaultImg ?? "atu_images/general/at_icon_loading.png", + fit: BoxFit.cover, + ); + } else { + return noDefaultImg + ? Container() + : Image.asset( + defaultImg ?? "atu_images/general/at_icon_loading.png", + fit: BoxFit.cover, + ); + } + }, + ); +} + +/// 空页面 +///默认空页面 +mainEmpty({ + String msg = "No data", + Widget? image, + bool center = true, + Color textColor = const Color(0xff4d4d4d), +}) { + List list = []; + list.add(SizedBox(height: height(center ? 40.w : 0.w))); + list.add( + image ?? + Image.asset( + 'atu_images/general/at_icon_no_data_icon.png', + width: 121.w, + fit: BoxFit.fitWidth, + ), + ); + list.add(SizedBox(height: height(10.w))); + list.add( + Text( + msg, + style: TextStyle( + fontSize: sp(12), + color: textColor, + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + height: 1, + ), + ), + ); + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: + center ? MainAxisAlignment.center : MainAxisAlignment.start, + children: list, + ), + ], + ); +} + +msgRoleTag(String role, {double? width, double? height}) { + String image = ""; + if (ATRoomRolesType.HOMEOWNER.name == role) { + image = 'fz'; + } else if (ATRoomRolesType.ADMIN.name == role) { + image = "gly"; + } else if (ATRoomRolesType.MEMBER.name == role) { + image = "hy"; + } else if (ATRoomRolesType.TOURIST.name == role) { + image = "guest"; + } + if (image.isEmpty) return SizedBox.shrink(); + return Container( + alignment: Alignment.center, + width: width ?? 22.w, + height: height ?? 22.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/room/at_icon_room_$image.png"), + ), + ), + ); +} + +///性别 0女 1男 +xb(num? sex, {double? height, Color? color = Colors.white}) { + // print('sex:$sex'); + // if (sex == null) return Container(); + String image = "atu_images/login/at_icon_sex_man.png"; + if (sex == 0) { + image = "atu_images/login/at_icon_sex_woman.png"; + } + return Image.asset( + image, + height: height ?? 14.w, + fit: BoxFit.fill, + color: color, + ); +} + +xb2(num? sex, {double? width, double? height}) { + // print('sex:$sex'); + if (sex == null) return Container(); + String image = "atu_images/login/at_icon_sex_man_bg.png"; + if (sex == 0) { + image = "atu_images/login/at_icon_sex_woman_bg.png"; + } + return Image.asset( + image, + width: width ?? 28.w, + height: height ?? 14.w, + fit: BoxFit.fill, + ); +} + +///搜索框 +searchWidget({ + Function? onChange, + Function? onTap, + String? hint, + double? hintSize, + Color? borderColor, + EdgeInsetsGeometry? padding, + TextEditingController? controller, + Color? textColor, + Color? serachIconColor, + FocusNode? focusNode, +}) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (onTap != null) { + onTap(); + } + }, + child: Container( + margin: + padding ?? + EdgeInsetsDirectional.only(start: width(15), end: width(0)), + alignment: AlignmentDirectional.centerStart, + height: width(36), + //width: width(345), + decoration: BoxDecoration( + color: Color(0xFFF2F2F2), + borderRadius: BorderRadius.all(Radius.circular(height(20))), + border: Border.all(color: borderColor ?? Colors.black12, width: 1.w), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(width: width(10)), + Image.asset( + "atu_images/index/at_icon_serach2.png", + width: 20.w, + height: 20.w, + ), + SizedBox(width: width(6)), + _buildPhoneInput( + onChange, + onTap == null, + hint ?? 'Search by ID', + controller, + textColor, + hintSize, + focusNode, + ), + ], + ), + ), + ); +} + +_buildPhoneInput( + Function? onChange, + bool enabled, + String tint, + TextEditingController? controller, + Color? textColor, + double? hintSize, + FocusNode? focusNode, +) { + return Expanded( + child: TextField( + controller: controller, + onChanged: (text) { + if (onChange != null) { + onChange(text); + } + }, + focusNode: focusNode, + autofocus: false, + enabled: enabled, + maxLength: 11, + cursorColor: ChatVibeTheme.primaryColor, + decoration: InputDecoration( + hintText: tint, + hintStyle: TextStyle( + color: ChatVibeTheme.textSecondary, + fontSize: hintSize ?? sp(14), + ), + counterText: '', + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + contentPadding: EdgeInsets.only(bottom: 0), + ), + style: TextStyle( + textBaseline: TextBaseline.alphabetic, + fontSize: hintSize ?? sp(14), + color: textColor ?? Colors.black, + ), + ), + ); +} + +///vip标识 +getVIPBadge(String? type, {double? width, double? height}) { + String icon = ""; + if (type == ATVIPType.VISCOUNT.name) { + icon = "atu_images/vip/at_icon_vip1_badge.png"; + } else if (type == ATVIPType.EARL.name) { + icon = "atu_images/vip/at_icon_vip2_badge.png"; + } else if (type == ATVIPType.MARQUIS.name) { + icon = "atu_images/vip/at_icon_vip3_badge.png"; + } else if (type == ATVIPType.DUKE.name) { + icon = "atu_images/vip/at_icon_vip4_badge.png"; + } else if (type == ATVIPType.KING.name) { + icon = "atu_images/vip/at_icon_vip5_badge.png"; + } else if (type == ATVIPType.EMPEROR.name) { + icon = "atu_images/vip/at_icon_vip6_badge.png"; + } + return icon.isNotEmpty + ? Image.asset(icon, width: width, height: height, fit: BoxFit.cover) + : Container(); +} + +///用户等级 +getUserLevel(num level, {double? width, double? height, double? fontSize}) { + String icon = ""; + if (level > 0 && level < 11) { + icon = "atu_images/level/at_icon_user_level_1_10.png"; + } else if (level > 10 && level < 21) { + icon = "atu_images/level/at_icon_user_level_10_20.png"; + } else if (level > 20 && level < 31) { + icon = "atu_images/level/at_icon_user_level_20_30.png"; + } else if (level > 30 && level < 41) { + icon = "atu_images/level/at_icon_user_level_30_40.png"; + } else if (level > 40 && level < 51) { + icon = "atu_images/level/at_icon_user_level_40_50.png"; + } + return icon.isNotEmpty + ? Directionality( + textDirection: TextDirection.ltr, + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + Image.asset(icon, width: width, height: height, fit: BoxFit.fill), + PositionedDirectional( + end: level > 9 ? 10.w : 15.w, + child: text( + "$level", + fontSize: fontSize ?? 12.sp, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ) + : Container(); +} + +///用户魅力等级 +getWealthLevel(num level, {double? width, double? height, double? fontSize}) { + String icon = ""; + if (level > 0 && level < 11) { + icon = "atu_images/level/at_icon_wealth_level_1_10.png"; + } else if (level > 10 && level < 21) { + icon = "atu_images/level/at_icon_wealth_level_10_20.png"; + } else if (level > 20 && level < 31) { + icon = "atu_images/level/at_icon_wealth_level_20_30.png"; + } else if (level > 30 && level < 41) { + icon = "atu_images/level/at_icon_wealth_level_30_40.png"; + } else if (level > 40 && level < 51) { + icon = "atu_images/level/at_icon_wealth_level_40_50.png"; + } + return icon.isNotEmpty + ? Directionality( + textDirection: TextDirection.ltr, + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + Image.asset(icon, width: width, height: height, fit: BoxFit.fill), + PositionedDirectional( + end: level > 9 ? 10.w : 15.w, + child: text( + "$level", + fontSize: fontSize ?? 12.sp, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ) + : Container(); +} + +getIdIcon( + num wealthLevel, { + double? width, + double? height, + Color textColor = Colors.white, + double fontSize = 12, + FontWeight fontWeight = FontWeight.w600, +}) { + String icon = ""; + if (wealthLevel < 11) { + icon = ""; + } else if (wealthLevel > 10 && wealthLevel < 21) { + icon = "atu_images/level/at_icon_wealth_id_icon_1.png"; + } else if (wealthLevel > 20 && wealthLevel < 31) { + icon = "atu_images/level/at_icon_wealth_id_icon_2.png"; + } else if (wealthLevel > 30 && wealthLevel < 41) { + icon = "atu_images/level/at_icon_wealth_id_icon_3.png"; + } else if (wealthLevel > 40 && wealthLevel < 51) { + icon = "atu_images/level/at_icon_wealth_id_icon_4.png"; + } + return icon.isNotEmpty + ? Image.asset(icon, width: width, height: height) + : Container( + child: text( + "ID:", + fontSize: fontSize, + fontWeight: fontWeight, + textColor: textColor, + ), + ); +} + +String getGiftObtCoinsBg(num obtCoins) { + String icon = ""; + if (obtCoins > -1 && obtCoins < 5000) { + icon = "atu_images/room/at_icon_luck_gift_obt_coins_bg_0.png"; + } else if (obtCoins > 4999 && obtCoins < 50000) { + icon = "atu_images/room/at_icon_luck_gift_obt_coins_bg_1.png"; + } else if (obtCoins > 49999 && obtCoins < 100000) { + icon = "atu_images/room/at_icon_luck_gift_obt_coins_bg_2.png"; + } else if (obtCoins > 99999 && obtCoins < 1000000) { + icon = "atu_images/room/at_icon_luck_gift_obt_coins_bg_3.png"; + } else if (obtCoins > 999999 && obtCoins < 10000000) { + icon = "atu_images/room/at_icon_luck_gift_obt_coins_bg_4.png"; + } else if (obtCoins > 9999999) { + icon = "atu_images/room/at_icon_luck_gift_obt_coins_bg_5.png"; + } + return icon; +} + +///数字图片 +buildNum(String num, {double? size = 18}) { + if (num.isNotEmpty) { + List eList = []; + int index = 0; + num.split("").forEach((item) { + if (item == "k") { + eList.add( + Container( + margin: EdgeInsetsDirectional.only( + start: (index * (size ?? 18) * 0.9).w, + ), + child: Image.asset( + "atu_images/room/at_icon_k.png", + width: size, + height: size, + ), + ), + ); + } else if (item == "m") { + eList.add( + Container( + margin: EdgeInsetsDirectional.only( + start: (index * (size ?? 18) * 0.9).w, + ), + child: Image.asset( + "atu_images/room/at_icon_m.png", + width: size, + height: size, + ), + ), + ); + } else { + eList.add( + Container( + margin: EdgeInsetsDirectional.only( + start: (index * (size ?? 18) * 0.8).w, + ), + child: Image.asset( + "atu_images/room/at_icon_number_$item.png", + width: size, + height: size, + ), + ), + ); + } + index = index + 1; + }); + return Directionality( + textDirection: TextDirection.ltr, + child: Stack(children: eList), + ); + } else { + return Container(); + } +} + +///游戏飘屏数字图片 +buildNumForGame(String num, {double? size = 18}) { + if (num.isNotEmpty) { + List eList = []; + int index = 0; + num.split("").forEach((item) { + if (item == "k") { + eList.add( + Container( + margin: EdgeInsetsDirectional.only( + start: (index * (size ?? 18) * 0.65).w, + ), + child: Image.asset( + "atu_images/general/at_icon_game_numk.png", + width: size, + height: size, + ), + ), + ); + } else if (item == "m") { + eList.add( + Container( + margin: EdgeInsetsDirectional.only( + start: (index * (size ?? 18) * 0.65).w, + ), + child: Image.asset( + "atu_images/general/at_icon_game_numm.png", + width: size, + height: size, + ), + ), + ); + } else { + eList.add( + Container( + margin: EdgeInsetsDirectional.only( + start: (index * (size ?? 18) * 0.6).w, + ), + child: Image.asset( + "atu_images/general/at_icon_game_num$item.png", + width: size, + height: size, + ), + ), + ); + } + index = index + 1; + }); + return Directionality( + textDirection: TextDirection.ltr, + child: Stack(children: eList), + ); + } else { + return Container(); + } +} + +///房间幸运数字 +buildNumForRoomLuckNum(String num, {double? size = 18}) { + if (num.isNotEmpty) { + List eList = []; + int index = 0; + num.split("").forEach((item) { + eList.add( + Container( + margin: EdgeInsetsDirectional.only( + start: (index * (size ?? 18) * 0.7).w, + ), + child: Image.asset( + "atu_images/room/at_icon_lucknumber_$item.png", + width: size, + height: size, + ), + ), + ); + index = index + 1; + }); + return Directionality( + textDirection: TextDirection.ltr, + child: Stack(children: eList), + ); + } else { + return Container(); + } +} + +buildFamilyProgressLinearGradient(int level) { + if (level == 0) { + return LinearGradient(colors: [Color(0xff2E4957), Color(0xffC9E7E8)]); + } else if (level == 1) { + return LinearGradient(colors: [Color(0xff5A3F31), Color(0xffF8CD85)]); + } else if (level == 2) { + return LinearGradient(colors: [Color(0xff0D5ACE), Color(0xffCBD9E8)]); + } else if (level == 3) { + return LinearGradient(colors: [Color(0xffC57E1C), Color(0xffCBD9E8)]); + } else if (level == 10) { + return LinearGradient(colors: [Color(0xff3C7FBE), Color(0xffADE1F1)]); + } else if (level == 11) { + return LinearGradient(colors: [Color(0xff300A67), Color(0xffA53EFF)]); + } + return LinearGradient(colors: [Color(0xff2E4957), Color(0xffC9E7E8)]); +} + +buildVipNameColor(String type) { + List? gradientColors; + if (type == ATVIPType.DUKE.name) { + gradientColors = [ + Color(0xffBFFDF8), + Color(0xff95E6EA), + Color(0xff63D1D0), + Color(0xff3FC5BE), + Color(0xff22B5AF), + ]; + } else if (type == ATVIPType.KING.name) { + gradientColors = [ + Color(0xffEF9897), + Color(0xffEA6160), + Color(0xffE9372E), + Color(0xffCA271C), + Color(0xff931913), + ]; + } else if (type == ATVIPType.KING.name) { + gradientColors = [ + Color(0xffF54EA3), + Color(0xffF64389), + Color(0xffF7666C), + Color(0xffF6805D), + Color(0xffF0C956), + Color(0xffCCE077), + Color(0xff9BE094), + Color(0xff5DDBCD), + Color(0xff0098F7), + Color(0xff1E6CEF), + Color(0xff5535B8), + ]; + } + return gradientColors; +} diff --git a/lib/chatvibe_ui/components/at_debounce_widget.dart b/lib/chatvibe_ui/components/at_debounce_widget.dart new file mode 100644 index 0000000..31de2d5 --- /dev/null +++ b/lib/chatvibe_ui/components/at_debounce_widget.dart @@ -0,0 +1,45 @@ +import 'package:flutter/cupertino.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; + +///防抖封装 +class ATDebounceWidget extends StatefulWidget { + final Widget child; + final VoidCallback onTap; + final Duration debounceTime; + final String? tips; + + const ATDebounceWidget({ + Key? key, + required this.child, + required this.onTap, + this.tips, + this.debounceTime = const Duration(milliseconds: 550), + }) : super(key: key); + + @override + _ATDebounceWidgetState createState() => _ATDebounceWidgetState(); +} + +class _ATDebounceWidgetState extends State { + DateTime? _lastClickTime; + + @override + Widget build(BuildContext context) { + return GestureDetector( + behavior: HitTestBehavior.opaque, // 确保透明区域也能点击 + child: widget.child, + onTap: () { + DateTime now = DateTime.now(); + if (_lastClickTime == null || + now.difference(_lastClickTime!) > widget.debounceTime) { + _lastClickTime = now; + widget.onTap(); + }else { + if (widget.tips != null) { + ATTts.show(widget.tips ?? ""); + } + } + }, + ); + } +} diff --git a/lib/chatvibe_ui/components/at_float_ichart.dart b/lib/chatvibe_ui/components/at_float_ichart.dart new file mode 100644 index 0000000..22cfa39 --- /dev/null +++ b/lib/chatvibe_ui/components/at_float_ichart.dart @@ -0,0 +1,227 @@ +import 'dart:async'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_data/sources/remote/net/network_client.dart'; +import 'package:aslan/chatvibe_domain/models/res/join_room_res.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; + +// 默认位置 +Offset kDefaultFloatOffset = Offset( + ScreenUtil().screenWidth - width(90), + ScreenUtil().screenHeight - height(120), +); + +class ATFloatIchart { + /// 单例模式 + static final ATFloatIchart _ATFloatIchart = ATFloatIchart._internal(); //1 + factory ATFloatIchart() { + return _ATFloatIchart; + } + + ATFloatIchart._internal(); + + bool _inserted = false; + OverlayEntry? overlayEntry; + BuildContext? context; + Offset offset = kDefaultFloatOffset; + + show() { + if (overlayEntry != null) { + remove(); // 先移除已有的 + } + var overlayState = Overlay.of(context!); + overlayEntry = OverlayEntry( + builder: (context) { + if (inLoginPage) { + remove(); + return Container(); + } else { + return buildToastLayout(); + } + }, + ); + overlayState.insert(overlayEntry!); + _inserted = true; // 标记已插入 + } + + bool isShow() { + return _inserted; + } + + remove() { + if (overlayEntry != null && overlayEntry!.mounted) { + if (_inserted) { + overlayEntry?.remove(); + _inserted = false; + } + } + } + + LayoutBuilder buildToastLayout() { + Timer? timer; + return LayoutBuilder( + builder: (context, constraints) { + return Stack( + children: [ + Positioned( + left: offset.dx, + top: offset.dy, + child: GestureDetector( + //更新child的位置 + onPanUpdate: (details) { + var localPosition = details.delta; + var dx = (offset.dx + localPosition.dx).clamp( + 0.0, + ScreenUtil().screenWidth - width(90), + ); + var dy = (offset.dy + localPosition.dy).clamp( + 0.0, + ScreenUtil().screenHeight - height(55) - kToolbarHeight, + ); + offset = Offset(dx, dy); + overlayEntry!.markNeedsBuild(); + }, + //拖动结束,处理child贴边悬浮 + onPanEnd: (details) { + var oldPosition = offset.dx; + var targets = + offset.dx + width(80) > (ScreenUtil().screenWidth / 2) + ? ScreenUtil().screenWidth - width(90) + : 0; + timer = Timer.periodic(Duration(milliseconds: 1), (t) { + if (targets > 0) { + oldPosition++; + } else { + oldPosition--; + } + offset = Offset(oldPosition.toDouble(), offset.dy); + overlayEntry!.markNeedsBuild(); + if (oldPosition < 0 || + oldPosition > ScreenUtil().screenWidth - width(90)) { + timer?.cancel(); + } + }); + }, + child: Container( + child: float(), + margin: EdgeInsets.only(bottom: height(kToolbarHeight)), + ), + ), + ), + ], + ); + }, + ); + } + + Widget float() { + JoinRoomRes? room = + Provider.of( + context!, + listen: false, + ).currenRoom; + // User user = Provider.of(context, listen: false).maiMap[0]; + ChatVibeLoginRes? user = AccountStorage().getCurrentUser(); + return FloatRoomWindow(room: room, user: user, remove: remove); + } + + void init(BuildContext context) { + this.context = context; + } +} + +class FloatRoomWindow extends StatefulWidget { + final JoinRoomRes? room; + final ChatVibeLoginRes? user; + final Function remove; + + const FloatRoomWindow({Key? key, this.room, this.user, required this.remove}) + : super(key: key); + + @override + _FloatRoomWindowState createState() => _FloatRoomWindowState(); +} + +class _FloatRoomWindowState extends State { + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ATDebounceWidget( + onTap: () { + ATRoomUtils.openCurrentRoom(context); + }, + child: Container( + height: 55.w, + width: 90.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.w), + gradient: LinearGradient( + colors: [ + Color(0xFFFEB219), + Color(0xFFFF9326), + ], + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(width: 8.w), + Stack( + alignment: Alignment.center, + children: [ + netImage( + url: widget.room?.roomProfile?.roomProfile?.roomCover ?? "", + width: 40.w, + height: 40.w, + borderRadius: BorderRadius.circular(8.w), + ), + Image.asset( + "atu_images/index/at_icon_room_flot_ani.gif", + width: 15.w, + height: 15.w, + ), + ], + ), + SizedBox(width: width(5)), + ATDebounceWidget( + onTap: () { + Provider.of( + context, + listen: false, + ).extRoom(false); + Timer(Duration(milliseconds: 550), () { + widget.remove.call(); + }); + }, + child: Padding( + padding: EdgeInsets.all(5.w), + child: Image.asset( + "atu_images/index/at_icon_room_flot_close.png", + width: 20.w, + height: 20.w, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/chatvibe_ui/components/at_lk_fading_edge_scrollview.dart b/lib/chatvibe_ui/components/at_lk_fading_edge_scrollview.dart new file mode 100644 index 0000000..eae3918 --- /dev/null +++ b/lib/chatvibe_ui/components/at_lk_fading_edge_scrollview.dart @@ -0,0 +1,298 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +/// Flutter widget for displaying fading edge at start/end of scroll views +class ATFadingEdgeScrollView extends StatefulWidget { + /// child widget + final Widget child; + + /// scroll controller of child widget + /// + /// Look for more documentation at [ScrollView.scrollController] + final ScrollController scrollController; + + /// Whether the scroll view scrolls in the reading direction. + /// + /// Look for more documentation at [ScrollView.reverse] + final bool reverse; + + /// The axis along which child view scrolls + /// + /// Look for more documentation at [ScrollView.scrollDirection] + final Axis scrollDirection; + + /// what part of screen on start half should be covered by fading edge gradient + /// [gradientFractionOnStart] must be 0 <= [gradientFractionOnStart] <= 1 + /// 0 means no gradient, + /// 1 means gradients on start half of widget fully covers it + final double gradientFractionOnStart; + + /// what part of screen on end half should be covered by fading edge gradient + /// [gradientFractionOnEnd] must be 0 <= [gradientFractionOnEnd] <= 1 + /// 0 means no gradient, + /// 1 means gradients on start half of widget fully covers it + final double gradientFractionOnEnd; + + /// set to true if you want scrollController passed to widget to be disposed when widget's state is disposed + final bool shouldDisposeScrollController; + + const ATFadingEdgeScrollView._internal({ + Key? key, + required this.child, + required this.scrollController, + required this.reverse, + required this.scrollDirection, + required this.gradientFractionOnStart, + required this.gradientFractionOnEnd, + required this.shouldDisposeScrollController, + }) : assert(child != null), + assert(scrollController != null), + assert(reverse != null), + assert(scrollDirection != null), + assert(gradientFractionOnStart >= 0 && gradientFractionOnStart <= 1), + assert(gradientFractionOnEnd >= 0 && gradientFractionOnEnd <= 1), + super(key: key); + + /// Constructor for creating [ATFadingEdgeScrollView] with [ScrollView] as child + /// child must have [ScrollView.controller] set + factory ATFadingEdgeScrollView.fromScrollView({ + Key? key, + required ScrollView child, + double gradientFractionOnStart = 0.1, + double gradientFractionOnEnd = 0.1, + bool shouldDisposeScrollController = false, + }) { + assert(child.controller != null, "Child must have controller set"); + + return ATFadingEdgeScrollView._internal( + key: key, + child: child, + scrollController: child.controller!, + scrollDirection: child.scrollDirection, + reverse: child.reverse, + gradientFractionOnStart: gradientFractionOnStart, + gradientFractionOnEnd: gradientFractionOnEnd, + shouldDisposeScrollController: shouldDisposeScrollController, + ); + } + + /// Constructor for creating [ATFadingEdgeScrollView] with [SingleChildScrollView] as child + /// child must have [SingleChildScrollView.controller] set + factory ATFadingEdgeScrollView.fromSingleChildScrollView({ + Key? key, + required SingleChildScrollView child, + double gradientFractionOnStart = 0.1, + double gradientFractionOnEnd = 0.1, + bool shouldDisposeScrollController = false, + }) { + assert(child.controller != null, "Child must have controller set"); + + return ATFadingEdgeScrollView._internal( + key: key, + child: child, + scrollController: child.controller!, + scrollDirection: child.scrollDirection, + reverse: child.reverse, + gradientFractionOnStart: gradientFractionOnStart, + gradientFractionOnEnd: gradientFractionOnEnd, + shouldDisposeScrollController: shouldDisposeScrollController, + ); + } + + /// Constructor for creating [ATFadingEdgeScrollView] with [PageView] as child + /// child must have [PageView.controller] set + factory ATFadingEdgeScrollView.fromPageView({ + Key? key, + required PageView child, + double gradientFractionOnStart = 0.1, + double gradientFractionOnEnd = 0.1, + bool shouldDisposeScrollController = false, + }) { + assert(child.controller != null, "Child must have controller set"); + + return ATFadingEdgeScrollView._internal( + key: key, + child: child, + scrollController: child.controller!, + scrollDirection: child.scrollDirection, + reverse: child.reverse, + gradientFractionOnStart: gradientFractionOnStart, + gradientFractionOnEnd: gradientFractionOnEnd, + shouldDisposeScrollController: shouldDisposeScrollController, + ); + } + + /// Constructor for creating [ATFadingEdgeScrollView] with [AnimatedList] as child + /// child must have [AnimatedList.controller] set + factory ATFadingEdgeScrollView.fromAnimatedList({ + Key? key, + required AnimatedList child, + double gradientFractionOnStart = 0.1, + double gradientFractionOnEnd = 0.1, + bool shouldDisposeScrollController = false, + }) { + assert(child.controller != null, "Child must have controller set"); + + return ATFadingEdgeScrollView._internal( + key: key, + child: child, + scrollController: child.controller!, + scrollDirection: child.scrollDirection, + reverse: child.reverse, + gradientFractionOnStart: gradientFractionOnStart, + gradientFractionOnEnd: gradientFractionOnEnd, + shouldDisposeScrollController: shouldDisposeScrollController, + ); + } + + @override + _FadingEdgeScrollViewState createState() => _FadingEdgeScrollViewState(); +} + +class _FadingEdgeScrollViewState extends State + with WidgetsBindingObserver { + late ScrollController _controller; + bool _isScrolledToStart = false; + bool _isScrolledToEnd = false; + + @override + void initState() { + super.initState(); + + _controller = widget.scrollController; + _isScrolledToStart = _controller.initialScrollOffset == 0; + _controller.addListener(_onScroll); + + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_controller == null) { + return; + } + + if (_isScrolledToEnd == null && + _controller.position.maxScrollExtent == 0) { + setState(() { + _isScrolledToEnd = true; + }); + } + }); + WidgetsBinding.instance.addObserver(this); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + _controller.removeListener(_onScroll); + if (widget.shouldDisposeScrollController) { + _controller.dispose(); + } + } + + void _onScroll() { + final offset = _controller.offset; + final minOffset = _controller.position.minScrollExtent; + final maxOffset = _controller.position.maxScrollExtent; + + final isScrolledToEnd = offset >= maxOffset; + final isScrolledToStart = offset <= minOffset; + + if (isScrolledToEnd != _isScrolledToEnd || + isScrolledToStart != _isScrolledToStart) { + setState(() { + _isScrolledToEnd = isScrolledToEnd; + _isScrolledToStart = isScrolledToStart; + }); + } + } + + @override + void didChangeMetrics() { + super.didChangeMetrics(); + setState(() { + // Add the shading or remove it when the screen resize (web/desktop) or mobile is rotated + if (_controller.hasClients) { + final offset = _controller.offset; + final maxOffset = _controller.position.maxScrollExtent; + if (maxOffset == 0 && offset == 0) { + // Not scrollable + _isScrolledToStart = true; + _isScrolledToEnd = true; + } else if (maxOffset == offset) { + // Scrollable but at end + _isScrolledToStart = false; + _isScrolledToEnd = true; + } else if (maxOffset > 0 && offset == 0) { + // Scrollable but at start + _isScrolledToStart = true; + _isScrolledToEnd = false; + } else { + // Scroll in progress/not are either end + _isScrolledToStart = false; + _isScrolledToEnd = false; + } + } + }); + } + + @override + Widget build(BuildContext context) { + if (_isScrolledToStart == null && _controller.hasClients) { + final offset = _controller.offset; + final minOffset = _controller.position.minScrollExtent; + final maxOffset = _controller.position.maxScrollExtent; + + _isScrolledToEnd = offset >= maxOffset; + _isScrolledToStart = offset <= minOffset; + } + + return ShaderMask( + shaderCallback: (bounds) => LinearGradient( + begin: _gradientStart, + end: _gradientEnd, + stops: [ + 0, + widget.gradientFractionOnStart * 0.5, + 1 - widget.gradientFractionOnEnd * 0.5, + 1, + ], + colors: _getColors( + widget.gradientFractionOnStart > 0 && !(_isScrolledToStart ?? true), + widget.gradientFractionOnEnd > 0 && !(_isScrolledToEnd ?? false)), + ).createShader( + bounds.shift(Offset(-bounds.left, -bounds.top)), + textDirection: Directionality.of(context), + ), + child: widget.child, + blendMode: BlendMode.dstIn, + ); + } + + AlignmentGeometry get _gradientStart => + widget.scrollDirection == Axis.vertical + ? _verticalStart + : _horizontalStart; + + AlignmentGeometry get _gradientEnd => + widget.scrollDirection == Axis.vertical ? _verticalEnd : _horizontalEnd; + + Alignment get _verticalStart => + widget.reverse ? Alignment.bottomCenter : Alignment.topCenter; + + Alignment get _verticalEnd => + widget.reverse ? Alignment.topCenter : Alignment.bottomCenter; + + AlignmentDirectional get _horizontalStart => widget.reverse + ? AlignmentDirectional.centerEnd + : AlignmentDirectional.centerStart; + + AlignmentDirectional get _horizontalEnd => widget.reverse + ? AlignmentDirectional.centerStart + : AlignmentDirectional.centerEnd; + + List _getColors(bool isStartEnabled, bool isEndEnabled) => [ + (isStartEnabled ? Colors.transparent : Colors.white), + Colors.white, + Colors.white, + (isEndEnabled ? Colors.transparent : Colors.white) + ]; +} diff --git a/lib/chatvibe_ui/components/at_page_list.dart b/lib/chatvibe_ui/components/at_page_list.dart new file mode 100644 index 0000000..7279b81 --- /dev/null +++ b/lib/chatvibe_ui/components/at_page_list.dart @@ -0,0 +1,213 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; + +///分页列表 +class ATPageList extends StatefulWidget { + const ATPageList({Key? key}) : super(key: key); + + @override + ATPageListState createState() => ATPageListState(); +} + +class ATPageListState extends State { + final RefreshController _refreshController = RefreshController( + initialRefresh: false, + ); + + ///当前第几页 + int currentPage = 0; + List items = []; + Color backgroundColor = Colors.white; + + ///是否开启分页 + bool enablePullUp = true; + bool enablePullDown = true; + bool isLoading = false; + bool needLoading = true; + int pageCount = 20; + EdgeInsetsGeometry? padding; + + bool isShowDivider = true; + bool isGridView = false; + bool isShowBottomMsg = true; + bool isShowFooter = true; + bool isCanClickEmpty = true; + int gridViewCount = 3; + + SliverGridDelegateWithFixedCrossAxisCount? gridDelegate; + + void loadComplete() { + isLoading = false; + _refreshController.refreshCompleted(); + _refreshController.loadComplete(); + } + + ///刷新列表 + void loadData(int page) { + setState(() { + isLoading = true; + }); + loadPage( + page: page, + onSuccess: (list) { + if (page == 1) { + items.clear(); + } + items.addAll(list); + if (mounted) setState(() {}); + currentPage = page; + _refreshController.refreshCompleted(); + _refreshController.loadComplete(); + if (list.length < pageCount) { + _refreshController.loadNoData(); + } + isLoading = false; + if (mounted) setState(() {}); + }, + onErr: () { + _refreshController.loadNoData(); + _refreshController.refreshCompleted(); + isLoading = false; + if (mounted) setState(() {}); + }, + ); + } + + Widget buildList(BuildContext context) { + return Container( + color: backgroundColor, + child: SmartRefresher( + enablePullDown: enablePullDown, + enablePullUp: enablePullUp, + controller: _refreshController, + onRefresh: () { + loadData(1); + }, + onLoading: () { + loadData(currentPage + 1); + }, + footer: + isShowFooter + ? null + : CustomFooter( + height: 1, + builder: (context, mode) { + return SizedBox( + height: 1, + width: 1, + child: SizedBox(height: 1, width: 1), + ); + }, + ), + child: + items.isEmpty + ? (isCanClickEmpty + ? GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + loadData(1); + }, + child: + needLoading && isLoading + ? Center(child: CupertinoActivityIndicator()) + : empty(), + ) + : needLoading && isLoading + ? Center(child: CupertinoActivityIndicator()) + : empty()) + : _buildList(), + ), + ); + } + + Widget _buildList() { + if (isGridView) { + return GridView.builder( + gridDelegate: + gridDelegate ?? + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: gridViewCount, + ), + itemBuilder: (c, i) { + return buildItemOne(items[i], i); + }, + padding: padding ?? EdgeInsets.zero, + itemCount: items.length, + ); + } + return ListView.separated( + itemBuilder: (context, i) => buildItemOne(items[i], i), + itemCount: items.length, + padding: padding ?? EdgeInsets.zero, + separatorBuilder: (context, i) => builderDivider(), + ); + } + + ///默认空页面 + empty() { + // List list = []; + // list.add(SizedBox( + // height: height(80), + // )); + // list.add(Image.asset('atu_images/main/empty.png')); + // list.add( + // SizedBox( + // height: height(15), + // ), + // ); + // list.add( + // Text("暂无数据", + // style: TextStyle( + // fontSize: sp(14), + // color: Color(0xff999999), + // fontWeight: FontWeight.w400, + // decoration: TextDecoration.none, + // height: 1, + // )), + // ); + + //return Column(mainAxisSize: MainAxisSize.min, children: list); + return mainEmpty(msg: ATAppLocalizations.of(context)!.noData); + } + + ///默认列表项 + Widget buildItemOne(M item, int index) { + return buildItem(item); + } + + ///默认列表项 + Widget buildItem(M item) { + return Card(child: Center(child: Text(item.toString()))); + } + + ///默认加载数据 + void loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) { + onSuccess([]); + } + + @override + Widget build(BuildContext context) { + return Container(); + } + + builderDivider() { + return isShowDivider + ? Divider( + height: height(1), + color: ChatVibeTheme.dividerColor, + indent: width(15), + endIndent: width(15), + ) + : Container(); + } +} diff --git a/lib/chatvibe_ui/components/at_tts.dart b/lib/chatvibe_ui/components/at_tts.dart new file mode 100644 index 0000000..6aeba63 --- /dev/null +++ b/lib/chatvibe_ui/components/at_tts.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +class ATTts{ + static show(String msg, {gravity = ToastGravity.BOTTOM}) { + Fluttertoast.showToast( + msg: msg, + toastLength: Toast.LENGTH_SHORT, + gravity: gravity, //位置 + timeInSecForIosWeb: 2, //时间 + backgroundColor: Colors.black45, + textColor: Colors.white); + } +} \ No newline at end of file diff --git a/lib/chatvibe_ui/components/chatvibe_gradient_button.dart b/lib/chatvibe_ui/components/chatvibe_gradient_button.dart new file mode 100644 index 0000000..7ba16b9 --- /dev/null +++ b/lib/chatvibe_ui/components/chatvibe_gradient_button.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +/// 背景带渐变色的button +chatvibeGradientButton({ + double radius = 25, + LinearGradient? gradient, + required Function onPress, + required String text, + Color textColor = Colors.white, + double textSize = 14, +}) { + return GestureDetector( + child: Container( + height: 28.w, + alignment: AlignmentDirectional.center, + margin: EdgeInsetsDirectional.fromSTEB(5, 5, 0, 5), + padding: EdgeInsets.symmetric(horizontal: 5.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(radius), + gradient: + gradient ?? LinearGradient(colors: [Colors.white, Colors.white]), + ), + child: Text(text, style: TextStyle(color: textColor, fontSize: textSize)), + ), + onTap: () { + onPress(); + }, + ); +} diff --git a/lib/chatvibe_ui/components/chatvibe_tap_widget.dart b/lib/chatvibe_ui/components/chatvibe_tap_widget.dart new file mode 100644 index 0000000..a4f7d80 --- /dev/null +++ b/lib/chatvibe_ui/components/chatvibe_tap_widget.dart @@ -0,0 +1,106 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +// ignore: must_be_immutable +class ChatVibeTapWidget extends StatefulWidget { + final Widget? child; + final Function? onTap; + Color highlightColor; + final Duration? duration; + final BorderRadius? borderRadius; + + ChatVibeTapWidget( + {Key? key, + this.child, + this.onTap, + this.highlightColor = Colors.black12, + this.duration, this.borderRadius}) + : super(key: key); + + @override + TapWidgetState createState() { + return TapWidgetState(); + } +} + +class TapWidgetState extends State + with SingleTickerProviderStateMixin { + late AnimationController _ctl; + late Animation _colorAnimation; + Duration? _duration; + bool get onTapEnable => widget.onTap != null; + + @override + void initState() { + super.initState(); + _duration = widget.duration; + _duration ??= Duration(milliseconds: 1); + _ctl = AnimationController(vsync: this, duration: _duration); + widget.highlightColor??=Colors.black12; + + _colorAnimation = Tween( + begin: widget.highlightColor.withOpacity(0.0), + end: widget.highlightColor, + ).animate(_ctl); + } + + @override + void dispose() { + _ctl.stop(); + _ctl.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + child: AnimatedBuilder( + animation: _ctl, + builder: (BuildContext context, Widget? child) { + return Container( + foregroundDecoration: BoxDecoration( + color: _colorAnimation.value, + borderRadius: widget.borderRadius + ), + child: widget.child, + ); + }, + ), + onTap: (){ + if(widget.onTap!=null){ + widget.onTap!(); + } + }, + onTapDown: (d) { + if (onTapEnable) _ctl.forward(); + }, + onTapUp: (d) { + if (onTapEnable) prepareToIdle(); + }, + onTapCancel: (){ + if (onTapEnable) prepareToIdle(); + }, + ); + } + + void prepareToIdle() { + late AnimationStatusListener listener; + listener = (AnimationStatus statue) { + if (statue == AnimationStatus.completed) { + _ctl.removeStatusListener(listener); + toStart(); + } + }; + _ctl.addStatusListener(listener); + if (!_ctl.isAnimating) { + _ctl.removeStatusListener(listener); + toStart(); + } + } + + void toStart() { + _ctl.stop(); + _ctl.reverse(); + } +} \ No newline at end of file diff --git a/lib/chatvibe_ui/components/custom_cached_image.dart b/lib/chatvibe_ui/components/custom_cached_image.dart new file mode 100644 index 0000000..d3f6fae --- /dev/null +++ b/lib/chatvibe_ui/components/custom_cached_image.dart @@ -0,0 +1,52 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; + +class CustomCachedImage extends StatelessWidget { + final String imageUrl; + final double? width; + final double? height; + final BoxFit fit; + final double borderRadius; + final Widget? placeholder; + final Widget? errorWidget; + + const CustomCachedImage({ + super.key, + required this.imageUrl, + this.width, + this.height, + this.fit = BoxFit.cover, + this.borderRadius = 0, + this.placeholder, + this.errorWidget, + }); + + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: BorderRadius.circular(borderRadius), + child: CachedNetworkImage( + imageUrl: imageUrl, + width: width, + height: height, + fit: fit, + placeholder: (context, url) => + placeholder ?? _defaultPlaceholder(), + errorWidget: (context, url, error) => + errorWidget ?? _defaultErrorWidget(), + ), + ); + } + + Widget _defaultPlaceholder() { + return Center( + child: Image.asset("atu_images/general/at_icon_loading.webp"), + ); + } + + Widget _defaultErrorWidget() { + return Center( + child: Image.asset("atu_images/general/at_icon_loading.webp"), + ); + } +} \ No newline at end of file diff --git a/lib/chatvibe_ui/components/dialog/dialog.dart b/lib/chatvibe_ui/components/dialog/dialog.dart new file mode 100644 index 0000000..dd7ba72 --- /dev/null +++ b/lib/chatvibe_ui/components/dialog/dialog.dart @@ -0,0 +1,145 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:loading_indicator_view_plus/loading_indicator_view_plus.dart'; + +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; + +typedef OnFutureFunc = Future Function(); +typedef OnSuccess = Function(dynamic v); + +class STLoadingDialog extends Dialog { + String text; + Widget? centerContent; + final GestureTapCallback? verifyCallback; + final double? height; + final double? width; + + STLoadingDialog({ + Key? key, + required this.text, + this.height, + this.width, + this.centerContent, + this.verifyCallback, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Material( + //创建透明层 + type: MaterialType.transparency, //透明类型 + child: new Center( + //保证控件居中效果 + child: new Container( + constraints: BoxConstraints(maxWidth: ScreenUtil().screenWidth * 0.4), + decoration: ShapeDecoration( + color: Color(0xffffffff), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(8.0), + ), + ), + ), + child: AspectRatio( + aspectRatio: 1, + child: new Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + BallClipRotateMultipleIndicator( + color: Color(0xffFF50BC), + ), + Text( + "${text ?? "loading..."}", + style: TextStyle(fontSize: 14.sp, color: Color(0xff888888)), + ) + ], + ), + ), + ), + ), + ); + } +} + +class FutureLoadingDialog extends StatefulWidget { + String text; + Widget? centerContent; + final GestureTapCallback? verifyCallback; + final double? height; + final double? width; + final OnFutureFunc future; + final OnSuccess? onSuccess; + final OnSuccess? onError; + + FutureLoadingDialog({ + Key? key, + this.text = "", + this.height, + this.width, + this.centerContent, + this.verifyCallback, + required this.future, + this.onSuccess, + this.onError, + }) : assert(future != null), + super(key: key); + + @override + _FutureLoadingDialogState createState() => _FutureLoadingDialogState(); +} + +class _FutureLoadingDialogState extends State { + @override + void initState() { + // TODO: implement initState + super.initState(); + widget.future.call().then((value) { + ATNavigatorUtils.goBack(context); + widget.onSuccess?.call(value); + }).catchError((e) { + ATNavigatorUtils.goBack(context); + widget.onError?.call(e); + }); + } + + @override + Widget build(BuildContext context) { + return Material( + //创建透明层 + type: MaterialType.transparency, //透明类型 + child: new Center( + //保证控件居中效果 + child: new Container( + constraints: BoxConstraints(maxWidth: ScreenUtil().screenWidth * 0.4), + decoration: ShapeDecoration( + color: Color(0xffffffff), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(8.0), + ), + ), + ), + child: AspectRatio( + aspectRatio: 1, + child: new Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + BallClipRotateMultipleIndicator( + color: Color(0xffFF50BC), + ), + Text( + "${widget.text ?? "loading..."}", + style: TextStyle(fontSize: 14.sp, color: Color(0xff888888)), + ) + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/chatvibe_ui/components/dialog/dialog_base.dart b/lib/chatvibe_ui/components/dialog/dialog_base.dart new file mode 100644 index 0000000..01248a7 --- /dev/null +++ b/lib/chatvibe_ui/components/dialog/dialog_base.dart @@ -0,0 +1,581 @@ +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; +import 'package:flutter_debouncer/flutter_debouncer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_msg_item.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/constants/at_room_msg_type.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; + +// 基础对话框 +class MsgDialog extends Dialog { + final String msg; + String title = ""; + String? btnText; + String? cancelText; + late BuildContext context; + final VoidCallback onEnsure; + final VoidCallback? onCancel; + bool isDark = false; + bool isBtnBgFlip = false; + bool leftConfirm = false; + + MsgDialog({ + Key? key, + this.title = "", + required this.msg, + required this.onEnsure, + this.btnText, + this.cancelText, + this.isDark = false, + this.isBtnBgFlip = false, + this.leftConfirm = false, + this.onCancel, + }); + + @override + Widget build(BuildContext context) { + this.context = context; + + return Material( + //创建透明层 + type: MaterialType.transparency, //透明类型 + child: Stack( + children: [ + GestureDetector( + onTap: () { + //空白区域点击对话框消失 + SmartDialog.dismiss(tag: "showConfirmDialog"); + }, + ), + _buildContentView(context), + ], + ), + ); + } + + Widget _buildContentView(BuildContext context) { + return Center( + //保证控件居中效果 + child: Container( + constraints: BoxConstraints(maxHeight: 420.w), + margin: EdgeInsets.symmetric(horizontal: 18.w), + decoration: ShapeDecoration( + color: isDark ? Color(0xff161616) : Color(0xffffffff), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(width(10))), + ), + ), + child: _buildDialog(context), + ), + ); + } + + Widget _buildDialog(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 15.w), + title.isNotEmpty + ? Container( + alignment: Alignment.center, + child: text( + title, + fontSize: 16.sp, + lineHeight: 1.1, + letterSpacing: 0.1, + textColor: isDark ? Colors.white : Colors.black, + fontWeight: FontWeight.bold, + ), + ) + : Container(), + SizedBox(height: 15.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 8.w), + alignment: Alignment.center, + child: text( + msg, + maxLines: 20, + fontSize: 14.sp, + lineHeight: 1.1, + letterSpacing: 0.1, + textColor: isDark ? Colors.white : Colors.black54, + textAlign: TextAlign.center, + ), + ), + SizedBox(height: 10.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 15.w, vertical: 8.w), + height: height(38), + alignment: Alignment.center, + child: Row( + textDirection: leftConfirm ? TextDirection.rtl : TextDirection.ltr, + children: [ + Expanded( + child: GestureDetector( + child: Container( + decoration: + isBtnBgFlip + ? BoxDecoration( + borderRadius: BorderRadius.circular(33.w), + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + isDark + ? Color(0xff260054) + : Color(0xffFF9326), + isDark + ? Color(0xff6105B7) + : Color(0xffFEB219), + ], + ), + ) + : BoxDecoration( + borderRadius: BorderRadius.circular(33.w), + color: isDark ? Colors.transparent : Colors.white, + border: Border.all( + color: Color(0xffFEB219), + width: 1.w, + ), + ), + alignment: Alignment.center, + child: text( + cancelText ?? ATAppLocalizations.of(context)!.cancel, + fontSize: 14.sp, + textColor: + isDark + ? Colors.grey + : (isBtnBgFlip + ? Colors.white + : Color(0xffFF9326)), + fontWeight: FontWeight.bold, + ), + ), + onTap: () { + onCancel?.call(); + SmartDialog.dismiss(tag: "showConfirmDialog"); + }, + ), + ), + SizedBox(width: 15.w), + Expanded( + child: GestureDetector( + child: Container( + decoration: + isBtnBgFlip + ? BoxDecoration( + borderRadius: BorderRadius.circular(33.w), + color: isDark ? Colors.transparent : Colors.white, + border: Border.all( + color: Color(0xffE6E6E6), + width: 1.w, + ), + ) + : BoxDecoration( + borderRadius: BorderRadius.circular(33.w), + gradient: LinearGradient( + begin: Alignment.bottomLeft, + end: Alignment.topRight, + colors: [ + isDark + ? Color(0xff260054) + : Color(0xffFF9326), + isDark + ? Color(0xff6105B7) + : Color(0xffFEB219), + ], + ), + ), + alignment: Alignment.center, + child: text( + btnText ?? ATAppLocalizations.of(context)!.confirm, + fontSize: 14.sp, + textColor: isBtnBgFlip ? Colors.grey : Colors.white, + fontWeight: FontWeight.bold, + ), + ), + onTap: () { + onEnsureClick(); + }, + ), + ), + ], + ), + ), + SizedBox(height: 15.w), + ], + ); + } + + /*确定按钮点击事件*/ + onEnsureClick() { + onEnsure(); + SmartDialog.dismiss(tag: "showConfirmDialog"); + } +} + +// ignore: must_be_immutable +class OpenRoomLoadingDialog extends Dialog { + String text; + Widget? centerContent; + final GestureTapCallback? verifyCallback; + final double? height; + final double? width; + + OpenRoomLoadingDialog({ + Key? key, + this.text = "", + this.height, + this.width, + this.centerContent, + this.verifyCallback, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Material( + //创建透明层 + type: MaterialType.transparency, //透明类型 + child: Center( + //保证控件居中效果 + child: Container( + constraints: BoxConstraints(maxWidth: ScreenUtil().screenWidth * 0.4), + decoration: ShapeDecoration( + color: Colors.black.withOpacity(0.78), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + ), + padding: EdgeInsets.all(15.w), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset("atu_images/raw/loading.webp", width: 80.w), + SizedBox(height: 12.w), + Text( + "正在进入聊天室......", + style: TextStyle(fontSize: 12.sp, color: Colors.white), + ), + ], + ), + ), + ), + ); + } +} + +class DialogLine extends StatefulWidget { + @override + _DialogLineState createState() => _DialogLineState(); +} + +class _DialogLineState extends State with TickerProviderStateMixin { + late AnimationController animationController1; + late AnimationController animationController2; + late AnimationController animationController3; + + @override + void initState() { + // TODO: implement initState + animationController1 = AnimationController( + vsync: this, + duration: Duration(milliseconds: 350), + ); + animationController3 = AnimationController( + vsync: this, + duration: Duration(milliseconds: 350), + ); + animationController2 = AnimationController( + vsync: this, + duration: Duration(milliseconds: 350), + ); + animationController1.forward(); + animationController1.addStatusListener((status) { + if (status == AnimationStatus.completed) { + animationController1.reverse(); + animationController2.forward(); + } + }); + animationController2.addStatusListener((status) { + if (status == AnimationStatus.completed) { + animationController2.reverse(); + animationController3.forward(); + } + }); + animationController3.addStatusListener((status) { + if (status == AnimationStatus.completed) { + animationController3.reverse(); + animationController1.forward(); + } + }); + super.initState(); + } + + @override + void dispose() { + // TODO: implement dispose + animationController1.dispose(); + animationController2.dispose(); + animationController3.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + ScaleTransition( + scale: Tween(begin: 0.5, end: 1.0).animate(animationController1), + child: _container(), + ), + SizedBox(width: 6), + ScaleTransition( + scale: Tween(begin: 0.5, end: 1.0).animate(animationController2), + child: _container(), + ), + SizedBox(width: 6), + ScaleTransition( + scale: Tween(begin: 0.5, end: 1.0).animate(animationController3), + child: _container(), + ), + ], + ); + } + + Container _container() { + return Container( + height: 28, + width: 28, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + colors: [ + Colors.deepPurpleAccent, + Colors.deepPurpleAccent.withOpacity(0.5), + ], + ), + ), + ); + } +} + +class KickedOutOfRoomDialog extends StatefulWidget { + final String roomId; + final String userId; + + const KickedOutOfRoomDialog({ + super.key, + required this.roomId, + required this.userId, + }); + + @override + _KickedOutOfRoomDialogState createState() => _KickedOutOfRoomDialogState(); +} + +class _KickedOutOfRoomDialogState extends State { + int selectType = -1; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Container( + width: 260.w, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(12.w)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 22.w), + text( + ATAppLocalizations.of(context)!.kickedOutOfRoom, + fontSize: 16.sp, + textColor: Colors.black, + fontWeight: FontWeight.bold, + ), + SizedBox(height: 14.w), + GestureDetector( + child: _buildItem("5 minutes", selectType == 0), + onTap: () { + setState(() { + selectType = 0; + }); + }, + ), + SizedBox(height: 14.w), + GestureDetector( + child: _buildItem("24 hours", selectType == 1), + onTap: () { + setState(() { + selectType = 1; + }); + }, + ), + SizedBox(height: 14.w), + GestureDetector( + child: _buildItem("1 month", selectType == 2), + onTap: () { + setState(() { + selectType = 2; + }); + }, + ), + SizedBox(height: 14.w), + GestureDetector( + child: _buildItem( + ATAppLocalizations.of(context)!.permanent, + selectType == 3, + ), + onTap: () { + setState(() { + selectType = 3; + }); + }, + ), + SizedBox(height: 13.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(width: 20.w), + GestureDetector( + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 12.w, + vertical: 5.w, + ), + child: text( + ATAppLocalizations.of(context)!.cancel, + fontSize: 14.sp, + textColor: Colors.grey, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showKickedOutOfRoomDialog"); + }, + ), + Spacer(), + ATDebounceWidget( + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 12.w, + vertical: 5.w, + ), + child: text( + ATAppLocalizations.of(context)!.confirm, + fontSize: 14.sp, + textColor: Colors.black, + ), + ), + onTap: () async { + if (selectType > -1) { + String roomId = + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.id ?? + ""; + if (selectType == 0) { + await ChatRoomRepository().joinBlacklist( + roomId, + widget.userId, + 5, + ); + ATTts.show( + ATAppLocalizations.of(context)!.operationSuccessful, + ); + SmartDialog.dismiss(tag: "showKickedOutOfRoomDialog"); + } else if (selectType == 1) { + await ChatRoomRepository().joinBlacklist( + roomId, + widget.userId, + 1440, + ); + ATTts.show( + ATAppLocalizations.of(context)!.operationSuccessful, + ); + SmartDialog.dismiss(tag: "showKickedOutOfRoomDialog"); + } else if (selectType == 2) { + await ChatRoomRepository().joinBlacklist( + roomId, + widget.userId, + 43200, + ); + ATTts.show( + ATAppLocalizations.of(context)!.operationSuccessful, + ); + SmartDialog.dismiss(tag: "showKickedOutOfRoomDialog"); + } else if (selectType == 3) { + await ChatRoomRepository().joinBlacklist( + roomId, + widget.userId, + 5184000, + ); + ATTts.show( + ATAppLocalizations.of(context)!.operationSuccessful, + ); + SmartDialog.dismiss(tag: "showKickedOutOfRoomDialog"); + } + } + }, + ), + SizedBox(width: 20.w), + ], + ), + SizedBox(height: 20.w), + ], + ), + ); + } + + _buildItem(String time, bool isSelect) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 6.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Colors.transparent, + border: Border.all(color: ChatVibeTheme.textSecondary, width: 1.w), + ), + width: 155.w, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(width: 3.w), + Image.asset( + isSelect + ? "atu_images/login/at_icon_login_ser_select.png" + : "atu_images/login/at_icon_login_ser_select_un.png", + width: 16.w, + height: 16.w, + ), + SizedBox(width: 10.w), + text(time, fontSize: 14.sp, textColor: Colors.black), + ], + ), + ); + } +} diff --git a/lib/chatvibe_ui/components/text/at_button.dart b/lib/chatvibe_ui/components/text/at_button.dart new file mode 100644 index 0000000..f677173 --- /dev/null +++ b/lib/chatvibe_ui/components/text/at_button.dart @@ -0,0 +1,51 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +Widget btn({GestureTapCallback? onTap, Widget? child}) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: onTap, + child: child, + ); +} + +Widget btnWithBorder({ + GestureTapCallback? onTap, + Widget? child, + double paddingVertical = 0, + double paddingHorizontal = 0, + double marginHorizontal = 0, + double marginVertical = 0, + EdgeInsetsGeometry? margin, + EdgeInsetsGeometry? padding, + Color borderColor = Colors.transparent, + double borderWidth = 0, + Color color = Colors.transparent, + double radius = 0, + double? width, + double? height, + AlignmentGeometry? alignment, + Gradient? gradient, + BorderRadiusGeometry? borderRadius, + ImageProvider? bgImage, +}) { + return btn( + onTap: onTap, + child: Container( + width: width, + height: height, + alignment: alignment, + padding: padding ?? EdgeInsets.symmetric(vertical: paddingVertical.w, horizontal: paddingHorizontal.w), + margin: margin ?? EdgeInsets.symmetric(horizontal: marginHorizontal, vertical: marginVertical), + decoration: BoxDecoration( + border: Border.all(color: borderColor, width: borderWidth), + borderRadius: borderRadius ?? BorderRadius.all(Radius.circular(radius)), + color: color, + gradient: gradient, + image: bgImage != null ? DecorationImage(image: bgImage, fit: BoxFit.fill) : null, + ), + child: child, + ), + ); +} diff --git a/lib/chatvibe_ui/components/text/at_marquee_gradient_text.dart b/lib/chatvibe_ui/components/text/at_marquee_gradient_text.dart new file mode 100644 index 0000000..d40416b --- /dev/null +++ b/lib/chatvibe_ui/components/text/at_marquee_gradient_text.dart @@ -0,0 +1,283 @@ +import 'dart:ui' show lerpDouble; + +import 'package:extended_text/extended_text.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class ATMarqueeGradientText extends StatefulWidget { + final String text; + final double speed; // 滚动速度(像素/秒) + final double sweepSpeed; // 扫光速度(单位/秒) + final double gapWidth; // 滚动时文字间的间距(像素) + final List gradientColors; + final double fontSize; + final bool needScroll; // 外部控制是否滚动 + final bool needGradient; // 外部控制是否扫光 + final bool pauseAnimation; // 外部控制是否暂停动画 + final Color textColor; + final FontWeight? fontWeight; + + const ATMarqueeGradientText({ + super.key, + required this.text, + this.speed = 30, + this.sweepSpeed = 0.5, + this.fontSize = 12, + this.needScroll = false, + this.needGradient = false, + this.pauseAnimation = false, + this.fontWeight, + this.textColor = Colors.white, + this.gapWidth = 80, + this.gradientColors = const [Colors.red, Colors.yellow, Colors.red], + }); + + @override + State createState() => _ATMarqueeGradientTextState(); +} + +class _ATMarqueeGradientTextState extends State + with TickerProviderStateMixin { + late ScrollController _scrollController; + AnimationController? _scrollAnimationController; + AnimationController? _sweepAnimationController; + + static final RegExp _emojiRegex = RegExp( + r'[\u{1F000}-\u{1FAFF}\u{2600}-\u{27BF}\u{FE0F}\u{200D}]', + unicode: true, + ); + + bool get _containsEmoji => _emojiRegex.hasMatch(widget.text); + + bool get _allowShaderEffects => !_containsEmoji; + + bool get _hasMeaningfulGradient { + if (widget.gradientColors.length < 2) { + return false; + } + final Color firstColor = widget.gradientColors.first; + for (final Color color in widget.gradientColors.skip(1)) { + if (color != firstColor) { + return true; + } + } + return false; + } + + bool get _shouldApplyStaticGradient => + _allowShaderEffects && _hasMeaningfulGradient; + + bool get _shouldApplySweep => widget.needGradient && _allowShaderEffects; + + Color get _emojiFallbackColor { + if (widget.gradientColors.isEmpty) { + return widget.textColor; + } + return widget.gradientColors[widget.gradientColors.length ~/ 2]; + } + + String get _fullText { + if (widget.needScroll) { + double spaceWidth = widget.fontSize * 0.6; + int gapSpaces = (widget.gapWidth / spaceWidth).ceil(); + String gap = List.filled(gapSpaces, ' ').join(); + return widget.text + gap + widget.text; + } else { + return widget.text; + } + } + + @override + void initState() { + super.initState(); + _scrollController = ScrollController(); + _rebuildSweepAnimation(); + + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted && + widget.needScroll && + !widget.pauseAnimation && + _scrollController.hasClients) { + _startScrollAnimation(); + } + }); + } + + void _rebuildSweepAnimation() { + _sweepAnimationController?.stop(); + _sweepAnimationController?.dispose(); + _sweepAnimationController = null; + + if (!_shouldApplySweep || widget.pauseAnimation) { + return; + } + + final double normalizedSpeed = widget.sweepSpeed.clamp(0.2, 5.0); + final int durationMs = (2600 / normalizedSpeed).round(); + + _sweepAnimationController = AnimationController( + vsync: this, + duration: Duration(milliseconds: durationMs), + )..repeat(); + } + + void _startScrollAnimation() { + _scrollAnimationController?.stop(); + _scrollAnimationController?.dispose(); + _scrollAnimationController = null; + + final double totalScrollDistance = + _scrollController.position.maxScrollExtent / 2; + if (totalScrollDistance <= 0) return; + + final double speed = widget.speed <= 0 ? 1 : widget.speed; + double durationSeconds = totalScrollDistance / speed; + _scrollAnimationController = AnimationController( + vsync: this, + duration: Duration(milliseconds: (durationSeconds * 1000).round()), + ); + + _scrollController.jumpTo(0); + _scrollAnimationController!.addListener(() { + if (!mounted || !_scrollController.hasClients) return; + final double offset = + _scrollAnimationController!.value * totalScrollDistance; + _scrollController.jumpTo(offset); + }); + + _scrollAnimationController!.repeat(); + } + + void _resetScrollAnimation() { + if (widget.needScroll && + !widget.pauseAnimation && + _scrollController.hasClients) { + _startScrollAnimation(); + } else { + _scrollAnimationController?.stop(); + } + } + + @override + void didUpdateWidget(covariant ATMarqueeGradientText oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.text != oldWidget.text || + widget.needScroll != oldWidget.needScroll || + widget.pauseAnimation != oldWidget.pauseAnimation || + widget.speed != oldWidget.speed || + widget.gapWidth != oldWidget.gapWidth || + widget.fontSize != oldWidget.fontSize) { + // 文本长度或滚动参数变化 -> 需要重新启动滚动动画 + // 但必须等布局完成后才能获取 maxScrollExtent + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _resetScrollAnimation(); + } + }); + } + + if (widget.needGradient != oldWidget.needGradient || + widget.pauseAnimation != oldWidget.pauseAnimation || + widget.sweepSpeed != oldWidget.sweepSpeed || + !listEquals(widget.gradientColors, oldWidget.gradientColors) || + _containsEmoji != _emojiRegex.hasMatch(oldWidget.text)) { + _rebuildSweepAnimation(); + } + } + + @override + void dispose() { + _scrollAnimationController?.dispose(); + _sweepAnimationController?.dispose(); + _scrollController.dispose(); + super.dispose(); + } + + Widget _buildText(String content) { + return + Padding(padding: EdgeInsets.symmetric(vertical: 1.5.w,),child: ExtendedText( + content, + style: TextStyle( + height: 1.11, + fontSize: widget.fontSize, + fontWeight: widget.fontWeight, + color: _allowShaderEffects ? widget.textColor : _emojiFallbackColor, + ), + overflow: TextOverflow.visible, + maxLines: 1, + ),) ; + } + + Widget _applyStaticGradient(Widget child) { + return ShaderMask( + shaderCallback: (Rect bounds) { + return LinearGradient( + colors: widget.gradientColors, + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ).createShader(bounds); + }, + blendMode: BlendMode.srcIn, + child: child, + ); + } + + Widget _applySweep(Widget child) { + final AnimationController? controller = _sweepAnimationController; + if (controller == null) { + return child; + } + + return AnimatedBuilder( + animation: controller, + builder: (context, _) { + final double center = lerpDouble(-1.2, 1.2, controller.value)!; + const double halfBand = 0.17; + return ShaderMask( + shaderCallback: (Rect bounds) { + return LinearGradient( + begin: Alignment(center - halfBand, 0), + end: Alignment(center + halfBand, 0), + colors: [ + Colors.transparent, + Colors.white.withValues(alpha: 0.9), + Colors.transparent, + ], + stops: const [0.0, 0.5, 1.0], + ).createShader(bounds); + }, + blendMode: BlendMode.srcATop, + child: child, + ); + }, + child: child, + ); + } + + @override + Widget build(BuildContext context) { + final String displayText = _fullText; + Widget textWidget = _buildText(displayText); + + if (widget.needScroll) { + textWidget = SingleChildScrollView( + scrollDirection: Axis.horizontal, + physics: const NeverScrollableScrollPhysics(), + controller: _scrollController, + child: textWidget, + ); + } + + if (_shouldApplyStaticGradient) { + textWidget = _applyStaticGradient(textWidget); + } + + if (_shouldApplySweep) { + textWidget = _applySweep(textWidget); + } + + return RepaintBoundary(child: textWidget); + } +} diff --git a/lib/chatvibe_ui/components/text/at_text.dart b/lib/chatvibe_ui/components/text/at_text.dart new file mode 100644 index 0000000..71efaa3 --- /dev/null +++ b/lib/chatvibe_ui/components/text/at_text.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +import '../../../chatvibe_data/models/enum/at_vip_type.dart'; +import 'at_marquee_gradient_text.dart'; + +final RegExp _nickNameEmojiRegex = RegExp( + r'[\u{1F000}-\u{1FAFF}\u{2600}-\u{27BF}\u{FE0F}\u{200D}]', + unicode: true, +); + +Widget text( + String content, { + double fontSize = 12, + Color textColor = Colors.white, + int maxLines = 1, + TextDecoration decoration = TextDecoration.none, + FontWeight fontWeight = FontWeight.w400, + TextOverflow overflow = TextOverflow.ellipsis, + TextAlign? textAlign, + double? maxWidth, + double? letterSpacing, + double? lineHeight, + FontStyle? fontStyle, + List? shadows, + TextDirection? textDirection, +}) { + return Container( + constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity), + child: Text( + content, + overflow: overflow, + maxLines: maxLines, + textAlign: textAlign, + style: TextStyle( + fontSize: fontSize.sp, + color: textColor, + fontWeight: fontWeight, + decoration: decoration, + letterSpacing: letterSpacing, + height: lineHeight, + fontStyle: fontStyle, + shadows: shadows, + decorationThickness: 2, + decorationColor: textColor, + ), + textDirection: textDirection, + ), + ); +} + +Widget chatvibeNickNameText( + String content, { + double fontSize = 12, + int maxLines = 1, + Color textColor = Colors.white, + double? maxWidth, + String? type, + bool needScroll = false, + bool pauseAnimation = false, + FontWeight fontWeight = FontWeight.w400, + double? speed, + double? sweepSpeed, + double? gapWidth, +}) { + if (content.isEmpty) { + return Container(); + } + List gradientColors = [textColor, textColor]; + bool isNeedGradient = false; + if (type == ATVIPType.VISCOUNT.name) { + textColor = Color(0xffFF7542); + gradientColors = [textColor, textColor]; + } else if (type == ATVIPType.EARL.name) { + textColor = Color(0xff5E9AFA); + gradientColors = [textColor, textColor]; + } else if (type == ATVIPType.MARQUIS.name) { + textColor = Color(0xff1CC483); + gradientColors = [textColor, textColor]; + } else if (type == ATVIPType.DUKE.name) { + gradientColors = [ + Color(0xffCC97EF), + Color(0xffAC60EA), + Color(0xffA42EE9), + Color(0xff961CCA), + Color(0xff601393), + ]; + isNeedGradient = true; + } else if (type == ATVIPType.KING.name) { + gradientColors = [ + Color(0xffEF9897), + Color(0xffEA6160), + Color(0xffE9372E), + Color(0xffCA271C), + Color(0xff931913), + ]; + isNeedGradient = true; + } else if (type == ATVIPType.EMPEROR.name) { + gradientColors = [ + Color(0xffF54EA3), + Color(0xffF64389), + Color(0xffF7666C), + Color(0xffF6805D), + Color(0xffF0C956), + Color(0xffCCE077), + Color(0xff9BE094), + Color(0xff5DDBCD), + Color(0xff0098F7), + Color(0xff1E6CEF), + Color(0xff5535B8), + ]; + isNeedGradient = true; + } + + final bool hasEmoji = _nickNameEmojiRegex.hasMatch(content); + if (hasEmoji && isNeedGradient && gradientColors.isNotEmpty) { + textColor = gradientColors[gradientColors.length ~/ 2]; + } + + return Container( + constraints: maxWidth != null ? BoxConstraints(maxWidth: maxWidth) : null, + child: ATMarqueeGradientText( + gradientColors: gradientColors, + text: content, + fontSize: fontSize, + fontWeight: fontWeight, + needScroll: needScroll, + pauseAnimation: pauseAnimation, + sweepSpeed: sweepSpeed ?? 1.0, + speed: speed ?? 20, + gapWidth: gapWidth ?? 100, + textColor: textColor, + needGradient: isNeedGradient && !hasEmoji, + ), + ); +} diff --git a/lib/chatvibe_ui/theme/chatvibe_theme.dart b/lib/chatvibe_ui/theme/chatvibe_theme.dart new file mode 100644 index 0000000..59cd45a --- /dev/null +++ b/lib/chatvibe_ui/theme/chatvibe_theme.dart @@ -0,0 +1,436 @@ +import 'package:flutter/material.dart'; + +/// ChatVibe马甲包独立主题系统 +/// 定义完全独立的视觉设计语言,与原始应用区分 + +class ChatVibeTheme { + // 主色调色板 (橙色主题) + static const Color primaryColor = Color(0xffFF5722); // 主色 - 橙色 + static const Color primaryLight = Color(0xffFF9500); // 浅橙色 + static const Color primaryDark = Color(0xffC41C00); // 深橙色 + + // 次要颜色 + static const Color secondaryColor = Color(0xff2196F3); // 蓝色作为强调色 + static const Color secondaryLight = Color(0xff6EC6FF); + static const Color secondaryDark = Color(0xff0069C0); + + // 中性色 + static const Color backgroundColor = Color(0xffF8F9FA); // 背景色 + static const Color surfaceColor = Color(0xffffffff); // 表面色 + static const Color errorColor = Color(0xffF44336); // 错误色 + + // 文本颜色 + static const Color textPrimary = Color(0xff212121); + static const Color textSecondary = Color(0xff757575); + static const Color textDisabled = Color(0xffBDBDBD); + static const Color textOnPrimary = Color(0xffffffff); // 主色上的文本 + static const Color textOnSecondary = Color(0xffffffff); // 次色上的文本 + + // 边框和分隔线 + static const Color dividerColor = Color(0xffEEEEEE); + static const Color borderColor = Color(0xffE0E0E0); + + // 功能颜色 + static const Color successColor = Color(0xff4CAF50); + static const Color warningColor = Color(0xffFF9800); + static const Color infoColor = Color(0xff2196F3); + + // 透明度 + static const Color transparent = Colors.transparent; + + // 阴影 + static List get cardShadow => [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ]; + + static List get buttonShadow => [ + BoxShadow( + color: primaryColor.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ]; + + // 间距系统 (基于8px网格) + static const double spacingXS = 4.0; + static const double spacingS = 8.0; + static const double spacingM = 16.0; + static const double spacingL = 24.0; + static const double spacingXL = 32.0; + static const double spacingXXL = 48.0; + + // 圆角系统 + static const double borderRadiusXS = 4.0; + static const double borderRadiusS = 8.0; + static const double borderRadiusM = 12.0; + static const double borderRadiusL = 16.0; + static const double borderRadiusXL = 24.0; + static const double borderRadiusCircle = 50.0; + + // 字体 + static const String fontFamily = 'Roboto'; // 使用Roboto字体,与原始应用区分 + static const String fontFamilyFallback = 'sans-serif'; + + // 文字样式 + static TextStyle get displayLarge => const TextStyle( + fontFamily: fontFamily, + fontSize: 32, + fontWeight: FontWeight.w700, + color: textPrimary, + ); + + static TextStyle get displayMedium => const TextStyle( + fontFamily: fontFamily, + fontSize: 28, + fontWeight: FontWeight.w600, + color: textPrimary, + ); + + static TextStyle get displaySmall => const TextStyle( + fontFamily: fontFamily, + fontSize: 24, + fontWeight: FontWeight.w600, + color: textPrimary, + ); + + static TextStyle get headlineMedium => const TextStyle( + fontFamily: fontFamily, + fontSize: 20, + fontWeight: FontWeight.w600, + color: textPrimary, + ); + + static TextStyle get headlineSmall => const TextStyle( + fontFamily: fontFamily, + fontSize: 18, + fontWeight: FontWeight.w600, + color: textPrimary, + ); + + static TextStyle get titleLarge => const TextStyle( + fontFamily: fontFamily, + fontSize: 16, + fontWeight: FontWeight.w600, + color: textPrimary, + ); + + static TextStyle get titleMedium => const TextStyle( + fontFamily: fontFamily, + fontSize: 14, + fontWeight: FontWeight.w500, + color: textPrimary, + ); + + static TextStyle get titleSmall => const TextStyle( + fontFamily: fontFamily, + fontSize: 12, + fontWeight: FontWeight.w500, + color: textSecondary, + ); + + static TextStyle get bodyLarge => const TextStyle( + fontFamily: fontFamily, + fontSize: 16, + fontWeight: FontWeight.w400, + color: textPrimary, + ); + + static TextStyle get bodyMedium => const TextStyle( + fontFamily: fontFamily, + fontSize: 14, + fontWeight: FontWeight.w400, + color: textPrimary, + ); + + static TextStyle get bodySmall => const TextStyle( + fontFamily: fontFamily, + fontSize: 12, + fontWeight: FontWeight.w400, + color: textSecondary, + ); + + static TextStyle get labelLarge => const TextStyle( + fontFamily: fontFamily, + fontSize: 14, + fontWeight: FontWeight.w500, + color: textOnPrimary, + ); + + static TextStyle get labelMedium => const TextStyle( + fontFamily: fontFamily, + fontSize: 12, + fontWeight: FontWeight.w500, + color: textOnPrimary, + ); + + static TextStyle get labelSmall => const TextStyle( + fontFamily: fontFamily, + fontSize: 10, + fontWeight: FontWeight.w500, + color: textSecondary, + ); + + // 暗色主题颜色 + static const Color darkBackgroundColor = Color(0xff121212); + static const Color darkSurfaceColor = Color(0xff1E1E1E); + static const Color darkTextPrimary = Color(0xffE0E0E0); + static const Color darkTextSecondary = Color(0xff9E9E9E); + static const Color darkDividerColor = Color(0xff373737); + static const Color darkBorderColor = Color(0xff424242); + + // 主题数据 + static ThemeData get lightTheme { + return ThemeData( + // 颜色 + primaryColor: primaryColor, + primaryColorLight: primaryLight, + primaryColorDark: primaryDark, + colorScheme: const ColorScheme.light( + primary: primaryColor, + secondary: secondaryColor, + background: backgroundColor, + surface: surfaceColor, + error: errorColor, + onPrimary: textOnPrimary, + onSecondary: textOnSecondary, + ), + + // 字体 + fontFamily: fontFamily, + + // 文字主题 + textTheme: TextTheme( + displayLarge: displayLarge, + displayMedium: displayMedium, + displaySmall: displaySmall, + headlineMedium: headlineMedium, + headlineSmall: headlineSmall, + titleLarge: titleLarge, + titleMedium: titleMedium, + titleSmall: titleSmall, + bodyLarge: bodyLarge, + bodyMedium: bodyMedium, + bodySmall: bodySmall, + labelLarge: labelLarge, + labelMedium: labelMedium, + labelSmall: labelSmall, + ), + + // 组件主题 + appBarTheme: const AppBarTheme( + backgroundColor: surfaceColor, + foregroundColor: textPrimary, + elevation: 0, + centerTitle: true, + ), + + cardTheme: CardThemeData( + color: surfaceColor, + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadiusM), + ), + margin: const EdgeInsets.all(spacingS), + ), + + buttonTheme: ButtonThemeData( + buttonColor: primaryColor, + textTheme: ButtonTextTheme.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadiusM), + ), + ), + + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: primaryColor, + foregroundColor: textOnPrimary, + padding: const EdgeInsets.symmetric( + horizontal: spacingL, + vertical: spacingM, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadiusM), + ), + textStyle: labelLarge, + ), + ), + + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + side: const BorderSide(color: primaryColor), + padding: const EdgeInsets.symmetric( + horizontal: spacingL, + vertical: spacingM, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadiusM), + ), + textStyle: labelLarge.copyWith(color: primaryColor), + ), + ), + + inputDecorationTheme: InputDecorationTheme( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(borderRadiusM), + borderSide: const BorderSide(color: borderColor), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(borderRadiusM), + borderSide: const BorderSide(color: borderColor), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(borderRadiusM), + borderSide: const BorderSide(color: primaryColor), + ), + filled: true, + fillColor: Colors.white, + contentPadding: const EdgeInsets.all(spacingM), + ), + + // 其他 + useMaterial3: true, + visualDensity: VisualDensity.adaptivePlatformDensity, + ); + } + + // 暗色主题 + static ThemeData get darkTheme { + return ThemeData( + // 颜色 + primaryColor: primaryColor, + primaryColorLight: primaryLight, + primaryColorDark: primaryDark, + colorScheme: const ColorScheme.dark( + primary: primaryColor, + secondary: secondaryColor, + background: darkBackgroundColor, + surface: darkSurfaceColor, + error: errorColor, + onPrimary: textOnPrimary, + onSecondary: textOnSecondary, + ), + + // 字体 + fontFamily: fontFamily, + + // 文字主题 + textTheme: TextTheme( + displayLarge: displayLarge.copyWith(color: darkTextPrimary), + displayMedium: displayMedium.copyWith(color: darkTextPrimary), + displaySmall: displaySmall.copyWith(color: darkTextPrimary), + headlineMedium: headlineMedium.copyWith(color: darkTextPrimary), + headlineSmall: headlineSmall.copyWith(color: darkTextPrimary), + titleLarge: titleLarge.copyWith(color: darkTextPrimary), + titleMedium: titleMedium.copyWith(color: darkTextPrimary), + titleSmall: titleSmall.copyWith(color: darkTextSecondary), + bodyLarge: bodyLarge.copyWith(color: darkTextPrimary), + bodyMedium: bodyMedium.copyWith(color: darkTextPrimary), + bodySmall: bodySmall.copyWith(color: darkTextSecondary), + labelLarge: labelLarge, + labelMedium: labelMedium, + labelSmall: labelSmall.copyWith(color: darkTextSecondary), + ), + + // 组件主题 + appBarTheme: const AppBarTheme( + backgroundColor: darkSurfaceColor, + foregroundColor: darkTextPrimary, + elevation: 0, + centerTitle: true, + ), + + cardTheme: CardThemeData( + color: darkSurfaceColor, + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadiusM), + ), + margin: const EdgeInsets.all(spacingS), + ), + + buttonTheme: ButtonThemeData( + buttonColor: primaryColor, + textTheme: ButtonTextTheme.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadiusM), + ), + ), + + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: primaryColor, + foregroundColor: textOnPrimary, + padding: const EdgeInsets.symmetric( + horizontal: spacingL, + vertical: spacingM, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadiusM), + ), + textStyle: labelLarge, + ), + ), + + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + side: const BorderSide(color: primaryColor), + padding: const EdgeInsets.symmetric( + horizontal: spacingL, + vertical: spacingM, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadiusM), + ), + textStyle: labelLarge.copyWith(color: primaryColor), + ), + ), + + inputDecorationTheme: InputDecorationTheme( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(borderRadiusM), + borderSide: const BorderSide(color: darkBorderColor), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(borderRadiusM), + borderSide: const BorderSide(color: darkBorderColor), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(borderRadiusM), + borderSide: const BorderSide(color: primaryColor), + ), + filled: true, + fillColor: darkSurfaceColor, + contentPadding: const EdgeInsets.all(spacingM), + ), + + // 其他 + useMaterial3: true, + visualDensity: VisualDensity.adaptivePlatformDensity, + brightness: Brightness.dark, + ); + } + + // 渐变 + static LinearGradient get primaryGradient => LinearGradient( + colors: [primaryColor, primaryLight], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ); + + static LinearGradient get secondaryGradient => LinearGradient( + colors: [secondaryColor, secondaryLight], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ); + + static LinearGradient get successGradient => LinearGradient( + colors: [successColor, Color(0xff66BB6A)], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ); +} \ No newline at end of file diff --git a/lib/chatvibe_ui/widgets/at_lk_tap_widget.dart b/lib/chatvibe_ui/widgets/at_lk_tap_widget.dart new file mode 100644 index 0000000..4b436e6 --- /dev/null +++ b/lib/chatvibe_ui/widgets/at_lk_tap_widget.dart @@ -0,0 +1,108 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +// ignore: must_be_immutable +class ATLkTapWidget extends StatefulWidget { + final Widget? child; + final Function? onTap; + Color highlightColor; + final Duration? duration; + final BorderRadius? borderRadius; + + ATLkTapWidget( + {Key? key, + this.child, + this.onTap, + this.highlightColor = Colors.black12, + this.duration, this.borderRadius}) + : super(key: key); + + @override + TapWidgetState createState() { + return new TapWidgetState(); + } +} + +class TapWidgetState extends State + with SingleTickerProviderStateMixin { + late AnimationController _ctl; + late Animation _colorAnimation; + Duration? _duration; + bool get onTapEnable => widget.onTap != null; + + @override + void initState() { + super.initState(); + _duration = widget.duration; + if (_duration == null) { + _duration = Duration(milliseconds: 1); + } + _ctl = AnimationController(vsync: this, duration: _duration); + widget.highlightColor??=Colors.black12; + + _colorAnimation = Tween( + begin: widget.highlightColor.withOpacity(0.0), + end: widget.highlightColor, + ).animate(_ctl); + } + + @override + void dispose() { + _ctl.stop(); + _ctl.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + child: AnimatedBuilder( + animation: _ctl, + builder: (BuildContext context, Widget? child) { + return Container( + foregroundDecoration: BoxDecoration( + color: _colorAnimation.value, + borderRadius: widget.borderRadius + ), + child: widget.child, + ); + }, + ), + onTap: (){ + if(widget.onTap!=null){ + widget.onTap!(); + } + }, + onTapDown: (d) { + if (onTapEnable) _ctl.forward(); + }, + onTapUp: (d) { + if (onTapEnable) prepareToIdle(); + }, + onTapCancel: (){ + if (onTapEnable) prepareToIdle(); + }, + ); + } + + void prepareToIdle() { + late AnimationStatusListener listener; + listener = (AnimationStatus statue) { + if (statue == AnimationStatus.completed) { + _ctl.removeStatusListener(listener); + toStart(); + } + }; + _ctl.addStatusListener(listener); + if (!_ctl.isAnimating) { + _ctl.removeStatusListener(listener); + toStart(); + } + } + + void toStart() { + _ctl.stop(); + _ctl.reverse(); + } +} \ No newline at end of file diff --git a/lib/chatvibe_ui/widgets/bag/props_bag_chatbox_detail_dialog.dart b/lib/chatvibe_ui/widgets/bag/props_bag_chatbox_detail_dialog.dart new file mode 100644 index 0000000..d919f0c --- /dev/null +++ b/lib/chatvibe_ui/widgets/bag/props_bag_chatbox_detail_dialog.dart @@ -0,0 +1,177 @@ +import 'package:extended_image/extended_image.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_debouncer/flutter_debouncer.dart'; +import 'package:flutter_ninepatch_image/flutter_ninepatch_image.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/store_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/store_list_res.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/bags_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_features/store/store_route.dart'; + +import '../../../chatvibe_data/models/enum/at_props_type.dart'; + +class PropsBagChatboxDetailDialog extends StatefulWidget { + BagsListRes res; + + PropsBagChatboxDetailDialog(this.res); + + @override + _PropsBagChatboxDetailDialogState createState() => + _PropsBagChatboxDetailDialogState(); +} + +class _PropsBagChatboxDetailDialogState + extends State { + final debouncer = Debouncer(); + + ///自己当前佩戴的气泡框 + PropsResources? myChatbox; + + @override + void initState() { + super.initState(); + myChatbox = AccountStorage().getChatbox(); + } + + @override + Widget build(BuildContext context) { + return Container( + height: 280.w, + width: 230.w, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(10.w)), + ), + child: Stack( + children: [ + Column( + children: [ + SizedBox(height: 20.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.viewFrame, + fontSize: 16.w, + textColor: Colors.black, + fontWeight: FontWeight.bold, + ), + ], + ), + SizedBox(height: 25.w), + NinePatchImage( + scale: 3, + alignment: Alignment.center, + imageProvider: ExtendedNetworkImageProvider( + widget.res.propsResources?.expand ?? "", + cache: true, // 启用缓存(默认即为true) + cacheMaxAge: Duration(days: 7), // 设置缓存有效期 + ), + sliceCachedKey: widget.res.propsResources?.expand ?? "", + child: SizedBox(width: 80.w, height: 25.w), + ), + SizedBox(height: 30.w), + GestureDetector( + child: Container( + alignment: Alignment.center, + width: 130.w, + padding: EdgeInsets.symmetric(vertical: 10.w), + decoration: BoxDecoration( + color: ChatVibeTheme.primaryLight, + borderRadius: BorderRadius.circular(12), + ), + child: text( + (widget.res.expireTime ?? 0) < + DateTime.now().millisecondsSinceEpoch + ? ATAppLocalizations.of(context)!.renewal + : myChatbox?.id == widget.res.propsResources?.id + ? ATAppLocalizations.of(context)!.unUse + : ATAppLocalizations.of(context)!.use, + fontSize: 16.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + debouncer.debounce( + duration: Duration(milliseconds: 350), + onDebounce: () { + if ((widget.res.expireTime ?? 0) < + DateTime.now().millisecondsSinceEpoch) { + ///过期 续费操作 + ATNavigatorUtils.push( + context, + StoreRoute.list, + replace: false, + ); + return; + } + if (myChatbox?.id != widget.res.propsResources?.id) { + ///佩戴 + _use(widget.res, false); + } else { + ///卸下 + _use(widget.res, true); + } + }, + ); + }, + ), + ], + ), + Positioned( + right: 20.w, + top: 18.w, + child: GestureDetector( + child: Icon(Icons.close, size: 20.w), + onTap: () { + SmartDialog.dismiss(tag: "showPropsDetail"); + }, + ), + ), + ], + ), + ); + } + + void _use(BagsListRes res, bool unload) { + ATLoadingManager.exhibitOperation(); + StoreRepositoryImp() + .switchPropsUse( + ATPropsType.CHAT_BUBBLE.name, + res.propsResources?.id ?? "", + unload, + ) + .then((value) async { + await Provider.of( + context, + listen: false, + ).getMyUserInfo(needLoadUserCountGuard: false); + Future.delayed(Duration(milliseconds: 400), () { + ATLoadingManager.veilRoutine(); + SmartDialog.dismiss(tag: "showPropsDetail"); + }); + if (!unload) { + ATTts.show(ATAppLocalizations.of(context)!.successfulWear); + } else { + ATTts.show(ATAppLocalizations.of(context)!.successfullyUnloaded); + } + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } +} diff --git a/lib/chatvibe_ui/widgets/bag/props_bag_headdress_detail_dialog.dart b/lib/chatvibe_ui/widgets/bag/props_bag_headdress_detail_dialog.dart new file mode 100644 index 0000000..abd4a65 --- /dev/null +++ b/lib/chatvibe_ui/widgets/bag/props_bag_headdress_detail_dialog.dart @@ -0,0 +1,172 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_debouncer/flutter_debouncer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/store_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/store_list_res.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/bags_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_features/store/store_route.dart'; +import 'package:aslan/chatvibe_ui/widgets/headdress/headdress_widget.dart'; + +import '../../../chatvibe_data/models/enum/at_props_type.dart'; + +class PropsBagHeaddressDetailDialog extends StatefulWidget { + BagsListRes res; + + PropsBagHeaddressDetailDialog(this.res); + + @override + _PropsBagHeaddressDetailDialogState createState() => + _PropsBagHeaddressDetailDialogState(); +} + +class _PropsBagHeaddressDetailDialogState + extends State { + final debouncer = Debouncer(); + + ///自己当前佩戴的头饰 + PropsResources? myHeaddress; + + @override + void initState() { + super.initState(); + myHeaddress = AccountStorage().getHeaddress(); + } + + @override + Widget build(BuildContext context) { + return Container( + height: 300.w, + width: 230.w, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(10.w)), + ), + child: Stack( + children: [ + Column( + children: [ + SizedBox(height: 20.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.viewFrame, + fontSize: 16.w, + textColor: Colors.black, + fontWeight: FontWeight.bold, + ), + ], + ), + SizedBox(height: 25.w), + head( + url: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userAvatar ?? + "", + width: 95.w, + height: 95.w, + headdress: widget.res.propsResources?.sourceUrl + ), + SizedBox(height: 30.w), + GestureDetector( + child: Container( + alignment: Alignment.center, + width: 130.w, + padding: EdgeInsets.symmetric(vertical: 10.w), + decoration: BoxDecoration( + color: ChatVibeTheme.primaryLight, + borderRadius: BorderRadius.circular(12), + ), + child: text( + (widget.res.expireTime ?? 0) < + DateTime.now().millisecondsSinceEpoch + ? ATAppLocalizations.of(context)!.renewal + : myHeaddress?.id == widget.res.propsResources?.id + ? ATAppLocalizations.of(context)!.unUse + : ATAppLocalizations.of(context)!.use, + fontSize: 16.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + debouncer.debounce( + duration: Duration(milliseconds: 350), + onDebounce: () { + if ((widget.res.expireTime ?? 0) < + DateTime.now().millisecondsSinceEpoch) { + ///过期 续费操作 + ATNavigatorUtils.push( + context, + StoreRoute.list, + replace: false, + ); + return; + } + if (myHeaddress?.id != widget.res.propsResources?.id) { + ///佩戴 + _use(widget.res, false); + } else { + ///卸下 + _use(widget.res, true); + } + }, + ); + }, + ), + ], + ), + Positioned( + right: 20.w, + top: 18.w, + child: GestureDetector( + child: Icon(Icons.close, size: 20.w), + onTap: () { + SmartDialog.dismiss(tag: "showPropsDetail"); + }, + ), + ), + ], + ), + ); + } + + void _use(BagsListRes res, bool unload) { + ATLoadingManager.exhibitOperation(); + StoreRepositoryImp() + .switchPropsUse( + ATPropsType.AVATAR_FRAME.name, + res.propsResources?.id ?? "", + unload, + ) + .then((value) async { + await Provider.of(context, listen: false).getMyUserInfo(needLoadUserCountGuard: false); + Future.delayed(Duration(milliseconds: 400),(){ + ATLoadingManager.veilRoutine(); + SmartDialog.dismiss(tag: "showPropsDetail"); + }); + if (!unload) { + ATTts.show(ATAppLocalizations.of(context)!.successfulWear); + } else { + ATTts.show(ATAppLocalizations.of(context)!.successfullyUnloaded); + } + }).catchError((e){ + ATLoadingManager.veilRoutine(); + }); + } +} diff --git a/lib/chatvibe_ui/widgets/bag/props_bag_mountains_detail_dialog.dart b/lib/chatvibe_ui/widgets/bag/props_bag_mountains_detail_dialog.dart new file mode 100644 index 0000000..f5b9983 --- /dev/null +++ b/lib/chatvibe_ui/widgets/bag/props_bag_mountains_detail_dialog.dart @@ -0,0 +1,163 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_debouncer/flutter_debouncer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/store_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/bags_list_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_features/store/store_route.dart'; + +import '../../../chatvibe_data/models/enum/at_props_type.dart'; + +class PropsBagMountainsDetailDialog extends StatefulWidget { + BagsListRes res; + + PropsBagMountainsDetailDialog(this.res); + + @override + _PropsBagMountainsDetailDialogState createState() => + _PropsBagMountainsDetailDialogState(); +} + +class _PropsBagMountainsDetailDialogState + extends State { + final debouncer = Debouncer(); + ///自己当前佩戴的坐骑 + PropsResources? myMountains; + @override + void initState() { + super.initState(); + myMountains = AccountStorage().getMountains(); + } + + @override + Widget build(BuildContext context) { + return Container( + height: 300.w, + width: 230.w, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(10.w)), + ), + child: Stack( + children: [ + Column( + children: [ + SizedBox(height: 20.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.viewFrame, + fontSize: 16.w, + textColor: Colors.black, + fontWeight: FontWeight.bold, + ), + ], + ), + SizedBox(height: 20.w), + netImage( + url: widget.res.propsResources?.cover ?? "", + width: 130.w, + height: 130.w, + ), + SizedBox(height: 20.w), + GestureDetector( + child: Container( + alignment: Alignment.center, + width: 130.w, + padding: EdgeInsets.symmetric(vertical: 10.w), + decoration: BoxDecoration( + color: ChatVibeTheme.primaryLight, + borderRadius: BorderRadius.circular(12), + ), + child: text( + (widget.res.expireTime ?? 0) < + DateTime.now().millisecondsSinceEpoch + ? ATAppLocalizations.of(context)!.renewal + : myMountains?.id == widget.res.propsResources?.id + ? ATAppLocalizations.of(context)!.unUse + : ATAppLocalizations.of(context)!.use, + fontSize: 16.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + debouncer.debounce( + duration: Duration(milliseconds: 350), + onDebounce: () { + if ((widget.res.expireTime ?? 0) < + DateTime.now().millisecondsSinceEpoch) { + ///过期 续费操作 + ATNavigatorUtils.push( + context, + StoreRoute.list, + replace: false, + ); + return; + } + if (myMountains?.id != widget.res.propsResources?.id) { + ///佩戴 + _use(widget.res, false); + } else { + ///卸下 + _use(widget.res, true); + } + }, + ); + }, + ), + ], + ), + Positioned( + right: 20.w, + top: 18.w, + child: GestureDetector( + child: Icon(Icons.close, size: 20.w), + onTap: () { + SmartDialog.dismiss(tag: "showPropsDetail"); + }, + ), + ), + ], + ), + ); + } + + void _use(BagsListRes res, bool unload) { + ATLoadingManager.exhibitOperation(); + StoreRepositoryImp() + .switchPropsUse( + ATPropsType.RIDE.name, + res.propsResources?.id ?? "", + unload, + ) + .then((value) async { + await Provider.of(context, listen: false).getMyUserInfo(needLoadUserCountGuard: false); + Future.delayed(Duration(milliseconds: 400),(){ + ATLoadingManager.veilRoutine(); + SmartDialog.dismiss(tag: "showPropsDetail"); + }); + if (!unload) { + ATTts.show(ATAppLocalizations.of(context)!.successfulWear); + } else { + ATTts.show(ATAppLocalizations.of(context)!.successfullyUnloaded); + } + }).catchError((e){ + ATLoadingManager.veilRoutine(); + }); + } + +} diff --git a/lib/chatvibe_ui/widgets/bag/props_bag_theme_detail_dialog.dart b/lib/chatvibe_ui/widgets/bag/props_bag_theme_detail_dialog.dart new file mode 100644 index 0000000..41f5054 --- /dev/null +++ b/lib/chatvibe_ui/widgets/bag/props_bag_theme_detail_dialog.dart @@ -0,0 +1,189 @@ +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_debouncer/flutter_debouncer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/constants/at_room_msg_type.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/store_repository_imp.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_features/store/store_route.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_msg_item.dart'; + +import '../../../chatvibe_domain/models/res/at_room_theme_list_res.dart'; + +class PropsBagThemeDetailDialog extends StatefulWidget { + ATRoomThemeListRes res; + Function() refresh; + + PropsBagThemeDetailDialog(this.res, this.refresh); + + @override + _PropsBagThemeDetailDialogState createState() => + _PropsBagThemeDetailDialogState(); +} + +class _PropsBagThemeDetailDialogState extends State { + final debouncer = Debouncer(); + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Container( + height: 280.w, + width: 230.w, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(10.w)), + ), + child: Stack( + children: [ + Column( + children: [ + SizedBox(height: 20.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.viewFrame, + fontSize: 16.w, + textColor: Colors.black, + fontWeight: FontWeight.bold, + ), + ], + ), + SizedBox(height: 20.w), + netImage( + url: widget.res?.themeBack ?? "", + width: 110.w, + height: 110.w, + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + SizedBox(height: 20.w), + GestureDetector( + child: Container( + alignment: Alignment.center, + width: 130.w, + padding: EdgeInsets.symmetric(vertical: 10.w), + decoration: BoxDecoration( + color: ChatVibeTheme.primaryLight, + borderRadius: BorderRadius.circular(12), + ), + child: text( + (widget.res.expireTime ?? 0) > 0 + ? ((widget.res.expireTime ?? 0) < + DateTime.now().millisecondsSinceEpoch + ? ATAppLocalizations.of(context)!.renewal + : widget.res.useTheme ?? false + ? ATAppLocalizations.of(context)!.unUse + : ATAppLocalizations.of(context)!.use) + : ((widget.res.useTheme ?? false) + ? ATAppLocalizations.of(context)!.unUse + : ATAppLocalizations.of(context)!.use), + fontSize: 16.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + debouncer.debounce( + duration: Duration(milliseconds: 350), + onDebounce: () { + if ((widget.res.expireTime ?? 0) <= 0) { + } else { + if ((widget.res.expireTime ?? 0) < + DateTime.now().millisecondsSinceEpoch) { + ///过期 续费操作 + ATNavigatorUtils.push( + context, + StoreRoute.list, + replace: false, + ); + return; + } + } + + if (widget.res.useTheme ?? false) { + ///卸下 + _use(widget.res, true); + } else { + ///佩戴 + _use(widget.res, false); + } + }, + ); + }, + ), + ], + ), + Positioned( + right: 20.w, + top: 18.w, + child: GestureDetector( + child: Icon(Icons.close, size: 20.w), + onTap: () { + SmartDialog.dismiss(tag: "showPropsDetail"); + }, + ), + ), + ], + ), + ); + } + + void _use(ATRoomThemeListRes res, bool unload) { + ATLoadingManager.exhibitOperation(); + StoreRepositoryImp() + .themeSwitchUse(res.id ?? "", unload) + .then((value) async { + ATLoadingManager.veilRoutine(); + SmartDialog.dismiss(tag: "showPropsDetail"); + if (!unload) { + ATTts.show(ATAppLocalizations.of(context)!.successfulWear); + Provider.of(context!, listen: false).updateRoomBG(res); + } else { + Provider.of( + context!, + listen: false, + ).updateRoomBG(ATRoomThemeListRes()); + ATTts.show(ATAppLocalizations.of(context)!.successfullyUnloaded); + } + + ///发送一条消息更新远端房间信息 + String? groupId = + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.roomAccount; + if (groupId != null) { + Msg msg = Msg( + groupId: groupId, + msg: unload ? "" : jsonEncode(res.toJson()), + type: ATRoomMsgType.roomBGUpdate, + ); + Provider.of( + context!, + listen: false, + ).sendMsg(msg, addLocal: false); + } + widget.refresh(); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } +} diff --git a/lib/chatvibe_ui/widgets/banner/index_banner_page.dart b/lib/chatvibe_ui/widgets/banner/index_banner_page.dart new file mode 100644 index 0000000..76067ce --- /dev/null +++ b/lib/chatvibe_ui/widgets/banner/index_banner_page.dart @@ -0,0 +1,116 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; +import 'package:aslan/main.dart'; + +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_banner_utils.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_index_banner_res.dart'; + +class IndexBannerPage extends StatefulWidget { + ATIndexBannerRes res; + + IndexBannerPage(this.res); + + @override + _IndexBannerPageState createState() => _IndexBannerPageState(); +} + +class _IndexBannerPageState extends State { + bool isSelect = false; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.center, + decoration: BoxDecoration(borderRadius: BorderRadius.circular(8.w)), + constraints: BoxConstraints(maxHeight: ScreenUtil().screenHeight * 0.7), + width: ScreenUtil().screenWidth * 0.9, + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + ATDebounceWidget( + child: netImage( + url: widget.res.cover ?? "", + borderRadius: BorderRadius.circular(8.w), + ), + onTap: () { + print('ads:${widget.res.toJson()}'); + SmartDialog.dismiss(tag: "showBannerDialog"); + ATBannerUtils.openBanner(widget.res, navigatorKey.currentState!.context); + }, + ), + PositionedDirectional( + end: 12.w, + top: 12.w, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + child: Icon(Icons.close, size: 18.w, color: Colors.white), + onTap: () { + ATRoomUtils.closeAllDialogs(); + }, + ), + ), + + PositionedDirectional( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + decoration: BoxDecoration( + color: Colors.black38, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(8.w), + bottomRight: Radius.circular(8.w), + ), + ), + width: ScreenUtil().screenWidth * 0.9, + padding: EdgeInsets.symmetric(vertical: 5.w), + child: Row( + children: [ + SizedBox(width: 8.w), + Image.asset( + !isSelect + ? "atu_images/general/at_icon_unselect.png" + : "atu_images/general/at_icon_select.png", + width: 16.w, + height: 16.w, + ), + SizedBox(width: 3.w), + text( + ATAppLocalizations.of(context)!.noPromptsToday, + fontSize: 14.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + ], + ), + ), + onTap: () { + isSelect = !isSelect; + setState(() {}); + DataPersistence.setBool( + "IndexBannerTodayShow${ATMDateUtils.formatDateTime(DateTime.now())}", + isSelect, + ); + }, + ), + start: 0.w, + bottom: 0.w, + ), + ], + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/countdown_timer.dart b/lib/chatvibe_ui/widgets/countdown_timer.dart new file mode 100644 index 0000000..8e20aeb --- /dev/null +++ b/lib/chatvibe_ui/widgets/countdown_timer.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'dart:async'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; + +class CountdownTimer extends StatefulWidget { + final DateTime expiryDate; + final Color? color; + final double? fontSize; + final FontWeight? fontWeight; + + const CountdownTimer({ + super.key, + required this.expiryDate, + this.color, + this.fontSize, + this.fontWeight, + }); + + @override + State createState() => _CountdownTimerState(); +} + +class _CountdownTimerState extends State { + Timer? _timer; + Duration _remainingTime = Duration.zero; + + @override + void initState() { + super.initState(); + _calculateRemainingTime(); + _startTimer(); + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + void _calculateRemainingTime() { + final now = DateTime.now(); + if (widget.expiryDate.isAfter(now)) { + setState(() { + _remainingTime = widget.expiryDate.difference(now); + }); + } else { + setState(() { + _remainingTime = Duration.zero; + }); + } + } + + void _startTimer() { + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + _calculateRemainingTime(); + }); + } + + String _formatDuration(Duration duration) { + if (duration.inSeconds <= 0) { + return ATAppLocalizations.of(context)!.expired; + } + + String twoDigits(int n) => n.toString().padLeft(2, '0'); + + final days = duration.inDays; + final hours = twoDigits(duration.inHours.remainder(24)); + final minutes = twoDigits(duration.inMinutes.remainder(60)); + final seconds = twoDigits(duration.inSeconds.remainder(60)); + + if (days > 0) { + return '${days}D $hours:$minutes:$seconds'; + } + return '$hours:$minutes:$seconds'; + } + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + _formatDuration(_remainingTime), + fontSize: widget.fontSize ?? 14.sp, + fontWeight: widget.fontWeight ?? FontWeight.bold, + textColor: + _remainingTime.inSeconds <= 0 + ? Colors.grey + : (widget.color ?? Colors.black), + ), + ], + ); + } +} diff --git a/lib/chatvibe_ui/widgets/country/at_country_flag_image.dart b/lib/chatvibe_ui/widgets/country/at_country_flag_image.dart new file mode 100644 index 0000000..43cee42 --- /dev/null +++ b/lib/chatvibe_ui/widgets/country/at_country_flag_image.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; + +class ATCountryFlagImage extends StatelessWidget { + final String? countryName; + final double? width; + final double? height; + final BorderRadius? borderRadius; + final BoxFit? fit; + + const ATCountryFlagImage({ + super.key, + required this.countryName, + this.width, + this.height, + this.borderRadius, + this.fit, + }); + + @override + Widget build(BuildContext context) { + return Selector( + selector: + (_, provider) => provider.getCountryFlagByName(countryName ?? ""), + builder: (_, flagUrl, __) { + if (flagUrl.isEmpty) { + return SizedBox(width: width, height: height ?? width); + } + return netImage( + url: flagUrl, + width: width, + height: height, + borderRadius: borderRadius, + fit: fit, + ); + }, + ); + } +} diff --git a/lib/chatvibe_ui/widgets/dynamic/comment/comment_gift_page.dart b/lib/chatvibe_ui/widgets/dynamic/comment/comment_gift_page.dart new file mode 100644 index 0000000..b94ff3c --- /dev/null +++ b/lib/chatvibe_ui/widgets/dynamic/comment/comment_gift_page.dart @@ -0,0 +1,547 @@ +import 'dart:ui' as ui; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/main.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/gift_res.dart'; +import 'package:aslan/chatvibe_features/wallet/wallet_route.dart'; +import 'package:aslan/chatvibe_features/gift/gift_tab_page.dart'; + +class CommentGiftPage extends StatefulWidget { + String toUserId; + String dynamicContentId; + Function(int count)? sendGiftCallBack; + CommentGiftPage(this.toUserId, this.dynamicContentId, {super.key,this.sendGiftCallBack}); + + @override + _CommentGiftPageState createState() => _CommentGiftPageState(); +} + +class _CommentGiftPageState extends State + with TickerProviderStateMixin { + late TabController _tabController; + + bool isAnimating = false; + final GlobalKey _sourceKey1 = GlobalKey(); + final GlobalKey _targetKey1 = GlobalKey(); + ChatVibeGiftRes? checkedGift; + final List _pages = []; + final List _tabs = []; + + ///数量的箭头是否朝上 + bool isNumberUp = true; + + ///数量 + int number = 1; + + int giveType = 1; + + int giftType = 0; + + @override + void initState() { + super.initState(); + Provider.of(context, listen: false).giftList(); + Provider.of(context, listen: false).balance(); + _pages.add( + GiftTabPage("ALL", (int checkedI) { + checkedGift = null; + var all = + Provider.of( + context, + listen: false, + ).giftByTab["ALL"]; + if (all != null) { + checkedGift = all[checkedI]; + } + setState(() { + giftType = 0; + }); + }, isDark: true), + ); + _pages.add( + GiftTabPage("NATIONAL_FLAG", (int checkedI) { + checkedGift = null; + var flag = + Provider.of( + context, + listen: false, + ).giftByTab["NATIONAL_FLAG"]; + if (flag != null) { + checkedGift = flag[checkedI]; + } + setState(() { + giftType = 0; + }); + }), + ); + _tabController = TabController(length: _pages.length, vsync: this); + _tabController.addListener(() {}); // 监听切换 + } + + @override + Widget build(BuildContext context) { + _tabs.clear(); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.gift)); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.country)); + return Consumer( + builder: (context, ref, child) { + return SafeArea( + top: false, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container(width: 50.w, height: 50.w, key: _targetKey1), + ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.0), + topRight: Radius.circular(12.0), + ), + child: Material( + color: Color(0xff232126), + child: Container( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + constraints: BoxConstraints(maxHeight: 350.w), + child: Column( + children: [ + Row( + children: [ + SizedBox(width: 5.w), + Expanded( + child: TabBar( + tabAlignment: TabAlignment.start, + labelPadding: EdgeInsets.symmetric( + horizontal: 8.w, + ), + labelColor: Colors.white, + isScrollable: true, + indicator: BoxDecoration(), + unselectedLabelColor: Colors.white54, + labelStyle: TextStyle( + fontSize: 14.sp, + fontFamily: 'MyCustomFont', + ), + unselectedLabelStyle: TextStyle( + fontSize: 12.sp, + fontFamily: 'MyCustomFont', + ), + indicatorColor: Colors.transparent, + dividerColor: Colors.transparent, + controller: _tabController, + tabs: _tabs, + ), + ), + SizedBox(width: 5.w), + ], + ), + Expanded( + child: Stack( + children: [ + TabBarView( + physics: NeverScrollableScrollPhysics(), + controller: _tabController, + children: _pages, + ), + isAnimating + ? GestureDetector( + child: Container( + color: Colors.transparent, + alignment: Alignment.center, + ), + onTap: () {}, + ) + : Container(), + ], + ), + ), + Row( + children: [ + SizedBox(width: 15.w), + GestureDetector( + onTap: () { + SmartDialog.dismiss( + tag: "showGiftControl", + ); + ATNavigatorUtils.push( + navigatorKey.currentState!.context, + WalletRoute.recharge, + replace: false, + ); + }, + child: Container( + padding: EdgeInsets.symmetric( + vertical: 8.w, + horizontal: 8.w, + ), + width: 120.w, + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.circular(18), + ), + child: Row( + children: [ + Image.asset( + "atu_images/general/at_icon_jb.png", + width: 14.w, + height: 14.w, + ), + SizedBox(width: 5.w), + Consumer( + builder: (context, ref, child) { + return Expanded( + child: text( + "${ref.myBalance}", + fontSize: 12.sp, + textColor: Colors.white, + ), + ); + }, + ), + SizedBox(width: 5.w), + Icon( + Icons.arrow_forward_ios, + color: Colors.white, + size: 14.w, + ), + ], + ), + ), + ), + Spacer(), + Builder( + builder: (ct) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + isNumberUp = false; + _showNumber(ct); + setState(() {}); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.circular( + 18, + ), + ), + child: Row( + children: [ + SizedBox(width: 10.w), + text("$number", fontSize: 12.sp), + Icon( + isNumberUp + ? Icons.keyboard_arrow_up + : Icons.keyboard_arrow_down, + color: Colors.white, + size: 20.w, + ), + SizedBox(width: 5.w), + GestureDetector( + onTap: () { + giveGifts(); + }, + child: Container( + key: _sourceKey1, + padding: EdgeInsets.symmetric( + vertical: 8.w, + horizontal: 20.w, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xffF5A8B7), + Color(0xffD61F3F), + ], + begin: + Alignment.topCenter, + end: + Alignment + .bottomCenter, + ), + borderRadius: + BorderRadius.circular( + 18, + ), + ), + child: text( + ATAppLocalizations.of( + context, + )!.send, + fontSize: 14.sp, + ), + ), + ), + ], + ), + ), + ); + }, + ), + SizedBox(width: 15.w), + ], + ), + SizedBox(height: 15.w), + ], + ), + ), + ], + ), + ), + ), + ), + ], + ), + ); + }, + ); + } + + ///数量选项 + void _showNumber(BuildContext ct) { + SmartDialog.showAttach( + tag: "showNumber", + targetContext: ct, + alignment: Alignment.topLeft, + animationType: SmartAnimationType.fade, + scalePointBuilder: (selfSize) => Offset(selfSize.width, 10), + builder: (_) { + return Transform.translate( + offset: Offset(ATGlobalConfig.lang == "ar" ? 20 : -20, -5), + child: CheckNumber( + onNumberChanged: (number) { + this.number = number; + isNumberUp = true; + setState(() {}); + }, + ), + ); + }, + ); + } + + void giveGifts() { + ChatRoomRepository() + .giveGift( + [widget.toUserId], + checkedGift!.id ?? "", + number, + false, + dynamicContentId: widget.dynamicContentId, + ) + .then((result) { + _startA1(); + widget.sendGiftCallBack?.call(number); + Provider.of( + context, + listen: false, + ).updateBalance(result); + }) + .catchError((e) {}); + } + + void _startA1() { + setState(() { + isAnimating = true; + }); + // 确保上下文有效 + if (_sourceKey1.currentContext == null) return; + AnimationController? _controllerX1; + Animation? _scaleAnimationX1; + Animation? _scaleAnimationX2; + Animation? _positionAnimationX1; + OverlayEntry? _overlayEntryX1; + + // 获取源控件位置和大小 + final RenderBox sourceBox = + _sourceKey1.currentContext!.findRenderObject() as RenderBox; + final Offset sourcePosition = sourceBox.localToGlobal(Offset.zero); + final Size sourceSize = sourceBox.size; + + // 计算源控件中心点 + Offset sourceCenter = Offset( + sourcePosition.dx + sourceSize.width / 2, + sourcePosition.dy + sourceSize.height / 2, + ); + + // 获取目标控件位置和大小 + if (_targetKey1?.currentContext == null) return; + final RenderBox targetBox = + _targetKey1!.currentContext!.findRenderObject() as RenderBox; + final Offset targetPosition = targetBox.localToGlobal(Offset.zero); + final Size targetSize = targetBox.size; + + // 计算目标控件中心点 + final Offset targetCenter = Offset( + targetPosition.dx + targetSize.width / 2, + targetPosition.dy + targetSize.height / 2, + ); + + // 计算图片中心点偏移(使图片中心对准控件中心) + final double imageOffsetX = 55.w / 2; + final double imageOffsetY = 55.w / 2; + + _controllerX1 = AnimationController( + vsync: this, + duration: Duration(milliseconds: 350), + )..addStatusListener((status) { + if (status == AnimationStatus.completed) { + if (_overlayEntryX1 != null) { + _overlayEntryX1?.remove(); + _overlayEntryX1 = null; + } + setState(() { + isAnimating = false; + }); + } + }); + + // 创建位置动画:从起始位置到目标位置 + _positionAnimationX1 = Tween( + begin: Offset( + sourceCenter.dx - imageOffsetX, + sourceCenter.dy - imageOffsetY, + ), + end: Offset( + targetCenter.dx - imageOffsetX, + targetCenter.dy - imageOffsetY, + ), + ).animate( + CurvedAnimation( + parent: _controllerX1!, + curve: const Interval(0.2, 0.4, curve: Curves.easeIn), + ), + ); + // 创建缩放动画 + _scaleAnimationX1 = Tween( + begin: 0.5, + end: 1.1, // 可以根据需要调整 + ).animate( + CurvedAnimation( + parent: _controllerX1!, + curve: const Interval(0, 0.2, curve: Curves.easeIn), + ), + ); + + // 创建缩放动画 + _scaleAnimationX2 = Tween( + begin: 1, + end: 1.5, // 可以根据需要调整 + ).animate( + CurvedAnimation( + parent: _controllerX1!, + curve: const Interval(0.4, 0.7, curve: Curves.easeIn), + ), + ); + // 创建浮动控件 + _overlayEntryX1 = OverlayEntry( + builder: + (context) => AnimatedBuilder( + animation: _controllerX1!, + builder: (context, child) { + return Positioned( + left: _positionAnimationX1!.value.dx, + top: _positionAnimationX1!.value.dy, + child: Transform.scale( + scale: _scaleAnimationX1!.value, + child: Transform.scale( + scale: _scaleAnimationX2!.value, + child: netImage( + url: checkedGift?.giftPhoto ?? "", + width: 55.w, + height: 55.w, + ), + ), + ), + ); + }, + ), + ); + + // 插入Overlay并启动动画 + Overlay.of(context).insert(_overlayEntryX1!); + _controllerX1.forward(from: 0); + } +} + +class CheckNumber extends StatelessWidget { + final Function(int) onNumberChanged; + late BuildContext context; + + CheckNumber({super.key, required this.onNumberChanged}); + + @override + Widget build(BuildContext context) { + this.context = context; + + return Material( + color: Colors.transparent, + child: Container( + alignment: AlignmentDirectional.topEnd, + margin: EdgeInsets.only(right: width(22)), + child: Container( + decoration: BoxDecoration( + color: Colors.black87, + borderRadius: BorderRadius.all(Radius.circular(height(10))), + ), + width: width(80), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: height(10)), + _item(1), + _item(7), + _item(17), + _item(77), + _item(777), + _item(7777), + SizedBox(height: height(10)), + ], + ), + ), + ), + ); + } + + _item(int number) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + SmartDialog.dismiss(tag: "showNumber"); + onNumberChanged(number); + }, + child: SizedBox( + height: height(24), + child: Row( + children: [ + SizedBox(width: width(10)), + Text( + "$number", + style: TextStyle( + fontSize: sp(14), + color: Colors.white, + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + ), + ), + SizedBox(width: width(10)), + ], + ), + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/dynamic/comment/comment_input.dart b/lib/chatvibe_ui/widgets/dynamic/comment/comment_input.dart new file mode 100644 index 0000000..7ab351f --- /dev/null +++ b/lib/chatvibe_ui/widgets/dynamic/comment/comment_input.dart @@ -0,0 +1,360 @@ +import 'package:extended_text/extended_text.dart'; +import 'package:extended_text_field/extended_text_field.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_core/constants/at_emoji_datas.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/utilities/at_keybord_util.dart'; +import 'package:aslan/main.dart'; +import 'package:aslan/chatvibe_managers/dynamic_content_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/dynamic/comment/comment_gift_page.dart'; + +///聊天输入框 +class CommentInput extends StatefulWidget { + String? toUserName; + String toUserId; + String dynamicContentId; + Function(String text)? callBack; + Function()? deleteCallBack; + Function(int count)? sendGiftCallBack; + + CommentInput( + this.toUserId, + this.dynamicContentId, { + this.toUserName, + this.callBack, + this.deleteCallBack, + this.sendGiftCallBack, + }); + + @override + _CommentInputState createState() => _CommentInputState(); +} + +class _CommentInputState extends State with RouteAware { + bool showSend = false; + + final FocusNode _focusNode = FocusNode(); + + TextEditingController controller = TextEditingController(); + + @override + void initState() { + super.initState(); + restState(); + _focusNode.addListener((){ + if(_focusNode.hasFocus){ + Provider.of(context, listen: false).updateShowEmojiState( + false, + () { + setState(() {}); + }, + ); + } + }); + HardwareKeyboard.instance.addHandler(_handleKeyEvent); + } + + bool _handleKeyEvent(KeyEvent event) { + // 检查是否是删除键 + final isDeleteKey = + event.logicalKey == LogicalKeyboardKey.backspace || + event.logicalKey == LogicalKeyboardKey.delete; + + if (isDeleteKey) { + if (event is KeyDownEvent) { + // 删除键按下 + if (controller.text.isEmpty) { + widget.deleteCallBack?.call(); + } + } + } + // 返回 false 让其他部件也能处理这个事件 + return false; + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + // 注册路由观察 + final route = ModalRoute.of(context); + if (route is PageRoute) { + routeObserver.subscribe(this, route); + } + } + + @override + void didPopNext() { + _focusNode.unfocus(); + FocusScope.of(context).unfocus(); // 彻底取消当前上下文的所有焦点 + } + + @override + void dispose() { + _focusNode.dispose(); + routeObserver.unsubscribe(this); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return float(context); + } + + Widget float(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [_input(), _emoji(), _gift()], + ); + } + + _gift() { + return Selector( + selector: (c, p) => p.showGift, + shouldRebuild: (prev, next) => prev != next, + builder: (_, showGift, __) { + return Visibility( + visible: showGift, + child: CommentGiftPage( + widget.toUserId, + widget.dynamicContentId, + sendGiftCallBack: widget.sendGiftCallBack, + ), + ); + }, + ); + } + + _emoji() { + return Selector( + selector: (c, p) => p.showEmoji, + shouldRebuild: (prev, next) => prev != next, + builder: (_, showEmoji, __) { + return Visibility( + visible: showEmoji, + child: Container( + color: Colors.white, + height: 150.w, + child: GridView.builder( + padding: EdgeInsets.symmetric(horizontal: 15.w), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 8, + crossAxisSpacing: 8.w, + mainAxisSpacing: 8.w, + ), + itemBuilder: (c, index) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + var text = ATEmojiDatas.smileys[index]; + final TextEditingValue value = controller.value; + controller.value = value.copyWith( + text: value.text + text, + selection: TextSelection.fromPosition( + TextPosition(offset: text.length), + ), + ); + showSend = true; + setState(() {}); + }, + child: Padding( + padding: EdgeInsets.all(4.0.w), + child: ExtendedText( + ATEmojiDatas.smileys[index], + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), + style: TextStyle(fontSize: sp(15)), + ), + ), + ); + }, + itemCount: ATEmojiDatas.smileys.length, + ), + ), + ); + }, + ); + } + + void restState() { + Provider.of(context, listen: false).updateShowEmojiState( + false, + () { + setState(() {}); + }, + ); + Provider.of(context, listen: false).updateShowGiftState( + false, + () { + setState(() {}); + }, + ); + } + + _input() { + return Selector( + selector: (c, p) => p.showGift, + shouldRebuild: (prev, next) => prev != next, + builder: (_, showGift, __) { + return Visibility( + visible: !showGift, + child: Container( + decoration: BoxDecoration(color: Colors.white), + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.w), + height: 56.w, + child: Row( + children: [ + ATDebounceWidget( + child: Image.asset( + "atu_images/dynamic/at_icon_dynamic_emoji.png", + height: 35.w, + ), + onTap: () { + ATKeybordUtil.conceal(context); + Provider.of( + context, + listen: false, + ).updateShowEmojiState( + !Provider.of( + context, + listen: false, + ).showEmoji, + () { + setState(() {}); + }, + ); + Provider.of( + context, + listen: false, + ).updateShowGiftState(false, () { + setState(() {}); + }); + }, + ), + SizedBox(width: 8.w), + Expanded( + child: Container( + decoration: BoxDecoration( + color: Color(0xff7726FF).withOpacity(0.1), + borderRadius: BorderRadius.circular(height(5)), + ), + child: Row( + children: [ + Expanded( + child: ExtendedTextField( + focusNode: _focusNode, + controller: controller, + autofocus: false, + onChanged: (s) { + setState(() { + showSend = controller.text.isNotEmpty; + }); + }, + onSubmitted: (s) { + if (s.isNotEmpty) { + showSend = false; + setState(() {}); + controller.clear(); + } + }, + decoration: InputDecoration( + hintText: + widget.toUserName == null || + (widget.toUserName?.isEmpty ?? false) + ? ATAppLocalizations.of( + context, + )!.saySomething + : "${ATAppLocalizations.of(context)!.reply}@${widget.toUserName}", + fillColor: Colors.transparent, + hintStyle: TextStyle( + color: Colors.grey, + fontSize: sp(13), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 15, + ), + counterText: '', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(height(5)), + borderSide: BorderSide.none, + ), + filled: true, + ), + style: TextStyle( + fontSize: ScreenUtil().setSp(13), + color: Colors.black.withOpacity(1), + ), + ), + ), + ATDebounceWidget( + onTap: () { + showSend = false; + Provider.of( + context, + listen: false, + ).updateShowEmojiState(false, () { + setState(() {}); + }); + if (widget.dynamicContentId.isNotEmpty && + controller.text.isNotEmpty) { + widget.callBack?.call(controller.text); + controller.text = ""; + } + }, + child: Container( + padding: EdgeInsets.all(4.w), + width: 30.w, + height: 30.w, + child: Image.asset( + showSend + ? "atu_images/dynamic/at_icon_comment_input_message_send_en.png" + : "atu_images/dynamic/at_icon_comment_input_message_send_no.png", + ), + ), + ), + SizedBox(width: 3.w), + ], + ), + ), + ), + SizedBox(width: 8.w), + widget.sendGiftCallBack != null + ? ATDebounceWidget( + child: Image.asset( + "atu_images/dynamic/at_icon_dynamic_comment_gift.png", + height: 35.w, + ), + onTap: () { + Provider.of( + context, + listen: false, + ).updateShowGiftState( + !Provider.of( + context, + listen: false, + ).showGift, + () { + setState(() {}); + }, + ); + Provider.of( + context, + listen: false, + ).updateShowEmojiState(false, () { + setState(() {}); + }); + }, + ) + : Container(), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/chatvibe_ui/widgets/dynamic/draggable_image_dynamic_photo_grid.dart b/lib/chatvibe_ui/widgets/dynamic/draggable_image_dynamic_photo_grid.dart new file mode 100644 index 0000000..659939a --- /dev/null +++ b/lib/chatvibe_ui/widgets/dynamic/draggable_image_dynamic_photo_grid.dart @@ -0,0 +1,459 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/config/pickImage.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_pick_utils.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +class DraggableImageDynamicPhotoGrid extends StatefulWidget { + final int maxImages; + Function(List images) callback; + + DraggableImageDynamicPhotoGrid({required this.callback, this.maxImages = 9}); + + @override + _DraggableImageDynamicPhotoGridState createState() => + _DraggableImageDynamicPhotoGridState(); +} + +class _DraggableImageDynamicPhotoGridState + extends State { + // 拖动相关状态 + int? draggedIndex; + int? targetIndex; + + // 点击相关状态 + bool _isTapping = false; + int? _tapIndex; + + List images = []; + + int itemCount = 0; + + @override + void initState() { + super.initState(); + ///从草稿箱获取 + images = DataPersistence.getDraftDynamicPhoto(); + } + + @override + Widget build(BuildContext context) { + // 修正判断逻辑 + if (images.length >= widget.maxImages) { + itemCount = widget.maxImages; // 已达到最大数量,只显示图片 + } else { + itemCount = images.length + 1; // 还有空位,显示添加按钮 + } + + return GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + childAspectRatio: 1, + mainAxisSpacing: 8, + crossAxisSpacing: 8, + ), + itemCount: itemCount, + itemBuilder: (context, index) { + // 判断是否为添加按钮位置:当前索引等于图片数量且图片数量小于最大限制 + if (index == images.length && images.length < widget.maxImages) { + return _buildAddButton(); + } + + // 图片项(确保索引不超出范围) + if (index < images.length) { + return _buildImageItem(index); + } + + // 如果计算有误,返回空容器 + return Container(); + }, + ); + } + + // 构建添加按钮 + Widget _buildAddButton() { + return GestureDetector( + onTap: () { + _showAddPicDialog(); + }, + child: Image.asset("atu_images/general/at_icon_create_dynamic_add_pic.png"), + ); + } + + // 构建图片项 + Widget _buildImageItem(int index) { + final image = images[index]; + final isDragging = draggedIndex == index; + final isTarget = targetIndex == index && draggedIndex != null; + final isTappingThis = _isTapping && _tapIndex == index; + + return GestureDetector( + onTap: () { + _handleImageTap(index); + }, + onTapDown: (details) { + setState(() { + _isTapping = true; + _tapIndex = index; + }); + }, + onTapCancel: () { + setState(() { + _isTapping = false; + _tapIndex = null; + }); + }, + onTapUp: (details) { + Future.delayed(Duration(milliseconds: 100), () { + if (mounted) { + setState(() { + _isTapping = false; + _tapIndex = null; + }); + } + }); + }, + child: DragTarget( + onWillAccept: (data) { + // 只接受图片索引,不接受其他类型 + if (data != null && data != index) { + setState(() { + targetIndex = index; + }); + return true; + } + return false; + }, + onAccept: (sourceIndex) { + _swapImages(sourceIndex, index); + widget.callback(images); + setState(() { + draggedIndex = null; + targetIndex = null; + }); + }, + onLeave: (data) { + setState(() { + targetIndex = null; + }); + }, + builder: (context, candidateData, rejectedData) { + return LongPressDraggable( + data: index, + // 传递图片索引 + onDragStarted: () { + setState(() { + draggedIndex = index; + _isTapping = false; // 拖动时取消点击状态 + }); + }, + onDragEnd: (details) { + setState(() { + draggedIndex = null; + targetIndex = null; + }); + }, + feedback: _buildDragFeedback(image), + childWhenDragging: _buildImagePlaceholder(), + child: _buildImageContent( + index, + image, + isDragging, + isTarget, + isTappingThis, + ), + ); + }, + ), + ); + } + + // 构建拖动时的反馈 + Widget _buildDragFeedback(String image) { + return Opacity( + opacity: 0.8, + child: Material( + color: Colors.transparent, + child: Container( + width: 100.w, + height: 100.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + boxShadow: [ + BoxShadow(color: Colors.black26, blurRadius: 10, spreadRadius: 2), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(12.w), + child: Image.file( + File(image), + width: double.infinity, + height: double.infinity, + fit: BoxFit.cover, + ), + ), + ), + ), + ); + } + + // 构建拖动时的占位符 + Widget _buildImagePlaceholder() { + return Container( + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(12.w), + border: Border.all(color: Colors.grey[300]!), + ), + child: Center( + child: Icon(Icons.photo, color: Colors.grey[400], size: 30.w), + ), + ); + } + + // 构建图片内容 + Widget _buildImageContent( + int index, + String image, + bool isDragging, + bool isTarget, + bool isTappingThis, + ) { + return AnimatedContainer( + duration: Duration(milliseconds: 350), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + border: isTarget ? Border.all(color: ChatVibeTheme.primaryColor, width: 2) : null, + ), + child: Stack( + alignment: AlignmentDirectional.bottomCenter, + children: [ + // 图片 + ClipRRect( + borderRadius: BorderRadius.circular(12.w), + child: Image.file( + File(image), + width: double.infinity, + height: double.infinity, + fit: BoxFit.cover, + ), + ), + // 点击效果遮罩 + if (isTappingThis && !isDragging) + Container( + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.1), + borderRadius: BorderRadius.circular(12.w), + ), + ), + // 删除按钮 + PositionedDirectional( + top: 2.w, + end: 2.w, + child: ATDebounceWidget( + child: Container( + padding: EdgeInsets.all(3.w), + child: Image.asset( + "atu_images/general/at_icon_pic_close.png", + height: 15.w, + ), + ), + onTap: () { + _showDeletePicDialog(index); + }, + ), + ), + ], + ), + ); + } + + // 处理图片点击 + void _handleImageTap(int index) { + // 重置点击状态 + setState(() { + _isTapping = false; + _tapIndex = null; + }); + + String encodedUrls = Uri.encodeComponent(jsonEncode(images)); + ATNavigatorUtils.push( + context, + "${MainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=$index", + ); + } + + void _showDeletePicDialog(int index) { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.doYouWantToDeleteIt, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + _removePhotoImage(index); + }, + ); + }, + ); + } + + // 交换图片位置 + void _swapImages(int sourceIndex, int targetIndex) { + if (sourceIndex < 0 || + targetIndex < 0 || + sourceIndex >= images.length || + targetIndex >= images.length || + sourceIndex == targetIndex) { + return; + } + + setState(() { + // 交换图片 + final temp = images[sourceIndex]; + images[sourceIndex] = images[targetIndex]; + images[targetIndex] = temp; + }); + } + + void _showAddPicDialog() { + SmartDialog.show( + tag: "showAddPicDialog", + alignment: Alignment.bottomCenter, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return SafeArea( + top: false, + child: Container( + padding: EdgeInsets.only(bottom: 10.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + alignment: AlignmentDirectional.center, + margin: EdgeInsets.only(top: 10.w), + child: text( + ATAppLocalizations.of(context)!.chooseFromAblum, + textColor: Colors.black, + fontWeight: FontWeight.w600, + fontSize: 15.sp, + ), + ), + onTap: () async { + SmartDialog.dismiss(tag: "showAddPicDialog"); + final File? result = await ImagePick.pickSingleFromGallery( + context, + ); + if (result == null) return; // 用户取消选择 + ATPickUtils.cropImage( + result, + context, + null, + true, + needUpload: false, + (bool success, String url) { + if (success) { + _addPhotoImage(url); + } + }, + ); + }, + ), + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + alignment: AlignmentDirectional.center, + margin: EdgeInsets.only(top: 10.w), + child: text( + ATAppLocalizations.of(context)!.camera, + textColor: Colors.black, + fontWeight: FontWeight.w600, + fontSize: 15.sp, + ), + ), + onTap: () async { + SmartDialog.dismiss(tag: "showAddPicDialog"); + var result = await ImagePick.pickFromCamera(context); + if (result != null) { + ATPickUtils.cropImage( + result, + context, + null, + true, + needUpload: false, + (bool success, String url) { + if (success) { + _addPhotoImage(url); + } + }, + ); + } + }, + ), + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + alignment: AlignmentDirectional.center, + margin: EdgeInsets.only(top: 10.w), + child: text( + ATAppLocalizations.of(context)!.cancel, + textColor: Colors.black, + fontWeight: FontWeight.w600, + fontSize: 15.sp, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showAddPicDialog"); + }, + ), + ], + ), + ), + ); + }, + ); + } + + void _addPhotoImage(String path) { + if (images.length < widget.maxImages) { + images.add(path); + setState(() {}); + widget.callback(images); + } + } + + void _removePhotoImage(int index) { + images.removeAt(index); + setState(() {}); + widget.callback(images); + } + +} diff --git a/lib/chatvibe_ui/widgets/headdress/headdress_widget.dart b/lib/chatvibe_ui/widgets/headdress/headdress_widget.dart new file mode 100644 index 0000000..455d5d3 --- /dev/null +++ b/lib/chatvibe_ui/widgets/headdress/headdress_widget.dart @@ -0,0 +1,209 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svga/flutter_svga.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; + +import 'package:aslan/chatvibe_core/utilities/at_gift_vap_svga_manager.dart'; + +class SVGAHeadwearWidget extends StatefulWidget { + // 资源路径(网络URL或本地assets路径) + final String resource; + + // 尺寸 + final double? width; + final double? height; + + // 动画控制参数 + final bool autoPlay; + final int loops; // 循环次数(0表示无限循环) + final bool clearsAfterStop; + + // 缓存控制 + final bool useCache; + + // 回调函数 + final VoidCallback? onCompleted; + final Function(String)? onError; + final VoidCallback? onStartLoading; + final VoidCallback? onFinishLoading; + final Function(SVGAAnimationController? animationController)? callback; + + SVGAHeadwearWidget({ + Key? key, + required this.resource, + this.width, + this.height, + this.autoPlay = true, + this.loops = 0, + this.clearsAfterStop = true, + this.useCache = true, + this.onCompleted, + this.onError, + this.onStartLoading, + this.onFinishLoading, + this.callback, + }) : super(key: key); + + @override + _SVGAHeadwearWidgetState createState() => _SVGAHeadwearWidgetState(); +} + +class _SVGAHeadwearWidgetState extends State + with SingleTickerProviderStateMixin { + SVGAAnimationController? _animationController; + bool _isLoading = true; + bool _isNetworkResource = false; + bool _hasError = false; + + @override + void initState() { + super.initState(); + _isNetworkResource = widget.resource.startsWith('http'); + _animationController = SVGAAnimationController(vsync: this); + widget.callback?.call(_animationController); + if (widget.autoPlay) { + _loadAnimation(); + } + + // 添加动画状态监听 + _animationController?.addStatusListener((status) { + if (status == AnimationStatus.completed && widget.onCompleted != null) { + widget.onCompleted!(); + } + }); + } + + // 加载动画(带缓存) + Future _loadAnimation() async { + if (widget.onStartLoading != null) { + widget.onStartLoading!(); + } + + setState(() { + _isLoading = true; + _hasError = false; + }); + + try { + // 检查缓存 + MovieEntity? videoItem; + if (widget.useCache) { + videoItem = ATGiftVapSvgaManager().videoItemCache[widget.resource]; + } + + // 如果没有缓存,则进行解析 + if (videoItem == null) { + videoItem = + _isNetworkResource + ? await ATGiftVapSvgaManager().loadSvgaMovie(widget.resource) + : await SVGAParser.shared.decodeFromAssets(widget.resource); + videoItem.autorelease = false; + // 存入缓存 + if (widget.useCache) { + ATGiftVapSvgaManager().videoItemCache[widget.resource] = videoItem; + } + } + + if (mounted) { + setState(() { + _animationController?.videoItem = videoItem; + _isLoading = false; + }); + + // 根据循环次数设置播放方式 + if (widget.loops == 0) { + _animationController?.repeat(); + } else { + _animationController?.repeat(count: widget.loops); + } + + if (widget.onFinishLoading != null) { + widget.onFinishLoading!(); + } + } + } catch (e) { + if (mounted) { + setState(() { + _isLoading = false; + _hasError = true; + }); + } + + if (widget.onError != null) { + ATTts.show("fail-${e.toString()}"); + widget.onError!(e.toString()); + } + } + } + + // 播放动画 + void play() { + if (_animationController?.videoItem != null) { + _animationController?.reset(); + if (widget.loops == 0) { + _animationController?.repeat(); + } else { + _animationController?.repeat(count: widget.loops); + } + } else { + _loadAnimation(); + } + } + + // 暂停动画 + void pause() { + _animationController?.stop(); + } + + // 停止动画 + void stop() { + _animationController?.stop(); + if (widget.clearsAfterStop) { + _animationController?.videoItem = null; + } + } + + // 重新加载动画 + void reload() { + if (widget.useCache) { + // 清除缓存项 + ATGiftVapSvgaManager().videoItemCache.remove(widget.resource); + } + _loadAnimation(); + } + + @override + void dispose() { + stop(); + _animationController?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (_isLoading && !widget.autoPlay) { + return Container( + width: widget.width, + height: widget.height, + child: Center(child: CircularProgressIndicator()), + ); + } + + if (_hasError) { + return Container(); + } + + return Container( + width: widget.width, + height: widget.height, + child: + _animationController != null && + _animationController!.videoItem != null + ? SVGAImage( + _animationController!, + fit: BoxFit.fill, + clearsAfterStop: widget.clearsAfterStop, + ) + : Container(), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/msg/bottom_message_conversation_list_page.dart b/lib/chatvibe_ui/widgets/msg/bottom_message_conversation_list_page.dart new file mode 100644 index 0000000..c0a89ee --- /dev/null +++ b/lib/chatvibe_ui/widgets/msg/bottom_message_conversation_list_page.dart @@ -0,0 +1,425 @@ +import 'dart:convert'; +import 'dart:ui' as ui; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_debouncer/flutter_debouncer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:tencent_cloud_chat_sdk/enum/conversation_type.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'; +import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_features/chat/at_chat_route.dart'; +import 'package:aslan/chatvibe_ui/widgets/msg/message_conversation_list_page.dart'; + +class BottomMessageConversationListPage extends StatefulWidget { + @override + _BottomMessageConversationListPageState createState() => + _BottomMessageConversationListPageState(); +} + +class _BottomMessageConversationListPageState + extends State { + bool showSystemAnnouncementTips = true; + + @override + void initState() { + super.initState(); + showSystemAnnouncementTips = DataPersistence.getBool( + "${AccountStorage().getCurrentUser()?.userProfile?.account}-ShowSystemAnnouncementTips", + defaultValue: true, + ); + _checkLogin(); + } + + @override + void dispose() { + super.dispose(); + } + + void _checkLogin() async { + V2TimValueCallback getLoginStatusRes = + await TencentImSDKPlugin.v2TIMManager.getLoginStatus(); + if (getLoginStatusRes.code == 0) { + var status = getLoginStatusRes.data; // getLoginStatusRes.data为用户登录状态值 + if (status == 1) { + // 已登录 + ///处理有时候拉不到最后一条消息 + Provider.of( + context, + listen: false, + ).initConversation().then((value) { + Future.delayed(Duration(milliseconds: 550), () { + Provider.of( + context, + listen: false, + ).initConversation().then((value) { + setState(() {}); + }); + }); + }); + } else if (status == 2) { + // 登录中 + } else if (status == 3) { + // 未登录 + Provider.of( + context, + listen: false, + ).loginTencetRtm(context).then((value) { + _checkLogin(); + }); + } + setState(() {}); + } + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: ClipRect( + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 15, sigmaY: 15), + child: Container( + height: 490.w, + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + ), + child: Column( + children: [ + SizedBox(height: 10.w), + Row( + children: [ + SizedBox(width: 30.w), + Spacer(), + text( + ATAppLocalizations.of(context)!.message, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + Spacer(), + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.all(3.w), + child: Icon( + Icons.clear, + size: 22.w, + color: Colors.white, + ), + ), + onTap: () { + ATNavigatorUtils.goBack(context); + }, + ), + SizedBox(width: 5.w), + ], + ), + _headMenu(context), + showSystemAnnouncementTips + ? Container( + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.circular(10.w), + ), + padding: EdgeInsets.all(6.w), + margin: EdgeInsets.all(10.w), + child: Column( + children: [ + Row( + children: [ + SizedBox(width: 5.w), + Image.asset( + "atu_images/family/at_icon_announcement_tag.png", + height: 20.w, + ), + SizedBox(width: 5.w), + Expanded( + child: text( + ATAppLocalizations.of( + context, + )!.systemAnnouncement, + textColor: Colors.white, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + ), + ATDebounceWidget( + child: Icon( + Icons.close, + size: 18.w, + color: Colors.white, + ), + onTap: () { + setState(() { + showSystemAnnouncementTips = false; + }); + DataPersistence.setBool( + "${AccountStorage().getCurrentUser()?.userProfile?.account}-ShowSystemAnnouncementTips", + showSystemAnnouncementTips, + ); + }, + ), + SizedBox(width: 5.w), + ], + ), + SizedBox(height: 3.w), + Container( + padding: EdgeInsets.symmetric(horizontal: 10.w), + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: + ATAppLocalizations.of( + context, + )!.systemAnnouncementTips1, + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + TextSpan( + text: + ATAppLocalizations.of( + context, + )!.systemAnnouncementTips, + style: TextStyle( + fontSize: 11.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + TextSpan( + text: + "【${ATAppLocalizations.of(context)!.contactUs}】", + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + ], + ), + ) + : Container(), + Expanded(child: MessageConversationListPage(true)), + ], + ), + ), + ), + ), + ); + } + + Widget _headMenu(BuildContext context) { + return Consumer( + builder: (context, provider, child) { + return Container( + margin: EdgeInsets.only( + top: 15.w, + bottom: 10.w, + left: 10.w, + right: 10.w, + ), + padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 10.w), + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ATDebounceWidget( + debounceTime: Duration(milliseconds: 3000), + child: + provider.activityUnReadCount > 0 + ? Badge( + backgroundColor: Colors.red, + label: text( + "${provider.activityUnReadCount > 99 ? "99+" : provider.activityUnReadCount}", + fontSize: 9.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + alignment: AlignmentDirectional.topEnd, + child: Column( + children: [ + Image.asset( + "atu_images/msg/at_icon_message_activity.png", + width: 55.w, + height: 55.w, + ), + text( + ATAppLocalizations.of(context)!.activity, + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + ) + : Column( + children: [ + Image.asset( + "atu_images/msg/at_icon_message_activity.png", + width: 55.w, + height: 55.w, + ), + text( + ATAppLocalizations.of(context)!.activity, + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + onTap: () { + provider.updateActivityCount(0); + ATNavigatorUtils.push( + context, + ATChatRouter.activity, + replace: false, + ); + }, + ), + ATDebounceWidget( + debounceTime: Duration(milliseconds: 3000), + child: + provider.systemUnReadCount > 0 + ? Badge( + backgroundColor: Colors.red, + label: text( + "${provider.systemUnReadCount > 99 ? "99+" : provider.systemUnReadCount}", + fontSize: 9.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + alignment: AlignmentDirectional.topEnd, + child: Column( + children: [ + Image.asset( + "atu_images/msg/at_icon_message_system.png", + width: 55.w, + height: 55.w, + ), + text( + ATAppLocalizations.of(context)!.system, + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + ) + : Column( + children: [ + Image.asset( + "atu_images/msg/at_icon_message_system.png", + width: 55.w, + height: 55.w, + ), + text( + ATAppLocalizations.of(context)!.system, + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + onTap: () async { + var conversation = V2TimConversation( + type: ConversationType.V2TIM_C2C, + userID: "atyou-admin", + conversationID: ATGlobalConfig.imAdmin, + ); + provider.updateSystemCount(0); + var bool = await provider.startConversation(conversation); + if (!bool) return; + var json = jsonEncode(conversation.toJson()); + ATNavigatorUtils.push( + context, + "${ATChatRouter.systemChat}?conversation=${Uri.encodeComponent(json)}", + ); + }, + ), + ATDebounceWidget( + debounceTime: Duration(milliseconds: 3000), + child: + provider.notifcationUnReadCount > 0 + ? Badge( + backgroundColor: Colors.red, + label: text( + "${provider.notifcationUnReadCount > 99 ? "99+" : provider.notifcationUnReadCount}", + fontSize: 9.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + alignment: AlignmentDirectional.topEnd, + child: Column( + children: [ + Image.asset( + "atu_images/msg/at_icon_message_noti.png", + width: 55.w, + height: 55.w, + ), + text( + ATAppLocalizations.of(context)!.notifcation, + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + ) + : Column( + children: [ + Image.asset( + "atu_images/msg/at_icon_message_noti.png", + width: 55.w, + height: 55.w, + ), + text( + ATAppLocalizations.of(context)!.notifcation, + textColor: Colors.white, + fontSize: 14.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + onTap: () { + provider.updateNotificationCount(0); + ATNavigatorUtils.push( + context, + ATChatRouter.notifcation, + replace: false, + ); + }, + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/chatvibe_ui/widgets/msg/message_conversation_list_page.dart b/lib/chatvibe_ui/widgets/msg/message_conversation_list_page.dart new file mode 100644 index 0000000..0249819 --- /dev/null +++ b/lib/chatvibe_ui/widgets/msg/message_conversation_list_page.dart @@ -0,0 +1,551 @@ +import 'dart:convert'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:tencent_cloud_chat_sdk/enum/conversation_type.dart'; +import 'package:tencent_cloud_chat_sdk/enum/message_elem_type.dart'; +import 'package:tencent_cloud_chat_sdk/enum/message_status.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_custom_elem.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_text_elem.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_date_utils.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_features/chat/at_chat_route.dart'; + +class MessageConversationListPage extends StatefulWidget { + bool isRoom; + + MessageConversationListPage(this.isRoom, {super.key}); + + @override + _MessageConversationListPageState createState() => + _MessageConversationListPageState(); +} + +class _MessageConversationListPageState + extends State { + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: Consumer( + builder: (_, provider, __) { + return Column( + children: [ + Container( + alignment: AlignmentDirectional.centerStart, + margin: EdgeInsets.symmetric(horizontal: 15.w).copyWith(top: 5.w), + child: text( + ATAppLocalizations.of(context)!.chats, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + textColor: widget.isRoom ? Colors.white : Colors.black, + ), + ), + Expanded( + child: CustomScrollView( + shrinkWrap: true, + slivers: [ + SliverToBoxAdapter( + child: + provider.customerInfo != null && + provider.customerInfo?.id != + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id + ? Stack( + children: [ + Container( + margin: EdgeInsets.symmetric( + horizontal: 15.w, + ), + child: Row( + children: [ + Image.asset( + "atu_images/general/at_icon_logo.png", + height: 48.w, + ), + SizedBox(width: 10.w), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + text( + ATAppLocalizations.of( + context, + )!.contactUs, + fontWeight: FontWeight.w600, + textColor: + widget.isRoom + ? Colors.white + : Colors.black, + fontSize: 15.sp, + ), + Row( + children: [ + Expanded( + child: Text( + "[${ATAppLocalizations.of(context)!.newMessage}]", + maxLines: 1, + overflow: + TextOverflow.ellipsis, + style: TextStyle( + fontSize: ScreenUtil() + .setSp(13), + color: + widget.isRoom + ? Colors.white54 + : Color( + 0xFF666666, + ), + ), + ), + ), + SizedBox(width: 5.w), + ], + ), + ], + ), + ), + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 88.w, + height: 26.w, + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(18.w), + gradient: LinearGradient( + colors: [ + Color(0xff963EFB), + Color(0xffDCAEF6), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: text( + ATAppLocalizations.of( + context, + )!.contactUs, + fontWeight: FontWeight.w600, + textColor: Colors.white, + fontSize: 13.sp, + ), + ), + onTap: () async { + var conversation = + V2TimConversation( + type: + ConversationType + .V2TIM_C2C, + userID: + provider.customerInfo?.id, + conversationID: '', + ); + provider.updateCustomerCount(0); + var bool = + await Provider.of( + context, + listen: false, + ).startConversation( + conversation, + ); + if (!bool) return; + var json = jsonEncode( + conversation.toJson(), + ); + ATNavigatorUtils.push( + context, + "${ATChatRouter.chat}?conversation=${Uri.encodeComponent(json)}", + ); + }, + ), + ], + ), + ), + // 未读数量 + PositionedDirectional( + bottom: 0, + end: 10.w, + child: Visibility( + visible: provider.customerUnReadCount > 0, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 6.w, + ), + alignment: Alignment.center, + height: 16.w, + decoration: BoxDecoration( + //gradient: LinearGradient(colors: [Color(0xffA447FF), Color(0xff623CE9)]), + color: Colors.red, + shape: BoxShape.circle, + //borderRadius: BorderRadius.all(Radius.circular(7.w)), + ), + child: Text( + "${provider.customerUnReadCount > 99 ? '99+' : provider.customerUnReadCount}", + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffffffff), + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + ), + ), + ), + ), + ), + ], + ) + : Container(), + ), + SliverToBoxAdapter( + child: _msgListView( + provider.conversationList, + isRoom: widget.isRoom, + ), + ), + ], + ), + ), + ], + ); + }, + ), + ); + } + + Widget _msgListView( + List chatList, { + EdgeInsetsGeometry? margin, + bool isTop = false, + bool isRoom = false, + }) { + if (chatList.isEmpty) { + if (isTop) return Container(); + return mainEmpty(); + } + return Container( + margin: margin ?? EdgeInsets.only(bottom: 56.w), + child: ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.only( + bottom: isTop ? 0 : ScreenUtil().bottomBarHeight, + ), + itemBuilder: + (_, index) => _item( + chatList[index], + isTop: isTop, + index: index, + isRoom: isRoom, + ), + separatorBuilder: + (_, index) => Divider( + color: Colors.black12, + indent: 65.w, + endIndent: 15.w, + height: 0.5.w, + thickness: 0.5.w, + ), + itemCount: chatList.length, + ), + ); + } + + Widget _item( + V2TimConversation conversation, { + bool isTop = false, + bool isRoom = false, + required int index, + }) { + return ConversationItem( + conversation: conversation, + index: index, + isRoom: isRoom, + key: Key(conversation.conversationID), + ); + } +} + +class ConversationItem extends StatefulWidget { + final V2TimConversation conversation; + final int index; + final bool isRoom; + + const ConversationItem({ + Key? key, + required this.conversation, + required this.index, + required this.isRoom, + }) : super(key: key); + + @override + _ConversationItemState createState() => _ConversationItemState(); +} + +class _ConversationItemState extends State { + V2TimConversation get conversation => widget.conversation; + ChatVibeUserProfile? user; + + @override + void initState() { + loadUserInfo(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + String content = ""; + if (conversation.lastMessage == null) return Container(); + // if (conversation.lastMessage.elemList.length == 0) return Container(); + // if (conversation.lastMessage != null && conversation.type != FTIMConversationType.System) { + if (conversation.lastMessage != null) { + if (conversation.lastMessage?.status == + MessageStatus.V2TIM_MSG_STATUS_LOCAL_REVOKED) { + content = "[${ATAppLocalizations.of(context)!.messageHasBeenRecalled}]"; + } else { + if (conversation.lastMessage!.elemType == + MessageElemType.V2TIM_ELEM_TYPE_TEXT) { + V2TimTextElem textElem = conversation.lastMessage!.textElem!; + content = textElem.text!; + } else if (conversation.lastMessage!.elemType == + MessageElemType.V2TIM_ELEM_TYPE_IMAGE) { + content = ATAppLocalizations.of(context)!.image; + } else if (conversation.lastMessage!.elemType == + MessageElemType.V2TIM_ELEM_TYPE_VIDEO) { + content = ATAppLocalizations.of(context)!.video; + } else if (conversation.lastMessage!.elemType == + MessageElemType.V2TIM_ELEM_TYPE_SOUND) { + content = ATAppLocalizations.of(context)!.sound; + } else if (conversation.lastMessage!.elemType == + MessageElemType.V2TIM_ELEM_TYPE_CUSTOM) { + V2TimCustomElem customElem = conversation.lastMessage!.customElem!; + content = customElem.desc ?? ""; + /*if (customElem.extension == 'sendGift') { + content = "[礼物]"; + } else { + content = "[收到一条消息]"; + }*/ + content = ATAppLocalizations.of(context)!.receivedAMessage; + } + } + } + /*if (conversation.type == ConversationType.V2TIM_C2C) { + userProfile = UserProfileCacheManager.getData(conversation.id); + if (userProfile == null) { + UserProfileCacheManager.checkCacheAndAdd([conversation.id]).then((value) { + setState(() {}); + }); + return Container(); + } + }*/ + // print('time :${Platform.isAndroid ? (conversation.lastMsg?.timestamp ?? 0) * 1000 : conversation.lastMsg?.timestamp}'); + return GestureDetector( + onTap: () async { + if (conversation != null) { + conversation.unreadCount = 0; + var bool = await Provider.of( + context, + listen: false, + ).startConversation(conversation); + if (!bool) return; + var json = jsonEncode(widget.conversation.toJson()); + ATNavigatorUtils.push( + context, + "${ATChatRouter.chat}?conversation=${Uri.encodeComponent(json)}", + ); + } + }, + onLongPress: () { + showDeleteConfirm(); + }, + child: Container( + decoration: BoxDecoration( + //color: Colors.white, + ), + child: Column( + children: [ + Container( + width: ScreenUtil().screenWidth, + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.w), + decoration: BoxDecoration( + //color: Colors.white, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + head(url: user?.userAvatar ?? "", width: 55.w), + SizedBox(width: 6.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Expanded( + child: Row( + children: [ + chatvibeNickNameText( + maxWidth: 152.w, + user?.userNickname ?? "", + fontSize: 14.sp, + fontWeight: FontWeight.w500, + textColor: + widget.isRoom + ? Colors.white + : Colors.black, + type: user?.getVIP()?.name ?? "", + needScroll: + (user + ?.userNickname + ?.characters + .length ?? + 0) > + 12, + ), + SizedBox(width: 5.w), + xb( + user?.userSex ?? 0, + color: + widget.isRoom + ? Colors.white54 + : Colors.black26, + ), + ], + ), + ), + SizedBox(width: 6.w), + Text( + ATMDateUtils.formatMessageTime( + context, + DateTime.fromMillisecondsSinceEpoch( + (conversation.lastMessage!.timestamp ?? 0) * + 1000, + ), + ), + style: TextStyle( + fontSize: 13.sp, + color: + widget.isRoom + ? Colors.white54 + : Color(0xff8d8d8d), + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + height: 1, + ), + ), + ], + ), + SizedBox(height: 3.w), + Row( + children: [ + Expanded( + child: Text( + content, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: ScreenUtil().setSp(13), + color: + widget.isRoom + ? Colors.white54 + : Color(0xFF666666), + ), + ), + ), + SizedBox(width: 5.w), + // 未读数量 + Visibility( + visible: conversation.unreadCount! > 0, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 6.w), + alignment: Alignment.center, + height: 16.w, + decoration: BoxDecoration( + //gradient: LinearGradient(colors: [Color(0xffA447FF), Color(0xff623CE9)]), + color: Colors.red, + shape: BoxShape.circle, + //borderRadius: BorderRadius.all(Radius.circular(7.w)), + ), + child: Text( + "${conversation.unreadCount! > 99 ? '99+' : conversation.unreadCount}", + style: TextStyle( + fontSize: 10.sp, + color: Color(0xffffffff), + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + ), + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + void showDeleteConfirm() { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.deleteConversationTips, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () async { + Provider.of( + context, + listen: false, + ).clearC2CHistoryMessage(conversation.conversationID ?? "", true); + }, + ); + }, + ); + } + + void loadUserInfo() { + if (conversation.conversationID != "administrator" && + conversation.conversationID != "customer" && + conversation.conversationID != "article") { + AccountRepository().loadUserInfo("${conversation.userID}").then((value) { + user = value; + setState(() {}); + }); + } + } +} diff --git a/lib/chatvibe_ui/widgets/person/draggable_image_grid.dart b/lib/chatvibe_ui/widgets/person/draggable_image_grid.dart new file mode 100644 index 0000000..77ca806 --- /dev/null +++ b/lib/chatvibe_ui/widgets/person/draggable_image_grid.dart @@ -0,0 +1,397 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/config/pickImage.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_pick_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +class DraggableImageGrid extends StatefulWidget { + Function(List images) callback; + + DraggableImageGrid(this.callback); + + @override + _DraggableImageGridState createState() => _DraggableImageGridState(); +} + +class _DraggableImageGridState extends State { + // 图片列表数据 + List images = [ + ImageItem('', 0, 0), + ImageItem('', 1, 0), + ImageItem('', 2, 0), + ImageItem('', 3, 0), + ImageItem('', 4, 0), + ]; + + // 当前拖动的图片索引 + int? draggedIndex; + + // 目标位置的索引 + int? targetIndex; + + @override + void initState() { + super.initState(); + List photoBackgoundImages = + (AccountStorage().getCurrentUser()?.userProfile?.backgroundPhotos ?? []) + .where((t) => t.status != 2) + .toList(); + for (int i = 0; i < photoBackgoundImages.length; i++) { + var element = photoBackgoundImages[i]; + images[i] = ImageItem(element.url ?? "", i, element.status ?? 0); + } + } + + @override + Widget build(BuildContext context) { + return Row( + children: [ + // 左边大图 + _buildImageItem(images[0], 0, isLarge: true), + SizedBox(width: 5.w), + // 右边4张小图 + Column( + children: [ + Row( + children: [ + _buildImageItem(images[1], 1), + SizedBox(width: 5.w), + _buildImageItem(images[2], 2), + ], + ), + SizedBox(height: 5.w), + Row( + children: [ + _buildImageItem(images[3], 3), + SizedBox(width: 5.w), + _buildImageItem(images[4], 4), + ], + ), + ], + ), + ], + ); + } + + Widget _buildImageItem(ImageItem item, int index, {bool isLarge = false}) { + final isDragging = draggedIndex == index; + final isTarget = targetIndex == index && draggedIndex != null; + + return GestureDetector( + child: DragTarget( + onWillAccept: (data) { + setState(() { + targetIndex = index; + }); + return data != null; + }, + onAccept: (data) { + _swapImages(data.id, item.id); + widget.callback(images); + setState(() { + draggedIndex = null; + targetIndex = null; + }); + }, + onLeave: (data) { + setState(() { + targetIndex = null; + }); + }, + builder: (context, candidateData, rejectedData) { + return LongPressDraggable( + data: item, + onDragStarted: () { + setState(() { + draggedIndex = index; + }); + }, + onDragEnd: (details) { + setState(() { + draggedIndex = null; + targetIndex = null; + }); + }, + feedback: Opacity( + opacity: 0.8, + child: Material( + color: Colors.transparent, + child: Container( + width: + isLarge + ? ScreenUtil().screenWidth * 0.45 + : ScreenUtil().screenWidth * 0.215, + height: + isLarge + ? ScreenUtil().screenWidth * 0.45 + : ScreenUtil().screenWidth * 0.215, + child: + item.path.isEmpty + ? Image.asset( + "atu_images/general/at_icon_edit_user_info_add_pic.png", + fit: BoxFit.cover, + ) + : netImage( + url: item.path, + borderRadius: BorderRadius.circular(8.w), + ), + ), + ), + ), + childWhenDragging: Container( + width: + isLarge + ? ScreenUtil().screenWidth * 0.45 + : ScreenUtil().screenWidth * 0.215, + height: + isLarge + ? ScreenUtil().screenWidth * 0.45 + : ScreenUtil().screenWidth * 0.215, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(8), + ), + ), + child: AnimatedContainer( + duration: Duration(milliseconds: 350), + width: + isLarge + ? ScreenUtil().screenWidth * 0.45 + : ScreenUtil().screenWidth * 0.215, + height: + isLarge + ? ScreenUtil().screenWidth * 0.45 + : ScreenUtil().screenWidth * 0.215, + decoration: BoxDecoration( + border: + isTarget ? Border.all(color: ChatVibeTheme.primaryColor, width: 2) : null, + borderRadius: BorderRadius.circular(8), + boxShadow: + isDragging + ? [ + BoxShadow( + color: Colors.black12, + blurRadius: 10, + spreadRadius: 2, + ), + ] + : null, + ), + child: Stack( + alignment: AlignmentDirectional.bottomCenter, + children: [ + // 图片 + item.path.isEmpty + ? Image.asset( + "atu_images/general/at_icon_edit_user_info_add_pic.png", + width: double.infinity, + height: double.infinity, + fit: BoxFit.cover, + ) + : netImage( + url: item.path, + width: double.infinity, + height: double.infinity, + borderRadius: BorderRadius.circular(8.w), + ), + item.path.isNotEmpty && item.status == 0 + ? Container( + alignment: AlignmentDirectional.center, + margin: EdgeInsets.symmetric( + horizontal: isLarge ? 1.w : 0, + vertical: isLarge ? 1.w : 0, + ), + height: isLarge ? 22.w : 18.w, + decoration: BoxDecoration( + color: Colors.black38, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(isLarge ? 12 : 8), + bottomRight: Radius.circular(isLarge ? 12 : 8), + ), + ), + child: text( + ATAppLocalizations.of(context)!.underReview, + textColor: Colors.white, + fontSize: isLarge ? 10.sp : 9.sp, + ), + ) + : Container(), + ], + ), + ), + ); + }, + ), + onTap: () { + if (images[index].path.isEmpty) { + _showAddPicDialog(index); + } else { + _showDeletePicDialog(index); + } + }, + ); + } + + // 交换图片位置 + void _swapImages(int sourceId, int targetId) { + setState(() { + final sourceIndex = images.indexWhere((item) => item.id == sourceId); + final targetIndex = images.indexWhere((item) => item.id == targetId); + + if (sourceIndex != -1 && targetIndex != -1) { + final temp = images[sourceIndex]; + images[sourceIndex] = images[targetIndex]; + images[targetIndex] = temp; + } + }); + } + + void _showAddPicDialog(int index) { + SmartDialog.show( + tag: "showAddPicDialog", + alignment: Alignment.bottomCenter, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return SafeArea(top:false,child: Container( + padding: EdgeInsets.only(bottom: 10.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + alignment: AlignmentDirectional.center, + margin: EdgeInsets.only(top: 10.w), + child: text( + ATAppLocalizations.of(context)!.chooseFromAblum, + textColor: Colors.black, + fontWeight: FontWeight.w600, + fontSize: 15.sp, + ), + ), + onTap: () async { + SmartDialog.dismiss(tag: "showAddPicDialog"); + final File? result = await ImagePick.pickSingleFromGallery( + context, + ); + if (result == null) return; // 用户取消选择 + ATPickUtils.cropImage(result, context, 1, true, ( + bool success, + String url, + ) { + if (success) { + _addBackgroundImage(url, index); + } + }); + }, + ), + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + alignment: AlignmentDirectional.center, + margin: EdgeInsets.only(top: 10.w), + child: text( + ATAppLocalizations.of(context)!.camera, + textColor: Colors.black, + fontWeight: FontWeight.w600, + fontSize: 15.sp, + ), + ), + onTap: () async { + SmartDialog.dismiss(tag: "showAddPicDialog"); + var result = await ImagePick.pickFromCamera(context); + if (result != null) { + ATPickUtils.cropImage(result, context, 1, true, ( + bool success, + String url, + ) { + if (success) { + _addBackgroundImage(url, index); + } + }); + } + }, + ), + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + alignment: AlignmentDirectional.center, + margin: EdgeInsets.only(top: 10.w), + child: text( + ATAppLocalizations.of(context)!.cancel, + textColor: Colors.black, + fontWeight: FontWeight.w600, + fontSize: 15.sp, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showAddPicDialog"); + }, + ), + ], + ), + )); + }, + ); + } + + void _addBackgroundImage(String url, int index) { + setState(() { + images[index] = ImageItem(url, index, 0); + }); + widget.callback(images); + } + + void _showDeletePicDialog(int index) { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.doYouWantToDeleteIt, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + _removeBackgroundImage(index); + }, + ); + }, + ); + } + + void _removeBackgroundImage(int index) { + setState(() { + images[index] = ImageItem("", index, 0); + }); + widget.callback(images); + } +} + +// 图片数据模型 +class ImageItem { + final String path; + final int id; + final num status; + + ImageItem(this.path, this.id, this.status); +} diff --git a/lib/chatvibe_ui/widgets/person/draggable_image_person_photo_grid.dart b/lib/chatvibe_ui/widgets/person/draggable_image_person_photo_grid.dart new file mode 100644 index 0000000..ee1ed81 --- /dev/null +++ b/lib/chatvibe_ui/widgets/person/draggable_image_person_photo_grid.dart @@ -0,0 +1,441 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/config/pickImage.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_pick_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; + +class DraggableImagePersonPhotoGrid extends StatefulWidget { + final int maxImages; + Function(List images) callback; + + DraggableImagePersonPhotoGrid({required this.callback, this.maxImages = 9}); + + @override + _DraggableImagePersonPhotoGridState createState() => + _DraggableImagePersonPhotoGridState(); +} + +class _DraggableImagePersonPhotoGridState + extends State { + // 拖动相关状态 + int? draggedIndex; + int? targetIndex; + + // 点击相关状态 + bool _isTapping = false; + int? _tapIndex; + + List images = []; + + int itemCount = 0; + + @override + void initState() { + super.initState(); + images = + (AccountStorage().getCurrentUser()?.userProfile?.personalPhotos ?? []) + .where((t) => t.status != 2) + .toList(); + } + + @override + Widget build(BuildContext context) { + // 修正判断逻辑 + if (images.length >= widget.maxImages) { + itemCount = widget.maxImages; // 已达到最大数量,只显示图片 + } else { + itemCount = images.length + 1; // 还有空位,显示添加按钮 + } + + return GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + childAspectRatio: 1, + mainAxisSpacing: 8, + crossAxisSpacing: 8, + ), + itemCount: itemCount, + itemBuilder: (context, index) { + // 判断是否为添加按钮位置:当前索引等于图片数量且图片数量小于最大限制 + if (index == images.length && images.length < widget.maxImages) { + return _buildAddButton(); + } + + // 图片项(确保索引不超出范围) + if (index < images.length) { + return _buildImageItem(index); + } + + // 如果计算有误,返回空容器 + return Container(); + }, + ); + } + + // 构建添加按钮 + Widget _buildAddButton() { + return GestureDetector( + onTap: () { + _showAddPicDialog(); + }, + child: Image.asset("atu_images/general/at_icon_edit_user_info_add_pic.png"), + ); + } + + // 构建图片项 + Widget _buildImageItem(int index) { + final image = images[index]; + final isDragging = draggedIndex == index; + final isTarget = targetIndex == index && draggedIndex != null; + final isTappingThis = _isTapping && _tapIndex == index; + + return GestureDetector( + onTap: () { + _handleImageTap(index, image); + }, + onTapDown: (details) { + setState(() { + _isTapping = true; + _tapIndex = index; + }); + }, + onTapCancel: () { + setState(() { + _isTapping = false; + _tapIndex = null; + }); + }, + onTapUp: (details) { + Future.delayed(Duration(milliseconds: 100), () { + if (mounted) { + setState(() { + _isTapping = false; + _tapIndex = null; + }); + } + }); + }, + child: DragTarget( + onWillAccept: (data) { + // 只接受图片索引,不接受其他类型 + if (data != null && data != index) { + setState(() { + targetIndex = index; + }); + return true; + } + return false; + }, + onAccept: (sourceIndex) { + _swapImages(sourceIndex, index); + widget.callback(images); + setState(() { + draggedIndex = null; + targetIndex = null; + }); + }, + onLeave: (data) { + setState(() { + targetIndex = null; + }); + }, + builder: (context, candidateData, rejectedData) { + return LongPressDraggable( + data: index, + // 传递图片索引 + onDragStarted: () { + setState(() { + draggedIndex = index; + _isTapping = false; // 拖动时取消点击状态 + }); + }, + onDragEnd: (details) { + setState(() { + draggedIndex = null; + targetIndex = null; + }); + }, + feedback: _buildDragFeedback(image), + childWhenDragging: _buildImagePlaceholder(), + child: _buildImageContent( + index, + image, + isDragging, + isTarget, + isTappingThis, + ), + ); + }, + ), + ); + } + + // 构建拖动时的反馈 + Widget _buildDragFeedback(PersonPhoto image) { + return Opacity( + opacity: 0.8, + child: Material( + color: Colors.transparent, + child: Container( + width: 100.w, + height: 100.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + boxShadow: [ + BoxShadow(color: Colors.black26, blurRadius: 10, spreadRadius: 2), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(12.w), + child: netImage(url: image.url ?? ""), + ), + ), + ), + ); + } + + // 构建拖动时的占位符 + Widget _buildImagePlaceholder() { + return Container( + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(12.w), + border: Border.all(color: Colors.grey[300]!), + ), + child: Center( + child: Icon(Icons.photo, color: Colors.grey[400], size: 30.w), + ), + ); + } + + // 构建图片内容 + Widget _buildImageContent( + int index, + PersonPhoto image, + bool isDragging, + bool isTarget, + bool isTappingThis, + ) { + return AnimatedContainer( + duration: Duration(milliseconds: 350), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + border: isTarget ? Border.all(color: ChatVibeTheme.primaryColor, width: 2) : null, + ), + child: Stack( + alignment: AlignmentDirectional.bottomCenter, + children: [ + // 图片 + netImage( + url: image.url ?? "", + width: double.infinity, + height: double.infinity, + borderRadius: BorderRadius.circular(12.w), + ), + + // 点击效果遮罩 + if (isTappingThis && !isDragging) + Container( + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.1), + borderRadius: BorderRadius.circular(12.w), + ), + ), + + // 审核状态 + if (image.status == 0 && !isDragging) + Container( + alignment: AlignmentDirectional.center, + height: 22.w, + decoration: BoxDecoration( + color: Colors.black38, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(12.w), + bottomRight: Radius.circular(12.w), + ), + ), + child: text( + ATAppLocalizations.of(context)!.underReview, + textColor: Colors.white, + fontSize: 10.sp, + ), + ), + ], + ), + ); + } + + // 处理图片点击 + void _handleImageTap(int index, PersonPhoto image) { + // 重置点击状态 + setState(() { + _isTapping = false; + _tapIndex = null; + }); + + _showDeletePicDialog(index); + } + + void _showDeletePicDialog(int index) { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.doYouWantToDeleteIt, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + _removePhotoImage(index); + }, + ); + }, + ); + } + + // 交换图片位置 + void _swapImages(int sourceIndex, int targetIndex) { + if (sourceIndex < 0 || + targetIndex < 0 || + sourceIndex >= images.length || + targetIndex >= images.length || + sourceIndex == targetIndex) { + return; + } + + setState(() { + // 交换图片 + final temp = images[sourceIndex]; + images[sourceIndex] = images[targetIndex]; + images[targetIndex] = temp; + }); + } + + void _showAddPicDialog() { + SmartDialog.show( + tag: "showAddPicDialog", + alignment: Alignment.bottomCenter, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return SafeArea( + top: false, + child: Container( + padding: EdgeInsets.only(bottom: 10.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + alignment: AlignmentDirectional.center, + margin: EdgeInsets.only(top: 10.w), + child: text( + ATAppLocalizations.of(context)!.chooseFromAblum, + textColor: Colors.black, + fontWeight: FontWeight.w600, + fontSize: 15.sp, + ), + ), + onTap: () async { + SmartDialog.dismiss(tag: "showAddPicDialog"); + final File? result = await ImagePick.pickSingleFromGallery( + context, + ); + if (result == null) return; // 用户取消选择 + ATPickUtils.cropImage(result, context, 1, true, ( + bool success, + String url, + ) { + if (success) { + _addPhotoImage(url); + } + }); + }, + ), + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + alignment: AlignmentDirectional.center, + margin: EdgeInsets.only(top: 10.w), + child: text( + ATAppLocalizations.of(context)!.camera, + textColor: Colors.black, + fontWeight: FontWeight.w600, + fontSize: 15.sp, + ), + ), + onTap: () async { + SmartDialog.dismiss(tag: "showAddPicDialog"); + var result = await ImagePick.pickFromCamera(context); + if (result != null) { + ATPickUtils.cropImage(result, context, 1, true, ( + bool success, + String url, + ) { + if (success) { + _addPhotoImage(url); + } + }); + } + }, + ), + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + alignment: AlignmentDirectional.center, + margin: EdgeInsets.only(top: 10.w), + child: text( + ATAppLocalizations.of(context)!.cancel, + textColor: Colors.black, + fontWeight: FontWeight.w600, + fontSize: 15.sp, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showAddPicDialog"); + }, + ), + ], + ), + ), + ); + }, + ); + } + + void _addPhotoImage(String url) { + if (images.length < widget.maxImages) { + images.add(PersonPhoto(url: url, status: 0)); + setState(() {}); + widget.callback(images); + } + } + + void _removePhotoImage(int index) { + images.removeAt(index); + setState(() {}); + widget.callback(images); + } +} diff --git a/lib/chatvibe_ui/widgets/pop/at_pop_widget.dart b/lib/chatvibe_ui/widgets/pop/at_pop_widget.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/chatvibe_ui/widgets/pop/pop_route.dart b/lib/chatvibe_ui/widgets/pop/pop_route.dart new file mode 100644 index 0000000..1288b83 --- /dev/null +++ b/lib/chatvibe_ui/widgets/pop/pop_route.dart @@ -0,0 +1,46 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class PopRoute extends PopupRoute { + final Duration _duration = const Duration(milliseconds: 350); + final Widget child; + + PopRoute({required this.child}); + + @override + Color? get barrierColor => null; + + @override + bool get barrierDismissible => false; // 改为 false + + @override + String? get barrierLabel => null; + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + return child; + } + + // 关键:添加这个方法来支持 iOS 手势 + @override + Widget buildTransitions(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + // 在 iOS 上使用 Cupertino 页面过渡 + if (Platform.isIOS) { + return CupertinoPageTransition( + primaryRouteAnimation: animation, + secondaryRouteAnimation: secondaryAnimation, + child: child, + linearTransition: false, + ); + } + // 保持原有过渡(没有过渡) + return child; + } + + @override + Duration get transitionDuration => _duration; +} \ No newline at end of file diff --git a/lib/chatvibe_ui/widgets/room/anim/l_gift_animal_view.dart b/lib/chatvibe_ui/widgets/room/anim/l_gift_animal_view.dart new file mode 100644 index 0000000..dbbfac0 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/anim/l_gift_animal_view.dart @@ -0,0 +1,424 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/main.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_managers/gift_animation_manager.dart'; + +import '../../../../chatvibe_data/models/enum/at_vip_type.dart'; + +///房间礼物滚屏动画 +class LGiftAnimalPage extends StatefulWidget { + const LGiftAnimalPage({super.key}); + + @override + State createState() { + return _GiftAnimalPageState(); + } +} + +class _GiftAnimalPageState extends State + with TickerProviderStateMixin { + @override + Widget build(BuildContext context) { + return SizedBox( + height: 305.w, + child: Stack( + children: [ + Consumer( + builder: (context, ref, child) { + return Stack( + children: List.generate( + 4, + (index) => _buildGiftLane(context, ref, index), + ), + ); + }, + ), + ], + ), + ); + } + + Widget _buildGiftLane( + BuildContext context, + GiftAnimationManager ref, + int index, + ) { + final LGiftModel? giftModel = ref.giftMap[index]; + if (giftModel == null) { + return Container(); + } + return AnimatedBuilder( + animation: ref.aninControllers[index].controller, + builder: (context, child) { + return _buildGiftCard( + context: context, + giftModel: giftModel, + animBean: ref.aninControllers[index], + ); + }, + ); + } + + Widget _buildGiftCard({ + required BuildContext context, + required LGiftModel giftModel, + required LGiftScrollingScreenAnimsBean animBean, + }) { + final bool isVip = hasVip(giftModel.viptype); + final double cardWidth = ScreenUtil().screenWidth * 0.68; + final double cardHeight = isVip ? 130.w : 35.w; + final double avatarSize = isVip ? 52.w : 25.w; + final double avatarStartSpacing = _avatarStartSpacing(giftModel.viptype); + final double textStartSpacing = + avatarStartSpacing + avatarSize + (isVip ? 20.w : 4.w); + + return Container( + margin: animBean.verticalAnimation.value, + width: ScreenUtil().screenWidth * 0.78, + child: Stack( + alignment: AlignmentDirectional.centerStart, + clipBehavior: Clip.none, + children: [ + if (isVip) + PositionedDirectional( + start: avatarStartSpacing, + top: (cardHeight - avatarSize) / 2, + child: netImage( + url: giftModel.sendUserPic, + shape: BoxShape.circle, + width: avatarSize, + height: avatarSize, + ), + ), + _buildBackground(context, giftModel.viptype, cardWidth, cardHeight), + if (isVip) + _buildVipForeground( + context: context, + giftModel: giftModel, + cardHeight: cardHeight, + textStartSpacing: textStartSpacing, + ) + else + _buildNormalForeground( + context: context, + giftModel: giftModel, + cardWidth: cardWidth, + cardHeight: cardHeight, + avatarStartSpacing: avatarStartSpacing, + avatarSize: avatarSize, + ), + _buildGiftCount(giftModel: giftModel, animBean: animBean), + ], + ), + ); + } + + Widget _buildBackground( + BuildContext context, + String vipType, + double width, + double height, + ) { + return Transform.scale( + scaleX: Directionality.of(context) == TextDirection.rtl ? -1 : 1, + child: Container( + width: width, + height: height, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(getBg(vipType)), + fit: BoxFit.fill, + ), + ), + ), + ); + } + + Widget _buildNormalForeground({ + required BuildContext context, + required LGiftModel giftModel, + required double cardWidth, + required double cardHeight, + required double avatarStartSpacing, + required double avatarSize, + }) { + return SizedBox( + width: cardWidth, + height: cardHeight, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: avatarStartSpacing), + netImage( + url: giftModel.sendUserPic, + shape: BoxShape.circle, + width: avatarSize, + height: avatarSize, + ), + SizedBox(width: 4.w), + _buildGiftUserInfo(context, giftModel), + SizedBox(width: 4.w), + ], + ), + ); + } + + Widget _buildVipForeground({ + required BuildContext context, + required LGiftModel giftModel, + required double cardHeight, + required double textStartSpacing, + }) { + return SizedBox( + height: cardHeight, + child: Padding( + padding: EdgeInsetsDirectional.only(start: textStartSpacing), + child: Align( + alignment: Alignment.centerLeft, + child: _buildGiftUserInfo(context, giftModel), + ), + ), + ); + } + + Widget _buildGiftUserInfo(BuildContext context, LGiftModel giftModel) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + chatvibeNickNameText( + maxWidth: 82.w, + giftModel.sendUserName, + fontSize: 12.sp, + fontWeight: FontWeight.w500, + type: giftModel.viptype, + needScroll: giftModel.sendUserName.characters.length > 8, + ), + Row( + children: [ + Text( + "${ATAppLocalizations.of(context)!.sendTo} ", + style: TextStyle(fontSize: 10.sp, color: Colors.white), + ), + ConstrainedBox( + constraints: BoxConstraints(maxWidth: 62.w), + child: Text( + giftModel.sendToUserName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 10.sp, color: Color(0xFFFFD400)), + ), + ), + ], + ), + ], + ); + } + + Widget _buildGiftCount({ + required LGiftModel giftModel, + required LGiftScrollingScreenAnimsBean animBean, + }) { + return PositionedDirectional( + start: 170.w, + child: Row( + children: [ + netImage( + url: giftModel.giftPic, + fit: BoxFit.cover, + borderRadius: BorderRadius.circular(4.w), + width: 32.w, + height: 32.w, + ), + SizedBox(width: 12.w), + Text.rich( + TextSpan( + children: [ + TextSpan( + text: "x", + style: TextStyle( + fontSize: animBean.sizeAnimation.value.w, + fontStyle: FontStyle.italic, + color: Color(0xFFFFD400), + fontWeight: FontWeight.bold, + ), + ), + TextSpan( + text: "${giftModel.giftCount}", + style: TextStyle( + fontSize: animBean.sizeAnimation.value, + fontStyle: FontStyle.italic, + color: Color(0xFFFFD400), + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ], + ), + ); + } + + double _avatarStartSpacing(String vipType) { + if (vipType.isEmpty) { + return 3.w; + } + if (vipType == ATVIPType.DUKE.name) { + return 21.w; + } + if (vipType == ATVIPType.KING.name) { + return 30.w; + } + return 22.w; + } + + @override + void dispose() { + Provider.of( + navigatorKey.currentState!.context, + listen: false, + ).disposeAnim(); + super.dispose(); + } + + @override + void initState() { + super.initState(); + initAnimal(); + } + + void initAnimal() { + List beans = []; + double top = 60; + for (int i = 0; i < 4; i++) { + var bean = LGiftScrollingScreenAnimsBean(); + var controller = AnimationController( + value: 0, + duration: const Duration(milliseconds: 5000), + vsync: this, + ); + bean.controller = controller; + // bean.transverseAnimation = Tween( + // begin: Offset(ScreenUtil().screenWidth, 0), + // end: Offset(0, 0), + // ).animate( + // CurvedAnimation( + // parent: controller, + // curve: Interval(0.0, 0.45, curve: Curves.ease), + // ), + // ); + bean.verticalAnimation = EdgeInsetsTween( + begin: EdgeInsets.only(top: top), + end: EdgeInsets.only(top: 0), + ).animate( + CurvedAnimation( + parent: controller, + curve: Interval(0.2, 1, curve: Curves.easeOut), + ), + ); + bean.sizeAnimation = Tween(begin: 0, end: 22).animate( + CurvedAnimation( + parent: controller, + curve: Interval(0.45, 0.55, curve: Curves.ease), + ), + ); + beans.add(bean); + top = top + 70; + } + beans[0].controller.addStatusListener((state) { + if (state == AnimationStatus.completed) { + //动画完成监听 + Provider.of( + context, + listen: false, + ).animCompleted(0); + } + }); + beans[1].controller.addStatusListener((state) { + if (state == AnimationStatus.completed) { + //动画完成监听 + Provider.of( + context, + listen: false, + ).animCompleted(1); + } + }); + beans[2].controller.addStatusListener((state) { + if (state == AnimationStatus.completed) { + //动画完成监听 + Provider.of( + context, + listen: false, + ).animCompleted(2); + } + }); + beans[3].controller.addStatusListener((state) { + if (state == AnimationStatus.completed) { + //动画完成监听 + Provider.of( + context, + listen: false, + ).animCompleted(3); + } + }); + Provider.of( + context, + listen: false, + ).bindController(beans); + } + + bool hasVip(String type) { + return type == ATVIPType.MARQUIS.name || + type == ATVIPType.DUKE.name || + type == ATVIPType.KING.name; + } + + String getBg(String type) { + String bg = "atu_images/room/at_icon_room_gift_left_no_vip_bg.png"; + if (type == ATVIPType.MARQUIS.name) { + bg = "atu_images/room/at_icon_room_gift_left_vip3_bg.png"; + } else if (type == ATVIPType.DUKE.name) { + bg = "atu_images/room/at_icon_room_gift_left_vip4_bg.png"; + } else if (type == ATVIPType.KING.name) { + bg = "atu_images/room/at_icon_room_gift_left_vip5_bg.png"; + } + return bg; + } +} + +class LGiftScrollingScreenAnimsBean { + // late Animation transverseAnimation; + late Animation verticalAnimation; + late AnimationController controller; + late Animation sizeAnimation; +} + +class LGiftModel { + //发送者的名字 + String sendUserName = ""; + + //发送给谁 + String sendToUserName = ""; + + //发送者的头像 + String sendUserPic = ""; + + //礼物的图片 + String giftPic = ""; + + //礼物的名字 + String giftName = ""; + + //一次发送礼物的数量 + num giftCount = 0; + + //id + String labelId = ""; + + String viptype = ""; +} diff --git a/lib/chatvibe_ui/widgets/room/anim/room_entrance_screen.dart b/lib/chatvibe_ui/widgets/room/anim/room_entrance_screen.dart new file mode 100644 index 0000000..4a150bc --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/anim/room_entrance_screen.dart @@ -0,0 +1,298 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_msg_item.dart'; + +import '../../../../chatvibe_data/models/enum/at_vip_type.dart'; + +class RoomAnimationQueueScreen extends StatefulWidget { + const RoomAnimationQueueScreen({super.key}); + + @override + State createState() => + _RoomAnimationQueueScreenState(); +} + +class _RoomAnimationQueueScreenState extends State { + final List _animationQueue = []; + bool _isQueueProcessing = false; + final Map> _animationKeys = {}; + + @override + void initState() { + super.initState(); + Provider.of(context, listen: false).msgUserJoinListener = + _msgUserJoinListener; + } + + _msgUserJoinListener(Msg msg) { + Future.delayed(Duration(milliseconds: 550), () { + _addToQueue(msg); + }); + } + + void _addToQueue(Msg msg) { + setState(() { + final taskId = DateTime.now().millisecondsSinceEpoch; + final animationKey = GlobalKey<_RoomEntranceAnimationState>(); + + _animationKeys[taskId] = animationKey; + + final task = AnimationTask( + id: taskId, + msg: msg, + onComplete: () { + if (_animationQueue.isNotEmpty && + _animationQueue.first.id == taskId) { + setState(() { + _animationQueue.removeAt(0); + _animationKeys.remove(taskId); + }); + + if (_animationQueue.isNotEmpty) { + _startNextAnimation(); + } else { + setState(() => _isQueueProcessing = false); + } + } + }, + ); + + _animationQueue.add(task); + }); + + if (!_isQueueProcessing && _animationQueue.isNotEmpty) { + setState(() => _isQueueProcessing = true); + _startNextAnimation(); + } + } + + void _startNextAnimation({int retryCount = 0}) { + if (_animationQueue.isEmpty) return; + + SchedulerBinding.instance.addPostFrameCallback((_) { + if (_animationQueue.isNotEmpty) { + final task = _animationQueue.first; + final key = _animationKeys[task.id]; + + if (key?.currentState != null) { + key!.currentState!._startAnimation(); + } else if (retryCount < 3) { + // 有限次重试,避免无限循环 + Future.delayed(const Duration(milliseconds: 50), () { + _startNextAnimation(retryCount: retryCount + 1); + }); + } else { + // 重试多次失败后,跳过当前动画 + print("动画启动失败,跳过当前任务"); + task.onComplete(); + } + } + }); + } + + void _clearQueue() { + setState(() { + _animationQueue.clear(); + _animationKeys.clear(); + _isQueueProcessing = false; + }); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Stack( + children: [ + if (_animationQueue.isNotEmpty) + RoomEntranceAnimation( + key: _animationKeys[_animationQueue.first.id], + msg: _animationQueue.first.msg, + onComplete: _animationQueue.first.onComplete, + ), + ], + ), + ], + ), + ); + } +} + +class AnimationTask { + final int id; + final Msg msg; + final VoidCallback onComplete; + + AnimationTask({ + required this.id, + required this.msg, + required this.onComplete, + }); +} + +class RoomEntranceAnimation extends StatefulWidget { + final VoidCallback onComplete; + final Msg msg; + + const RoomEntranceAnimation({ + super.key, + required this.onComplete, + required this.msg, + }); + + @override + State createState() => _RoomEntranceAnimationState(); +} + +class _RoomEntranceAnimationState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _offsetAnimation; + bool _isAnimating = false; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: const Duration(seconds: 4), + vsync: this, + ); + + _offsetAnimation = TweenSequence([ + TweenSequenceItem( + tween: Tween( + begin: const Offset(1.0, 0.0), + end: const Offset(0.0, 0.0), + ).chain(CurveTween(curve: Curves.easeOut)), + weight: 40.0, + ), + TweenSequenceItem( + tween: ConstantTween(const Offset(0.0, 0.0)), + weight: 20.0, + ), + TweenSequenceItem( + tween: Tween( + begin: const Offset(0.0, 0.0), + end: const Offset(-1.0, 0.0), + ).chain(CurveTween(curve: Curves.easeIn)), + weight: 40.0, + ), + ]).animate(_controller); + + _controller.addStatusListener((status) { + if (status == AnimationStatus.completed) { + setState(() => _isAnimating = false); + widget.onComplete(); + } + }); + } + + void _startAnimation() { + if (_isAnimating) return; + + setState(() => _isAnimating = true); + _controller.reset(); + _controller.forward(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SlideTransition( + position: _offsetAnimation, + child: Container( + width: ScreenUtil().screenWidth * 0.8, + height: widget.msg.user?.getVIP() == null ? 37.w : 150.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + getEntranceBg(widget.msg.user?.getVIP()?.name ?? ""), + ), + fit: BoxFit.fill, + ), + ), + child: Row( + textDirection: TextDirection.ltr, + children: [ + widget.msg.user?.getVIP() == null + ? Padding( + padding: EdgeInsets.all(2.w), + child: netImage( + url: widget.msg.user?.userAvatar ?? "", + width: 28.w, + height: 28.w, + shape: BoxShape.circle, + ), + ) + : Padding( + padding: EdgeInsets.only(left: 44.w), + child: SizedBox(width: 40.w, height: 40.w), + ), + widget.msg.user?.getVIP() == null + ? SizedBox(width: 5.w) + : SizedBox(width: 25.w), + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: + widget.msg.user?.getVIP()?.name == ATVIPType.VISCOUNT.name + ? 10.w + : 2.w, + ), + chatvibeNickNameText( + maxWidth: 150.w, + widget.msg.user?.userNickname ?? "", + fontSize: 14.sp, + type: widget.msg.user?.getVIP()?.name ?? "", + needScroll: + (widget.msg.user?.userNickname?.characters.length ?? 0) > + 15, + ), + text( + ATAppLocalizations.of(context)!.enterTheRoom, + fontSize: 12.sp, + lineHeight: 0.9, + textColor: Colors.white, + ), + ], + ), + ], + ), + ), + ); + } + + String getEntranceBg(String type) { + String bg = "atu_images/room/entrance/at_icon_room_entrance_no_vip_bg.png"; + if (type == ATVIPType.VISCOUNT.name) { + bg = "atu_images/room/entrance/at_icon_room_entrance_vip1_bg.png"; + } else if (type == ATVIPType.EARL.name) { + bg = "atu_images/room/entrance/at_icon_room_entrance_vip2_bg.png"; + } else if (type == ATVIPType.MARQUIS.name) { + bg = "atu_images/room/entrance/at_icon_room_entrance_vip3_bg.png"; + } else if (type == ATVIPType.DUKE.name) { + bg = "atu_images/room/entrance/at_icon_room_entrance_vip4_bg.png"; + } else if (type == ATVIPType.KING.name) { + bg = "atu_images/room/entrance/at_icon_room_entrance_vip5_bg.png"; + } else if (type == ATVIPType.EMPEROR.name) { + bg = "atu_images/room/entrance/at_icon_room_entrance_vip6_bg.png"; + } + return bg; + } +} diff --git a/lib/chatvibe_ui/widgets/room/anim/room_entrance_widget.dart b/lib/chatvibe_ui/widgets/room/anim/room_entrance_widget.dart new file mode 100644 index 0000000..9fac975 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/anim/room_entrance_widget.dart @@ -0,0 +1,734 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svga/flutter_svga.dart'; +import 'dart:async'; +import 'dart:typed_data'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/utilities/at_gift_vap_svga_manager.dart'; + +// 动画队列项 +class AnimationQueueItem { + final String resource; + final Map? dynamicData; // 动态数据(文本、图片等) + final VoidCallback? onCompleted; + final Function(String)? onError; + Completer? completer; + + AnimationQueueItem({ + required this.resource, + this.dynamicData, + this.onCompleted, + this.onError, + }); +} + +// 动态数据类型 +class DynamicData { + final Map? textReplacements; // 文本替换 + final Map? imageReplacements; // 图片替换(二进制数据) + final Map? imageAssetPaths; // 图片资源路径 + + DynamicData({ + this.textReplacements, + this.imageReplacements, + this.imageAssetPaths, + }); +} + +// 动画队列管理器 +class RoomEntranceQueueManager { + static final RoomEntranceQueueManager _instance = + RoomEntranceQueueManager._internal(); + + factory RoomEntranceQueueManager() => _instance; + + RoomEntranceQueueManager._internal(); + + // 队列列表 + final List _queue = []; + + // 当前是否正在播放 + bool _isPlaying = false; + + // 当前播放器实例 + _RoomEntranceWidgetState? _currentPlayer; + + // 队列回调 + void Function()? onQueueUpdated; + + // 最大队列长度限制 + int maxQueueLength = 20; + + // 自动播放延迟时间(毫秒) + int autoPlayDelay = 500; + + // 添加动画到队列 + Future addToQueue(AnimationQueueItem item) { + final completer = Completer(); + item.completer = completer; + + // 检查队列长度限制 + if (_queue.length >= maxQueueLength) { + // 移除最早的动画 + final removedItem = _queue.removeAt(0); + removedItem.completer?.completeError('Queue overflow, item removed'); + } + + _queue.add(item); + + // 通知队列更新 + if (onQueueUpdated != null) { + onQueueUpdated!(); + } + + // 如果没有正在播放,则开始播放队列 + if (!_isPlaying) { + _startNextAnimation(); + } + + return completer.future; + } + + // 获取当前队列长度 + int get queueLength => _queue.length; + + // 获取等待中的队列项 + List get waitingItems => + _queue.isNotEmpty && _isPlaying + ? _queue.sublist(1) + : List.from(_queue); + + // 设置当前播放器 + void setCurrentPlayer(_RoomEntranceWidgetState player) { + _currentPlayer = player; + } + + // 清除当前播放器 + void clearCurrentPlayer() { + _currentPlayer = null; + } + + // 开始播放下一个动画 + void _startNextAnimation() { + if (_queue.isEmpty) { + _isPlaying = false; + if (onQueueUpdated != null) { + onQueueUpdated!(); + } + return; + } + + _isPlaying = true; + final nextItem = _queue.first; + + // 如果有延迟,则延迟播放 + Future.delayed(Duration(milliseconds: autoPlayDelay), () { + if (_currentPlayer != null && _currentPlayer!.mounted) { + // 播放队列中的动画 + _currentPlayer!.playFromQueue( + resource: nextItem.resource, + dynamicData: + nextItem.dynamicData != null + ? DynamicData( + textReplacements: nextItem.dynamicData?['textReplacements'], + imageReplacements: + nextItem.dynamicData?['imageReplacements'], + imageAssetPaths: nextItem.dynamicData?['imageAssetPaths'], + ) + : null, + onCompleted: () { + // 动画完成回调 + if (nextItem.onCompleted != null) { + nextItem.onCompleted!(); + } + + // 完成Completer + nextItem.completer?.complete(); + + // 从队列中移除当前项 + _queue.removeAt(0); + + // 通知队列更新 + if (onQueueUpdated != null) { + onQueueUpdated!(); + } + + // 播放下一个 + _startNextAnimation(); + }, + onError: (error) { + // 错误处理 + if (nextItem.onError != null) { + nextItem.onError!(error); + } + + // 完成Completer(带错误) + nextItem.completer?.completeError(error); + + // 从队列中移除当前项 + _queue.removeAt(0); + + // 通知队列更新 + if (onQueueUpdated != null) { + onQueueUpdated!(); + } + + // 播放下一个 + _startNextAnimation(); + }, + ); + } else { + // 如果播放器不可用,跳过当前项 + _queue.removeAt(0); + _startNextAnimation(); + } + }); + } + + // 清空队列 + void clearQueue() { + for (final item in _queue) { + item.completer?.completeError('Queue cleared'); + } + _queue.clear(); + _isPlaying = false; + + if (onQueueUpdated != null) { + onQueueUpdated!(); + } + } + + // 移除指定位置的队列项 + void removeAtIndex(int index) { + if (index >= 0 && index < _queue.length) { + final removedItem = _queue.removeAt(index); + removedItem.completer?.completeError('Item removed from queue'); + + if (onQueueUpdated != null) { + onQueueUpdated!(); + } + } + } + + // 获取队列状态 + Map get queueStatus { + return { + 'isPlaying': _isPlaying, + 'queueLength': _queue.length, + 'currentResource': _queue.isNotEmpty ? _queue.first.resource : null, + 'waitingCount': + _queue.length > (_isPlaying ? 1 : 0) + ? _queue.length - (_isPlaying ? 1 : 0) + : 0, + }; + } +} + +class RoomEntranceWidget extends StatefulWidget { + // 尺寸 + final double? width; + final double? height; + + // 动画控制参数 + final int loops; // 循环次数(0表示无限循环) + final bool clearsAfterStop; + + // 缓存控制 + final bool useCache; + + // 队列相关参数 + final bool useQueue; // 是否使用队列 + final int queueDelay; // 队列播放延迟(毫秒) + + // 回调函数(不再需要onCompleted和onError,因为通过队列管理) + final VoidCallback? onStartLoading; + final VoidCallback? onFinishLoading; + + // 是否在初始化时自动开始监听队列 + final bool autoStartQueue; + + const RoomEntranceWidget({ + Key? key, + this.width, + this.height, + this.loops = 0, + this.clearsAfterStop = true, + this.useCache = true, + this.useQueue = true, + this.queueDelay = 500, + this.onStartLoading, + this.onFinishLoading, + this.autoStartQueue = true, + }) : super(key: key); + + @override + _RoomEntranceWidgetState createState() => _RoomEntranceWidgetState(); +} + +class _RoomEntranceWidgetState extends State + with SingleTickerProviderStateMixin { + SVGAAnimationController? _animationController; + bool _isLoading = false; + bool _hasError = false; + String? _currentResource; + + // 用于存储SVGAImage组件的key,以便刷新占位符 + GlobalKey _svgaKey = GlobalKey(); + + // 队列管理器实例 + final RoomEntranceQueueManager _queueManager = RoomEntranceQueueManager(); + + // 队列播放相关 + VoidCallback? _queueCompletedCallback; + Function(String)? _queueErrorCallback; + + // 动态数据 + DynamicData? _currentDynamicData; + + @override + void initState() { + super.initState(); + _animationController = SVGAAnimationController(vsync: this); + + // 设置队列延迟 + _queueManager.autoPlayDelay = widget.queueDelay; + + // 设置当前播放器 + _queueManager.setCurrentPlayer(this); + + // 添加动画状态监听 + _animationController?.addStatusListener((status) { + if (status == AnimationStatus.completed) { + // 如果是在队列播放中,调用队列完成回调 + if (_queueCompletedCallback != null) { + _queueCompletedCallback!(); + _queueCompletedCallback = null; + _queueErrorCallback = null; + } + } + }); + + // 如果需要自动开始队列,则检查队列 + if (widget.autoStartQueue && widget.useQueue) { + // 延迟一小段时间,确保组件已经挂载 + Future.delayed(Duration(milliseconds: 100), () { + _queueManager._startNextAnimation(); + }); + } + } + + // 从队列播放(内部使用) + void playFromQueue({ + required String resource, + DynamicData? dynamicData, + VoidCallback? onCompleted, + Function(String)? onError, + }) { + _queueCompletedCallback = onCompleted; + _queueErrorCallback = onError; + _currentDynamicData = dynamicData; + _currentResource = resource; + + // 加载动画 + _loadAnimation( + resource: resource, + dynamicData: dynamicData, + isFromQueue: true, + ); + } + + // 加载动画 + Future _loadAnimation({ + required String resource, + DynamicData? dynamicData, + bool isFromQueue = false, + }) async { + if (widget.onStartLoading != null) { + widget.onStartLoading!(); + } + + setState(() { + _isLoading = true; + _hasError = false; + }); + + try { + final isNetworkResource = resource.startsWith('http'); + MovieEntity? videoItem; + + if (widget.useCache) { + videoItem = ATGiftVapSvgaManager().videoItemCache[resource]; + } + + if (videoItem == null) { + videoItem = + isNetworkResource + ? await SVGAParser.shared.decodeFromURL(resource) + : await SVGAParser.shared.decodeFromAssets(resource); + videoItem.autorelease = false; + + if (widget.useCache) { + ATGiftVapSvgaManager().videoItemCache[resource] = videoItem; + } + } + + if (mounted) { + // 应用动态数据(文本、图片替换) + await _applyDynamicData(videoItem, dynamicData); + + setState(() { + _animationController?.videoItem = videoItem; + _isLoading = false; + }); + + // 开始播放动画 + _startAnimation(); + + if (widget.onFinishLoading != null) { + widget.onFinishLoading!(); + } + } + } catch (e) { + if (mounted) { + setState(() { + _isLoading = false; + _hasError = true; + }); + } + + // 队列错误回调 + if (isFromQueue && _queueErrorCallback != null) { + _queueErrorCallback!(e.toString()); + _queueErrorCallback = null; + _queueCompletedCallback = null; + } + + // 显示错误信息 + ATTts.show("SVGA加载失败: ${e.toString()}"); + } + } + + // 应用动态数据(文本、图片替换) + Future _applyDynamicData( + MovieEntity videoItem, + DynamicData? dynamicData, + ) async { + if (dynamicData == null) return; + + // 1. 应用文本替换 + if (dynamicData.textReplacements != null) { + await _applyTextReplacements(videoItem, dynamicData.textReplacements!); + } + + // 2. 应用图片替换(二进制数据) + if (dynamicData.imageReplacements != null) { + await _applyImageReplacements(videoItem, dynamicData.imageReplacements!); + } + + // 3. 应用图片资源路径替换 + if (dynamicData.imageAssetPaths != null) { + await _applyImageAssetReplacements( + videoItem, + dynamicData.imageAssetPaths!, + ); + } + } + + // 应用文本替换 + Future _applyTextReplacements( + MovieEntity videoItem, + Map textReplacements, + ) async { + // 这里需要根据具体的SVGA插件API来替换文本 + // 示例代码,具体实现取决于插件支持 + if (videoItem.dynamicItem != null) { + textReplacements.forEach((key, value) { + try { + // 根据插件API设置文本 + // 注意:flutter_svga插件的API可能会有所不同 + videoItem.dynamicItem!.setText( + TextPainter( + text: TextSpan( + text: "Hello, World!", + style: TextStyle( + fontSize: 28, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + key, + ); + } catch (e) { + print('设置文本占位符 $key 失败: $e'); + } + }); + } + } + + // 应用图片替换(二进制数据) + Future _applyImageReplacements( + MovieEntity videoItem, + Map imageReplacements, + ) async { + // 检查是否支持图片替换 + if (videoItem.images != null) { + imageReplacements.forEach((key, imageData) { + try { + // 替换图片数据 + // 注意:flutter_svga插件的API可能会有所不同 + if (videoItem.images!.containsKey(key)) { + videoItem.images![key] = imageData; + } + } catch (e) { + print('设置图片占位符 $key 失败: $e'); + } + }); + } + } + + // 应用图片资源路径替换 + Future _applyImageAssetReplacements( + MovieEntity videoItem, + Map imageAssetPaths, + ) async { + // 如果需要从assets加载图片,可以实现这里 + // 这通常需要将assets图片转换为Uint8List + } + + // 开始播放动画 + void _startAnimation() { + _animationController?.reset(); + + // 根据循环次数设置播放方式 + if (widget.loops == 0) { + _animationController?.repeat(); + } else { + _animationController?.repeat(count: widget.loops); + } + } + + // 播放指定资源(外部调用) + Future playResource({ + required String resource, + DynamicData? dynamicData, + }) async { + if (widget.useQueue) { + // 使用队列,添加到队列 + final queueItem = AnimationQueueItem( + resource: resource, + dynamicData: + dynamicData != null + ? { + 'textReplacements': dynamicData.textReplacements, + 'imageReplacements': dynamicData.imageReplacements, + 'imageAssetPaths': dynamicData.imageAssetPaths, + } + : null, + ); + + return _queueManager.addToQueue(queueItem); + } else { + // 不使用队列,直接播放 + return _loadAnimation( + resource: resource, + dynamicData: dynamicData, + isFromQueue: false, + ); + } + } + + // 暂停动画 + void pause() { + _animationController?.stop(); + } + + // 停止动画 + void stop() { + _animationController?.stop(); + if (widget.clearsAfterStop) { + _animationController?.videoItem = null; + setState(() { + _currentResource = null; + }); + } + } + + // 重新加载当前动画 + void reload() { + if (_currentResource != null) { + if (widget.useCache) { + ATGiftVapSvgaManager().videoItemCache.remove(_currentResource); + } + + _loadAnimation( + resource: _currentResource!, + dynamicData: _currentDynamicData, + isFromQueue: false, + ); + } + } + + @override + void dispose() { + stop(); + _animationController?.dispose(); + // 从队列管理器中移除当前播放器 + _queueManager.clearCurrentPlayer(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + // 显示加载状态 + if (_isLoading) { + return Container( + width: widget.width, + height: widget.height, + child: Center(child: CircularProgressIndicator()), + ); + } + + // 显示错误状态 + if (_hasError) { + return Container( + width: widget.width, + height: widget.height, + child: Center(child: Icon(Icons.error, color: Colors.red)), + ); + } + + // 显示空状态(没有动画时) + if (_animationController?.videoItem == null) { + return Container( + width: widget.width, + height: widget.height, + child: Center( + child: Text('等待动画...', style: TextStyle(color: Colors.grey)), + ), + ); + } + + // 显示SVGA动画 + return Container( + width: widget.width, + height: widget.height, + child: SVGAImage( + key: _svgaKey, + _animationController!, + fit: BoxFit.contain, + clearsAfterStop: widget.clearsAfterStop, + ), + ); + } +} + +// 便捷使用的全局方法 +class RoomEntranceHelper { + // 添加入场动画到队列 + static Future addEntranceAnimation({ + required String resource, + Map? textReplacements, + Map? imageReplacements, + Map? imageAssetPaths, + VoidCallback? onCompleted, + Function(String)? onError, + }) async { + final dynamicData = DynamicData( + textReplacements: textReplacements, + imageReplacements: imageReplacements, + imageAssetPaths: imageAssetPaths, + ); + + final queueItem = AnimationQueueItem( + resource: resource, + dynamicData: { + 'textReplacements': dynamicData.textReplacements, + 'imageReplacements': dynamicData.imageReplacements, + 'imageAssetPaths': dynamicData.imageAssetPaths, + }, + onCompleted: onCompleted, + onError: onError, + ); + + return RoomEntranceQueueManager().addToQueue(queueItem); + } + + // 获取队列状态 + static Map getQueueStatus() { + return RoomEntranceQueueManager().queueStatus; + } + + // 清空队列 + static void clearQueue() { + RoomEntranceQueueManager().clearQueue(); + } + + // 设置队列更新回调 + static void setQueueUpdateCallback(void Function() callback) { + RoomEntranceQueueManager().onQueueUpdated = callback; + } + + // 设置队列最大长度 + static void setMaxQueueLength(int length) { + RoomEntranceQueueManager().maxQueueLength = length; + } + + // 设置自动播放延迟 + static void setAutoPlayDelay(int delayMs) { + RoomEntranceQueueManager().autoPlayDelay = delayMs; + } +} + +// 使用示例 +class RoomEntranceExample extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Column( + children: [ + // 创建播放器(不需要初始资源) + RoomEntranceWidget( + width: 200, + height: 200, + useQueue: true, + autoStartQueue: true, + ), + + SizedBox(height: 20), + + // 添加动画到队列的按钮 + ElevatedButton( + onPressed: () { + RoomEntranceHelper.addEntranceAnimation( + resource: 'assets/animations/entrance.svga', + textReplacements: {'username': '张三', 'level': 'VIP 10'}, + onCompleted: () { + print('入场动画播放完成'); + }, + ); + }, + child: Text('播放入场动画'), + ), + + // 添加另一个动画 + ElevatedButton( + onPressed: () { + RoomEntranceHelper.addEntranceAnimation( + resource: 'https://example.com/animations/vip_entrance.svga', + textReplacements: {'username': '李四', 'gift_name': '超级火箭'}, + ); + }, + child: Text('播放VIP入场动画'), + ), + + // 查看队列状态 + ElevatedButton( + onPressed: () { + final status = RoomEntranceHelper.getQueueStatus(); + print('队列状态: $status'); + }, + child: Text('查看队列状态'), + ), + ], + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/at_room_user_info_card.dart b/lib/chatvibe_ui/widgets/room/at_room_user_info_card.dart new file mode 100644 index 0000000..11dfa93 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/at_room_user_info_card.dart @@ -0,0 +1,1375 @@ +import 'dart:convert'; +import 'package:aslan/main.dart'; +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_msg_input.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_user_card_setting.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'; +import 'package:tencent_cloud_chat_sdk/enum/conversation_type.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_core/utilities/at_lk_dialog_util.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/user_count_guard_res.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_features/chat/at_chat_route.dart'; +import 'package:aslan/chatvibe_features/gift/gift_page.dart'; +import '../../../chatvibe_core/utilities/at_user_utils.dart'; +import '../../../chatvibe_data/models/enum/at_room_roles_type.dart'; +import '../../../chatvibe_data/models/enum/at_vip_type.dart'; +import '../../../chatvibe_domain/models/res/at_user_identity_res.dart'; + +class ATRoomUserInfoCard extends StatefulWidget { + String? userId; + + ATRoomUserInfoCard({super.key, this.userId}); + + @override + _ATRoomUserInfoCardState createState() => _ATRoomUserInfoCardState(); +} + +class _ATRoomUserInfoCardState extends State { + ChatVibeUserProfileManager? userProvider; + String roomId = ""; + ATUserIdentityRes? userIdentity; + List? userCountGuardResList; + + @override + void initState() { + super.initState(); + userProvider = Provider.of( + context, + listen: false, + ); + roomId = + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.id ?? + ""; + userProvider?.roomUserCard(roomId, widget.userId ?? ""); + ChatRoomRepository().userCountGuard(widget.userId ?? "").then((result) { + userCountGuardResList = result; + setState(() {}); + }); + AccountRepository().userIdentity(userId: widget.userId).then((v) { + userIdentity = v; + setState(() {}); + }); + } + + @override + void dispose() { + super.dispose(); + userProvider?.userCardInfo = null; + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: Consumer( + builder: (context, ref, child) { + if (ref.userCardInfo == null) { + return SizedBox( + height: ScreenUtil().screenHeight * 0.7, + child: Center( + child: Container( + decoration: BoxDecoration( + color: Colors.black26, + borderRadius: BorderRadius.circular(8), + ), + padding: EdgeInsets.all(2.w), + width: 62.w, + height: 62.w, + child: Image.asset("atu_images/general/at_icon_loading.webp"), + ), + ), + ); + } + return Container( + constraints: BoxConstraints( + maxHeight: ScreenUtil().screenHeight * 0.75, + ), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Stack( + alignment: Alignment.center, + children: [ + Container( + margin: EdgeInsets.only( + top: + ref.userCardInfo?.userProfile + ?.getDataCard() + ?.sourceUrl != + null + ? 45.w + : 55.w, + ), + child: Stack( + children: [ + Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: + ref.userCardInfo?.userProfile + ?.getDataCard() + ?.sourceUrl != + null + ? [ + Colors.transparent, + Colors.transparent, + ] + : [ + Color(0xffFFDAC6), + Color(0xffFFEEE4), + Color(0xffFFEEE4), + Color(0xffFFFFFF), + ], + ), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + ), + child: Stack( + children: [ + userProvider?.userCardInfo?.userProfile + ?.getVIP() != + null + ? Positioned.fill( + child: Image.asset( + getUserCardBg(), + fit: BoxFit.fill, + ), + ) + : Container(), + Column( + children: [ + SizedBox( + height: + ref.userCardInfo?.userProfile + ?.getDataCard() + ?.sourceUrl != + null + ? 150.w + : 60.w, + ), + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + msgRoleTag( + ref.userCardInfo?.roomRole ?? "", + width: 22.w, + height: 22.w, + ), + SizedBox(width: 2.w), + chatvibeNickNameText( + maxWidth: 115.w, + ref + .userCardInfo + ?.userProfile + ?.userNickname ?? + "", + fontSize: 14.sp, + textColor: Colors.black, + fontWeight: FontWeight.bold, + type: + ref.userCardInfo?.userProfile + ?.getVIP() + ?.name ?? + "", + needScroll: + (ref + .userCardInfo + ?.userProfile + ?.userNickname + ?.characters + .length ?? + 0) > + 10, + ), + ref.userCardInfo?.userProfile + ?.getVIP() != + null + ? getVIPBadge( + ref.userCardInfo?.userProfile + ?.getVIP() + ?.name, + width: 85.w, + height: 38.w, + ) + : Container(), + SizedBox(width: 5.w), + Row( + children: [ + getIdIcon( + ref + .userCardInfo + ?.userLevel + ?.wealthLevel ?? + 0, + width: 32.w, + height: 32.w, + textColor: Color(0xFF333333), + fontSize: 14.sp, + fontWeight: FontWeight.w600, + ), + chatvibeNickNameText( + maxWidth: 135.w, + ref.userCardInfo?.userProfile + ?.getID() ?? + "", + textColor: Color(0xFF333333), + fontSize: 14.sp, + fontWeight: FontWeight.w600, + type: + ref + .userCardInfo + ?.userProfile + ?.getVIP() + ?.name ?? + "", + needScroll: + (ref + .userCardInfo + ?.userProfile + ?.getID() + .characters + .length ?? + 0) > + 13, + ), + ], + ), + ], + ), + SizedBox(height: 6.w), + Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Container( + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(15.w), + color: Colors.transparent, + ), + padding: EdgeInsets.symmetric( + horizontal: 10.w, + vertical: 5.w, + ), + child: Row( + children: [ + netImage( + url: + Provider.of< + AppGeneralManager + >( + context, + listen: false, + ) + .getCountryByName( + ref + .userCardInfo + ?.userProfile + ?.countryName ?? + "", + ) + ?.nationalFlag ?? + "", + borderRadius: + BorderRadius.all( + Radius.circular(3.w), + ), + width: 19.w, + height: 14.w, + ), + SizedBox(width: 5.w), + Container( + constraints: BoxConstraints( + maxWidth: 110.w, + ), + child: text( + ref + .userCardInfo + ?.userProfile + ?.countryName ?? + "", + textColor: Colors.black, + fontSize: 12.sp, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + SizedBox(width: 5.w), + Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + ref + .userCardInfo + ?.userProfile + ?.userSex == + 0 + ? "atu_images/login/at_icon_sex_woman_bg.png" + : "atu_images/login/at_icon_sex_man_bg.png", + ), + ), + ), + padding: EdgeInsets.symmetric( + horizontal: 10.w, + vertical: 5.w, + ), + child: Row( + children: [ + xb( + ref + .userCardInfo + ?.userProfile + ?.userSex, + ), + SizedBox(width: 3.w), + text( + "${ref.userCardInfo?.userProfile?.age ?? 0}", + textColor: Colors.white, + fontSize: 12.sp, + fontWeight: FontWeight.bold, + ), + ], + ), + ), + SizedBox(width: 5.w), + getWealthLevel( + ref + .userCardInfo + ?.userLevel + ?.wealthLevel ?? + 0, + width: 58.w, + height: 30.w, + ), + SizedBox(width: 5.w), + getUserLevel( + ref + .userCardInfo + ?.userLevel + ?.charmLevel ?? + 0, + width: 58.w, + height: 30.w, + ), + ], + ), + SizedBox(height: 3.w), + ref.userCardInfo?.userProfile?.wearHonor + ?.where((item) { + return item.use ?? false; + }) + .isNotEmpty ?? + false + ? ConstrainedBox( + constraints: BoxConstraints( + maxHeight: 58.w, + ), + child: SingleChildScrollView( + child: Row( + children: [ + SizedBox(width: 15.w), + Expanded( + child: Wrap( + runSpacing: 2.w, + spacing: 4.w, + direction: + Axis.horizontal, + children: + ref + .userCardInfo! + .userProfile! + .wearHonor! + .where((item) { + return item + .use ?? + false; + }) + .map((item) { + return netImage( + url: + item.animationUrl ?? + "", + height: 28.w, + ); + }) + .toList(), + ), + ), + SizedBox(width: 5.w), + ], + ), + ), + ) + : Container(), + SizedBox(height: 8.w), + Container( + margin: EdgeInsetsDirectional.symmetric( + horizontal: 10.w, + ), + alignment: + AlignmentDirectional.centerStart, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 5.w), + ref + .userCardInfo + ?.userProfile + ?.wearBadge + ?.where((item) { + return item.use ?? + false; + }) + .isNotEmpty ?? + false + ? SizedBox( + height: 35.w, + child: ListView.separated( + scrollDirection: + Axis.horizontal, + shrinkWrap: true, + itemCount: + ref + .userCardInfo + ?.userProfile + ?.wearBadge + ?.where((item) { + return item + .use ?? + false; + }) + .length ?? + 0, + itemBuilder: ( + context, + index, + ) { + return netImage( + width: 35.w, + height: 35.w, + url: + ref + .userCardInfo + ?.userProfile + ?.wearBadge + ?.where(( + item, + ) { + return item + .use ?? + false; + }) + .toList()[index] + .animationUrl ?? + "", + ); + }, + separatorBuilder: ( + BuildContext context, + int index, + ) { + return SizedBox( + width: 5.w, + ); + }, + ), + ) + : Container(), + SizedBox(width: 5.w), + ], + ), + ), + ), + SizedBox(height: 5.w), + (ref + .userCardInfo + ?.userProfile + ?.cpList + ?.isNotEmpty ?? + false) + ? SizedBox( + height: 188.w, + child: CarouselSlider( + options: CarouselOptions( + height: 188.w, + autoPlay: + (ref + .userCardInfo + ?.userProfile + ?.cpList + ?.length ?? + 0) > + 1, + // 启用自动播放 + enlargeCenterPage: false, + // 居中放大当前页面 + aspectRatio: 1 / 1, + // 宽高比 + enableInfiniteScroll: true, + // 启用无限循环 + autoPlayAnimationDuration: + Duration(milliseconds: 800), + // 自动播放动画时长 + viewportFraction: 1, + // 视口分数 + onPageChanged: (index, reason) { + setState(() {}); + }, + ), + items: + ref + .userCardInfo + ?.userProfile + ?.cpList + ?.map((item) { + return GestureDetector( + child: _buildCpItem( + item, + ), + onTap: () {}, + ); + }) + .toList(), + ), + ) + : _buildEmptyCpItem(false), + Row( + spacing: 8.w, + children: [ + SizedBox(width: 3.w), + Expanded( + child: Image.asset( + fit: BoxFit.fill, + "atu_images/room/at_icon_user_card_${ref.userCardInfo?.userProfile?.getVIP() == null ? "vip0" : ATAccountHelper.getVipLevel(ref.userCardInfo?.userProfile?.getVIP()?.name ?? "").toLowerCase()}_bg.png", + ), + ), + Expanded( + child: Stack( + alignment: Alignment.center, + children: [ + userCountGuardResList != null && + userCountGuardResList! + .isNotEmpty + ? ATDebounceWidget( + child: Padding( + padding: + EdgeInsets.only( + top: 10.w, + ), + child: head( + url: + userCountGuardResList![0] + .userProfile + ?.userAvatar ?? + "", + width: 48.w, + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == userCountGuardResList![0].userProfile?.id}&tageId=${userCountGuardResList![0].userProfile?.id}", + ); + }, + ) + : Container(), + PositionedDirectional( + top: 33.w, + start: 23.w, + child: + userCountGuardResList != + null && + userCountGuardResList! + .length > + 1 + ? ATDebounceWidget( + child: head( + url: + userCountGuardResList![1] + .userProfile + ?.userAvatar ?? + "", + width: 40.w, + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == userCountGuardResList![1].userProfile?.id}&tageId=${userCountGuardResList![1].userProfile?.id}", + ); + }, + ) + : Container(), + ), + PositionedDirectional( + top: 33.w, + end: 23.w, + child: + userCountGuardResList != + null && + userCountGuardResList! + .length > + 2 + ? ATDebounceWidget( + child: head( + url: + userCountGuardResList![2] + .userProfile + ?.userAvatar ?? + "", + width: 40.w, + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == userCountGuardResList![2].userProfile?.id}&tageId=${userCountGuardResList![2].userProfile?.id}", + ); + }, + ) + : Container(), + ), + Image.asset( + "atu_images/room/at_icon_sup_fans_bg.png", + fit: BoxFit.fill, + ), + ], + ), + ), + SizedBox(width: 3.w), + ], + ), + SizedBox(height: 15.w), + Container( + margin: EdgeInsets.symmetric( + horizontal: 25.w, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + widget.userId != + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id + ? ATDebounceWidget( + child: Column( + children: [ + Image.asset( + "atu_images/room/at_icon_send_user_message.png", + width: 28.w, + height: 28.w, + ), + SizedBox(height: 5.w), + Container( + alignment: + Alignment.center, + width: 70.w, + child: text( + ATAppLocalizations.of( + context, + )!.sayHi2, + textColor: Color( + 0xff808080, + ), + fontWeight: + FontWeight.w600, + fontSize: 14.sp, + ), + ), + ], + ), + onTap: () async { + Navigator.of(context).pop(); + var conversation = + V2TimConversation( + type: + ConversationType + .V2TIM_C2C, + userID: widget.userId, + conversationID: '', + ); + var bool = + await Provider.of< + RtmProvider + >( + context, + listen: false, + ).startConversation( + conversation, + ); + if (!bool) return; + var json = jsonEncode( + conversation.toJson(), + ); + ATNavigatorUtils.push( + navigatorKey + .currentState! + .context, + "${ATChatRouter.chat}?conversation=${Uri.encodeComponent(json)}", + ); + }, + ) + : Container(), + widget.userId != + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id + ? ATDebounceWidget( + child: Selector< + ChatVibeUserProfileManager, + bool + >( + selector: + (context, provider) => + provider + .userCardInfo + ?.follow ?? + false, + shouldRebuild: + (prev, next) => + prev != next, + builder: ( + context, + follow, + _, + ) { + return Column( + children: [ + Image.asset( + follow + ? "atu_images/room/at_icon_user_un_follow.png" + : "atu_images/room/at_icon_user_follow.png", + width: 28.w, + height: 28.w, + ), + SizedBox(height: 5.w), + Container( + alignment: + Alignment + .center, + width: 70.w, + child: text( + // 使用 Flutter 内置 Text + follow + ? ATAppLocalizations.of( + context, + )!.followed + : ATAppLocalizations.of( + context, + )!.follow, // + fontSize: 14.sp, + textColor: Color( + 0xff808080, + ), + fontWeight: + FontWeight + .bold, + ), + ), + ], + ); + }, + ), + onTap: () { + // ✅ 使用正确的 Provider 访问方式 + final provider = Provider.of< + ChatVibeUserProfileManager + >(context, listen: false); + provider.followUser( + widget.userId ?? "", + ); + }, + ) + : Container(), + widget.userId != + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id + ? ATDebounceWidget( + child: Column( + children: [ + Image.asset( + "atu_images/room/at_icon_at_tag_user.png", + width: 28.w, + height: 28.w, + ), + SizedBox(height: 5.w), + Container( + alignment: + Alignment.center, + width: 70.w, + child: text( + ATAppLocalizations.of( + context, + )!.atTag, + textColor: Color( + 0xff808080, + ), + fontWeight: + FontWeight.w600, + fontSize: 14.sp, + ), + ), + ], + ), + onTap: () async { + if (ATRoomUtils.touristCanMsg( + context, + )) { + if ((ref + .userCardInfo + ?.userProfile + ?.userNickname ?? + "") + .isEmpty) { + return; + } + Navigator.of( + context, + ).pop(); + Navigator.push( + context, + PopRoute( + child: RoomMsgInput( + atTextContent: + "${ref.userCardInfo?.userProfile?.userNickname}", + ), + ), + ); + } + }, + ) + : Container(), + widget.userId != + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id + ? ATDebounceWidget( + child: Column( + children: [ + Image.asset( + "atu_images/room/at_icon_send_user_gift.png", + width: 28.w, + height: 28.w, + ), + SizedBox(height: 5.w), + Container( + alignment: + Alignment.center, + width: 70.w, + child: text( + ATAppLocalizations.of( + context, + )!.send, + textColor: Color( + 0xff808080, + ), + fontWeight: + FontWeight.w600, + fontSize: 14.sp, + ), + ), + ], + ), + onTap: () { + Navigator.of(context).pop(); + SmartDialog.show( + tag: "showGiftControl", + alignment: + Alignment + .bottomCenter, + maskColor: + Colors.transparent, + animationType: + SmartAnimationType + .fade, + clickMaskDismiss: true, + builder: (_) { + return GiftPage( + toUser: + ref + .userCardInfo + ?.userProfile, + ); + }, + ); + }, + ) + : Container(), + ], + ), + ), + SizedBox(height: 35.w), + ], + ), + ], + ), + ), + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id != + widget.userId + ? Positioned( + top: + ref.userCardInfo?.userProfile + ?.getDataCard() + ?.sourceUrl != + null + ? 75.w + : 23.w, + left: 15.w, + child: GestureDetector( + child: Image.asset( + "atu_images/room/at_icon_user_card_report.png", + width: 24.w, + height: 24.w, + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.report}?type=user&tageId=${widget.userId}", + replace: false, + ); + }, + ), + ) + : Container(), + ref.userCardInfo?.roomRole == + ATRoomRolesType.HOMEOWNER.name + ? Container() + : (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id != + widget.userId && + Provider.of( + context, + listen: false, + ).isFz() || + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id != + widget.userId && + Provider.of( + context, + listen: false, + ).isGL()) + ? Positioned( + top: + ref.userCardInfo?.userProfile + ?.getDataCard() + ?.sourceUrl != + null + ? 75.w + : 23.w, + left: 45.w, + child: GestureDetector( + child: Image.asset( + "atu_images/room/at_icon_room_user_card_setting.png", + width: 24.w, + height: 24.w, + ), + onTap: () { + if (userProvider?.userCardInfo != null) { + Navigator.of(context).pop(); + showBottomInBottomDialog( + context!, + RoomUserCardSetting( + roomId: roomId, + userCardInfo: + userProvider?.userCardInfo + ?.copyWith(), + ), + ); + } + }, + ), + ) + : Container(), + ], + ), + ), + Positioned( + top: + ref.userCardInfo?.userProfile + ?.getDataCard() + ?.sourceUrl != + null + ? 75.w + : 12.w, + child: SizedBox( + width: 100.w, + child: GestureDetector( + child: head( + url: + ref.userCardInfo?.userProfile?.userAvatar ?? + "", + width: 100.w, + border: Border.all(color: Colors.white, width: 2), + headdress: + ref.userCardInfo?.userProfile + ?.getHeaddress() + ?.sourceUrl, + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.person}?isMe=${AccountStorage().getCurrentUser()?.userProfile?.id == widget.userId}&tageId=${widget.userId}", + ); + }, + ), + ), + ), + ], + ), + ], + ), + ), + ); + }, + ), + ); + } + + String roleName(String role) { + if (role == ATRoomRolesType.HOMEOWNER.name) { + return ATAppLocalizations.of(context)!.owner; + } else if (role == ATRoomRolesType.ADMIN.name) { + return ATAppLocalizations.of(context)!.admin; + } else if (role == ATRoomRolesType.MEMBER.name) { + return ATAppLocalizations.of(context)!.member; + } + return ATAppLocalizations.of(context)!.guest; + } + + _buildEmptyCpItem(bool canAdd) { + return Container( + width: ScreenUtil().screenWidth, + margin: EdgeInsets.symmetric(horizontal: 6.w), + height: 200.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + canAdd + ? "atu_images/person/at_icon_no_cp_item_bg.png" + : "atu_images/person/at_icon_no_cp_item_bg4.png", + ), + fit: BoxFit.fill, + ), + ), + child: Container( + margin: EdgeInsets.only(bottom: 48.w, left: 12.w), + alignment: AlignmentDirectional.bottomCenter, + child: text( + ATAppLocalizations.of(context)!.noMatchedCP, + fontWeight: FontWeight.w600, + fontSize: 10.sp, + textColor: Colors.white, + ), + ), + ); + } + + Widget _buildCpItem(CPRes item) { + return Stack( + alignment: AlignmentDirectional.center, + children: [ + Container( + width: ScreenUtil().screenWidth, + margin: EdgeInsets.symmetric(horizontal: 10.w), + height: 188.w, + child: Stack( + children: [ + Image.asset( + "atu_images/person/at_icon_no_cp_item_bg2_lv_${(item.profileCardLevel ?? 0) <= 3 ? (item.profileCardLevel ?? 1) : 3}.png", + height: 188.w, + width: ScreenUtil().screenWidth, + fit: BoxFit.fill, + ), + Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: 30.w), + Stack( + alignment: AlignmentGeometry.bottomCenter, + children: [ + Transform.scale( + scale: 1.2, + child: netImage( + url: item.selfRing?.sourceUrl ?? "", + width: 65.w, + ), + ), + Transform.translate( + offset: Offset(0, 15.w), + child: Container( + width: 70.w, + height: 32.w, + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/person/at_icon_user_cp_level_value_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Directionality(textDirection: TextDirection.ltr, child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + margin: EdgeInsets.only(top: 5.w, left: 15.w), + child: text( + "LV.${item.cpLevel ?? 1}", + fontWeight: FontWeight.w600, + fontSize: 12.sp, + textColor: Colors.white, + ), + ), + ], + )), + ), + ), + ], + ), + SizedBox(height: 18.w), + Container( + alignment: AlignmentDirectional.bottomCenter, + child: text( + ATAppLocalizations.of( + context, + )!.timeSpentTogether(item.days ?? ""), + fontWeight: FontWeight.w600, + fontSize: 12.sp, + textColor: Color(0xFFFF79A1), + ), + ), + Container( + alignment: AlignmentDirectional.bottomCenter, + child: text( + ATAppLocalizations.of( + context, + )!.firstDay(item.firstDay ?? ""), + fontWeight: FontWeight.w600, + fontSize: 11.sp, + textColor: Colors.white, + ), + ), + ], + ), + ], + ), + ), + PositionedDirectional( + top: 50.w, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + GestureDetector( + child: SizedBox( + height: 50.w, + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + netImage( + url: item.meUserAvatar ?? "", + width: 48.w, + shape: BoxShape.circle, + defaultImg: + "atu_images/general/at_icon_avar_defalt.png", + ), + Image.asset( + "atu_images/person/at_icon_cp_head_ring.png", + ), + ], + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + replace: false, + "${MainRoute.person}?isMe=${UserManager().getCurrentUser()?.userProfile?.id == item.meUserId}&tageId=${item.meUserId}", + ); + }, + ), + Container( + alignment: Alignment.center, + width: 80.w, + height: 15.w, + child: + (item.meUserNickname?.length ?? 0) > 8 + ? Marquee( + text: item.meUserNickname ?? "", + style: TextStyle( + fontSize: 10.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.linear, + decelerationDuration: Duration(milliseconds: 500), + decelerationCurve: Curves.easeOut, + ) + : Text( + item.meUserNickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + SizedBox(width: 120.w), + Column( + children: [ + GestureDetector( + child: SizedBox( + height: 50.w, + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + netImage( + url: item.cpUserAvatar ?? "", + width: 48.w, + shape: BoxShape.circle, + defaultImg: + "atu_images/general/at_icon_avar_defalt.png", + ), + Image.asset( + "atu_images/person/at_icon_cp_head_ring.png", + ), + ], + ), + ), + onTap: () { + ATNavigatorUtils.push( + context, + replace: false, + "${MainRoute.person}?isMe=${UserManager().getCurrentUser()?.userProfile?.id == item.cpUserId}&tageId=${item.cpUserId}", + ); + }, + ), + Container( + alignment: Alignment.center, + width: 80.w, + height: 15.w, + child: + (item.cpUserNickname?.length ?? 0) > 8 + ? Marquee( + text: item.cpUserNickname ?? "", + style: TextStyle( + fontSize: 10.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.linear, + decelerationDuration: Duration(milliseconds: 500), + decelerationCurve: Curves.easeOut, + ) + : Text( + item.cpUserNickname ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + ], + ), + ), + ], + ); + } + + String getUserCardBg() { + String path = ""; + String? vipName = userProvider?.userCardInfo?.userProfile?.getVIP()?.name; + if (vipName == ATVIPType.VISCOUNT.name) { + path = "atu_images/vip/at_icon_room_user_card_vip1_bg.png"; + } else if (vipName == ATVIPType.EARL.name) { + path = "atu_images/vip/at_icon_room_user_card_vip2_bg.png"; + } else if (vipName == ATVIPType.MARQUIS.name) { + path = "atu_images/vip/at_icon_room_user_card_vip3_bg.png"; + } else if (vipName == ATVIPType.DUKE.name) { + path = "atu_images/vip/at_icon_room_user_card_vip4_bg.png"; + } else if (vipName == ATVIPType.KING.name) { + path = "atu_images/vip/at_icon_room_user_card_vip5_bg.png"; + } else if (vipName == ATVIPType.EMPEROR.name) { + path = "atu_images/vip/at_icon_room_user_card_vip6_bg.png"; + } + return path; + } + + String _cpVaFormat(double cpValue) { + int value = cpValue.toInt(); + if (value > 99999) { + return "${(value / 1000).toStringAsFixed(0)}k"; + } + return "$value"; + } +} diff --git a/lib/chatvibe_ui/widgets/room/edit_room_announcement_page.dart b/lib/chatvibe_ui/widgets/room/edit_room_announcement_page.dart new file mode 100644 index 0000000..2c9a262 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/edit_room_announcement_page.dart @@ -0,0 +1,115 @@ +import 'dart:convert'; +import 'dart:ui' as ui; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; + +class EditRoomAnnouncementPage extends StatefulWidget { + EditRoomAnnouncementPage(); + + @override + _EditRoomAnnouncementPageState createState() => + _EditRoomAnnouncementPageState(); +} + +class _EditRoomAnnouncementPageState extends State { + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(12.w)), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 15, sigmaY: 15), + child: Container( + width: ScreenUtil().screenWidth * 0.75, + padding: EdgeInsets.symmetric(horizontal: 10.w), + decoration: BoxDecoration(color: Colors.black38), + child: Stack( + alignment: Alignment.center, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 15.w), + text( + ATAppLocalizations.of(context)!.roomAnnouncement, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + SizedBox(height: 10.w), + Row( + children: [ + Expanded( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + padding: EdgeInsets.only( + top: 5.w, + bottom: 10.w, + left: width(12), + right: width(12), + ), + alignment: AlignmentDirectional.centerStart, + height: 110.w, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.all( + Radius.circular(height(8)), + ), + border: Border.all( + color: Color(0xffE6E6E6), + width: 0.5.w, + ), + ), + child: SingleChildScrollView( + child: Text( + Provider.of(context, listen: false) + .currenRoom + ?.roomProfile + ?.roomProfile + ?.roomDesc ?? + "", + style: TextStyle( + fontSize: sp(12), + color: Colors.white, + textBaseline: TextBaseline.alphabetic, + ), + ), + ), + ), + ), + ], + ), + SizedBox(height: 25.w), + ], + ), + PositionedDirectional( + top: 10.w, + end: 10.w, + child: GestureDetector( + child: Icon(Icons.close, color: Colors.white, size: 18.w), + onTap: () { + SmartDialog.dismiss(tag: "showEditNotiPage"); + }, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/effect/luck_gift_nomor_anim_widget.dart b/lib/chatvibe_ui/widgets/room/effect/luck_gift_nomor_anim_widget.dart new file mode 100644 index 0000000..5a0f4cb --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/effect/luck_gift_nomor_anim_widget.dart @@ -0,0 +1,162 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:flutter_svga/flutter_svga.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; + +import '../../../../chatvibe_domain/models/res/at_broad_cast_luck_gift_push.dart'; +import '../../headdress/headdress_widget.dart'; + +class LuckGiftNomorAnimWidget extends StatefulWidget { + @override + _LuckGiftNomorAnimWidgetState createState() => + _LuckGiftNomorAnimWidgetState(); +} + +class _LuckGiftNomorAnimWidgetState extends State { + static const double _headHideAtRatio = 0.55; + + int _lastGiftIdentity = -1; + SVGAAnimationController? _svgaController; + bool _headVisible = true; + + void _syncGiftCycle(ATBroadCastLuckGiftPush? gift) { + final int identity = gift == null ? -1 : identityHashCode(gift); + if (identity == _lastGiftIdentity) { + return; + } + _lastGiftIdentity = identity; + _headVisible = gift != null; + } + + void _bindController(SVGAAnimationController? controller) { + if (identical(_svgaController, controller)) { + return; + } + _svgaController?.removeListener(_handleFrameChanged); + _svgaController = controller; + _svgaController?.addListener(_handleFrameChanged); + } + + void _handleFrameChanged() { + final SVGAAnimationController? controller = _svgaController; + if (!mounted || !_headVisible || controller == null) { + return; + } + final int totalFrames = controller.frames; + if (totalFrames <= 0) { + return; + } + final int hideAtFrame = (totalFrames * _headHideAtRatio).floor(); + if (controller.currentFrame >= hideAtFrame) { + setState(() { + _headVisible = false; + }); + } + } + + @override + void dispose() { + _svgaController?.removeListener(_handleFrameChanged); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final ATBroadCastLuckGiftPush? currentPlayingLuckGift = context + .select( + (p) => p.currentPlayingLuckGift, + ); + _syncGiftCycle(currentPlayingLuckGift); + if (currentPlayingLuckGift == null) { + return const IgnorePointer(child: SizedBox.shrink()); + } + + return IgnorePointer( + child: Container( + height: 380.w, + margin: EdgeInsets.only(top: 10.w), + child: Stack( + children: [ + RepaintBoundary( + child: SVGAHeadwearWidget( + key: ValueKey(_lastGiftIdentity), + resource: "atu_images/room/at_icon_luck_gift_nomore.svga", + loops: 1, + callback: _bindController, + onCompleted: () { + if (!mounted) { + return; + } + context.read().finishCurrentLuckGiftBackCoins(); + }, + ), + ), + if (_headVisible) + RepaintBoundary( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 125.w), + netImage( + url: currentPlayingLuckGift.data?.userAvatar ?? "", + shape: BoxShape.circle, + height: 105.w, + width: 105.w, + ), + SizedBox(height: 16.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset("atu_images/general/at_icon_jb.png", height: 35.w), + SizedBox(width: 3.w), + Image.asset( + "atu_images/general/at_icon_game_numxx.png", + height: 20.w, + ), + Transform.translate( + offset: Offset(-5, 0), + child: buildNumForGame( + (currentPlayingLuckGift.data?.awardAmount ?? 0) > + 9999 + ? "${((currentPlayingLuckGift.data?.awardAmount ?? 0) / 1000).toStringAsFixed(0)}k" + : "${(currentPlayingLuckGift.data?.awardAmount ?? 0)}", + size: 22.w, + ), + ), + ], + ), + SizedBox(height: 12.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(width: 15.w), + buildNumForGame( + "${currentPlayingLuckGift.data?.multiple ?? 0}", + size: 22.w, + ), + Transform.translate( + offset: Offset(-3, 3), + child: Image.asset( + ATGlobalConfig.lang == "ar" + ? "atu_images/room/at_icon_times_text_ar.png" + : "atu_images/room/at_icon_times_text_en.png", + height: 12.w, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/effect/vapp_svga_layer_widget.dart b/lib/chatvibe_ui/widgets/room/effect/vapp_svga_layer_widget.dart new file mode 100644 index 0000000..531df7d --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/effect/vapp_svga_layer_widget.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svga/flutter_svga.dart'; +import 'package:tancent_vap/utils/constant.dart'; +import 'package:tancent_vap/widgets/vap_view.dart'; +import 'package:aslan/chatvibe_core/utilities/at_gift_vap_svga_manager.dart'; + +class VapPlusSvgaPlayer extends StatefulWidget { + final String tag; + + const VapPlusSvgaPlayer({super.key, required this.tag}); + + @override + State createState() => _VapPlusSvgaPlayerState(); +} + +class _VapPlusSvgaPlayerState extends State + with TickerProviderStateMixin { + late SVGAAnimationController _svgaController; + + @override + void initState() { + super.initState(); + _svgaController = SVGAAnimationController(vsync: this); + if (widget.tag == "room_gift") { + ATGiftVapSvgaManager().bindRoomGiftSvgaController(_svgaController); + } + } + + @override + void dispose() { + ATGiftVapSvgaManager().dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Positioned.fill( + child: IgnorePointer( + // VapView可以通过外层包Container(),设置宽高来限制弹出视频的宽高 + // VapView can set the width and height through the outer package Container() to limit the width and height of the pop-up video + child: VapView( + scaleType: ScaleType.centerCrop, + onViewCreated: (controller) { + if (widget.tag == "room_gift") { + ATGiftVapSvgaManager().bindRoomGiftVapController(controller); + // VapManager().play("https://res.cloudinary.com/dkmchpua1/video/upload/v1737624783/vcg9co6yyfqsadgety1n.mp4"); + } + }, + ), + ), + ), + Positioned.fill(child: SVGAImage(_svgaController, fit: BoxFit.fill)), + ], + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/emoji/emoji_tab_page.dart b/lib/chatvibe_ui/widgets/room/emoji/emoji_tab_page.dart new file mode 100644 index 0000000..c8d9546 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/emoji/emoji_tab_page.dart @@ -0,0 +1,271 @@ +import 'dart:ui'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_core/constants/at_room_msg_type.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/main.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_msg_item.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +import '../../../../chatvibe_data/models/enum/at_vip_type.dart'; +import '../../../../chatvibe_domain/models/res/at_room_emoji_res.dart'; +import '../../../components/at_compontent.dart'; +import '../../../components/at_tts.dart'; + +class EmojiTabPage extends StatefulWidget { + String type; + + EmojiTabPage(this.type, {super.key}); + + @override + _EmojiTabPageState createState() => _EmojiTabPageState(); +} + +class _EmojiTabPageState extends State { + PageController _giftPageController = PageController(); + int _index = 0; + bool isHasPermission = false; + + @override + void initState() { + super.initState(); + if (widget.type == "VIP1-3") { + if (AccountStorage().getVIP()?.name == ATVIPType.VISCOUNT.name || + AccountStorage().getVIP()?.name == ATVIPType.EARL.name || + AccountStorage().getVIP()?.name == ATVIPType.MARQUIS.name || + AccountStorage().getVIP()?.name == ATVIPType.DUKE.name || + AccountStorage().getVIP()?.name == ATVIPType.KING.name || + AccountStorage().getVIP()?.name == ATVIPType.EMPEROR.name) { + isHasPermission = true; + } + } else if (widget.type == "VIP4-6") { + if (AccountStorage().getVIP()?.name == ATVIPType.DUKE.name || + AccountStorage().getVIP()?.name == ATVIPType.KING.name || + AccountStorage().getVIP()?.name == ATVIPType.EMPEROR.name) { + isHasPermission = true; + } + } else { + isHasPermission = true; + } + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + return (ref.emojiByTab[widget.type] ?? []).isEmpty + ? mainEmpty( + textColor: Colors.white54, + msg: ATAppLocalizations.of(context)!.noData, + ) + : Stack( + children: [ + Column( + children: [ + Expanded( + child: PageView.builder( + controller: _giftPageController, + onPageChanged: (i) { + setState(() { + _index = i; + }); + }, + itemBuilder: (c, i) { + var current = + (ref.emojiByTab[widget.type]!.length - (i * 8)); + int size = + current > 8 + ? 8 + : current % 8 == 0 + ? 8 + : current % 8; + return GridView.builder( + shrinkWrap: true, + padding: EdgeInsets.symmetric(horizontal: 12.w), + physics: NeverScrollableScrollPhysics(), + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, + childAspectRatio: 0.9, + mainAxisSpacing: 5.w, + crossAxisSpacing: 33.w, + ), + itemCount: size, + itemBuilder: (BuildContext context, int index) { + return _emojiItem( + ref.emojiByTab[widget.type]![(i * 8) + index], + ref, + ); + }, + ); + }, + itemCount: + (ref.emojiByTab[widget.type]!.length / 8).ceil(), + ), + ), + _indicator(ref), + SizedBox(height: 10.w), + ], + ), + isHasPermission ? Container() : Stack( + alignment: Alignment.bottomCenter, + children: [ + BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + child: Container(color: Colors.black38), + ), + Image.asset( + widget.type == "VIP1-3" + ? "atu_images/room/at_icon_emoji_vip1_3_bg.png" + : "atu_images/room/at_icon_emoji_vip4_6_bg.png", + width: ScreenUtil().screenWidth, + height: double.infinity, + fit: BoxFit.fill, + ), + PositionedDirectional( + bottom: 15.w, + child: Container( + width: ScreenUtil().screenWidth * 0.8, + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: + ATAppLocalizations.of( + context, + )!.exclusiveEmojiWillBeReleasedAfterBecoming, + style: TextStyle( + color: Colors.white, + fontSize: 12.sp, + fontWeight: FontWeight.w400, + ), + ), + TextSpan( + text: " ${widget.type}", + style: TextStyle( + color: Colors.white, + fontSize: 12.sp, + fontWeight: FontWeight.w400, + ), + ), + TextSpan( + text: ".", + style: TextStyle( + color: Colors.white, + fontSize: 12.sp, + fontWeight: FontWeight.w400, + ), + ), + TextSpan( + text: ATAppLocalizations.of(context)!.goToUpgrade, + style: TextStyle( + decoration: TextDecoration.underline, + color: Colors.white, + fontSize: 12.sp, + decorationColor: Colors.white, + decorationStyle: TextDecorationStyle.solid, + decorationThickness: 1.5, + fontWeight: FontWeight.w500, + ), + recognizer: + TapGestureRecognizer() + ..onTap = () async { + SmartDialog.dismiss( + tag: "showRoomEmojiPage", + ); + ATNavigatorUtils.push( + navigatorKey.currentState!.context, + MainRoute.vipList, + replace: false, + ); + }, + ), + ], + ), + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + ], + ); + }, + ); + } + + int beginTime = 0; + + Widget _emojiItem(Emojis emoji, AppGeneralManager ref) { + return GestureDetector( + child: netImage( + url: emoji.coverUrl ?? "", + width: 35.w, + height: 35.w, + borderRadius: BorderRadius.all(Radius.circular(5.w)), + ), + onTap: () { + if (Provider.of(context, listen: false).isOnMai()) { + int endTime = DateTime.now().millisecondsSinceEpoch; + if (endTime - beginTime > 5000) { + beginTime = endTime; + Msg msg = Msg( + groupId: + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.roomAccount, + msg: emoji.coverUrl, + number: Provider.of( + context, + listen: false, + ).userOnMaiInIndex( + AccountStorage().getCurrentUser()?.userProfile?.id ?? "", + ), + type: ATRoomMsgType.emoticons, + ); + Provider.of( + context, + listen: false, + ).sendMsg(msg, addLocal: false); + Provider.of(context, listen: false).starPlayEmoji(msg); + SmartDialog.dismiss(tag: "showRoomEmojiPage"); + } else { + ATTts.show(ATAppLocalizations.of(context)!.operationsAreTooFrequent); + } + } else { + ATTts.show(ATAppLocalizations.of(context)!.pleaseGetOnTheMicFirst); + } + }, + ); + } + + _indicator(AppGeneralManager ref) { + var size = (ref.emojiByTab[widget.type]!.length) / 8; + var list = []; + for (int i = 0; i < size; i++) { + list.add( + Container( + width: 6.5.w, + height: 6.5.w, + margin: EdgeInsets.symmetric(horizontal: 4.w), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _index == i ? ChatVibeTheme.primaryColor : Color(0xffDADADA), + ), + ), + ); + } + return Row(mainAxisAlignment: MainAxisAlignment.center, children: list); + } +} diff --git a/lib/chatvibe_ui/widgets/room/emoji/room_emoji_page.dart b/lib/chatvibe_ui/widgets/room/emoji/room_emoji_page.dart new file mode 100644 index 0000000..d822c07 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/emoji/room_emoji_page.dart @@ -0,0 +1,110 @@ +import 'dart:ui' as ui; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/emoji/emoji_tab_page.dart'; + +class RoomEmojiPage extends StatefulWidget { + @override + _RoomEmojiPageState createState() => _RoomEmojiPageState(); +} + +class _RoomEmojiPageState extends State + with SingleTickerProviderStateMixin { + TabController? _tabController; + final List _pages = []; + final List _tabs = []; + + @override + void initState() { + super.initState(); + Provider.of(context, listen: false).emojiAll(); + } + + @override + void dispose() { + super.dispose(); + _tabController?.dispose(); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Consumer( + builder: (context, ref, child) { + if (ref.emojiByTab.isNotEmpty) { + _tabs.clear(); + _pages.clear(); + for (var value in ref.emojiByTab.keys) { + _tabs.add(Tab(text: value)); + _pages.add(EmojiTabPage(value)); + } + _tabController ??= TabController( + length: _pages.length, + vsync: this, + ); + } + return ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.0), + topRight: Radius.circular(12.0), + ), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 15, sigmaY: 15), + child: Material( + color: Colors.black54, + child: SizedBox( + height: 235.w, + child: + ref.emojiByTab.isNotEmpty + ? Column( + children: [ + SizedBox(height: 12.w), + Row( + children: [ + SizedBox(width: 5.w), + TabBar( + tabAlignment: TabAlignment.start, + labelPadding: EdgeInsets.symmetric( + horizontal: 12.w, + ), + labelColor: Colors.white, + isScrollable: true, + indicator: BoxDecoration(), + unselectedLabelColor: Colors.white54, + labelStyle: TextStyle( + fontSize: 15.sp, + fontWeight: FontWeight.bold, + ), + unselectedLabelStyle: TextStyle( + fontSize: 13.sp, + ), + indicatorColor: Colors.transparent, + dividerColor: Colors.transparent, + controller: _tabController, + tabs: _tabs, + ), + ], + ), + Expanded( + child: TabBarView( + controller: _tabController, + children: _pages, + ), + ), + ], + ) + : mainEmpty(), + ), + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/empty_mai_select.dart b/lib/chatvibe_ui/widgets/room/empty_mai_select.dart new file mode 100644 index 0000000..182f559 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/empty_mai_select.dart @@ -0,0 +1,325 @@ +import 'dart:ui' as ui; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_domain/models/res/login_res.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_msg_item.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/at_room_user_info_card.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_core/constants/at_room_msg_type.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/utilities/at_lk_dialog_util.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/mic_res.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; + +import '../../../chatvibe_data/models/enum/at_room_roles_type.dart'; + +class EmptyMaiSelect extends StatelessWidget { + final num index; + final ChatVibeUserProfile? clickUser; + + const EmptyMaiSelect({Key? key, required this.index, this.clickUser}) + : super(key: key); + + @override + Widget build(BuildContext context) { + RtcProvider provider = Provider.of(context, listen: false); + List list = []; + list.add(SizedBox(height: 10.w)); + if (index == -1) { + if (provider.isFz() || provider.isGL()) { + if (clickUser?.id != + AccountStorage().getCurrentUser()?.userProfile?.id) { + list.add( + _item( + ATAppLocalizations.of(context)!.inviteToTheMicrophone, + "atu_images/room/at_icon_mic_go_up.png", + context, + () { + Navigator.of(context).pop(); + Provider.of(context, listen: false).sendMsg( + Msg( + groupId: + provider + .currenRoom + ?.roomProfile + ?.roomProfile + ?.roomAccount ?? + "", + msg: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname, + role: AccountStorage().getCurrentUser()?.userProfile?.id, + type: ATRoomMsgType.bsm, + toUser: clickUser, + ), + addLocal: false, + ); + }, + ), + ); + } + } + } else { + MicRes? userWheat = provider.roomWheatMap[index]; + if (userWheat?.user?.id == + AccountStorage().getCurrentUser()?.userProfile?.id) { + if (provider.isFz()) { + ///点击的是自己 + if (userWheat?.micMute ?? false) { + list.add( + _item( + ATAppLocalizations.of(context)!.openTheMic, + "atu_images/room/at_icon_mic_open.png", + context, + () { + Navigator.of(context).pop(); + provider.jieJinMai(index); + }, + ), + ); + } else { + list.add( + _item( + ATAppLocalizations.of(context)!.muteTheMic, + "atu_images/room/at_icon_mic_mute.png", + context, + () { + Navigator.of(context).pop(); + provider.jinMai(index); + }, + ), + ); + } + } + + list.add( + _item( + ATAppLocalizations.of(context)!.leavelTheMic, + "atu_images/room/at_icon_mic_leavel.png", + context, + () { + Navigator.of(context).pop(); + provider.xiaMai(index); + }, + ), + ); + } else { + if (provider.isFz() || provider.isGL()) { + ///操作的人是房主,并且座位没人 + if (clickUser == null) { + if (!(userWheat?.micLock ?? false)) { + list.add( + _item( + ATAppLocalizations.of(context)!.takeTheMic, + "atu_images/room/at_icon_mic_go_up.png", + context, + () { + Navigator.of(context).pop(); + provider.shangMai(index); + }, + ), + ); + } + if ((userWheat?.micLock ?? false)) { + list.add( + _item( + ATAppLocalizations.of(context)!.unlockTheMic, + "atu_images/room/at_icon_mic_unlock.png", + context, + () { + Navigator.of(context).pop(); + provider.jieFeng(index); + }, + ), + ); + } else { + list.add( + _item( + ATAppLocalizations.of(context)!.lockTheMic, + "atu_images/room/at_icon_mic_lock.png", + context, + () { + Navigator.of(context).pop(); + provider.fengMai(index); + }, + ), + ); + } + } + + if (clickUser?.roles == ATRoomRolesType.HOMEOWNER.name || + clickUser?.roles == ATRoomRolesType.ADMIN.name) { + if (provider.isFz()) { + if (userWheat?.micMute ?? false) { + list.add( + _item( + ATAppLocalizations.of(context)!.openTheMic, + "atu_images/room/at_icon_mic_open.png", + context, + () { + Navigator.of(context).pop(); + provider.jieJinMai(index); + }, + ), + ); + } else { + list.add( + _item( + ATAppLocalizations.of(context)!.muteTheMic, + "atu_images/room/at_icon_mic_mute.png", + context, + () { + Navigator.of(context).pop(); + provider.jinMai(index); + }, + ), + ); + } + } + } else { + if (userWheat?.micMute ?? false) { + list.add( + _item( + ATAppLocalizations.of(context)!.openTheMic, + "atu_images/room/at_icon_mic_open.png", + context, + () { + Navigator.of(context).pop(); + provider.jieJinMai(index); + }, + ), + ); + } else { + list.add( + _item( + ATAppLocalizations.of(context)!.muteTheMic, + "atu_images/room/at_icon_mic_mute.png", + context, + () { + Navigator.of(context).pop(); + provider.jinMai(index); + }, + ), + ); + } + } + } else { + if (userWheat?.user == null) { + if (!(userWheat?.micLock ?? false)) { + list.add( + _item( + ATAppLocalizations.of(context)!.takeTheMic, + "atu_images/room/at_icon_mic_go_up.png", + context, + () { + Navigator.of(context).pop(); + provider.shangMai(index); + }, + ), + ); + } else { + // list.add( + // _item('Leavel the mic ', "atu_images/room/at_icon_mic_leavel.png", () { + // Navigator.of(context).pop(); + // provider.xiaMai(index); + // }), + // ); + } + } + } + } + } + if (clickUser != null) { + list.add( + _item( + ATAppLocalizations.of(context)!.openUserProfleCard, + "atu_images/room/at_icon_open_card.png", + context, + () { + Navigator.of(context).pop(); + showBottomInBottomDialog( + context!, + ATRoomUserInfoCard(userId: clickUser?.id), + ); + }, + ), + ); + } + list.add( + _item(ATAppLocalizations.of(context)!.cancel, null, context, () { + Navigator.of(context).pop(); + }), + ); + list.add(SizedBox(height: 10.w)); + return SafeArea( + child: Container( + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + ), + child: Column(children: list), + ), + ); + } + + Widget _item( + String msg, + String? icon, + BuildContext context, + Function onClick, + ) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + onClick(); + }, + child: Column( + children: [ + Container( + decoration: + msg == ATAppLocalizations.of(context)!.cancel + ? BoxDecoration( + color: Color(0xffF2F2F2), + borderRadius: BorderRadius.all(Radius.circular(32.w)), + ) + : null, + width: ScreenUtil().screenWidth * 0.7, + padding: EdgeInsets.symmetric(vertical: 10.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + alignment: Alignment.center, + child: Text( + msg, + style: TextStyle( + fontSize: sp(16), + color: + msg == ATAppLocalizations.of(context)!.cancel + ? Color(0xffB1B1B1) + : Colors.black, + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/exit_min_room_page.dart b/lib/chatvibe_ui/widgets/room/exit_min_room_page.dart new file mode 100644 index 0000000..3cd9881 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/exit_min_room_page.dart @@ -0,0 +1,161 @@ +import 'dart:convert'; + +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_res.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_ui/components/at_float_ichart.dart'; +import 'package:aslan/chatvibe_core/routes/at_routes.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_user_identity_res.dart'; +import 'package:aslan/chatvibe_managers/gift_animation_manager.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +class ExitMinRoomPage extends StatefulWidget { + bool isFz; + String roomId; + + ExitMinRoomPage(this.isFz, this.roomId); + + @override + _ExitMinRoomPageState createState() => _ExitMinRoomPageState(); +} + +class _ExitMinRoomPageState extends State { + ATUserIdentityRes? userIdentity; + bool isLoading = true; + + @override + void initState() { + super.initState(); + AccountRepository() + .userIdentity( + userId: + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.userId, + ) + .then((v) { + userIdentity = v; + isLoading = false; + setState(() {}); + }) + .catchError((e) { + isLoading = false; + setState(() {}); + }); + } + + @override + Widget build(BuildContext context) { + return Stack( + alignment: Alignment.center, + children: [ + ATDebounceWidget( + child: Container(color: Colors.transparent), + onTap: () { + Navigator.of(context).pop(); + }, + ), + Column( + spacing: 40.w, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + !widget.isFz + ? ATDebounceWidget( + child: Column( + children: [ + Image.asset( + "atu_images/room/at_icon_room_report.png", + height: 48.w, + width: 48.w, + ), + SizedBox(height: 8.w), + text( + ATAppLocalizations.of(context)!.report, + textColor: ChatVibeTheme.primaryLight, + fontSize: 13.sp, + fontWeight: FontWeight.w500, + ), + ], + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.report}?type=room&tageId=${widget.roomId}", + replace: false, + ); + }, + ) + : Container(), + ATDebounceWidget( + child: Column( + children: [ + Image.asset( + "atu_images/room/at_icon_min_room.png", + width: 48.w, + height: 48.w, + ), + SizedBox(height: 8.w), + text( + ATAppLocalizations.of(context)!.mInimize, + fontSize: 15.sp, + textColor: ChatVibeTheme.primaryLight, + fontWeight: FontWeight.w500, + ), + ], + ), + onTap: () { + ATFloatIchart().show(); + ATNavigatorUtils.popUntil( + context, + ModalRoute.withName(ATRoutes.home), + ); + Provider.of( + context, + listen: false, + ).disposeAnim(); + }, + ), + ATDebounceWidget( + child: Column( + children: [ + Image.asset( + "atu_images/room/at_icon_exit_room.png", + width: 48.w, + height: 48.w, + ), + SizedBox(height: 8.w), + text( + ATAppLocalizations.of(context)!.exit, + fontSize: 15.sp, + textColor: ChatVibeTheme.primaryLight, + fontWeight: FontWeight.w500, + ), + ], + ), + onTap: () { + ATNavigatorUtils.goBack(context); + Provider.of( + context, + listen: false, + ).extRoom(false); + }, + ), + ], + ), + ], + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/explore_banner_view.dart b/lib/chatvibe_ui/widgets/room/explore_banner_view.dart new file mode 100644 index 0000000..1e9c01a --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/explore_banner_view.dart @@ -0,0 +1,94 @@ +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_core/utilities/at_banner_utils.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; + +class ExploreBannerView extends StatefulWidget { + @override + _ExploreBannerViewState createState() => _ExploreBannerViewState(); +} + +class _ExploreBannerViewState extends State { + int _currentIndex = 0; + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + return _banner(ref); + }, + ); + } + + _banner(AppGeneralManager ref) { + if (ref.exploreBanners.isEmpty) { + return Container(); + } + return Container( + child: Stack( + alignment: AlignmentDirectional.bottomCenter, + children: [ + CarouselSlider( + options: CarouselOptions( + height: 118.w, + autoPlay: true, + // 启用自动播放 + enlargeCenterPage: false, + // 居中放大当前页面 + enableInfiniteScroll: true, + // 启用无限循环 + autoPlayAnimationDuration: Duration(milliseconds: 800), + // 自动播放动画时长 + viewportFraction: 1, + // 视口分数 + onPageChanged: (index, reason) { + _currentIndex = index; + setState(() {}); + }, + ), + items: + ref.exploreBanners.map((item) { + return GestureDetector( + child: netImage( + url: item.cover ?? "", + height: 118.w, + fit: BoxFit.fill, + borderRadius: BorderRadius.circular(12.w), + ), + onTap: () { + print('ads:${item.toJson()}'); + ATBannerUtils.openBanner(item,context); + }, + ); + }).toList(), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: + ref.exploreBanners.asMap().entries.map((entry) { + return Container( + width: 6.0, + height: 6.0, + margin: EdgeInsets.symmetric( + vertical: 8.0, + horizontal: 4.0, + ), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: + _currentIndex == entry.key + ? Colors.blue + : Colors.white, + ), + ); + }).toList(), + ), + ], + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/floating/floating_cp_gift_screen_widget.dart b/lib/chatvibe_ui/widgets/room/floating/floating_cp_gift_screen_widget.dart new file mode 100644 index 0000000..44142b0 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/floating/floating_cp_gift_screen_widget.dart @@ -0,0 +1,330 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_debouncer/flutter_debouncer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:marquee/marquee.dart'; + +import '../../../../app_localizations.dart'; +import '../../../../chatvibe_core/utilities/at_room_utils.dart'; +import '../../../../chatvibe_data/models/message/at_floating_message.dart'; +import '../../../../main.dart'; +import '../../../components/at_compontent.dart'; +import '../../../components/text/at_text.dart'; + +class FloatingCPGiftScreenWidget extends StatefulWidget { + final ATFloatingMessage message; + final VoidCallback onAnimationCompleted; // 动画完成回调 + + const FloatingCPGiftScreenWidget({ + Key? key, + required this.message, + required this.onAnimationCompleted, + }) : super(key: key); + + @override + _FloatingCPGiftScreenWidgetState createState() => + _FloatingCPGiftScreenWidgetState(); +} + +class _FloatingCPGiftScreenWidgetState extends State + with TickerProviderStateMixin { + late AnimationController _controller; + late Animation _offsetAnimation; + late AnimationController _swipeController; // 新增:滑动动画控制器 + late Animation _swipeAnimation; // 新增:滑动动画 + Debouncer debouncer = Debouncer(); + + bool _isSwipeAnimating = false; // 标记是否正在执行滑动动画 + + @override + void initState() { + super.initState(); + + // 主动画控制器 + _controller = AnimationController( + duration: const Duration(seconds: 5), + vsync: this, + ); + + // 滑动动画控制器 + _swipeController = AnimationController( + duration: const Duration(milliseconds: 500), // 滑动动画500ms + vsync: this, + ); + + // 监听滑动动画完成 + _swipeController.addStatusListener((status) { + if (status == AnimationStatus.completed) { + widget.onAnimationCompleted(); + } + }); + + // 主动画:从右向左移动 + _offsetAnimation = Tween( + begin: const Offset(1.0, 0.0), + end: const Offset(-1.0, 0.0), + ).animate(CurvedAnimation(parent: _controller, curve: Curves.linear)); + + // 滑动动画:从当前位置快速向左滑出 + _swipeAnimation = Tween( + begin: Offset.zero, // 从当前位置开始 + end: const Offset(-1.5, 0.0), // 到屏幕左侧外 + ).animate(CurvedAnimation(parent: _swipeController, curve: Curves.easeIn)); + + // 监听主动画完成 + _controller.addStatusListener((status) { + if (status == AnimationStatus.completed && !_isSwipeAnimating) { + widget.onAnimationCompleted(); + } + }); + + // 延迟启动动画,确保组件已经完全构建 + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _controller.forward(); + } + }); + } + + // 处理向左滑动 + void _handleSwipeLeft() { + if (_isSwipeAnimating) return; + + setState(() { + _isSwipeAnimating = true; + }); + + // 停止主动画 + _controller.stop(); + + // 启动滑动动画 + _swipeController.reset(); + _swipeController.forward(); + } + + @override + void dispose() { + _controller.dispose(); + _swipeController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return _buildGiftAnimation(); + } + + ///礼物总价钱达到10000 + Widget _buildGiftAnimation() { + return GestureDetector( + onTap: () { + debouncer.debounce( + duration: Duration(milliseconds: 400), + onDebounce: () { + if (widget.message.roomId != null && + widget.message.roomId!.isNotEmpty) { + ATRoomUtils.goRoom( + widget.message.roomId!, + navigatorKey.currentState!.context, + fromFloting: true, + ); + } + }, + ); + }, + onHorizontalDragEnd: (details) { + final double velocity = details.primaryVelocity ?? 0; + if (velocity < 0) { + _handleSwipeLeft(); + } + }, + child: SlideTransition( + position: _isSwipeAnimating ? _swipeAnimation : _offsetAnimation, + child: Directionality( + textDirection: TextDirection.ltr, + child: Container( + alignment: Alignment.center, + height: 100.w, + width: 365.w, + child: Stack( + alignment: AlignmentDirectional.centerStart, + children: [ + if (widget.message.userAvatarUrl?.isNotEmpty ?? false) + PositionedDirectional( + start: 10.w, + child: Container( + margin: EdgeInsets.only(top: 7.w), + child: head( + url: widget.message.userAvatarUrl ?? "", + width: 65.w, + ), + ), + ), + Image.asset( + "atu_images/room/at_icon_cp_gift_floating_bg_lv${(widget.message.rocketLevel ?? 1) <= 3 ? (widget.message.rocketLevel ?? 1) : 3}.png", + fit: BoxFit.fill, + ), + Container( + height: 50.w, + width: 180.w, + margin: EdgeInsetsDirectional.only(start: 90.w), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: 8.w), + Row( + mainAxisSize: MainAxisSize.min, // 宽度由内容决定 + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 3.w), + Container( + constraints: BoxConstraints( + maxWidth: 100.w, + maxHeight: 18.w, + ), + child: + (widget + .message + .userName + ?.characters + .length ?? + 0) > + 10 + ? Marquee( + text: widget.message.userName ?? "", + style: TextStyle( + fontSize: 13.sp, + color: Colors.orange, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration( + seconds: 1, + ), + accelerationDuration: Duration( + seconds: 1, + ), + accelerationCurve: Curves.linear, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + widget.message.userName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13.sp, + color: Colors.orange, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + Row( + children: [ + text( + ATAppLocalizations.of(context)!.sendTo, + textColor: Colors.white, + fontSize: 13.sp, + fontWeight: FontWeight.bold, + lineHeight: 1.1, + ), + SizedBox(width: 3.w), + Container( + constraints: BoxConstraints( + maxWidth: 58.w, + maxHeight: 18.w, + ), + child: + (widget + .message + .toUserName + ?.characters + .length ?? + 0) > + 8 + ? Marquee( + text: + widget.message.toUserName ?? + "", + style: TextStyle( + fontSize: 13.sp, + color: Colors.orange, + fontWeight: FontWeight.bold, + decoration: + TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration( + seconds: 1, + ), + accelerationDuration: Duration( + seconds: 1, + ), + accelerationCurve: + Curves.linear, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: + Curves.easeOut, + ) + : Text( + widget.message.toUserName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13.sp, + color: Colors.orange, + fontWeight: FontWeight.bold, + decoration: + TextDecoration.none, + ), + ), + ), + ], + ), + ], + ), + ), + netImage( + url: widget.message.giftUrl ?? "", + width: 32.w, + ), + SizedBox(width: 3.w), + Image.asset( + "atu_images/room/at_icon_x.png", + width: + (widget.message.number ?? 0) > 99 ? 15.w : 18.w, + ), + buildNum( + "${widget.message.number}", + size: + (widget.message.number ?? 0) > 99 ? 15.w : 18.w, + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/floating/floating_game_screen_widget.dart b/lib/chatvibe_ui/widgets/room/floating/floating_game_screen_widget.dart new file mode 100644 index 0000000..2d94e08 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/floating/floating_game_screen_widget.dart @@ -0,0 +1,412 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_debouncer/flutter_debouncer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/main.dart'; +import 'package:marquee/marquee.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_data/models/message/at_floating_message.dart'; + +class FloatingGameScreenWidget extends StatefulWidget { + final ATFloatingMessage message; + final VoidCallback onAnimationCompleted; // 动画完成回调 + + const FloatingGameScreenWidget({ + Key? key, + required this.message, + required this.onAnimationCompleted, + }) : super(key: key); + + @override + _FloatingGameScreenWidgetState createState() => + _FloatingGameScreenWidgetState(); +} + +class _FloatingGameScreenWidgetState extends State + with TickerProviderStateMixin { + Debouncer debouncer = Debouncer(); + late AnimationController _controller; + late Animation _firstAnimation; // 第一段:从右到中 + late Animation _secondAnimation; // 第二段:从中到左 + late AnimationController _swipeController; // 新增:滑动动画控制器 + late Animation _swipeAnimation; // 新增:滑动动画 + + bool _firstPhaseCompleted = false; + bool _isSwipeAnimating = false; // 标记是否正在执行滑动动画 + + ///游戏赢的金币按数量分等级,不同等级特效背景也不一样 + int level = 1; + + @override + void initState() { + super.initState(); + if ((widget.message.coins ?? 0) > 14999 && + (widget.message.coins ?? 0) < 50000) { + level = 1; + } else if ((widget.message.coins ?? 0) > 49999 && + (widget.message.coins ?? 0) < 100000) { + level = 2; + } else if ((widget.message.coins ?? 0) > 99999 && + (widget.message.coins ?? 0) < 300000) { + level = 3; + } else if ((widget.message.coins ?? 0) > 299999 && + (widget.message.coins ?? 0) < 500000) { + level = 4; + } else if ((widget.message.coins ?? 0) > 499999) { + level = 5; + } + + // 主动画控制器 + _controller = AnimationController( + duration: const Duration(milliseconds: 1500), // 每段动画1.5秒 + vsync: this, + ); + + // 滑动动画控制器 + _swipeController = AnimationController( + duration: const Duration(milliseconds: 550), // 滑动动画500ms + vsync: this, + ); + + // 监听滑动动画完成 + _swipeController.addStatusListener((status) { + if (status == AnimationStatus.completed) { + widget.onAnimationCompleted(); + } + }); + + // 第一段动画:从右侧飘到中间 + _firstAnimation = Tween( + begin: const Offset(1.5, 0.0), // 从屏幕右侧外开始 + end: Offset.zero, // 到屏幕中心 + ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn)); + + // 第二段动画:从中间飘到左侧 + _secondAnimation = Tween( + begin: Offset.zero, // 从屏幕中心开始 + end: const Offset(-1.5, 0.0), // 到屏幕左侧外 + ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn)); + + // 滑动动画:从当前位置滑动到左侧屏幕外 + _swipeAnimation = Tween( + begin: Offset.zero, // 从当前位置开始 + end: const Offset(-1.5, 0.0), // 到屏幕左侧外 + ).animate(CurvedAnimation( + parent: _swipeController, + curve: Curves.easeIn, + )); + + // 延迟启动动画 + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _startAnimationSequence(); + } + }); + } + + void _startAnimationSequence() async { + // 第一阶段:从右到中 + _controller.reset(); + await _controller.forward(); + + // 停顿2秒(你代码中是3秒,这里保持原样) + await Future.delayed(const Duration(seconds: 3)); + + if (!mounted) return; + + // 第二阶段:从中到左 + setState(() { + _firstPhaseCompleted = true; + }); + _controller.reset(); + await _controller.forward(); + + if (mounted && !_isSwipeAnimating) { + widget.onAnimationCompleted(); + } + } + + // 处理向左滑动 + void _handleSwipeLeft() { + if (_isSwipeAnimating) return; + + setState(() { + _isSwipeAnimating = true; + }); + + // 停止主动画 + _controller.stop(); + + // 启动滑动动画 + _swipeController.reset(); + _swipeController.forward(); + } + + String _coinsVaFormat(num coins) { + int value = coins.toInt(); + if (value > 99999) { + return "${(value / 1000).toStringAsFixed(0)}k"; + } + return "$value"; + } + + @override + void dispose() { + _controller.dispose(); + _swipeController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return _buildGameAnimation(); + } + + Widget _buildGameAnimation() { + // 如果正在执行滑动动画,使用滑动动画 + if (_isSwipeAnimating) { + return SlideTransition( + position: _swipeAnimation, + child: _buildContent(), + ); + } + + // 否则使用原来的动画 + return SlideTransition( + position: _firstPhaseCompleted ? _secondAnimation : _firstAnimation, + child: _buildContent(), + ); + } + + Widget _buildContent() { + return GestureDetector( + onTap: () { + debouncer.debounce( + duration: Duration(milliseconds: 350), + onDebounce: () { + if (widget.message.roomId != null && + widget.message.roomId!.isNotEmpty) { + ATRoomUtils.goRoom( + widget.message.roomId!, + navigatorKey.currentState!.context, + fromFloting: true, + ); + } + }, + ); + }, + onHorizontalDragEnd: (details) { + // 获取当前的文本方向 + final textDirection = Directionality.of(context); + + // 定义一个根据方向转换速度符号的辅助函数 + double effectiveVelocity(double velocity) { + // 在RTL模式下,反转速度的正负号 + return textDirection == TextDirection.rtl + ? -velocity + : velocity; + } + + double velocity = effectiveVelocity(details.primaryVelocity ?? 0); + if (velocity < 0) { + ///向左滑动,把当前视图向左平移出去 + _handleSwipeLeft(); + } + }, + child: Align( + alignment: Alignment.topCenter, + child: Directionality( + textDirection: TextDirection.ltr, + child: Container( + alignment: Alignment.topCenter, + height: 120.w, + width: 325.w, + margin: EdgeInsets.only(top: 10.w), + child: Stack( + alignment: + level == 5 + ? AlignmentDirectional.topCenter + : AlignmentDirectional.center, + children: [ + if (widget.message.userAvatarUrl?.isNotEmpty ?? false) + _buildHead(), + Image.asset("atu_images/index/at_icon_gamebroad_lv${level}_bg.webp"), + Row( + mainAxisSize: MainAxisSize.min, // 宽度由内容决定 + children: [ + SizedBox(width: level < 2 ? 82.w : 95.w), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + height: + level < 2 + ? 22.w + : (level == 5 ? 31.w : 25.w), + ), + Row( + children: [ + Container( + constraints: BoxConstraints( + maxWidth: 55.w, + maxHeight: 18.w, + ), + child: + (widget.message.userName?.length ?? + 0) > + 8 + ? Marquee( + text: + widget.message.userName ?? + "", + style: TextStyle( + fontSize: 12.sp, + color: Colors.orange, + fontWeight: FontWeight.bold, + decoration: + TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration( + seconds: 1, + ), + accelerationDuration: Duration( + seconds: 1, + ), + accelerationCurve: + Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: + Curves.easeOut, + ) + : Text( + widget.message.userName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12.sp, + color: Colors.orange, + fontWeight: FontWeight.bold, + decoration: + TextDecoration.none, + ), + ), + ), + text( + "ID:${widget.message.userId} ", + textColor: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 12.sp, + ), + ], + ), + Transform.translate( + offset: Offset( + 0, + level < 2 ? -5 : (level == 5 ? -5 : 0), + ), + child: Row( + children: [ + text( + ATAppLocalizations.of(context)!.get, + textColor: Colors.orange, + fontWeight: FontWeight.w900, + fontSize: 16.sp, + fontStyle: FontStyle.italic + ), + buildNumForGame( + _coinsVaFormat( + widget.message.coins ?? 0, + ), + size: + (widget.message.coins ?? 0) > 99999 + ? 26.w + : 20.w, + ), + ], + ), + ), + ], + ), + ], + ), + ), + Container( + margin: EdgeInsets.only(top: level < 2 ? 10.w : 21.w), + child: netImage( + url: widget.message.giftUrl ?? "", + width: 28.w, + borderRadius: BorderRadius.all(Radius.circular(3.0)), + ), + ), + SizedBox(width: level < 2 ? 63.w : 65.w), + ], + ), + ], + ), + ), + ), + ), + ); + } + + Widget _buildHead() { + if (level == 1) { + return PositionedDirectional( + start: 43.w, + child: Container( + margin: EdgeInsets.only(top: 12.w), + child: head(url: widget.message.userAvatarUrl ?? "", width: 40.w), + ), + ); + } else if (level == 2) { + return PositionedDirectional( + start: 10.w, + child: Container( + margin: EdgeInsets.only(top: 25.w), + child: head(url: widget.message.userAvatarUrl ?? "", width: 70.w), + ), + ); + } else if (level == 3) { + return PositionedDirectional( + start: 10.w, + child: Container( + margin: EdgeInsets.only(top: 25.w), + child: head(url: widget.message.userAvatarUrl ?? "", width: 70.w), + ), + ); + } else if (level == 4) { + return PositionedDirectional( + start: 10.w, + child: Container( + margin: EdgeInsets.only(top: 25.w), + child: head(url: widget.message.userAvatarUrl ?? "", width: 70.w), + ), + ); + } else if (level == 5) { + return PositionedDirectional( + start: 22.w, + top: 20.w, + child: Container( + child: head(url: widget.message.userAvatarUrl ?? "", width: 48.w), + ), + ); + } + return Container(); + } +} \ No newline at end of file diff --git a/lib/chatvibe_ui/widgets/room/floating/floating_gift_screen_widget.dart b/lib/chatvibe_ui/widgets/room/floating/floating_gift_screen_widget.dart new file mode 100644 index 0000000..35db32f --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/floating/floating_gift_screen_widget.dart @@ -0,0 +1,297 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_debouncer/flutter_debouncer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/main.dart'; +import 'package:marquee/marquee.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_data/models/message/at_floating_message.dart'; + +class FloatingGiftScreenWidget extends StatefulWidget { + final ATFloatingMessage message; + final VoidCallback onAnimationCompleted; // 动画完成回调 + + const FloatingGiftScreenWidget({ + Key? key, + required this.message, + required this.onAnimationCompleted, + }) : super(key: key); + + @override + _FloatingGiftScreenWidgetState createState() => + _FloatingGiftScreenWidgetState(); +} + +class _FloatingGiftScreenWidgetState extends State + with TickerProviderStateMixin { + late AnimationController _controller; + late Animation _offsetAnimation; + late AnimationController _swipeController; // 新增:滑动动画控制器 + late Animation _swipeAnimation; // 新增:滑动动画 + Debouncer debouncer = Debouncer(); + + bool _isSwipeAnimating = false; // 标记是否正在执行滑动动画 + + @override + void initState() { + super.initState(); + + // 主动画控制器 + _controller = AnimationController( + duration: const Duration(seconds: 5), + vsync: this, + ); + + // 滑动动画控制器 + _swipeController = AnimationController( + duration: const Duration(milliseconds: 550), // 滑动动画500ms + vsync: this, + ); + + // 监听滑动动画完成 + _swipeController.addStatusListener((status) { + if (status == AnimationStatus.completed) { + widget.onAnimationCompleted(); + } + }); + + // 主动画:从右向左移动 + _offsetAnimation = Tween( + begin: const Offset(1.0, 0.0), + end: const Offset(-1.0, 0.0), + ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut)); + + // 滑动动画:从当前位置快速向左滑出 + _swipeAnimation = Tween( + begin: Offset.zero, // 从当前位置开始 + end: const Offset(-1.5, 0.0), // 到屏幕左侧外 + ).animate(CurvedAnimation(parent: _swipeController, curve: Curves.easeIn)); + + // 监听主动画完成 + _controller.addStatusListener((status) { + if (status == AnimationStatus.completed && !_isSwipeAnimating) { + widget.onAnimationCompleted(); + } + }); + + // 延迟启动动画,确保组件已经完全构建 + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _controller.forward(); + } + }); + } + + // 处理向左滑动 + void _handleSwipeLeft() { + if (_isSwipeAnimating) return; + + setState(() { + _isSwipeAnimating = true; + }); + + // 停止主动画 + _controller.stop(); + + // 启动滑动动画 + _swipeController.reset(); + _swipeController.forward(); + } + + @override + void dispose() { + _controller.dispose(); + _swipeController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return _buildGiftAnimation(); + } + + ///礼物总价钱达到10000 + Widget _buildGiftAnimation() { + return GestureDetector( + onTap: () { + debouncer.debounce( + duration: Duration(milliseconds: 350), + onDebounce: () { + if (widget.message.roomId != null && + widget.message.roomId!.isNotEmpty) { + ATRoomUtils.goRoom( + widget.message.roomId!, + navigatorKey.currentState!.context, + fromFloting: true + ); + } + }, + ); + }, + onHorizontalDragEnd: (details) { + // 获取当前的文本方向 + final textDirection = Directionality.of(context); + + // 定义一个根据方向转换速度符号的辅助函数 + double effectiveVelocity(double velocity) { + // 在RTL模式下,反转速度的正负号 + return textDirection == TextDirection.rtl ? -velocity : velocity; + } + + double velocity = effectiveVelocity(details.primaryVelocity ?? 0); + if (velocity < 0) { + // 向左滑动,把当前视图向左平移出去 + _handleSwipeLeft(); + } + }, + child: SlideTransition( + position: _isSwipeAnimating ? _swipeAnimation : _offsetAnimation, + child: Container( + alignment: Alignment.center, + height: 54.w, + width: 320.w, + margin: EdgeInsets.only(top: 20.w), + padding: EdgeInsets.symmetric(horizontal: 10.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/room/at_icon_gift_float_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, // 宽度由内容决定 + children: [ + // 可以根据消息模型丰富内容,例如显示头像 + if (widget.message.userAvatarUrl?.isNotEmpty ?? false) + head(url: widget.message.userAvatarUrl ?? "", width: 42.w), + SizedBox(width: 4.w), + Expanded( + child: Row( + children: [ + Container( + constraints: BoxConstraints( + maxWidth: 48.w, + maxHeight: 21.w, + ), + child: + (widget.message.userName?.length ?? 0) > 6 + ? Marquee( + text: widget.message.userName ?? "", + style: TextStyle( + fontSize: 13.sp, + color: Colors.orange, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + widget.message.userName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13.sp, + color: Colors.orange, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + text( + ATAppLocalizations.of(context)!.sendTo, + textColor: Colors.white, + fontSize: 13.sp, + fontWeight: FontWeight.bold, + ), + Container( + constraints: BoxConstraints( + maxWidth: 48.w, + maxHeight: 21.w, + ), + child: + (widget.message.toUserName?.length ?? 0) > 6 + ? Marquee( + text: widget.message.toUserName ?? "", + style: TextStyle( + fontSize: 13.sp, + color: Colors.orange, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + widget.message.toUserName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13.sp, + color: Colors.orange, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + ), + netImage(url: widget.message.giftUrl ?? "", width: 32.w), + SizedBox(width: 3.w), + Image.asset( + "atu_images/room/at_icon_x.png", + width: (widget.message.number ?? 0) > 99 ? 15.w : 18.w, + ), + buildNum( + "${widget.message.number}", + size: (widget.message.number ?? 0) > 99 ? 15.w : 18.w, + ), + ], + ), + ), + ), + ); + } + + String _getLuckGiftFloatBg() { + if ((widget.message.coins ?? 0) > 4999 && + (widget.message.coins ?? 0) < 10000) { + return "atu_images/room/at_icon_luck_gift_float_bg1.png"; + } else if ((widget.message.coins ?? 0) > 9999 && + (widget.message.coins ?? 0) < 75000) { + return "atu_images/room/at_icon_luck_gift_float_bg2.png"; + } else if ((widget.message.coins ?? 0) > 74999 && + (widget.message.coins ?? 0) < 150000) { + return "atu_images/room/at_icon_luck_gift_float_bg3.png"; + } else if ((widget.message.coins ?? 0) > 149999 && + (widget.message.coins ?? 0) < 1500000) { + return "atu_images/room/at_icon_luck_gift_float_bg4.png"; + } else if ((widget.message.coins ?? 0) > 1499999) { + return "atu_images/room/at_icon_luck_gift_float_bg5.png"; + } + + return ""; + } +} diff --git a/lib/chatvibe_ui/widgets/room/floating/floating_luck_gift_screen_widget.dart b/lib/chatvibe_ui/widgets/room/floating/floating_luck_gift_screen_widget.dart new file mode 100644 index 0000000..48d8c23 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/floating/floating_luck_gift_screen_widget.dart @@ -0,0 +1,468 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_debouncer/flutter_debouncer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/main.dart'; +import 'package:marquee/marquee.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_data/models/message/at_floating_message.dart'; + +class FloatingLuckGiftScreenWidget extends StatefulWidget { + final ATFloatingMessage message; + final VoidCallback onAnimationCompleted; // 动画完成回调 + + const FloatingLuckGiftScreenWidget({ + Key? key, + required this.message, + required this.onAnimationCompleted, + }) : super(key: key); + + @override + _FloatingLuckGiftScreenWidgetState createState() => + _FloatingLuckGiftScreenWidgetState(); +} + +class _FloatingLuckGiftScreenWidgetState + extends State + with TickerProviderStateMixin { + late AnimationController _controller; + late Animation _offsetAnimation; + late AnimationController _swipeController; // 新增:滑动动画控制器 + late Animation _swipeAnimation; // 新增:滑动动画 + Debouncer debouncer = Debouncer(); + + bool _isSwipeAnimating = false; // 标记是否正在执行滑动动画 + + @override + void initState() { + super.initState(); + + // 主动画控制器 + _controller = AnimationController( + duration: const Duration(seconds: 5), + vsync: this, + ); + + // 滑动动画控制器 + _swipeController = AnimationController( + duration: const Duration(milliseconds: 550), // 滑动动画500ms + vsync: this, + ); + + // 监听滑动动画完成 + _swipeController.addStatusListener((status) { + if (status == AnimationStatus.completed) { + widget.onAnimationCompleted(); + } + }); + + // 主动画:从右向左移动 + _offsetAnimation = Tween( + begin: const Offset(1.0, 0.0), + end: const Offset(-1.0, 0.0), + ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut)); + + // 滑动动画:从当前位置快速向左滑出 + _swipeAnimation = Tween( + begin: Offset.zero, // 从当前位置开始 + end: const Offset(-1.5, 0.0), // 到屏幕左侧外 + ).animate(CurvedAnimation(parent: _swipeController, curve: Curves.easeIn)); + + // 监听主动画完成 + _controller.addStatusListener((status) { + if (status == AnimationStatus.completed && !_isSwipeAnimating) { + widget.onAnimationCompleted(); + } + }); + + // 延迟启动动画,确保组件已经完全构建 + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _controller.forward(); + } + }); + } + + // 处理向左滑动 + void _handleSwipeLeft() { + if (_isSwipeAnimating) return; + + setState(() { + _isSwipeAnimating = true; + }); + + // 停止主动画 + _controller.stop(); + + // 启动滑动动画 + _swipeController.reset(); + _swipeController.forward(); + } + + @override + void dispose() { + _controller.dispose(); + _swipeController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return _buildLuckGiftAnimation(); + } + + ///幸运礼物飘屏 + Widget _buildLuckGiftAnimation() { + return GestureDetector( + onTap: () { + debouncer.debounce( + duration: Duration(milliseconds: 350), + onDebounce: () { + if (widget.message.roomId != null && + widget.message.roomId!.isNotEmpty) { + ATRoomUtils.goRoom( + widget.message.roomId!, + navigatorKey.currentState!.context, + fromFloting: true + ); + } + }, + ); + }, + onHorizontalDragEnd: (details) { + // 获取当前的文本方向 + final textDirection = Directionality.of(context); + + // 定义一个根据方向转换速度符号的辅助函数 + double effectiveVelocity(double velocity) { + // 在RTL模式下,反转速度的正负号 + return textDirection == TextDirection.rtl ? -velocity : velocity; + } + + double velocity = effectiveVelocity(details.primaryVelocity ?? 0); + if (velocity < 0) { + // 向左滑动,把当前视图向左平移出去 + _handleSwipeLeft(); + } + }, + child: SlideTransition( + position: _isSwipeAnimating ? _swipeAnimation : _offsetAnimation, + child: Container( + alignment: Alignment.center, + height: 83.w, + width: 350.w, + child: Stack( + children: [ + Transform.flip( + flipX: ATGlobalConfig.lang == "ar" ? true : false, // 水平翻转 + flipY: false, // 垂直翻转设为 false + child: Image.asset( + "atu_images/room/at_icon_luck_gift_float_n_bg.png", + fit: BoxFit.fill, + ), + ), + Row( + mainAxisSize: MainAxisSize.min, // 宽度由内容决定 + children: [ + Container( + margin: EdgeInsetsDirectional.only(top: 13.w, start: 3.w), + child: netImage( + url: widget.message.userAvatarUrl ?? "", + width: 50.w, + shape: BoxShape.circle, + ), + ), + SizedBox(width: 4.w), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 10.w), + child: Row( + children: [ + Container( + constraints: BoxConstraints( + maxWidth: 85.w, + maxHeight: 20.w, + ), + child: + (widget.message.userName?.length ?? 0) > 6 + ? Marquee( + text: "${widget.message.userName} ", + style: TextStyle( + fontSize: 13.sp, + color: Color(0xffFEF129), + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + letterSpacing: 0.1, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration( + seconds: 1, + ), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + "${widget.message.userName} ", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13.sp, + color: Color(0xffFEF129), + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + SizedBox(width: 3.w), + Expanded( + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: ATAppLocalizations.of(context)!.get, + style: TextStyle( + fontSize: 12.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + letterSpacing: 0.1, + ), + ), + TextSpan( + text: + " ${widget.message.coins} ${ATAppLocalizations.of(context)!.coins3} ", + style: TextStyle( + fontSize: 12.sp, + color: Color(0xffFEF129), + fontWeight: FontWeight.bold, + letterSpacing: 0.1, + ), + ), + TextSpan( + text: + ATAppLocalizations.of( + context, + )!.fromLuckyGifts, + style: TextStyle( + fontSize: 12.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + letterSpacing: 0.1, + ), + ), + ], + ), + textAlign: TextAlign.start, + strutStyle: StrutStyle( + height: 1.1, // 行高倍数 + fontWeight: FontWeight.bold, + forceStrutHeight: true, // 强制应用行高 + ), + ), + ), + SizedBox(width: 6.w), + Container( + width: 65.w, + alignment: Alignment.center, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 2.w), + buildNumForGame( + "${widget.message.multiple ?? 0}", + size: 25.w, + ), + SizedBox(height: 3.w), + ATGlobalConfig.lang == "ar" + ? Image.asset( + "atu_images/room/at_icon_times_text_ar.png", + height: 12.w, + ) + : Image.asset( + "atu_images/room/at_icon_times_text_en.png", + height: 12.w, + ), + ], + ), + ), + SizedBox(width: 2.w), + ], + ), + ), + ), + ], + ), + ], + ), + ), + ), + ); + } + + ///礼物总价钱达到10000 + Widget _buildGiftAnimation() { + return GestureDetector( + onTap: () { + debouncer.debounce( + duration: Duration(milliseconds: 350), + onDebounce: () { + if (widget.message.roomId != null && + widget.message.roomId!.isNotEmpty) { + ATRoomUtils.goRoom( + widget.message.roomId!, + navigatorKey.currentState!.context, + fromFloting: true + ); + } + }, + ); + }, + onHorizontalDragEnd: (details) { + // 获取当前的文本方向 + final textDirection = Directionality.of(context); + + // 定义一个根据方向转换速度符号的辅助函数 + double effectiveVelocity(double velocity) { + // 在RTL模式下,反转速度的正负号 + return textDirection == TextDirection.rtl ? -velocity : velocity; + } + + double velocity = effectiveVelocity(details.primaryVelocity ?? 0); + if (velocity < 0) { + // 向左滑动,把当前视图向左平移出去 + _handleSwipeLeft(); + } + }, + child: SlideTransition( + position: _isSwipeAnimating ? _swipeAnimation : _offsetAnimation, + child: Container( + alignment: Alignment.center, + height: 50.w, + width: 290.w, + margin: EdgeInsets.only(top: 20.w), + padding: EdgeInsets.symmetric(horizontal: 15.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/room/at_icon_gift_float_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, // 宽度由内容决定 + children: [ + // 可以根据消息模型丰富内容,例如显示头像 + if (widget.message.userAvatarUrl?.isNotEmpty ?? false) + head(url: widget.message.userAvatarUrl ?? "", width: 42.w), + SizedBox(width: 4.w), + Expanded( + child: Row( + children: [ + Container( + constraints: BoxConstraints( + maxWidth: 48.w, + maxHeight: 21.w, + ), + child: + (widget.message.userName?.length ?? 0) > 6 + ? Marquee( + text: widget.message.userName ?? "", + style: TextStyle( + fontSize: 13.sp, + color: Colors.orange, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + widget.message.userName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13.sp, + color: Colors.orange, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + text( + ATAppLocalizations.of(context)!.sendTo, + textColor: Colors.white, + fontSize: 13.sp, + fontWeight: FontWeight.bold, + ), + Container( + constraints: BoxConstraints( + maxWidth: 48.w, + maxHeight: 21.w, + ), + child: + (widget.message.toUserName?.length ?? 0) > 6 + ? Marquee( + text: widget.message.toUserName ?? "", + style: TextStyle( + fontSize: 13.sp, + color: Colors.orange, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + widget.message.toUserName ?? "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13.sp, + color: Colors.orange, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + ), + netImage(url: widget.message.giftUrl ?? "", width: 32.w), + SizedBox(width: 3.w), + Image.asset("atu_images/room/at_icon_x.png", width: 18.w), + buildNum("${widget.message.number}", size: 18.w), + ], + ), + ), + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/floating/floating_room_redenvelope_screen_widget.dart b/lib/chatvibe_ui/widgets/room/floating/floating_room_redenvelope_screen_widget.dart new file mode 100644 index 0000000..bdf43a4 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/floating/floating_room_redenvelope_screen_widget.dart @@ -0,0 +1,289 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_debouncer/flutter_debouncer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/main.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_data/models/message/at_floating_message.dart'; +import 'package:marquee/marquee.dart'; + +class FloatingRoomRedenvelopeScreenWidget extends StatefulWidget { + final ATFloatingMessage message; + final VoidCallback onAnimationCompleted; // 动画完成回调 + + const FloatingRoomRedenvelopeScreenWidget({ + Key? key, + required this.message, + required this.onAnimationCompleted, + }) : super(key: key); + + @override + _FloatingRoomRedenvelopeScreenWidgetState createState() => + _FloatingRoomRedenvelopeScreenWidgetState(); +} + +class _FloatingRoomRedenvelopeScreenWidgetState + extends State + with TickerProviderStateMixin { + Debouncer debouncer = Debouncer(); + late AnimationController _controller; + late Animation _firstAnimation; // 第一段:从右到中 + late Animation _secondAnimation; // 第二段:从中到左 + late AnimationController _swipeController; // 新增:滑动动画控制器 + late Animation _swipeAnimation; // 新增:滑动动画 + + bool _firstPhaseCompleted = false; + bool _isSwipeAnimating = false; // 标记是否正在执行滑动动画 + + @override + void initState() { + super.initState(); + // 主动画控制器 + _controller = AnimationController( + duration: const Duration(milliseconds: 1500), // 每段动画1.5秒 + vsync: this, + ); + + // 滑动动画控制器 + _swipeController = AnimationController( + duration: const Duration(milliseconds: 550), // 滑动动画500ms + vsync: this, + ); + + // 监听滑动动画完成 + _swipeController.addStatusListener((status) { + if (status == AnimationStatus.completed) { + widget.onAnimationCompleted(); + } + }); + + // 第一段动画:从右侧飘到中间 + _firstAnimation = Tween( + begin: const Offset(1.5, 0.0), // 从屏幕右侧外开始 + end: Offset.zero, // 到屏幕中心 + ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn)); + + // 第二段动画:从中间飘到左侧 + _secondAnimation = Tween( + begin: Offset.zero, // 从屏幕中心开始 + end: const Offset(-1.5, 0.0), // 到屏幕左侧外 + ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn)); + + // 滑动动画:从当前位置滑动到左侧屏幕外 + _swipeAnimation = Tween( + begin: Offset.zero, // 从当前位置开始 + end: const Offset(-1.5, 0.0), // 到屏幕左侧外 + ).animate(CurvedAnimation(parent: _swipeController, curve: Curves.easeIn)); + + // 延迟启动动画 + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _startAnimationSequence(); + } + }); + } + + void _startAnimationSequence() async { + // 第一阶段:从右到中 + _controller.reset(); + await _controller.forward(); + + // 停顿2秒(你代码中是3秒,这里保持原样) + await Future.delayed(const Duration(seconds: 3)); + + if (!mounted) return; + + // 第二阶段:从中到左 + setState(() { + _firstPhaseCompleted = true; + }); + _controller.reset(); + await _controller.forward(); + + if (mounted && !_isSwipeAnimating) { + widget.onAnimationCompleted(); + } + } + + @override + void dispose() { + _controller.dispose(); + _swipeController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + // 如果正在执行滑动动画,使用滑动动画 + if (_isSwipeAnimating) { + return SlideTransition( + position: _swipeAnimation, + child: _buildRedenvelopeAnimation(), + ); + } + + // 否则使用原来的动画 + return SlideTransition( + position: _firstPhaseCompleted ? _secondAnimation : _firstAnimation, + child: _buildRedenvelopeAnimation(), + ); + } + + Widget _buildRedenvelopeAnimation() { + // 使用Positioned.fill确保飘屏覆盖全屏宽度,并定位在特定高度(如顶部100像素) + return GestureDetector( + onTap: () { + debouncer.debounce( + duration: Duration(milliseconds: 350), + onDebounce: () { + if (widget.message.roomId != null && + widget.message.roomId!.isNotEmpty) { + ATRoomUtils.goRoom( + widget.message.roomId!, + navigatorKey.currentState!.context, + needOpenRedenvelope: true, + redPackId: widget.message.toUserId ?? "", + fromFloting: true, + ); + } + }, + ); + }, + onHorizontalDragEnd: (details) { + // 获取当前的文本方向 + final textDirection = Directionality.of(context); + + // 定义一个根据方向转换速度符号的辅助函数 + double effectiveVelocity(double velocity) { + // 在RTL模式下,反转速度的正负号 + return textDirection == TextDirection.rtl ? -velocity : velocity; + } + + double velocity = effectiveVelocity(details.primaryVelocity ?? 0); + if (velocity < 0) { + ///向左滑动,把当前视图向左平移出去 + _handleSwipeLeft(); + } + }, + child: Align( + alignment: Alignment.topCenter, + child: Directionality( + textDirection: TextDirection.ltr, + child: Container( + alignment: Alignment.topCenter, + height: 120.w, + width: 325.w, + margin: EdgeInsets.only(top: 10.w), + child: Stack( + alignment: AlignmentDirectional.centerStart, + children: [ + PositionedDirectional( + start: 3.w, + top: 22.w, + child: netImage( + url: widget.message.userAvatarUrl ?? "", + width: 46.w, + shape: BoxShape.circle, + ), + ), + Image.asset( + "atu_images/room/at_icon_redenvelope_broad_bg.webp", + ), + PositionedDirectional( + start: 55.w, + top: 25.w, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + constraints: BoxConstraints( + maxWidth: 85.w, + maxHeight: 20.w, + ), + child: + (widget.message.userName?.length ?? 0) > 6 + ? Marquee( + text: "${widget.message.userName} ", + style: TextStyle( + fontSize: 13.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + letterSpacing: 0.1, + height: 1.1, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration( + seconds: 1, + ), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + "${widget.message.userName} ", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13.sp, + height: 1.1, + letterSpacing: 0.1, + color: Colors.white, + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + SizedBox(width: 3.w), + text( + "ID:${widget.message.userId ?? ""}", + fontSize: 13.sp, + fontWeight: FontWeight.bold, + textColor: Colors.white, + ), + ], + ), + text( + ATAppLocalizations.of(context)!.openTheTreasureChest, + textColor: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 14.sp, + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } + + // 处理向左滑动 + void _handleSwipeLeft() { + if (_isSwipeAnimating) return; + + setState(() { + _isSwipeAnimating = true; + }); + + // 停止主动画 + _controller.stop(); + + // 启动滑动动画 + _swipeController.reset(); + _swipeController.forward(); + } +} diff --git a/lib/chatvibe_ui/widgets/room/floating/floating_room_rocket_screen_widget.dart b/lib/chatvibe_ui/widgets/room/floating/floating_room_rocket_screen_widget.dart new file mode 100644 index 0000000..5c1c23e --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/floating/floating_room_rocket_screen_widget.dart @@ -0,0 +1,288 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_debouncer/flutter_debouncer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/main.dart'; +import 'package:marquee/marquee.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_data/models/message/at_floating_message.dart'; + +class FloatingRoomRocketScreenWidget extends StatefulWidget { + final ATFloatingMessage message; + final VoidCallback onAnimationCompleted; // 动画完成回调 + + const FloatingRoomRocketScreenWidget({ + Key? key, + required this.message, + required this.onAnimationCompleted, + }) : super(key: key); + + @override + _FloatingRoomRocketScreenWidgetState createState() => + _FloatingRoomRocketScreenWidgetState(); +} + +class _FloatingRoomRocketScreenWidgetState + extends State + with TickerProviderStateMixin { + Debouncer debouncer = Debouncer(); + late AnimationController _controller; + late Animation _firstAnimation; // 第一段:从右到中 + late Animation _secondAnimation; // 第二段:从中到左 + late AnimationController _swipeController; // 新增:滑动动画控制器 + late Animation _swipeAnimation; // 新增:滑动动画 + + bool _firstPhaseCompleted = false; + bool _isSwipeAnimating = false; // 标记是否正在执行滑动动画 + + @override + void initState() { + super.initState(); + // 主动画控制器 + _controller = AnimationController( + duration: const Duration(milliseconds: 1500), // 每段动画1.5秒 + vsync: this, + ); + + // 滑动动画控制器 + _swipeController = AnimationController( + duration: const Duration(milliseconds: 550), // 滑动动画500ms + vsync: this, + ); + + // 监听滑动动画完成 + _swipeController.addStatusListener((status) { + if (status == AnimationStatus.completed) { + widget.onAnimationCompleted(); + } + }); + + // 第一段动画:从右侧飘到中间 + _firstAnimation = Tween( + begin: const Offset(1.5, 0.0), // 从屏幕右侧外开始 + end: Offset.zero, // 到屏幕中心 + ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn)); + + // 第二段动画:从中间飘到左侧 + _secondAnimation = Tween( + begin: Offset.zero, // 从屏幕中心开始 + end: const Offset(-1.5, 0.0), // 到屏幕左侧外 + ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeIn)); + + // 滑动动画:从当前位置滑动到左侧屏幕外 + _swipeAnimation = Tween( + begin: Offset.zero, // 从当前位置开始 + end: const Offset(-1.5, 0.0), // 到屏幕左侧外 + ).animate(CurvedAnimation( + parent: _swipeController, + curve: Curves.easeIn, + )); + + // 延迟启动动画 + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _startAnimationSequence(); + } + }); + } + void _startAnimationSequence() async { + // 第一阶段:从右到中 + _controller.reset(); + await _controller.forward(); + + // 停顿2秒(你代码中是3秒,这里保持原样) + await Future.delayed(const Duration(seconds: 3)); + + if (!mounted) return; + + // 第二阶段:从中到左 + setState(() { + _firstPhaseCompleted = true; + }); + _controller.reset(); + await _controller.forward(); + + if (mounted && !_isSwipeAnimating) { + widget.onAnimationCompleted(); + } + } + + + @override + void dispose() { + _controller.dispose(); + _swipeController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + // 如果正在执行滑动动画,使用滑动动画 + if (_isSwipeAnimating) { + return SlideTransition( + position: _swipeAnimation, + child: _buildRocketAnimation(), + ); + } + + // 否则使用原来的动画 + return SlideTransition( + position: _firstPhaseCompleted ? _secondAnimation : _firstAnimation, + child: _buildRocketAnimation(), + ); + } + + Widget _buildRocketAnimation() { + // 使用Positioned.fill确保飘屏覆盖全屏宽度,并定位在特定高度(如顶部100像素) + return GestureDetector( + onTap: () { + debouncer.debounce( + duration: Duration(milliseconds: 350), + onDebounce: () { + if (widget.message.roomId != null && + widget.message.roomId!.isNotEmpty) { + ATRoomUtils.goRoom( + widget.message.roomId!, + navigatorKey.currentState!.context, + fromFloting: true + ); + } + }, + ); + }, + onHorizontalDragEnd: (details) { + // 获取当前的文本方向 + final textDirection = Directionality.of(context); + + // 定义一个根据方向转换速度符号的辅助函数 + double effectiveVelocity(double velocity) { + // 在RTL模式下,反转速度的正负号 + return textDirection == TextDirection.rtl + ? -velocity + : velocity; + } + + double velocity = effectiveVelocity(details.primaryVelocity ?? 0); + if (velocity < 0) { + ///向左滑动,把当前视图向左平移出去 + _handleSwipeLeft(); + } + }, + child: Align( + alignment: Alignment.topCenter, + child: Directionality( + textDirection: TextDirection.ltr, + child: Container( + alignment: Alignment.topCenter, + height: 128.w, + width: 290.w, + margin: EdgeInsets.only(top: 10.w), + padding: EdgeInsetsDirectional.only(start: 3.w, end: 25.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/index/at_icon_rocket_broad_bg_lv${widget.message.rocketLevel}.webp", + ), + fit: BoxFit.fill, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, // 宽度由内容决定 + children: [ + SizedBox(width: 5.w), + Container( + child: head( + url: widget.message.userAvatarUrl ?? "", + width: 66.w, + ), + ), + SizedBox(width: 4.w), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: 6.w), + Row( + children: [ + SizedBox( + width: 60.w, + height: 22.w, + child: Marquee( + text: "ID:${widget.message.userId} ", + style: TextStyle( + fontSize: 15.sp, + color: Colors.yellow, + fontStyle: FontStyle.italic, + fontWeight: FontWeight.w900, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ), + ), + text( + ATAppLocalizations.of(context)!.launchedARocket, + textColor: Colors.white, + fontWeight: FontWeight.bold, + fontStyle: FontStyle.italic, + fontSize: 13.sp, + ), + ], + ), + Transform.translate( + offset: Offset(0, -5), + child: text( + ATAppLocalizations.of(context)!.letGoToWatch, + textColor: Colors.white, + fontWeight: FontWeight.w900, + fontSize: 17.sp, + fontStyle: FontStyle.italic, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } + + // 处理向左滑动 + void _handleSwipeLeft() { + if (_isSwipeAnimating) return; + + setState(() { + _isSwipeAnimating = true; + }); + + // 停止主动画 + _controller.stop(); + + // 启动滑动动画 + _swipeController.reset(); + _swipeController.forward(); + } +} diff --git a/lib/chatvibe_ui/widgets/room/game/room_game_list_page.dart b/lib/chatvibe_ui/widgets/room/game/room_game_list_page.dart new file mode 100644 index 0000000..2bd4a9b --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/game/room_game_list_page.dart @@ -0,0 +1,520 @@ +import 'dart:ui' as ui; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_get_list_game_config_res.dart'; +import 'package:aslan/main.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/game/room_games_tab_page.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/config_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_top_four_with_reward_res.dart'; +import 'package:aslan/chatvibe_domain/usecases/at_fixed_width_tabIndicator.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +import '../../../../chatvibe_data/models/enum/at_game_mode_type.dart'; + +class RoomGameListPage extends StatefulWidget { + @override + _RoomGameListPageState createState() => _RoomGameListPageState(); +} + +class _RoomGameListPageState extends State { + List listHistoryRoomGame = []; + ATTopFourWithRewardRes? topWeekFourWithReward1; + ATTopFourWithRewardRes? topWeekFourWithReward2; + ATTopFourWithRewardRes? topWeekFourWithReward3; + + ATTopFourWithRewardRes? topDayFourWithReward1; + ATTopFourWithRewardRes? topDayFourWithReward2; + ATTopFourWithRewardRes? topDayFourWithReward3; + int _carouselSliderCurrentIndex = 0; + + @override + void initState() { + super.initState(); + String roomId = + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.id ?? + ""; + getListGameConfig(roomId); + } + + void getListGameConfig(String roomId) { + ATLoadingManager.exhibitOperation(); + listHistoryRoomGame.clear(); + Future.wait([ + // ConfigRepositoryImp().topFourWithReward(), + // ConfigRepositoryImp().kingGamesDailyTopThree(), + ChatRoomRepository().gameLudoHistoryRecent("IN_ROOM"), + ]) + .then((res) { + ATLoadingManager.veilRoutine(); + // List topWeekFourWithRewards = + // res[0] as List; + // if (topWeekFourWithRewards.isNotEmpty) { + // topWeekFourWithReward1 = topWeekFourWithRewards[0]; + // if (topWeekFourWithRewards.length > 1) { + // topWeekFourWithReward2 = topWeekFourWithRewards[1]; + // } + // if (topWeekFourWithRewards.length > 2) { + // topWeekFourWithReward3 = topWeekFourWithRewards[2]; + // } + // } + // + // List topDayFourWithRewards = + // res[1] as List; + // if (topDayFourWithRewards.isNotEmpty) { + // topDayFourWithReward1 = topDayFourWithRewards[0]; + // if (topDayFourWithRewards.length > 1) { + // topDayFourWithReward2 = topDayFourWithRewards[1]; + // } + // if (topDayFourWithRewards.length > 2) { + // topDayFourWithReward3 = topDayFourWithRewards[2]; + // } + // } + List listRoomGame = + res[0] as List; + + for (var g in listRoomGame) { + if (g.gameMode == ATGameModeType.CHAT_ROOM.name) { + //如果是语聊房游戏只有房主才能看到 + if (Provider.of( + context, + listen: false, + ).isFz()) { + listHistoryRoomGame.add(g); + } + } else { + listHistoryRoomGame.add(g); + } + setState(() {}); + } + setState(() {}); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: Consumer( + builder: (context, ref, child) { + return ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 15, sigmaY: 15), + child: Container( + // height: 535.w, + height: 400.w, + decoration: BoxDecoration(color: Colors.black54), + child: Column( + children: [ + SizedBox(height: 15.w), + text( + ATAppLocalizations.of(context)!.gameCenter, + textColor: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 16.sp, + ), + SizedBox(height: 8.w), + // Stack( + // alignment: AlignmentDirectional.bottomCenter, + // children: [ + // CarouselSlider( + // options: CarouselOptions( + // height: 115.w, + // autoPlay: true, + // // 启用自动播放 + // enlargeCenterPage: false, + // // 居中放大当前页面 + // enableInfiniteScroll: true, + // // 启用无限循环 + // autoPlayAnimationDuration: Duration( + // milliseconds: 800, + // ), + // // 自动播放动画时长 + // viewportFraction: 1, + // // 视口分数 + // onPageChanged: (index, reason) { + // _carouselSliderCurrentIndex = index; + // setState(() {}); + // }, + // ), + // items: [ + // ATDebounceWidget( + // child: Container( + // margin: EdgeInsets.symmetric(horizontal: 10.w), + // child: Stack( + // alignment: Alignment.center, + // children: [ + // Container( + // margin: EdgeInsets.only(top: 22.w), + // child: Row( + // mainAxisAlignment: + // MainAxisAlignment.center, + // spacing: 48.w, + // children: [ + // topDayFourWithReward2 != null + // ? netImage( + // url: + // topDayFourWithReward2 + // ?.avatar ?? + // "", + // shape: BoxShape.circle, + // width: 40.w, + // height: 40.w, + // ) + // : Container( + // width: 40.w, + // height: 40.w, + // ), + // topDayFourWithReward1 != null + // ? netImage( + // url: + // topDayFourWithReward1 + // ?.avatar ?? + // "", + // shape: BoxShape.circle, + // width: 50.w, + // height: 50.w, + // ) + // : Container( + // width: 50.w, + // height: 50.w, + // ), + // topDayFourWithReward3 != null + // ? netImage( + // url: + // topDayFourWithReward3 + // ?.avatar ?? + // "", + // shape: BoxShape.circle, + // width: 40.w, + // height: 40.w, + // ) + // : Container( + // width: 40.w, + // height: 40.w, + // ), + // ], + // ), + // ), + // Image.asset( + // "atu_images/room/at_icon_game_king_day_bg.png", + // fit: BoxFit.fill, + // ), + // ], + // ), + // ), + // onTap: () { + // ATNavigatorUtils.push( + // context, + // "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.gamesKingUrl)}&showTitle=false", + // replace: false, + // ); + // }, + // ), + // ATDebounceWidget( + // child: Container( + // margin: EdgeInsets.symmetric(horizontal: 10.w), + // child: Stack( + // alignment: AlignmentDirectional.center, + // children: [ + // Container( + // child: Row( + // mainAxisAlignment: + // MainAxisAlignment.center, + // spacing: 48.w, + // children: [ + // topWeekFourWithReward2 != null + // ? netImage( + // url: + // topWeekFourWithReward2 + // ?.avatar ?? + // "", + // shape: BoxShape.circle, + // width: 40.w, + // height: 40.w, + // ) + // : Container( + // width: 40.w, + // height: 40.w, + // ), + // topWeekFourWithReward1 != null + // ? netImage( + // url: + // topWeekFourWithReward1 + // ?.avatar ?? + // "", + // shape: BoxShape.circle, + // width: 50.w, + // height: 50.w, + // ) + // : Container( + // width: 50.w, + // height: 50.w, + // ), + // topWeekFourWithReward3 != null + // ? netImage( + // url: + // topWeekFourWithReward3 + // ?.avatar ?? + // "", + // shape: BoxShape.circle, + // width: 40.w, + // height: 40.w, + // ) + // : Container( + // width: 40.w, + // height: 40.w, + // ), + // ], + // ), + // margin: EdgeInsets.only(top: 22.w), + // ), + // Image.asset( + // "atu_images/room/at_icon_game_king_week_bg.png", + // fit: BoxFit.fill, + // ), + // ], + // ), + // ), + // onTap: () { + // ATNavigatorUtils.push( + // context, + // "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.gamesKingUrl)}&showTitle=false", + // replace: false, + // ); + // }, + // ), + // ], + // ), + // Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // Container( + // width: 6.0, + // height: 6.0, + // margin: EdgeInsets.symmetric( + // vertical: 8.0, + // horizontal: 4.0, + // ), + // decoration: BoxDecoration( + // shape: BoxShape.circle, + // color: + // _carouselSliderCurrentIndex == 0 + // ? Colors.blue + // : Colors.white, + // ), + // ), + // Container( + // width: 6.0, + // height: 6.0, + // margin: EdgeInsets.symmetric( + // vertical: 8.0, + // horizontal: 4.0, + // ), + // decoration: BoxDecoration( + // shape: BoxShape.circle, + // color: + // _carouselSliderCurrentIndex == 1 + // ? Colors.blue + // : Colors.white, + // ), + // ), + // ], + // ), + // ], + // ), + DefaultTabController( + length: 2, + child: Column( + children: [ + TabBar( + tabAlignment: TabAlignment.start, + labelPadding: EdgeInsets.symmetric( + horizontal: 12.w, + ), + isScrollable: true, + labelStyle: TextStyle( + fontSize: 15.sp, + fontFamily: 'MyCustomFont', + color: Colors.white, + fontWeight: FontWeight.w600, + ), + unselectedLabelStyle: TextStyle( + fontSize: 13.sp, + fontFamily: 'MyCustomFont', + color: Colors.white54, + fontWeight: FontWeight.w500, + ), + // indicatorPadding: EdgeInsets.symmetric( + // vertical: 5.w, + // horizontal: 15.w, + // ), + indicator: ATFixedWidthTabIndicator( + width: 15.w, + color: ChatVibeTheme.primaryLight, + ), + indicatorSize: TabBarIndicatorSize.label, + dividerColor: Colors.transparent, + tabs: + [ + ATAppLocalizations.of(context)!.allGames, + // ATAppLocalizations.of( + // context, + // )!.casualInteraction, + ] + .map( + (e) => Padding( + padding: EdgeInsets.only( + bottom: 3.w, + top: 4.w, + ), + child: Text(e), + ), + ) + .toList(), + ), + Container( + height: 200.w, + child: TabBarView( + children: [ + RoomGamesTabPage(tyoe: 0), + // RoomGamesTabPage(tyoe: 1), + ], + ), + ), + ], + ), + ), + SizedBox(height: 8.w), + ATGlobalConfig.isReview + ? Container() + : Expanded( + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + 'atu_images/room/at_icon_room_game_history_bg.png', + ), + fit: BoxFit.fill, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 15.w), + Row( + children: [ + SizedBox(width: 25.w), + text( + ATAppLocalizations.of( + context, + )!.playLog, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + Expanded( + child: Container( + margin: EdgeInsets.symmetric( + horizontal: 15.w, + ), + child: GridView.builder( + padding: EdgeInsets.symmetric( + vertical: 8.w, + ), + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 5, + crossAxisSpacing: 2, + mainAxisSpacing: 2, + childAspectRatio: 0.9, + ), + itemBuilder: (c, i) { + return _buildItem( + listHistoryRoomGame[i], + i, + ); + }, + itemCount: listHistoryRoomGame.length, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + }, + ), + ); + } + + Widget _buildItem(ATGetListGameConfigRes item, int i) { + return GestureDetector( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.all(2.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_game_item_bg_v.png", + ), + fit: BoxFit.fill, + ), + ), + child: netImage( + url: item.cover ?? "", + width: 45.w, + height: 45.w, + fit: BoxFit.cover, + borderRadius: BorderRadius.all(Radius.circular(2.w)), + ), + ), + SizedBox(height: 10.w), + text( + item.name ?? "", + fontSize: 10.sp, + textColor: Colors.white, + maxLines: 2, + textAlign: TextAlign.center, + ), + ], + ), + onTap: () { + ATRoomUtils.openRoomGame(navigatorKey.currentState!.context, item); + }, + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/game/room_games_tab_page.dart b/lib/chatvibe_ui/widgets/room/game/room_games_tab_page.dart new file mode 100644 index 0000000..ee17447 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/game/room_games_tab_page.dart @@ -0,0 +1,156 @@ +import 'dart:ui' as ui; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/main.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/config_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_get_list_game_config_res.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; + +import '../../../../chatvibe_data/models/enum/at_game_mode_type.dart'; + +class RoomGamesTabPage extends StatefulWidget { + int tyoe = 0; + + RoomGamesTabPage({Key? key, required this.tyoe}) : super(key: key); + + @override + _RoomGamesTabPageState createState() => _RoomGamesTabPageState(); +} + +class _RoomGamesTabPageState extends State { + int _gameCurrentIndex = 0; + List listRoomGame = []; + + @override + void initState() { + super.initState(); + _getListGameConfig(); + } + + @override + Widget build(BuildContext context) { + return listRoomGame.isNotEmpty + ? Stack( + alignment: Alignment.bottomCenter, + children: [ + PageView.builder( + itemCount: (listRoomGame.length / 8).ceil(), + itemBuilder: (context, pageIndex) { + return Container( + child: GridView.builder( + padding: EdgeInsets.symmetric(vertical: 8.w), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, + crossAxisSpacing: 2, + mainAxisSpacing: 2, + childAspectRatio: 1.1, + ), + itemBuilder: (c, i) { + return _buildItem( + listRoomGame[pageIndex * 8 + i], + pageIndex * 8 + i, + ); + }, + itemCount: ((listRoomGame.length - pageIndex * 8).clamp( + 0, + 8, + )), + ), + ); + }, + onPageChanged: (index) { + _gameCurrentIndex = index; + setState(() {}); + }, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + (listRoomGame.length / 8).ceil(), + (index) => Container( + width: 6.0, + height: 6.0, + margin: EdgeInsets.symmetric(vertical: 8.0, horizontal: 4.0), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: + _gameCurrentIndex == index ? Colors.blue : Colors.white, + ), + ), + ), + ), + ], + ) + : mainEmpty(); + } + + Widget _buildItem(ATGetListGameConfigRes item, int i) { + return GestureDetector( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.all(2.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/room/at_icon_room_game_item_bg_v.png"), + fit: BoxFit.fill, + ), + ), + child: netImage( + url: item.cover ?? "", + width: 45.w, + height: 45.w, + fit: BoxFit.cover, + borderRadius: BorderRadius.all(Radius.circular(2.w)), + ), + ), + SizedBox(height: 10.w), + text( + item.name ?? "", + fontSize: 10.sp, + textColor: Colors.white, + maxLines: 2, + textAlign: TextAlign.center, + ), + ], + ), + onTap: () { + ATRoomUtils.openRoomGame(navigatorKey.currentState!.context, item); + }, + ); + } + + void _getListGameConfig() { + listRoomGame.clear(); + ConfigRepositoryImp() + .getListGameConfig( + category: widget.tyoe == 0 ? null : "CASUAL", + roomId: + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.id ?? + "", + countryCode: ui.window.locale.countryCode, + ) + .then((res) { + res.forEach((g) { + if (g.gameMode == ATGameModeType.CHAT_ROOM.name) { + //如果是语聊房游戏只有房主才能看到 + if (Provider.of(context, listen: false).isFz()) { + listRoomGame.add(g); + } + } else { + listRoomGame.add(g); + } + setState(() {}); + }); + }); + } +} diff --git a/lib/chatvibe_ui/widgets/room/give_room_luck_page.dart b/lib/chatvibe_ui/widgets/room/give_room_luck_page.dart new file mode 100644 index 0000000..33263b4 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/give_room_luck_page.dart @@ -0,0 +1,549 @@ +import 'dart:async'; +import 'dart:collection'; +import 'package:flutter/material.dart'; +import 'package:flutter_debouncer/flutter_debouncer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:provider/provider.dart'; +import '../../../chatvibe_core/at_lk_event_bus.dart'; +import '../../../chatvibe_core/constants/at_global_config.dart'; +import '../../../chatvibe_domain/models/res/gift_res.dart'; +import '../../../chatvibe_managers/gift_system_manager.dart'; +import '../../../chatvibe_managers/rtc_manager.dart'; +import '../../../main.dart'; +import '../../components/at_compontent.dart'; + +class GiveRoomLuckPage extends StatefulWidget { + ChatVibeGiftRes checkedGift; + List acceptUserIds = []; + Function() call; + + GiveRoomLuckPage(this.checkedGift, this.acceptUserIds, this.call); + + @override + _GiveRoomLuckPageState createState() => _GiveRoomLuckPageState(); +} + +class _GiveRoomLuckPageState extends State + with TickerProviderStateMixin { + static const int _maxOverlayAnimations = 10; + var debouncer = Debouncer(); + final Set _runningControllers = {}; + final Set _activeOverlays = {}; + final Queue<_LuckFanOutTask> _pendingFanOutTasks = Queue<_LuckFanOutTask>(); + + // bool isFasheOk = true; + final GlobalKey _sourceKey1 = GlobalKey(); + final GlobalKey _targetKey1 = GlobalKey(); + + bool isFashe = false; + + late AnimationController _fasheController; + late Animation _fasheAnimation; + + Timer? timer; + int startFasheTime = 0; + + @override + void initState() { + super.initState(); + Provider.of( + navigatorKey.currentContext!, + listen: false, + ).reset(); + Provider.of( + navigatorKey.currentContext!, + listen: false, + ).updateHideLGiftAnimal(true); + initFasheAnim(); + // initScanAnim(); + startTimer(); + // GiftVapSvgaManager().pauseAnim(); + } + + @override + void dispose() { + eventBus.fire(ATGiveRoomLuckPageDisposeEvent()); + _disposeTransientEffects(); + _fasheController.dispose(); + timer?.cancel(); + + // GiftVapSvgaManager().resumeAnim(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + GestureDetector( + child: Container(color: Colors.transparent), + onTap: () { + SmartDialog.dismiss(tag: "showGiftControl"); + }, + ), + Positioned( + right: ATGlobalConfig.lang == "ar" ? null : 0, + left: ATGlobalConfig.lang == "ar" ? 0 : null, + bottom: 0, + child: Transform.rotate( + angle: ATGlobalConfig.lang == "ar" ? 0.5 : -0.5, + child: Transform.translate( + offset: + ATGlobalConfig.lang == "ar" + ? Offset(-22, 35) + : Offset(22, 35 ), + child: AnimatedBuilder( + animation: _fasheAnimation, + builder: (context, child) { + return Transform.scale( + scale: _fasheAnimation.value, + child: SizedBox( + width: 200.w, + height: 200.w, + child: Stack( + children: [ + Image.asset( + "atu_images/room/at_icon_luck_gift_daiji.webp", + ), + isFashe + ? Image.asset( + "atu_images/room/at_icon_luck_fashe.webp", + ) + : Container(), + ], + ), + ), + ); + }, + ), + ), + ), + ), + + Positioned( + bottom: 350.w, + child: SizedBox(width: 100.w, height: 100.w, key: _targetKey1), + ), + + Positioned( + bottom: 160.w, + right: ATGlobalConfig.lang == "ar" ? null : 105.w, + left: ATGlobalConfig.lang == "ar" ? 105.w : null, + child: SizedBox(key: _sourceKey1, width: 35.w, height: 35.w,), + ), + Positioned( + right: ATGlobalConfig.lang == "ar" ? null : 0, + left: ATGlobalConfig.lang == "ar" ? 0 : null, + child: GestureDetector( + child: Container( + width: 100.w, + height: 100.w, + color: Colors.transparent, + ), + onTap: () { + startFashe(); + debouncer.debounce( + duration: Duration(milliseconds: 600), + onDebounce: () { + isFashe = false; + setState(() {}); + }, + ); + }, + ), + ), + ], + ), + ); + } + + ///发射动画 + void startFashe() { + startTimer(); + _fasheController.reset(); + _fasheController.forward(); + + int now = DateTime.now().millisecondsSinceEpoch; + if (now - startFasheTime < 150) { + return; + } + startFasheTime = now; + isFashe = true; + _startA1(); + } + + void fasheComlete() { + widget.call(); + } + + void startTimer() { + timer?.cancel(); + timer = Timer(Duration(seconds: 5), () { + SmartDialog.dismiss(tag: "showGiveRoomLuckPage"); + }); + } + + void _startA1() { + // 确保上下文有效 + final Offset? sourceCenter = _centerFromKey(_sourceKey1); + final Offset? targetCenter = _centerFromKey(_targetKey1); + if (sourceCenter == null || targetCenter == null) { + return; + } + + // 计算图片中心点偏移(使图片中心对准控件中心) + final double imageOffsetX = 55.w / 2; + final double imageOffsetY = 55.w / 2; + + final AnimationController controller = AnimationController( + vsync: this, + duration: Duration(milliseconds: 1100), + ); + _runningControllers.add(controller); + OverlayEntry? overlayEntry; + controller.addStatusListener((status) { + if (status == AnimationStatus.completed) { + if (overlayEntry != null) { + _removeOverlay(overlayEntry); + overlayEntry = null; + bool hasUser = false; + for (var uid in widget.acceptUserIds) { + GlobalKey>? targetKey = + Provider.of( + context, + listen: false, + ).getSeatGlobalKeyByIndex(uid); + if (targetKey?.currentContext != null) { + hasUser = true; + } + } + fasheComlete(); + + ///麦上都没人 + if (!hasUser) { + return; + } + _startA2(); + } + _disposeTransientController(controller); + } + }); + + // 创建位置动画:从起始位置到目标位置 + final Animation positionAnimationX1 = Tween( + begin: Offset( + sourceCenter.dx - imageOffsetX, + sourceCenter.dy - imageOffsetY, + ), + end: Offset( + targetCenter.dx - imageOffsetX, + targetCenter.dy - imageOffsetY, + ), + ).animate( + CurvedAnimation( + parent: controller, + curve: const Interval(0.2, 0.4, curve: Curves.easeInOut), + ), + ); + // 创建缩放动画 + final Animation scaleAnimationX1 = Tween( + begin: 0.5, + end: 1.1, // 可以根据需要调整 + ).animate( + CurvedAnimation( + parent: controller, + curve: const Interval(0, 0.2, curve: Curves.easeInOut), + ), + ); + + // 创建缩放动画 + final Animation scaleAnimationX2 = Tween( + begin: 1, + end: 1.5, // 可以根据需要调整 + ).animate( + CurvedAnimation( + parent: controller, + curve: const Interval(0.4, 0.7, curve: Curves.easeInOut), + ), + ); + + // 创建浮动控件 + overlayEntry = OverlayEntry( + builder: + (context) => AnimatedBuilder( + animation: controller, + child: RepaintBoundary( + child: netImage( + url: widget.checkedGift.giftPhoto ?? "", + width: 55.w, + height: 55.w, + ), + ), + builder: (context, child) { + return Positioned( + left: positionAnimationX1.value.dx, + top: positionAnimationX1.value.dy, + child: Transform.scale( + scale: scaleAnimationX1.value, + child: Transform.scale( + scale: scaleAnimationX2.value, + child: child, + ), + ), + ); + }, + ), + ); + + // 插入Overlay并启动动画 + if (!_insertOverlay(overlayEntry!)) { + _disposeTransientController(controller); + return; + } + controller.forward(from: 0); + } + + void _startA2() { + // 确保上下文有效 + final Offset? sourceCenter = _centerFromKey(_targetKey1); + if (sourceCenter == null) return; + final RtcProvider rtc = Provider.of(context, listen: false); + final List targetUserIds = _pickTargetUserIds(widget.acceptUserIds); + for (var userId in targetUserIds) { + final GlobalKey>? targetKey = rtc + .getSeatGlobalKeyByIndex(userId); + final Offset? targetCenter = + targetKey == null ? null : _centerFromKey(targetKey); + if (targetCenter == null) { + continue; + } + _pendingFanOutTasks.add( + _LuckFanOutTask( + sourceCenter: sourceCenter, + targetCenter: targetCenter, + giftUrl: widget.checkedGift.giftPhoto ?? "", + ), + ); + } + _drainFanOutQueue(); + } + + void _drainFanOutQueue() { + while (mounted && + _activeOverlays.length < _maxOverlayAnimations && + _pendingFanOutTasks.isNotEmpty) { + _startFanOutTask(_pendingFanOutTasks.removeFirst()); + } + } + + void _startFanOutTask(_LuckFanOutTask task) { + final double imageOffsetX = 55.w / 2; + final double imageOffsetY = 55.w / 2; + + final AnimationController controller = AnimationController( + vsync: this, + duration: Duration(milliseconds: 1100), + ); + _runningControllers.add(controller); + OverlayEntry? overlayEntry; + controller.addStatusListener((status) { + if (status == AnimationStatus.completed) { + if (overlayEntry != null) { + _removeOverlay(overlayEntry); + overlayEntry = null; + } + _disposeTransientController(controller); + _drainFanOutQueue(); + } + }); + + // 创建位置动画:从起始位置到目标位置 + final Animation positionAnimationX1 = Tween( + begin: Offset( + task.sourceCenter.dx - imageOffsetX, + task.sourceCenter.dy - imageOffsetY, + ), + end: Offset( + task.targetCenter.dx - imageOffsetX, + task.targetCenter.dy - imageOffsetY, + ), + ).animate( + CurvedAnimation( + parent: controller, + curve: const Interval(0.3, 0.7, curve: Curves.easeInOut), + ), + ); + + // 创建缩放动画 + final Animation scaleAnimationX1 = Tween( + begin: 1.1, + end: 0.7, // 可以根据需要调整 + ).animate( + CurvedAnimation( + parent: controller, + curve: const Interval(0.7, 0.8, curve: Curves.easeInOut), + ), + ); + + // 创建浮动控件 + overlayEntry = OverlayEntry( + builder: + (context) => AnimatedBuilder( + animation: controller, + child: RepaintBoundary( + child: netImage(url: task.giftUrl, width: 55.w, height: 55.w), + ), + builder: (context, child) { + return Positioned( + left: positionAnimationX1.value.dx, + top: positionAnimationX1.value.dy, + child: Transform.scale( + scale: scaleAnimationX1.value, + child: child, + ), + ); + }, + ), + ); + + // 插入Overlay并启动动画 + if (!_insertOverlay(overlayEntry!)) { + _disposeTransientController(controller); + _drainFanOutQueue(); + return; + } + controller.forward(from: 0); + } + + void _disposeTransientEffects() { + for (final OverlayEntry entry in _activeOverlays.toList()) { + entry.remove(); + } + _activeOverlays.clear(); + for (final AnimationController controller in _runningControllers.toList()) { + controller.dispose(); + } + _runningControllers.clear(); + _pendingFanOutTasks.clear(); + } + + void _removeOverlay(OverlayEntry? entry) { + if (entry == null) { + return; + } + if (_activeOverlays.remove(entry)) { + entry.remove(); + } + } + + void _disposeTransientController(AnimationController controller) { + if (_runningControllers.remove(controller)) { + controller.dispose(); + } + } + + bool _insertOverlay(OverlayEntry entry) { + if (!mounted || _activeOverlays.length >= _maxOverlayAnimations) { + return false; + } + final OverlayState? overlayState = Overlay.of(context, rootOverlay: true); + if (overlayState == null) { + return false; + } + overlayState.insert(entry); + _activeOverlays.add(entry); + return true; + } + + Offset? _centerFromKey(GlobalKey key) { + final BuildContext? keyContext = key.currentContext; + if (keyContext == null) { + return null; + } + final RenderObject? renderObject = keyContext.findRenderObject(); + if (renderObject is! RenderBox) { + return null; + } + final Offset position = renderObject.localToGlobal(Offset.zero); + final Size size = renderObject.size; + return Offset(position.dx + size.width / 2, position.dy + size.height / 2); + } + + List _pickTargetUserIds(List userIds) { + final List uniqueUserIds = []; + final Set duplicateFilter = {}; + for (final String userId in userIds) { + if (userId.isEmpty || !duplicateFilter.add(userId)) { + continue; + } + uniqueUserIds.add(userId); + } + return uniqueUserIds; + } + + void initFasheAnim() { + _fasheController = AnimationController( + duration: const Duration(milliseconds: 500), + vsync: this, + ); + // 使用TweenSequence来定义先缩小后放大的动画 + _fasheAnimation = TweenSequence([ + TweenSequenceItem( + tween: Tween(begin: 1.0, end: 1.1), // 缩小到0.5 + weight: 1, // 权重,这里两个阶段各占一半 + ), + TweenSequenceItem( + tween: Tween(begin: 1.1, end: 1.0), // 放大到1.0 + weight: 1, + ), + ]).animate(_fasheController); + } + + // void initScanAnim() { + // _giftController = AnimationController( + // duration: const Duration(milliseconds: 600), + // vsync: this, + // )..addStatusListener((status) { + // if (status == AnimationStatus.completed) { + // isShowGiftIcon1 = false; + // setState(() {}); + // } + // }); + // + // // 创建缩放动画 + // _scalegiftAnimation1 = Tween( + // begin: 1, + // end: 1.2, // 可以根据需要调整 + // ).animate( + // CurvedAnimation( + // parent: _giftController, + // curve: const Interval(0, 0.4, curve: Curves.easeInOut), + // ), + // ); + // _scalegiftAnimation2 = Tween( + // begin: 1.2, + // end: 1, // 可以根据需要调整 + // ).animate( + // CurvedAnimation( + // parent: _giftController, + // curve: const Interval(0.4, 0.8, curve: Curves.easeInOut), + // ), + // ); + // } +} + +class _LuckFanOutTask { + final Offset sourceCenter; + final Offset targetCenter; + final String giftUrl; + + const _LuckFanOutTask({ + required this.sourceCenter, + required this.targetCenter, + required this.giftUrl, + }); +} diff --git a/lib/chatvibe_ui/widgets/room/give_room_luck_with_other_page.dart b/lib/chatvibe_ui/widgets/room/give_room_luck_with_other_page.dart new file mode 100644 index 0000000..2ab737e --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/give_room_luck_with_other_page.dart @@ -0,0 +1,297 @@ +import 'dart:async'; +import 'dart:collection'; + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:provider/provider.dart'; + +import '../../../chatvibe_core/at_lk_event_bus.dart'; +import '../../../chatvibe_managers/rtc_manager.dart'; +import '../../components/at_compontent.dart'; + +class GiveRoomLuckWithOtherWidget extends StatefulWidget { + GiveRoomLuckWithOtherWidget(); + + @override + _GiveRoomLuckWithOtherWidgetState createState() => + _GiveRoomLuckWithOtherWidgetState(); +} + +class _GiveRoomLuckWithOtherWidgetState + extends State + with TickerProviderStateMixin { + static const int _maxOverlayAnimations = 10; + final GlobalKey _targetKey1 = GlobalKey(); + late StreamSubscription _subscription; + int startFasheTime = 0; + final Set _runningControllers = + {}; + final Set _activeOverlays = {}; + final Queue<_LuckOtherFanOutTask> _pendingFanOutTasks = + Queue<_LuckOtherFanOutTask>(); + + @override + void initState() { + super.initState(); + _subscription = eventBus.on().listen((event) { + startFashe(event.giftPic, event.acceptUserIds); + }); + } + + @override + void dispose() { + _disposeTransientEffects(); + _subscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return IgnorePointer( + child: SafeArea( + top: false, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + Positioned( + bottom: 250.w, + child: SizedBox(width: 100.w, height: 100.w, key: _targetKey1), + ), + ], + ), + ), + ); + } + + ///发射动画 + void startFashe(String giftPic, List acceptUserIds) { + int now = DateTime.now().millisecondsSinceEpoch; + if (now - startFasheTime < 150) { + return; + } + startFasheTime = now; + _startA2(giftPic, acceptUserIds); + } + + void _startA2(String giftPic, List acceptUserIds) { + // 确保上下文有效 + final Offset? sourceCenter = _centerFromKey(_targetKey1); + if (sourceCenter == null) return; + final RtcProvider rtc = Provider.of(context, listen: false); + final List targetUserIds = _pickTargetUserIds(acceptUserIds); + for (var userId in targetUserIds) { + final GlobalKey>? targetKey = rtc + .getSeatGlobalKeyByIndex(userId); + final Offset? targetCenter = + targetKey == null ? null : _centerFromKey(targetKey); + if (targetCenter == null) { + continue; + } + _pendingFanOutTasks.add( + _LuckOtherFanOutTask( + sourceCenter: sourceCenter, + targetCenter: targetCenter, + giftUrl: giftPic, + ), + ); + } + _drainFanOutQueue(); + } + + void _drainFanOutQueue() { + while (mounted && + _activeOverlays.length < _maxOverlayAnimations && + _pendingFanOutTasks.isNotEmpty) { + _startFanOutTask(_pendingFanOutTasks.removeFirst()); + } + } + + void _startFanOutTask(_LuckOtherFanOutTask task) { + final double imageOffsetX = 55.w / 2; + final double imageOffsetY = 55.w / 2; + + final AnimationController controller = AnimationController( + vsync: this, + duration: Duration(milliseconds: 1100), + ); + _runningControllers.add(controller); + OverlayEntry? overlayEntry; + controller.addStatusListener((status) { + if (status == AnimationStatus.completed) { + if (overlayEntry != null) { + _removeOverlay(overlayEntry); + overlayEntry = null; + } + _disposeTransientController(controller); + _drainFanOutQueue(); + } + }); + + // 创建位置动画:从起始位置到目标位置 + final Animation positionAnimationX1 = Tween( + begin: Offset( + task.sourceCenter.dx - imageOffsetX, + task.sourceCenter.dy - imageOffsetY, + ), + end: Offset( + task.targetCenter.dx - imageOffsetX, + task.targetCenter.dy - imageOffsetY, + ), + ).animate( + CurvedAnimation( + parent: controller, + curve: const Interval(0.3, 0.7, curve: Curves.easeInOut), + ), + ); + + // 创建缩放动画 + final Animation scaleAnimationX1 = Tween( + begin: 1.1, + end: 0.7, // 可以根据需要调整 + ).animate( + CurvedAnimation( + parent: controller, + curve: const Interval(0.7, 0.8, curve: Curves.easeInOut), + ), + ); + + // 创建浮动控件 + overlayEntry = OverlayEntry( + builder: + (context) => AnimatedBuilder( + animation: controller, + child: RepaintBoundary( + child: netImage(url: task.giftUrl, width: 55.w, height: 55.w), + ), + builder: (context, child) { + return Positioned( + left: positionAnimationX1.value.dx, + top: positionAnimationX1.value.dy, + child: Transform.scale( + scale: scaleAnimationX1.value, + child: child, + ), + ); + }, + ), + ); + + // 插入Overlay并启动动画 + if (!_insertOverlay(overlayEntry!)) { + _disposeTransientController(controller); + _drainFanOutQueue(); + return; + } + controller.forward(from: 0); + } + + void _disposeTransientEffects() { + for (final OverlayEntry entry in _activeOverlays.toList()) { + entry.remove(); + } + _activeOverlays.clear(); + for (final AnimationController controller in _runningControllers.toList()) { + controller.dispose(); + } + _runningControllers.clear(); + _pendingFanOutTasks.clear(); + } + + void _removeOverlay(OverlayEntry? entry) { + if (entry == null) { + return; + } + if (_activeOverlays.remove(entry)) { + entry.remove(); + } + } + + void _disposeTransientController(AnimationController controller) { + if (_runningControllers.remove(controller)) { + controller.dispose(); + } + } + + bool _insertOverlay(OverlayEntry entry) { + if (!mounted || _activeOverlays.length >= _maxOverlayAnimations) { + return false; + } + final OverlayState? overlayState = Overlay.of(context, rootOverlay: true); + if (overlayState == null) { + return false; + } + overlayState.insert(entry); + _activeOverlays.add(entry); + return true; + } + + Offset? _centerFromKey(GlobalKey key) { + final BuildContext? keyContext = key.currentContext; + if (keyContext == null) { + return null; + } + final RenderObject? renderObject = keyContext.findRenderObject(); + if (renderObject is! RenderBox) { + return null; + } + final Offset position = renderObject.localToGlobal(Offset.zero); + final Size size = renderObject.size; + return Offset(position.dx + size.width / 2, position.dy + size.height / 2); + } + + List _pickTargetUserIds(List userIds) { + final List uniqueUserIds = []; + final Set duplicateFilter = {}; + for (final String userId in userIds) { + if (userId.isEmpty || !duplicateFilter.add(userId)) { + continue; + } + uniqueUserIds.add(userId); + } + return uniqueUserIds; + } + + // void initScanAnim() { + // _giftController = AnimationController( + // duration: const Duration(milliseconds: 600), + // vsync: this, + // )..addStatusListener((status) { + // if (status == AnimationStatus.completed) { + // isShowGiftIcon1 = false; + // setState(() {}); + // } + // }); + // + // // 创建缩放动画 + // _scalegiftAnimation1 = Tween( + // begin: 1, + // end: 1.2, // 可以根据需要调整 + // ).animate( + // CurvedAnimation( + // parent: _giftController, + // curve: const Interval(0, 0.4, curve: Curves.easeInOut), + // ), + // ); + // _scalegiftAnimation2 = Tween( + // begin: 1.2, + // end: 1, // 可以根据需要调整 + // ).animate( + // CurvedAnimation( + // parent: _giftController, + // curve: const Interval(0.4, 0.8, curve: Curves.easeInOut), + // ), + // ); + // } +} + +class _LuckOtherFanOutTask { + final Offset sourceCenter; + final Offset targetCenter; + final String giftUrl; + + const _LuckOtherFanOutTask({ + required this.sourceCenter, + required this.targetCenter, + required this.giftUrl, + }); +} diff --git a/lib/chatvibe_ui/widgets/room/invite/invite_room_dialog.dart b/lib/chatvibe_ui/widgets/room/invite/invite_room_dialog.dart new file mode 100644 index 0000000..a197834 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/invite/invite_room_dialog.dart @@ -0,0 +1,138 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_data/models/message/at_floating_message.dart'; +import 'package:aslan/main.dart'; + +class InviteRoomDialog extends StatefulWidget { + ATFloatingMessage msg; + + InviteRoomDialog(this.msg); + + @override + _InviteRoomDialogState createState() => _InviteRoomDialogState(); +} + +class _InviteRoomDialogState extends State { + Timer? _timer; + int countdown = 10; + + @override + void initState() { + super.initState(); + _startTimer(); + } + + /// 启动倒计时 + void _startTimer() { + // 计时器(`Timer`)组件的定期(`periodic`)构造函数,创建一个新的重复计时器。 + _timer = Timer.periodic(Duration(seconds: 1), (timer) { + if (countdown == 0) { + _cancelTimer(); + SmartDialog.dismiss(tag: "showInviteRoom"); + return; + } + countdown--; + setState(() {}); + }); + } + + /// 取消倒计时的计时器。 + void _cancelTimer() { + // 计时器(`Timer`)组件的取消(`cancel`)方法,取消计时器。 + _timer?.cancel(); + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + width: ScreenUtil().screenWidth * 0.8, + padding: EdgeInsets.all(15.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(12.w)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 3.w), + Row( + children: [ + Spacer(), + text("$countdown", textColor: Colors.black87, fontSize: 14.sp), + SizedBox(width: 3.w), + GestureDetector( + child: Image.asset( + "atu_images/general/at_icon_clear_c.png", + width: 18.w, + ), + onTap: () { + SmartDialog.dismiss(tag: "showInviteRoom"); + }, + ), + ], + ), + netImage( + url: widget.msg.userAvatarUrl ?? "", + width: 80.w, + height: 80.w, + shape: BoxShape.circle, + ), + SizedBox(height: 10.w), + Row( + children: [ + Expanded( + child: text( + ATAppLocalizations.of(context)!.inviteGoRoomTips, + textColor: Colors.black87, + fontSize: 15.sp, + maxLines: 5, + ), + ), + ], + ), + SizedBox(height: 10.w), + ATDebounceWidget( + child: Container( + height: 45.w, + margin: EdgeInsets.symmetric(horizontal: 35.w), + alignment: Alignment.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/room/at_icon_inv_go_btn.png"), + fit: BoxFit.fill, + ), + ), + child: text( + ATAppLocalizations.of(context)!.enterTheRoom, + textColor: Colors.white, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showInviteRoom"); + ATRoomUtils.goRoom(widget.msg.roomId ?? "", navigatorKey.currentState!.context); + }, + ), + ], + ), + ); + } +} + +class _timer {} diff --git a/lib/chatvibe_ui/widgets/room/join_room_member_page.dart b/lib/chatvibe_ui/widgets/room/join_room_member_page.dart new file mode 100644 index 0000000..c5e2702 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/join_room_member_page.dart @@ -0,0 +1,213 @@ +import 'dart:ui' as ui; + +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; + +class JoinRoomMemberPage extends StatefulWidget { + String roomId; + Function() joinMember; + Function() enterRoom; + Function() close; + + JoinRoomMemberPage(this.roomId, this.joinMember, this.close, this.enterRoom); + + @override + _JoinRoomMemberPageState createState() => _JoinRoomMemberPageState(); +} + +class _JoinRoomMemberPageState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + return Scaffold( + backgroundColor: Colors.transparent, + body: Container( + margin: EdgeInsets.symmetric(horizontal: 25.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(12.w)), + // 可选:添加背景色来覆盖直角阴影 + color: Colors.transparent, // 或者设置一个透明背景 + ), + child: Column( + children: [ + Spacer(), + ClipRRect( + // 添加这层 ClipRRect + borderRadius: BorderRadius.all(Radius.circular(12.w)), + child: Stack( + children: [ + Container( + padding: EdgeInsets.all(25.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(12.w)), + ), + child: Column( + children: [ + SizedBox(height: 10.w), + netImage( + url: + ref + .currenRoom + ?.roomProfile + ?.roomProfile + ?.roomCover ?? + "", + borderRadius: BorderRadius.all( + Radius.circular(6.w), + ), + width: 68.w, + ), + SizedBox(height: 5.w), + text( + "${ref.currenRoom?.roomProfile?.roomProfile?.roomName}", + fontSize: 16.sp, + textColor: Colors.black, + fontWeight: FontWeight.w600, + ), + SizedBox(height: 3.w), + text( + "ID:${ref.currenRoom?.roomProfile?.roomProfile?.roomAccount}", + fontSize: 14.sp, + textColor: Colors.black54, + ), + SizedBox(height: 10.w), + Row( + children: [ + Expanded( + child: ATDebounceWidget( + debounceTime: Duration(milliseconds: 800), + child: Container( + padding: EdgeInsets.symmetric( + vertical: 10.w, + horizontal: 10.w, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(99), + color: Colors.transparent, + border: Border.all( + color: Color(0xffFFBC01), + width: 1.w, + ), + ), + alignment: Alignment.center, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + text( + "Join(", + fontWeight: FontWeight.w600, + textColor: + ChatVibeTheme.primaryLight, + fontSize: 16.sp, + ), + Image.asset( + "atu_images/general/at_icon_jb.png", + width: 20.w, + height: 20.w, + ), + text( + "${ref.currenRoom?.roomProfile?.roomSetting?.joinGolds})", + fontWeight: FontWeight.w600, + textColor: Color(0xffFFBC01), + fontSize: 16.sp, + ), + ], + ), + ), + onTap: () { + widget.joinMember(); + SmartDialog.dismiss( + tag: "showJoinRoomMember", + ); + }, + ), + ), + SizedBox(width: 15.w), + Expanded( + child: ATDebounceWidget( + debounceTime: Duration(milliseconds: 800), + child: Container( + alignment: Alignment.center, + padding: EdgeInsets.symmetric( + vertical: 10.w, + horizontal: 10.w, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(99), + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color(0xffFEB219), + Color(0xffFF9326), + ], + ), + ), + child: text( + ATAppLocalizations.of(context)!.enter, + fontWeight: FontWeight.w600, + textColor: Colors.white, + fontSize: 16.sp, + ), + ), + onTap: () { + SmartDialog.dismiss( + tag: "showJoinRoomMember", + ); + widget.enterRoom(); + }, + ), + ), + ], + ), + ], + ), + ), + PositionedDirectional( + start: 10.w, + top: 10.w, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + padding: EdgeInsets.all(3.w), + child: Image.asset( + "atu_images/general/at_icon_clear_c.png", + width: 20.w, + height: 20.w, + ), + ), + onTap: () { + widget.close(); + SmartDialog.dismiss(tag: "showJoinRoomMember"); + }, + ), + ), + ], + ), + ), + Spacer(), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/lucky_gift_tag_widget.dart b/lib/chatvibe_ui/widgets/room/lucky_gift_tag_widget.dart new file mode 100644 index 0000000..e78163a --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/lucky_gift_tag_widget.dart @@ -0,0 +1,361 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_managers/gift_system_manager.dart'; + +import '../../../chatvibe_data/models/enum/at_vip_type.dart'; + +class LuckyGiftTagWidget extends StatefulWidget { + @override + _LuckyGiftTagWidgetState createState() => _LuckyGiftTagWidgetState(); +} + +class _LuckyGiftTagWidgetState extends State { + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + return ref.hideLGiftAnimal && ref.gift != null + ? Container( + width: ScreenUtil().screenWidth * 0.98, + child: Stack( + alignment: AlignmentDirectional.centerStart, + clipBehavior: Clip.none, + children: [ + Container( + //padding: EdgeInsets.symmetric(vertical: 5.w), + width: ScreenUtil().screenWidth * 0.98, + height: hasBg() ? 98.w : 52.w, + child: Stack( + children: [ + Transform.flip( + flipX: + ATGlobalConfig.lang == "ar" + ? true + : false, // 水平翻转 + flipY: false, // 垂直翻转设为 false + child: Image.asset( + getBg(AccountStorage().getVIP()?.name ?? ""), + width: + hasBg() + ? ScreenUtil().screenWidth * 0.76 + : ScreenUtil().screenWidth * 0.98, + height: hasBg() ? 98.w : 52.w, + fit: BoxFit.fill, + ), + ), + Container( + margin: EdgeInsets.only(top: 8.w), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: + AccountStorage().getVIP()?.name == + ATVIPType.MARQUIS.name + ? 16.w + : (AccountStorage().getVIP()?.name == + ATVIPType.DUKE.name + ? 20 + : (AccountStorage().getVIP()?.name == + ATVIPType.KING.name + ?27.w:5.w)), + ), + Container( + margin: EdgeInsetsDirectional.only( + bottom: hasBg() ? 5.w : 0.w, + ), + child: netImage( + url: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userAvatar ?? + "", + fit: BoxFit.cover, + width: 47.w, + height: 47.w, + shape: BoxShape.circle, + ), + ), + SizedBox(width: hasBg() ? 20.w : 4.w), + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + chatvibeNickNameText( + maxWidth: 100.w, + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname ?? + "", + fontSize: 13.sp, + fontWeight: FontWeight.w500, + textColor: Color(0xffFFD400), + type: + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.getVIP() + ?.name ?? + "", + needScroll: + (AccountStorage() + .getCurrentUser() + ?.userProfile + ?.userNickname + ?.characters + .length ?? + 0) > + 10, + ), + //SizedBox(width: 2.w,), + Row( + children: [ + Text( + "${ATAppLocalizations.of(context)!.sendTo} ", + style: TextStyle( + fontSize: 11.sp, + color: Colors.white, + height: 1.0, + ), + ), + ConstrainedBox( + constraints: BoxConstraints( + maxWidth: 76.w, + ), + child: Text( + ref.isManyPeople + ? ATAppLocalizations.of( + context, + )!.multiple + : ref + .toUser + ?.user + ?.userNickname ?? + "", + maxLines: 1, + overflow: TextOverflow.ellipsis, + + style: TextStyle( + fontSize: 13.sp, + color: Color(0xffFFD400), + ), + ), + ), + ], + ), + ], + ), + SizedBox(width: 4.w), + ], + ), + ), + ], + ), + ), + PositionedDirectional( + start: 185.w, + child: TweenAnimationBuilder( + duration: const Duration(milliseconds: 100), + tween: Tween(begin: 1.0, end: ref.giftAnimSize), + builder: (context, scale, child) { + return Transform.scale(scale: scale, child: child); + }, + onEnd: () { + setState(() { + ref.giftAnimSize = 1.0; + }); + }, + child: Row( + children: [ + Transform.scale( + scale: 1.4, + child: netImage( + url: ref.gift?.giftPhoto ?? "", + fit: BoxFit.cover, + borderRadius: BorderRadius.circular(4.w), + width: 38.w, + height: 38.w, + ), + ), + SizedBox(width: 7.w), + Text.rich( + TextSpan( + children: [ + TextSpan( + text: "x", + style: TextStyle( + fontSize: 21.sp, + fontStyle: FontStyle.italic, + color: Color(0xffFFD400), + fontWeight: FontWeight.bold, + ), + ), + TextSpan( + text: "${ref.number}", + style: TextStyle( + fontSize: 20.sp, + fontStyle: FontStyle.italic, + color: Color(0xffFFD400), + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ], + ), + ), + ), + PositionedDirectional( + end: 0.w, + child: Stack( + alignment: Alignment.topCenter, + children: [ + Container( + alignment: Alignment.center, + width: 90.w, + height: 40.w, + margin: EdgeInsets.only(bottom: hasBg() ? 5.w : 0.w), + padding: EdgeInsets.only(top: 12.w), + decoration: BoxDecoration( + image: + getGiftObtCoinsBg( + ref.luckGiftObtainCoins, + ).isEmpty + ? null + : DecorationImage( + image: AssetImage( + getGiftObtCoinsBg( + ref.luckGiftObtainCoins, + ), + ), + fit: BoxFit.fill, + ), + ), + child: ShaderMask( + shaderCallback: (Rect bounds) { + // 定义渐变区域和颜色 + return LinearGradient( + colors: [ + Color(0xFFFFDF6B), + Color(0xFFFFE562), + Color(0xFFFFFFFF), + Color(0xFFFFE562), + Color(0xFFFFA615), + ], + // 渐变颜色数组 + begin: Alignment.topCenter, + // 渐变起点 + end: Alignment.bottomCenter, + // 渐变终点 + tileMode: TileMode.clamp, // 渐变模式 + ).createShader(bounds); + }, + blendMode: BlendMode.srcIn, // 关键:将渐变应用到文字内容 + child: TweenAnimationBuilder( + duration: const Duration(milliseconds: 100), + tween: Tween( + begin: 1.0, + end: ref.obtainCoinsAnimSize, + ), + builder: (context, scale, child) { + return Transform.scale( + scale: scale, + child: child, + ); + }, + onEnd: () { + setState(() { + ref.obtainCoinsAnimSize = 1.0; + }); + }, + child: text( + "+${ref.luckGiftObtainCoins}", + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + Transform.translate( + offset: Offset(0, -26), + child: Image.asset( + "atu_images/room/at_icon_luckgift_coins_anim.webp", + height: 40.w, + ), + ), + ref.awardAmount > 0 + ? PositionedDirectional( + end: 5.w, + child: Transform.translate( + offset: Offset(0, -10), + child: TweenAnimationBuilder( + duration: const Duration(milliseconds: 150), + tween: Tween( + begin: 1.0, + end: ref.awardAmountAnimSize, + ), + builder: (context, scale, child) { + return Transform.scale( + scale: scale, + child: child, + ); + }, + onEnd: () { + setState(() { + ref.awardAmountAnimSize = 0; + }); + }, + child: text( + "+${ref.awardAmount}", + textColor: Colors.yellowAccent, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + ) + : Container(), + ], + ), + ), + ], + ), + ) + : Container(); + }, + ); + } + + bool hasBg() { + if (AccountStorage().getVIP()?.name == ATVIPType.MARQUIS.name) { + return true; + } else if (AccountStorage().getVIP()?.name == ATVIPType.DUKE.name) { + return true; + } else if (AccountStorage().getVIP()?.name == ATVIPType.KING.name) { + return true; + } + return false; + } + + String getBg(String type) { + String bg = "atu_images/room/at_icon_room_gift_left_no_vip_bg.png"; + if (type == ATVIPType.MARQUIS.name) { + bg = "atu_images/room/at_icon_room_luck_gift_tag_vip3_bg.png"; + } else if (type == ATVIPType.DUKE.name) { + bg = "atu_images/room/at_icon_room_luck_gift_tag_vip4_bg.png"; + } else if (type == ATVIPType.KING.name) { + bg = "atu_images/room/at_icon_room_luck_gift_tag_vip5_bg.png"; + } + return bg; + } +} diff --git a/lib/chatvibe_ui/widgets/room/pwd/input_room_pwd_page.dart b/lib/chatvibe_ui/widgets/room/pwd/input_room_pwd_page.dart new file mode 100644 index 0000000..105ff02 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/pwd/input_room_pwd_page.dart @@ -0,0 +1,170 @@ +import 'dart:ui' as ui; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/pwd/room_password_box.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; + +class InputRoomPwdPage extends StatefulWidget { + String roomId; + String redPackId = ""; + bool needOpenRedenvelope = false; + + InputRoomPwdPage( + this.roomId, { + this.redPackId = "", + this.needOpenRedenvelope = false, + }); + + @override + _InputRoomPwdPageState createState() => _InputRoomPwdPageState(); +} + +class _InputRoomPwdPageState extends State { + final FocusNode _focusNode = FocusNode(); // 焦点控制 + final TextEditingController _controller = TextEditingController(); + final int _passwordLength = 5; // 密码长度 + String _enteredPassword = ''; // 已输入的密码 + + @override + void initState() { + super.initState(); + _controller.addListener(() { + final text = _controller.text; + if (text.length <= _passwordLength) { + setState(() { + _enteredPassword = text; + }); + } else { + // 如果文本长度超过限制,截断它 + _controller.text = text.substring(0, _passwordLength); + _controller.selection = TextSelection.fromPosition( + TextPosition(offset: _controller.text.length), + ); + } + }); + WidgetsBinding.instance.addPostFrameCallback((_) { + FocusScope.of(context).requestFocus(_focusNode); + }); + } + + @override + void dispose() { + _focusNode.dispose(); + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: Column( + children: [ + Expanded( + child: GestureDetector( + onTap: () { + // Navigator.pop(context); + }, + ), + ), + float(context), + ], + ), + ); + } + + Widget float(BuildContext context) { + return ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + child: Container( + height: 205.w, + decoration: BoxDecoration(color: Colors.white), + child: Stack( + children: [ + Column( + children: [ + SizedBox(height: 20.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.inputRoomPassword, + fontSize: 16.sp, + textColor: Colors.black, + fontWeight: FontWeight.w600, + ), + ], + ), + SizedBox(height: 20.w), + SizedBox( + height: 55.w, + child: RoomPasswordBox( + controller: _controller, + passwordLength: _passwordLength, + isPasswordVisible: true, + focusNode: _focusNode, + ), + ), + SizedBox(height: 20.w), + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(35.w), + color: ChatVibeTheme.primaryLight + ), + height: 40.w, + width: 120.w, + child: text( + ATAppLocalizations.of(context)!.enter, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () { + if (_enteredPassword.length == _passwordLength) { + SmartDialog.dismiss(tag: "showInputRoomPwd"); + Provider.of( + context, + listen: false, + ).joinRoom( + context, + widget.roomId, + pwd: _enteredPassword, + needOpenRedenvelope: widget.needOpenRedenvelope, + redPackId: widget.redPackId, + ); + } + }, + ), + ], + ), + Positioned( + left: 15.w, + top: 25.w, + child: GestureDetector( + child: Icon(Icons.close, color: Colors.black, size: 22.w), + onTap: () { + SmartDialog.dismiss(tag: "showInputRoomPwd"); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/pwd/room_password_box.dart b/lib/chatvibe_ui/widgets/room/pwd/room_password_box.dart new file mode 100644 index 0000000..7f4013e --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/pwd/room_password_box.dart @@ -0,0 +1,205 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import '../../../theme/chatvibe_theme.dart'; + +class RoomPasswordBox extends StatefulWidget { + final TextEditingController controller; + final int passwordLength; + final bool isPasswordVisible; + final FocusNode focusNode; + final ValueChanged? onPasswordChanged; + + const RoomPasswordBox({ + super.key, + required this.controller, + required this.passwordLength, + required this.isPasswordVisible, + required this.focusNode, + this.onPasswordChanged, + }); + + @override + State createState() => _RoomPasswordBoxState(); +} + +class _RoomPasswordBoxState extends State { + late FocusNode _rawKeyboardFocusNode; + + @override + void initState() { + super.initState(); + _rawKeyboardFocusNode = FocusNode(); + + // 监听控制器变化 + widget.controller.addListener(_onTextChanged); + + // 设置焦点节点的键盘处理 + widget.focusNode.onKey = _handleKeyEvent; + } + + @override + void dispose() { + _rawKeyboardFocusNode.dispose(); + widget.controller.removeListener(_onTextChanged); + super.dispose(); + } + + void _onTextChanged() { + setState(() {}); + widget.onPasswordChanged?.call(widget.controller.text); + } + + // 使用 FocusNode 的 onKey 处理键盘事件 + KeyEventResult _handleKeyEvent(FocusNode node, RawKeyEvent event) { + if (event is RawKeyDownEvent) { + if (event.logicalKey == LogicalKeyboardKey.backspace) { + _handleBackspace(); + return KeyEventResult.handled; // 阻止事件继续传播 + } + } + return KeyEventResult.ignored; + } + + void _handleBackspace() { + if (widget.controller.text.isEmpty) return; + + final currentText = widget.controller.text; + final currentSelection = widget.controller.selection; + + if (currentSelection.isCollapsed) { + final int cursorPosition = currentSelection.baseOffset; + if (cursorPosition > 0) { + // 删除光标前的一个字符 + final newText = + currentText.substring(0, cursorPosition - 1) + + currentText.substring(cursorPosition); + widget.controller.value = TextEditingValue( + text: newText, + selection: TextSelection.collapsed(offset: cursorPosition - 1), + ); + } + } else { + // 处理选中文本的删除 + final newText = currentText.replaceRange( + currentSelection.start, + currentSelection.end, + '', + ); + widget.controller.value = TextEditingValue( + text: newText, + selection: TextSelection.collapsed(offset: currentSelection.start), + ); + } + } + + void _handleBoxTap(int index) { + // 点击密码框时设置光标位置 + final textLength = widget.controller.text.length; + final newPosition = index < textLength ? index + 1 : textLength; + + widget.controller.selection = TextSelection.collapsed(offset: newPosition); + widget.focusNode.requestFocus(); + } + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.transparent, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 密码显示框 + Stack( + children: [ + // 自定义密码显示 + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: List.generate(widget.passwordLength, (index) { + final hasValue = index < widget.controller.text.length; + final isFocused = + index == widget.controller.text.length && + widget.focusNode.hasFocus; + + return GestureDetector( + onTap: () => _handleBoxTap(index), + child: Container( + width: 50, + height: 50, + margin: const EdgeInsets.symmetric(horizontal: 5), + decoration: BoxDecoration( + border: Border.all( + color: + isFocused + ? ChatVibeTheme.primaryLight + : Colors.grey, + width: isFocused ? 2 : 1, + ), + borderRadius: BorderRadius.circular(8), + color: Colors.white30, + ), + child: Center( + child: + hasValue + ? Text( + widget.isPasswordVisible + ? widget.controller.text[index] + : '•', + style: TextStyle( + fontSize: + widget.isPasswordVisible ? 20 : 24, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ) + : null, + ), + ), + ); + }), + ), + + // 隐藏的文本输入框,用于接收键盘输入 + Opacity( + opacity: 0, + child: TextField( + controller: widget.controller, + focusNode: widget.focusNode, + maxLength: widget.passwordLength, + keyboardType: TextInputType.number, + obscureText: !widget.isPasswordVisible, + textInputAction: TextInputAction.done, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, // 只允许数字 + ], + decoration: const InputDecoration( + counterText: '', + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + contentPadding: EdgeInsets.zero, + ), + style: const TextStyle( + color: Colors.transparent, + fontSize: 1, + ), + cursorColor: Colors.transparent, + cursorWidth: 0, + autofocus: true, + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/pwd/set_room_pwd_page.dart b/lib/chatvibe_ui/widgets/room/pwd/set_room_pwd_page.dart new file mode 100644 index 0000000..de8dcf0 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/pwd/set_room_pwd_page.dart @@ -0,0 +1,226 @@ +import 'dart:ui' as ui; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_debouncer/flutter_debouncer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/pwd/room_password_box.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; + +import '../../../theme/chatvibe_theme.dart'; + +class SetRoomPwdPage extends StatefulWidget { + @override + _SetRoomPwdPageState createState() => _SetRoomPwdPageState(); +} + +class _SetRoomPwdPageState extends State { + final FocusNode _focusNode = FocusNode(); // 焦点控制 + final TextEditingController _controller = TextEditingController(); + final int _passwordLength = 5; // 密码长度 + String _enteredPassword = ''; // 已输入的密码 + + @override + void initState() { + super.initState(); + _controller.addListener(() { + final text = _controller.text; + if (text.length <= _passwordLength) { + setState(() { + _enteredPassword = text; + }); + } else { + // 如果文本长度超过限制,截断它 + _controller.text = text.substring(0, _passwordLength); + _controller.selection = TextSelection.fromPosition( + TextPosition(offset: _controller.text.length), + ); + } + }); + WidgetsBinding.instance.addPostFrameCallback((_) { + FocusScope.of(context).requestFocus(_focusNode); + }); + } + + @override + void dispose() { + _focusNode.dispose(); + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black12, + body: Container( + margin: EdgeInsets.symmetric(horizontal: 8.w), + child: Column( + children: [ + Expanded( + child: GestureDetector( + onTap: () { + Navigator.pop(context); + }, + ), + ), + float(context), + Expanded( + child: GestureDetector( + onTap: () { + Navigator.pop(context); + }, + ), + ), + ], + ),), + ); + } + + Widget float(BuildContext context) { + return ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + bottomLeft: Radius.circular(12.w), + bottomRight: Radius.circular(12.w), + ), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 15, sigmaY: 15), + child: Container( + height: 205.w, + decoration: BoxDecoration(color: Colors.white), + child: Stack( + children: [ + Column( + children: [ + SizedBox(height: 20.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.setRoomPassword, + fontSize: 16.sp, + textColor: Colors.black, + fontWeight: FontWeight.w600, + ), + ], + ), + SizedBox(height: 20.w), + SizedBox( + height: 55.w, + child: RoomPasswordBox( + controller: _controller, + passwordLength: _passwordLength, + isPasswordVisible: true, + focusNode: _focusNode, + ), + ), + SizedBox(height: 20.w), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(35.w), + color: Colors.transparent, + border: Border.all( + color: ChatVibeTheme.primaryLight, + width: 1.w, + ), + ), + height: 40.w, + width: 120.w, + child: text( + ATAppLocalizations.of(context)!.confirm, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + textColor: ChatVibeTheme.primaryLight, + ), + ), + onTap: () { + if (_enteredPassword.length == _passwordLength) { + ChatRoomRepository() + .roomLocked( + Provider.of( + context, + listen: false, + ) + .currenRoom + ?.roomProfile + ?.roomProfile + ?.id ?? + "", + _enteredPassword, + ) + .whenComplete(() { + Provider.of( + context, + listen: false, + ).loadRoomInfo( + Provider.of( + context, + listen: false, + ) + .currenRoom + ?.roomProfile + ?.roomProfile + ?.id ?? + "", + ); + Navigator.pop(context); + }); + } + }, + ), + SizedBox(width: 10.w), + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(35.w), + color: ChatVibeTheme.primaryLight, + ), + height: 40.w, + width: 120.w, + child: text( + ATAppLocalizations.of(context)!.cancel, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () { + Navigator.pop(context); + }, + ), + ], + ), + ], + ), + // Positioned( + // left: 15.w, + // top: 25.w, + // child: GestureDetector( + // child: Icon(Icons.close, color: Colors.white, size: 22.w), + // onTap: () { + // Navigator.pop(context); + // }, + // ), + // ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/recommend_banner_view.dart b/lib/chatvibe_ui/widgets/room/recommend_banner_view.dart new file mode 100644 index 0000000..1fad520 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/recommend_banner_view.dart @@ -0,0 +1,95 @@ +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_core/utilities/at_banner_utils.dart'; +import 'package:aslan/chatvibe_core/utilities/at_string_utils.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/pop/pop_route.dart'; + +class RecommendBannerView extends StatefulWidget { + @override + _RecommendBannerViewState createState() => _RecommendBannerViewState(); +} + +class _RecommendBannerViewState extends State { + int _currentIndex = 0; + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + return _banner(ref); + }, + ); + } + + _banner(AppGeneralManager ref) { + if (ref.homeBanners.isEmpty) { + return Container( + height: 118.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + color: Color(0xfff1f1f1), + ), + ); + } + return Stack( + alignment: AlignmentDirectional.bottomCenter, + children: [ + CarouselSlider( + options: CarouselOptions( + height: 118.w, + autoPlay: true, + // 启用自动播放 + enlargeCenterPage: false, + // 居中放大当前页面 + enableInfiniteScroll: true, + // 启用无限循环 + autoPlayAnimationDuration: Duration(milliseconds: 800), + // 自动播放动画时长 + viewportFraction: 1, + // 视口分数 + onPageChanged: (index, reason) { + _currentIndex = index; + setState(() {}); + }, + ), + items: + ref.homeBanners.map((item) { + return GestureDetector( + child: netImage( + url: item.cover ?? "", + height: 118.w, + fit: BoxFit.fill, + borderRadius: BorderRadius.circular(12.w), + ), + onTap: () { + print('ads:${item.toJson()}'); + ATBannerUtils.openBanner(item,context); + }, + ); + }).toList(), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: + ref.homeBanners.asMap().entries.map((entry) { + return Container( + width: 6.0, + height: 6.0, + margin: EdgeInsets.symmetric(vertical: 8.0, horizontal: 4.0), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: + _currentIndex == entry.key ? Colors.blue : Colors.white, + ), + ); + }).toList(), + ), + ], + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/redpack/room_redenvelope_config_page.dart b/lib/chatvibe_ui/widgets/room/redpack/room_redenvelope_config_page.dart new file mode 100644 index 0000000..440318f --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/redpack/room_redenvelope_config_page.dart @@ -0,0 +1,576 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_domain/models/res/join_room_res.dart'; +import 'package:aslan/chatvibe_domain/usecases/custom_tab_selector.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; + +import '../../../../chatvibe_managers/user_profile_manager.dart'; + +class RoomRedenvelopeConfigPage extends StatefulWidget { + int sourceType = 1; + num? rewardCoins; + + RoomRedenvelopeConfigPage(this.sourceType, {this.rewardCoins}); + + @override + _RoomRedenvelopeConfigPageState createState() => + _RoomRedenvelopeConfigPageState(); +} + +class _RoomRedenvelopeConfigPageState extends State { + JoinRoomRes? room; + List coinsTitles = ["3777", "13777", "33777", "77777"]; + List numberTitles = ["10", "20", "30", "50"]; + + // List countdownMinutesTitles = ["1", "3", "5", "10"]; + String selecteCoins = "3777"; + String selecteNum = "10"; + String selectecountdownMinutes = "10"; + + @override + void initState() { + super.initState(); + room = Provider.of(context!, listen: false).currenRoom; + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: Container( + width: ScreenUtil().screenWidth, + constraints: BoxConstraints(minHeight: 560.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_red_envelope_config_bg2.png", + ), + fit: BoxFit.fill, + ), + ), + margin: EdgeInsets.symmetric(horizontal: 8.w), + padding: EdgeInsets.symmetric(horizontal: 42.w), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Spacer(), + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + color: Colors.transparent, + padding: EdgeInsets.all(3.w), + child: Icon(Icons.close, size: 22.w, color: Colors.white), + ), + onTap: () { + SmartDialog.dismiss(tag: "showRedEnvelopeConfig"); + }, + ), + ], + ), + SizedBox(height: 35.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + "atu_images/room/at_icon_config_red_bag_text.png", + height: 130.w, + ), + ], + ), + SizedBox(height: 5.w), + text( + ATAppLocalizations.of(context)!.redEnvelopeTips1, + maxLines: 2, + fontWeight: FontWeight.w600, + textColor: Color(0xff532909), + fontSize: 14.sp, + ), + text( + ATAppLocalizations.of(context)!.canOnlyBeShown, + maxLines: 2, + letterSpacing: 0.1, + fontWeight: FontWeight.w500, + textColor: Color(0xff916311), + fontSize: 12.sp, + ), + Row( + children: [ + text( + ATAppLocalizations.of(context)!.walletBalance, + maxLines: 2, + fontWeight: FontWeight.w600, + textColor: Color(0xff532909), + fontSize: 14.sp, + ), + Spacer(), + Image.asset( + "atu_images/general/at_icon_jb.png", + width: 15.w, + height: 15.w, + ), + SizedBox(width: 3.w), + Consumer( + builder: (context, ref, child) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + text( + "${ref.myBalance}", + textColor: Color(0xff532909), + fontWeight: FontWeight.w600, + fontSize: 14.sp, + ), + ], + ); + }, + ), + ], + ), + SizedBox(height: 6.w), + + // 第一个 TabBar - 金币 + Row( + spacing: 5.w, + children: [ + Expanded( + child: GestureDetector( + child: Container( + height: 85.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + selecteCoins == coinsTitles[0] + ? "atu_images/room/at_icon_red_envelope_coins_item_bg_en.png" + : "atu_images/room/at_icon_red_envelope_coins_item_bg_no.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Image.asset( + "atu_images/general/at_icon_jb3.png", + width: 35.w, + height: 35.w, + ), + SizedBox(height: 12.w), + text( + coinsTitles[0], + fontSize: 13.sp, + fontWeight: FontWeight.w600, + textColor: + selecteCoins == coinsTitles[0] + ? Color(0xffFFBF5D) + : Color(0xff5C360C), + ), + SizedBox(height: 4.w), + ], + ), + ), + onTap: () { + selecteCoins = coinsTitles[0]; + setState(() {}); + }, + ), + ), + Expanded( + child: GestureDetector( + child: Container( + height: 85.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + selecteCoins == coinsTitles[1] + ? "atu_images/room/at_icon_red_envelope_coins_item_bg_en.png" + : "atu_images/room/at_icon_red_envelope_coins_item_bg_no.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Image.asset( + "atu_images/general/at_icon_jb3.png", + width: 35.w, + height: 35.w, + ), + SizedBox(height: 12.w), + text( + coinsTitles[1], + fontSize: 13.sp, + fontWeight: FontWeight.w600, + textColor: + selecteCoins == coinsTitles[1] + ? Color(0xffFFBF5D) + : Color(0xff5C360C), + ), + SizedBox(height: 4.w), + ], + ), + ), + onTap: () { + selecteCoins = coinsTitles[1]; + setState(() {}); + }, + ), + ), + Expanded( + child: GestureDetector( + child: Container( + height: 85.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + selecteCoins == coinsTitles[2] + ? "atu_images/room/at_icon_red_envelope_coins_item_bg_en.png" + : "atu_images/room/at_icon_red_envelope_coins_item_bg_no.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Image.asset( + "atu_images/general/at_icon_jb3.png", + width: 35.w, + height: 35.w, + ), + SizedBox(height: 12.w), + text( + coinsTitles[2], + fontSize: 13.sp, + fontWeight: FontWeight.w600, + textColor: + selecteCoins == coinsTitles[2] + ? Color(0xffFFBF5D) + : Color(0xff5C360C), + ), + SizedBox(height: 4.w), + ], + ), + ), + onTap: () { + selecteCoins = coinsTitles[2]; + setState(() {}); + }, + ), + ), + Expanded( + child: GestureDetector( + child: Container( + height: 85.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + selecteCoins == coinsTitles[3] + ? "atu_images/room/at_icon_red_envelope_coins_item_bg_en.png" + : "atu_images/room/at_icon_red_envelope_coins_item_bg_no.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Image.asset( + "atu_images/general/at_icon_jb3.png", + width: 35.w, + height: 35.w, + ), + SizedBox(height: 12.w), + text( + coinsTitles[3], + fontSize: 13.sp, + fontWeight: FontWeight.w600, + textColor: + selecteCoins == coinsTitles[3] + ? Color(0xffFFBF5D) + : Color(0xff5C360C), + ), + SizedBox(height: 4.w), + ], + ), + ), + onTap: () { + selecteCoins = coinsTitles[3]; + setState(() {}); + }, + ), + ), + ], + ), + + SizedBox(height: 3.w), + text( + ATAppLocalizations.of(context)!.number2, + maxLines: 2, + textColor: Color(0xff532909), + fontWeight: FontWeight.w600, + fontSize: 14.sp, + ), + SizedBox(height: 3.w), + // 第二个 TabBar - 数量 + Row( + spacing: 5.w, + children: [ + Expanded( + child: GestureDetector( + child: Container( + alignment: Alignment.center, + height: 65.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + selecteNum == numberTitles[0] + ? "atu_images/room/at_icon_red_envelope_num_item_bg_en.png" + : "atu_images/room/at_icon_red_envelope_num_item_bg_no.png", + ), + fit: BoxFit.fill, + ), + ), + child: text( + numberTitles[0], + fontSize: 20.sp, + fontWeight: FontWeight.w600, + textColor: + selecteNum == numberTitles[0] + ? Color(0xffFFBF5D) + : Color(0xff5C360C), + ), + ), + onTap: () { + selecteNum = numberTitles[0]; + setState(() {}); + }, + ), + ), + Expanded( + child: GestureDetector( + child: Container( + alignment: Alignment.center, + height: 65.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + selecteNum == numberTitles[1] + ? "atu_images/room/at_icon_red_envelope_num_item_bg_en.png" + : "atu_images/room/at_icon_red_envelope_num_item_bg_no.png", + ), + fit: BoxFit.fill, + ), + ), + child: text( + numberTitles[1], + fontSize: 20.sp, + fontWeight: FontWeight.w600, + textColor: + selecteNum == numberTitles[1] + ? Color(0xffFFBF5D) + : Color(0xff5C360C), + ), + ), + onTap: () { + selecteNum = numberTitles[1]; + setState(() {}); + }, + ), + ), + Expanded( + child: GestureDetector( + child: Container( + alignment: Alignment.center, + height: 65.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + selecteNum == numberTitles[2] + ? "atu_images/room/at_icon_red_envelope_num_item_bg_en.png" + : "atu_images/room/at_icon_red_envelope_num_item_bg_no.png", + ), + fit: BoxFit.fill, + ), + ), + child: text( + numberTitles[2], + fontSize: 20.sp, + fontWeight: FontWeight.w600, + textColor: + selecteNum == numberTitles[2] + ? Color(0xffFFBF5D) + : Color(0xff5C360C), + ), + ), + onTap: () { + selecteNum = numberTitles[2]; + setState(() {}); + }, + ), + ), + Expanded( + child: GestureDetector( + child: Container( + alignment: Alignment.center, + height: 65.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + selecteNum == numberTitles[3] + ? "atu_images/room/at_icon_red_envelope_num_item_bg_en.png" + : "atu_images/room/at_icon_red_envelope_num_item_bg_no.png", + ), + fit: BoxFit.fill, + ), + ), + child: text( + numberTitles[3], + fontSize: 20.sp, + fontWeight: FontWeight.w600, + textColor: + selecteNum == numberTitles[3] + ? Color(0xffFFBF5D) + : Color(0xff5C360C), + ), + ), + onTap: () { + selecteNum = numberTitles[3]; + setState(() {}); + }, + ), + ), + ], + ), + SizedBox(height: 3.w), + Row( + children: [ + SizedBox(width: 10.w), + Expanded( + child: text( + ATAppLocalizations.of(context)!.redEnvelopeTips2, + maxLines: 5, + letterSpacing: 0.5, + lineHeight: 1.1, + textColor: Color(0xff916311), + fontSize: 10.sp, + ), + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 8.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_config_red_bag_btn_bg.png", + ), + fit: BoxFit.fill, + ), + ), + width: 135.w, + height: 42.w, + child: text( + ATAppLocalizations.of(context)!.send, + fontSize: 20.sp, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showConfirmDialog"); + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.sendRedPackConfirmTips, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + sendRedPack(); + }, + ); + }, + ); + }, + ), + ], + ), + SizedBox(height: 20.w), + ], + ), + ), + ); + } + + void sendRedPack() { + ATLoadingManager.exhibitOperation(); + if (widget.sourceType == 1) { + ChatRoomRepository() + .roomSendRedPacket( + room?.roomProfile?.roomProfile?.id ?? "", + selecteCoins, + selecteNum, + selectecountdownMinutes, + ) + .then((result) { + ATTts.show(ATAppLocalizations.of(context)!.operationSuccessful); + ATLoadingManager.veilRoutine(); + SmartDialog.dismiss(tag: "showRedEnvelopeConfig"); + Provider.of( + context!, + listen: false, + ).loadRoomRedPacketList(1); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } else { + ChatRoomRepository() + .sendPlatformRedPack( + room?.roomProfile?.roomProfile?.id ?? "", + "${widget.rewardCoins ?? 0}", + selecteNum, + selectecountdownMinutes, + ) + .then((result) { + ATTts.show(ATAppLocalizations.of(context)!.operationSuccessful); + ATLoadingManager.veilRoutine(); + SmartDialog.dismiss(tag: "showRedEnvelopeConfig"); + Provider.of( + context!, + listen: false, + ).loadRoomRedPacketList(1); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } + } +} diff --git a/lib/chatvibe_ui/widgets/room/redpack/room_redenvelope_list_page.dart b/lib/chatvibe_ui/widgets/room/redpack/room_redenvelope_list_page.dart new file mode 100644 index 0000000..02a5d4d --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/redpack/room_redenvelope_list_page.dart @@ -0,0 +1,435 @@ +import 'dart:async'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/redpack/room_redenvelope_rec_record_page.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/at_page_list.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/utilities/at_reg_exp_utils.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; + +import '../../../../chatvibe_domain/models/res/at_room_red_packet_list_res.dart'; + +class RoomRedenvelopeListPage extends ATPageList { + @override + _RoomRedenvelopeListPageState createState() => + _RoomRedenvelopeListPageState(); +} + +class _RoomRedenvelopeListPageState + extends ATPageListState { + Timer? _globalCountdownTimer; + Map _packetCountdowns = {}; // 存储每个红包的倒计时秒数 + + @override + void initState() { + super.initState(); + enablePullUp = true; + isShowFooter = false; + enablePullDown = false; + backgroundColor = Colors.transparent; + loadData(1); + } + + // 启动所有红包的倒计时 + void _startAllCountdowns(List packets) { + // 初始化所有红包的倒计时 + for (var packet in packets) { + String packetId = packet.packetId ?? packet.hashCode.toString(); + int countdown = packet.countdown ?? 0; + + // 只有当倒计时大于0时才添加 + if (countdown > 0) { + _packetCountdowns[packetId] = countdown; + } + } + + // 如果已经有计时器在运行,先取消 + _globalCountdownTimer?.cancel(); + + // 如果没有需要倒计时的红包,直接返回 + if (_packetCountdowns.isEmpty) { + return; + } + + // 启动全局计时器 + _globalCountdownTimer = Timer.periodic(Duration(seconds: 1), (timer) { + setState(() { + bool hasActiveCountdown = false; + + // 更新所有红包的倒计时 + _packetCountdowns.forEach((packetId, seconds) { + if (seconds > 0) { + _packetCountdowns[packetId] = seconds - 1; + hasActiveCountdown = true; + } + }); + + // 如果没有活跃的倒计时,停止计时器 + if (!hasActiveCountdown) { + timer.cancel(); + _globalCountdownTimer = null; + } + }); + }); + } + + // 添加单个红包的倒计时(用于后续操作,如新增红包) + void _addCountdown(String packetId, int seconds) { + if (seconds > 0) { + _packetCountdowns[packetId] = seconds; + _ensureTimerRunning(); + } + } + + // 确保计时器在运行 + void _ensureTimerRunning() { + if (_globalCountdownTimer == null || !_globalCountdownTimer!.isActive) { + _startTimer(); + } + } + + // 启动计时器 + void _startTimer() { + _globalCountdownTimer?.cancel(); + + _globalCountdownTimer = Timer.periodic(Duration(seconds: 1), (timer) { + setState(() { + bool hasActiveCountdown = false; + + _packetCountdowns.forEach((packetId, seconds) { + if (seconds > 0) { + _packetCountdowns[packetId] = seconds - 1; + hasActiveCountdown = true; + } + }); + + if (!hasActiveCountdown) { + timer.cancel(); + _globalCountdownTimer = null; + } + }); + }); + } + + // 格式化时间显示 + String _formatCountdown(int seconds, num remainCount, num totalCount) { + int minutes = seconds ~/ 60; + int remainingSeconds = seconds % 60; + return ATAppLocalizations.of(context)!.collectionTimeTips( + "${minutes.toString().padLeft(2, '0')}:${remainingSeconds.toString().padLeft(2, '0')}", + "$remainCount", + "$totalCount", + ); + } + + // 获取指定红包的倒计时 + int _getCountdownForPacket(ATRoomRedPacketListRes res) { + String packetId = res.packetId ?? res.hashCode.toString(); + return _packetCountdowns[packetId] ?? 0; + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + height: 460.w, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_red_envelope_config_bg.png", + ), + fit: BoxFit.fill, + ), + ), + margin: EdgeInsets.symmetric(horizontal: 15.w), + padding: EdgeInsets.symmetric(horizontal: 15.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 15.w), + Row( + children: [ + Spacer(), + Image.asset( + "atu_images/room/at_icon_red_envelope_title.png", + height: 35.w, + ), + Spacer(), + ], + ), + SizedBox(height: 5.w), + text( + ATAppLocalizations.of(context)!.redEnvelopeSendingRecords, + maxLines: 2, + textColor: Color(0xff532909), + fontSize: 14.sp, + ), + SizedBox(height: 10.w), + Expanded(child: buildList(context)), + SizedBox(height: 40.w), + ], + ), + ), + SizedBox(height: 12.w), + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 1.w), + ), + padding: EdgeInsets.all(3.w), + child: Icon(Icons.close, size: 22.w, color: Colors.white), + ), + onTap: () { + SmartDialog.dismiss(tag: "showRoomRedenvelopeList"); + }, + ), + ], + ), + ); + } + + @override + builderDivider() { + return Container(height: 0.w); + } + + @override + Widget buildItem(ATRoomRedPacketListRes res) { + int countdown = _getCountdownForPacket(res); + + return Container( + padding: EdgeInsets.only(top: 20.w, bottom: 30.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_red_envelope_rec_item_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Row( + children: [ + SizedBox(width: 15.w), + Stack( + alignment: AlignmentDirectional.bottomEnd, + children: [ + Padding( + padding: EdgeInsetsDirectional.only(bottom: 3.w, end: 3.w), + child: Image.asset( + res.sourceType == 1 + ? "atu_images/room/at_icon_room_redpack_tag.png" + : "atu_images/room/at_icon_red_envelope_rew.png", + width: 38.w, + ), + ), + PositionedDirectional( + child: head(url: res.userAvatar ?? "", width: 22.w), + bottom: 0, + end: 0, + ), + ], + ), + SizedBox(width: 3.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text( + _formatCountdown( + countdown, + res.remainCount ?? 0, + res.totalCount ?? 0, + ), + lineHeight: 1.1, + maxLines: 2, + textColor: Colors.white, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + ), + res.sourceType == 1 + ? Row( + children: [ + Container( + alignment: AlignmentDirectional.centerStart, + constraints: BoxConstraints( + maxWidth: 64.w, + maxHeight: 18.w, + ), + child: + (res.userName?.length ?? 0) > 6 + ? Marquee( + text: (res.userName ?? "").replaceAll( + ATRegExpUtils.regExNicname, + '', + ), + + style: TextStyle( + fontSize: 11.sp, + textBaseline: TextBaseline.alphabetic, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + (res.userName ?? "").replaceAll( + ATRegExpUtils.regExNicname, + '', + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 11.sp, + color: Colors.white, + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + text( + ATAppLocalizations.of(context)!.sendARedEnvelope, + textColor: Colors.white, + fontSize: 11.sp, + fontWeight: FontWeight.w600, + ), + ], + ) + : Row( + children: [ + text( + ATAppLocalizations.of( + context, + )!.ownerSendTheRedEnvelope, + textColor: Colors.white, + fontSize: 11.sp, + fontWeight: FontWeight.w600, + ), + ], + ), + ], + ), + ), + GestureDetector( + child: Container( + padding: EdgeInsets.only(bottom: 3.w), + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_red_envelope_rec_btn.png", + ), + fit: BoxFit.fill, + ), + ), + width: 66.w, + height: 32.w, + child: text( + res.status == 3 + ? ATAppLocalizations.of(context)!.expired + : ((res.grabbed ?? false) + ? ATAppLocalizations.of(context)!.received + : ATAppLocalizations.of(context)!.receive), + fontSize: 11.sp, + textColor: Colors.white, + ), + ), + onTap: () { + if ((res.grabbed) ?? false) { + SmartDialog.dismiss(tag: "showRoomRedenvelopeList"); + SmartDialog.dismiss(tag: "showRoomRedenvelopeRec"); + SmartDialog.show( + tag: "showRoomRedenvelopeRec", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return RoomRedenvelopeRecRecordPage(res.packetId ?? ""); + }, + ); + } else { + receive(res.packetId ?? ""); + } + }, + ), + SizedBox(width: 10.w), + ], + ), + ); + } + + @override + void dispose() { + _globalCountdownTimer?.cancel(); + _packetCountdowns.clear(); + super.dispose(); + } + + ///加载数据 + @override + loadPage({ + required int page, + required Function(List) onSuccess, + Function? onErr, + }) async { + try { + var redPacketList = await Provider.of( + context!, + listen: false, + ).loadRoomRedPacketList(page); + // 启动所有红包的倒计时 + _startAllCountdowns(redPacketList); + + onSuccess(redPacketList); + } catch (e) { + if (onErr != null) { + onErr(); + } + } + } + + /// 当加载更多数据时,合并倒计时 + + void onLoadMore() { + loadData(currentPage + 1); + } + + /// 刷新数据时,重置倒计时 + + void onRefresh() { + _packetCountdowns.clear(); + _globalCountdownTimer?.cancel(); + _globalCountdownTimer = null; + loadData(1); + } + + ///领取红包 + void receive(String packetId) { + ATRoomUtils.getRoomRedPacket(packetId); + } +} diff --git a/lib/chatvibe_ui/widgets/room/redpack/room_redenvelope_rec_record_page.dart b/lib/chatvibe_ui/widgets/room/redpack/room_redenvelope_rec_record_page.dart new file mode 100644 index 0000000..d7796ca --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/redpack/room_redenvelope_rec_record_page.dart @@ -0,0 +1,314 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:marquee/marquee.dart'; +import 'package:aslan/chatvibe_core/utilities/at_reg_exp_utils.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_red_packet_detail_res.dart'; + +class RoomRedenvelopeRecRecordPage extends StatefulWidget { + final String packetId; + + const RoomRedenvelopeRecRecordPage(this.packetId, {super.key}); + + @override + State createState() => + _RoomRedenvelopeRecRecordPageState(); +} + +class _RoomRedenvelopeRecRecordPageState + extends State + with TickerProviderStateMixin { + ATRoomRedPacketDetailRes? detailRes; + + @override + void initState() { + super.initState(); + ChatRoomRepository() + .roomRedPacketDetail(widget.packetId) + .then((result) { + if (!mounted) return; + detailRes = result; + setState(() {}); + }) + .catchError((e) {}); + } + + @override + Widget build(BuildContext context) { + final bool isLoading = detailRes == null; + final records = detailRes?.records ?? []; + + return SafeArea( + top: false, + child: Stack( + clipBehavior: Clip.none, + children: [ + Container( + height: 460.w, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_red_envelope_config_bg.png", + ), + fit: BoxFit.fill, + ), + ), + margin: EdgeInsetsDirectional.only( + start: 15.w, + bottom: 40.w, + end: 15.w, + top: 42.w, + ), + + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_redenvelope_rec_record_list_bg.png", + ), + fit: BoxFit.fill, + ), + ), + padding: EdgeInsets.only(left: 20.w,right: 20.w,top: 12.w,bottom: 20.w), + margin: EdgeInsetsDirectional.only( + top: 163.w, + start: 10.w, + end: 10.w, + bottom: 35.w, + ), + child: _buildRecordBody(isLoading: isLoading, records: records), + ), + ), + _buildPacketSummary(isLoading: isLoading), + PositionedDirectional( + bottom: 0, + start: 0, + end: 0, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 1.w), + ), + padding: EdgeInsets.all(3.w), + child: Icon(Icons.close, size: 22.w, color: Colors.white), + ), + onTap: () { + SmartDialog.dismiss(tag: "showRoomRedenvelopeRec"); + }, + ), + ), + ], + ), + ); + } + + Widget _buildPacketSummary({required bool isLoading}) { + final userName = (detailRes?.userName ?? "").replaceAll( + ATRegExpUtils.regExNicname, + '', + ); + final tips = + isLoading + ? "" + : ((detailRes?.grabbed ?? false) + ? ATAppLocalizations.of(context)!.redEnvelopeRecTips1 + : (detailRes?.status == 2 + ? ATAppLocalizations.of(context)!.redEnvelopeRecTips2 + : ATAppLocalizations.of(context)!.redEnvelopeRecTips3)); + + return PositionedDirectional( + top: 42.w, + start: 15.w, + end: 15.w, + child: Transform.translate( + offset: Offset(0, -44.w), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + head( + url: detailRes?.userAvatar ?? "", + border: Border.all(color: Colors.yellow, width: 2.w), + width: 88.w, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + constraints: BoxConstraints(maxWidth: 98.w, maxHeight: 22.w), + child: + userName.length > 8 + ? Marquee( + text: userName, + style: _summaryTextStyle(fontSize: 14.sp), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 20.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration(milliseconds: 500), + decelerationCurve: Curves.easeOut, + ) + : Text( + isLoading ? "" : userName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: _summaryTextStyle(fontSize: 14.sp), + ), + ), + SizedBox(width: 2.w), + text( + ATAppLocalizations.of(context)!.sendARedEnvelope, + fontSize: 14.sp, + textColor: Color(0xff532909), + fontWeight: FontWeight.w600, + ), + ], + ), + SizedBox(height: 4.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset("atu_images/general/at_icon_jb.png", width: 28.w), + SizedBox(width: 3.w), + text( + ATAppLocalizations.of(context)!.coins2( + isLoading ? "--" : "${detailRes?.totalAmount ?? 0}", + ), + textColor: Color(0xff532909), + fontSize: 15.sp, + ), + ], + ), + SizedBox(height: 4.w), + Padding( + padding: EdgeInsets.symmetric(horizontal: 32.w), + child: SizedBox( + width: double.infinity, + child: text( + tips, + maxLines: 2, + textColor: Color(0xff532909), + fontSize: 13.sp, + textAlign: TextAlign.center, + ), + ), + ), + SizedBox(height: 4.w), + Padding( + padding: EdgeInsets.symmetric(horizontal: 32.w), + child: SizedBox( + width: double.infinity, + child: text( + isLoading + ? "" + : ATAppLocalizations.of(context)!.remainingNumberTips( + "${detailRes?.remainCount ?? 0}", + "${detailRes?.totalCount ?? 0}", + ), + maxLines: 2, + textColor: Color(0xff532909), + fontSize: 13.sp, + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + ), + ); + } + + TextStyle _summaryTextStyle({required double fontSize}) { + return TextStyle( + fontSize: fontSize, + color: Color(0xff532909), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ); + } + + Widget _buildRecordBody({ + required bool isLoading, + required List records, + }) { + if (isLoading) { + return Center( + child: CupertinoActivityIndicator(color: Colors.white, radius: 10.w), + ); + } + if (records.isEmpty) { + return Center( + child: text( + ATAppLocalizations.of(context)!.noData, + textColor: Colors.white70, + fontSize: 13.sp, + fontWeight: FontWeight.w600, + ), + ); + } + return ListView.separated( + padding: EdgeInsets.zero, + itemBuilder: (context, i) => _buildItem(records[i], i), + itemCount: records.length, + separatorBuilder: (context, i) => SizedBox(height: 0.w), + ); + } + + _buildItem(Records item, int i) { + return Container( + padding: EdgeInsets.symmetric(vertical: 5.w), + child: Row( + children: [ + SizedBox(width: 5.w), + GestureDetector( + child: head(url: item.userAvatar ?? "", width: 35.w), + onTap: () {}, + ), + SizedBox(width: 3.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text( + item.userName ?? "", + textColor: Colors.yellow, + fontSize: 13.sp, + fontWeight: FontWeight.w600, + ), + text( + "ID:${item.account ?? ""}", + textColor: Colors.white, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + ), + ], + ), + ), + Row( + children: [ + Image.asset("atu_images/general/at_icon_jb.png", width: 20.w), + SizedBox(width: 3.w), + text( + "${item.amount ?? 0}", + fontSize: 13.sp, + textColor: Colors.orangeAccent, + ), + SizedBox(width: 5.w), + ], + ), + SizedBox(width: 5.w), + ], + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/rocket/room_rocket_page.dart b/lib/chatvibe_ui/widgets/room/rocket/room_rocket_page.dart new file mode 100644 index 0000000..15165c2 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/rocket/room_rocket_page.dart @@ -0,0 +1,582 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_rocket_config_res.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_room_rocket_status_res.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; + +import '../../../../chatvibe_data/models/enum/at_activity_reward_props_type.dart'; + +class RoomRocketPage extends StatefulWidget { + @override + _RoomRocketPageState createState() => _RoomRocketPageState(); +} + +class _RoomRocketPageState extends State { + ATRoomRocketConfigRes? rocketLevel1; + ATRoomRocketConfigRes? rocketLevel2; + ATRoomRocketConfigRes? rocketLevel3; + ATRoomRocketConfigRes? currernRocketLevel; + ATRoomRocketStatusRes? roomRocketStatus; + bool _isLoading = true; + + @override + void initState() { + super.initState(); + roomRocketStatus = + Provider.of(context, listen: false).roomRocketStatus; + ChatRoomRepository() + .rocketConfigEnabled() + .then((res) { + rocketLevel1 = res.first; + rocketLevel2 = res[1]; + rocketLevel3 = res[2]; + if (roomRocketStatus?.level == 1) { + currernRocketLevel = rocketLevel1; + } else if (roomRocketStatus?.level == 2) { + currernRocketLevel = rocketLevel2; + } else if (roomRocketStatus?.level == 3) { + currernRocketLevel = rocketLevel3; + } + _isLoading = false; + setState(() {}); + }) + .catchError((e) { + setState(() { + _isLoading = false; + }); + }); + } + + @override + Widget build(BuildContext context) { + return Directionality( + textDirection: TextDirection.ltr, + child: SafeArea( + top: false, + child: Container( + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight * 0.75, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/room/at_icon_room_rocket_bg.png"), + fit: BoxFit.fitWidth, + ), + ), + child: _isLoading?Container():SingleChildScrollView( + child: Stack( + alignment: AlignmentDirectional.topCenter, + children: [ + PositionedDirectional( + top: 30.w, + child: Image.asset( + "atu_images/room/at_icon_room_rocket_lv${currernRocketLevel?.level ?? 1}.png", + width: 140.w, + height: 190.w, + ), + ), + Column( + children: [ + SizedBox(height: 130.w), + GestureDetector( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Image.asset( + (currernRocketLevel?.level ?? 1) == 3 + ? "atu_images/room/at_icon_lab_lv3_en.png" + : "atu_images/room/at_icon_lab_lv3_un.png", + width: + (currernRocketLevel?.level ?? 1) == 3 + ? 80.w + : 50.w, + ), + ], + ), + onTap: () { + currernRocketLevel = rocketLevel3; + setState(() {}); + }, + ), + SizedBox(height: 10.w), + GestureDetector( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Image.asset( + (currernRocketLevel?.level ?? 1) == 2 + ? "atu_images/room/at_icon_lab_lv2_en.png" + : "atu_images/room/at_icon_lab_lv2_un.png", + width: + (currernRocketLevel?.level ?? 1) == 2 + ? 80.w + : 50.w, + ), + ], + ), + onTap: () { + currernRocketLevel = rocketLevel2; + setState(() {}); + }, + ), + SizedBox(height: 10.w), + GestureDetector( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Image.asset( + (currernRocketLevel?.level ?? 1) == 1 + ? "atu_images/room/at_icon_lab_lv1_en.png" + : "atu_images/room/at_icon_lab_lv1_un.png", + width: + (currernRocketLevel?.level ?? 1) == 1 + ? 80.w + : 50.w, + ), + ], + ), + onTap: () { + currernRocketLevel = rocketLevel1; + setState(() {}); + }, + ), + SizedBox(height: 15.w), + Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_rocket_title_bg.png", + ), + fit: BoxFit.fill, + ), + ), + margin: EdgeInsets.symmetric(horizontal: 15.w), + width: ScreenUtil().screenWidth, + height: 100.w, + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + Consumer( + builder: (context, ref, child) { + return Row( + children: [ + SizedBox(width: 22.w), + Image.asset( + "atu_images/room/at_icon_room_rocket_lv${currernRocketLevel?.level ?? 1}_text.png", + width: 25.w, + ), + SizedBox(width: 10.w), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + text( + "${ref.roomRocketStatus?.currentEnergy}/${currernRocketLevel?.maxEnergy}", + fontSize: 13.sp, + fontWeight: FontWeight.w600, + ), + Stack( + alignment: + AlignmentDirectional.centerStart, + children: [ + Container( + width: 250.w, + height: 8.w, + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(5), + color: Color(0xff17224B), + border: Border.all( + color: Color(0xffF9CE82), + width: 1.w, + ), + ), + ), + Row( + children: [ + Container( + width: ATRoomUtils.collectCurrenProgressProcess( + (ref + .roomRocketStatus + ?.currentEnergy ?? + 0), + (currernRocketLevel?.maxEnergy ?? 1), + 250.w, + ), + height: 8.w, + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(5), + gradient: LinearGradient( + colors: [ + Color(0xFF2E4AFE), + Color(0xFF04A5FF), + Color(0xFF05FEFF), + ], + ), + ), + ), + Transform.translate( + offset: Offset(-5, 0), + child: Image.asset( + "atu_images/room/at_icon_room_rocket_progress_lv${(currernRocketLevel?.level ?? 1)}_tag.png", + width: 35.w, + ), + ), + ], + ), + ], + ), + ], + ), + ], + ); + }, + ), + PositionedDirectional( + end: 30.w, + top: 20.w, + child: GestureDetector( + child: Image.asset( + "atu_images/room/at_icon_room_rocket_help.png", + width: 30.w, + ), + onTap: () { + SmartDialog.show( + tag: "showRoomRocketHelpDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return Container( + width: ScreenUtil().screenWidth, + margin: EdgeInsets.symmetric( + horizontal: 15.w, + ), + padding: EdgeInsets.symmetric( + horizontal: 20.w, + ), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_rocket_hepl_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 13.w), + Image.asset( + ATGlobalConfig.lang == "ar" + ? "atu_images/room/at_icon_room_rocket_rule_text_ar.png" + : "atu_images/room/at_icon_room_rocket_rule_text_en.png", + width: 58.w, + ), + SizedBox(height: 8.w), + text( + ATAppLocalizations.of( + context, + )!.roomRocketHelpTips, + maxLines: 25, + fontSize: 14.sp, + fontWeight: FontWeight.bold, + ), + SizedBox(height: 20.w), + ], + ), + ); + }, + ); + }, + ), + ), + ], + ), + ), + SizedBox(height: 10.w), + Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_rocket_rew_bg.png", + ), + fit: BoxFit.fill, + ), + ), + margin: EdgeInsets.symmetric(horizontal: 15.w), + width: ScreenUtil().screenWidth, + height: 170.w, + child: Row( + children: [ + SizedBox(width: 20.w), + Container( + width: 118.w, + height: 118.w, + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_rocket_rew_item_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: + (currernRocketLevel?.rewardConfig ?? []) + .isNotEmpty + ? (currernRocketLevel! + .rewardConfig! + .first + .type == + ATActivityRewardATPropsType.GOLD.name + ? Image.asset( + "atu_images/general/at_icon_jb.png", + width: 82.w, + height: 82.w, + ) + : netImage( + url: + currernRocketLevel! + .rewardConfig! + .first + .cover ?? + "", + width: 82.w, + height: 82.w, + )) + : Container(), + ), + SizedBox(width: 5.w), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + children: [ + Container( + width: 57.w, + height: 57.w, + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_rocket_rew_item_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: + (currernRocketLevel?.rewardConfig ?? []) + .length > + 1 + ? (currernRocketLevel! + .rewardConfig![1] + .type == + ATActivityRewardATPropsType + .GOLD + .name + ? Image.asset( + "atu_images/general/at_icon_jb.png", + width: 40.w, + height: 40.w, + ) + : netImage( + url: + currernRocketLevel! + .rewardConfig![1] + .cover ?? + "", + width: 40.w, + height: 40.w, + )) + : Container(), + ), + SizedBox(width: 5.w), + Container( + width: 57.w, + height: 57.w, + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_rocket_rew_item_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: + (currernRocketLevel?.rewardConfig ?? []) + .length > + 2 + ? (currernRocketLevel! + .rewardConfig![2] + .type == + ATActivityRewardATPropsType + .GOLD + .name + ? Image.asset( + "atu_images/general/at_icon_jb.png", + width: 40.w, + height: 40.w, + ) + : netImage( + url: + currernRocketLevel! + .rewardConfig![2] + .cover ?? + "", + width: 40.w, + height: 40.w, + )) + : Container(), + ), + SizedBox(width: 5.w), + Container( + width: 57.w, + height: 57.w, + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_rocket_rew_item_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: + (currernRocketLevel?.rewardConfig ?? []) + .length > + 3 + ? (currernRocketLevel! + .rewardConfig![3] + .type == + ATActivityRewardATPropsType + .GOLD + .name + ? Image.asset( + "atu_images/general/at_icon_jb.png", + width: 40.w, + height: 40.w, + ) + : netImage( + url: + currernRocketLevel! + .rewardConfig![3] + .cover ?? + "", + width: 40.w, + height: 40.w, + )) + : Container(), + ), + ], + ), + SizedBox(height: 5.w), + Row( + children: [ + Container( + width: 57.w, + height: 57.w, + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_rocket_rew_item_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: + (currernRocketLevel?.rewardConfig ?? []) + .length > + 4 + ? (currernRocketLevel! + .rewardConfig![4] + .type == + ATActivityRewardATPropsType + .GOLD + .name + ? Image.asset( + "atu_images/general/at_icon_jb.png", + width: 40.w, + height: 40.w, + ) + : netImage( + url: + currernRocketLevel! + .rewardConfig![4] + .cover ?? + "", + width: 40.w, + height: 40.w, + )) + : Container(), + ), + SizedBox(width: 5.w), + Container( + width: 57.w, + height: 57.w, + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_rocket_rew_item_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: + (currernRocketLevel?.rewardConfig ?? []) + .length > + 5 + ? (currernRocketLevel! + .rewardConfig![5] + .type == + ATActivityRewardATPropsType + .GOLD + .name + ? Image.asset( + "atu_images/general/at_icon_jb.png", + width: 40.w, + height: 40.w, + ) + : netImage( + url: + currernRocketLevel! + .rewardConfig![5] + .cover ?? + "", + width: 40.w, + height: 40.w, + )) + : Container(), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + ], + ), + ), + ), + ), + ); + } + +} diff --git a/lib/chatvibe_ui/widgets/room/rocket/room_rocket_playing_anim_page.dart b/lib/chatvibe_ui/widgets/room/rocket/room_rocket_playing_anim_page.dart new file mode 100644 index 0000000..8c0120d --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/rocket/room_rocket_playing_anim_page.dart @@ -0,0 +1,117 @@ +import 'dart:async'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_debouncer/flutter_debouncer.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:tancent_vap/utils/constant.dart'; +import 'package:tancent_vap/widgets/vap_view.dart'; +import 'package:aslan/chatvibe_core/utilities/at_gift_vap_svga_manager.dart'; + +import '../../../../chatvibe_data/models/enum/at_activity_reward_props_type.dart'; + +class RoomRocketPlayingAnimPage extends StatefulWidget { + String tagPath = ""; + num level = 1; + String rewardType = ""; + + RoomRocketPlayingAnimPage(this.tagPath, this.rewardType, this.level); + + @override + _RoomRocketPlayingAnimPageState createState() => + _RoomRocketPlayingAnimPageState(); +} + +class _RoomRocketPlayingAnimPageState extends State { + VapController? vapController; + Timer? _timer; + bool canClick = false; + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: Stack( + children: [ + SizedBox( + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + child: VapView( + scaleType: ScaleType.centerCrop, + onViewCreated: (controller) { + vapController = controller; + _initVapAnim(); + _playAnim(); + }, + ), + ), + GestureDetector( + behavior: HitTestBehavior.opaque, + child: SizedBox( + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + ), + onTap: () { + if (canClick) { + HapticFeedback.lightImpact(); + } + }, + ), + ], + ), + ); + } + + @override + void initState() { + super.initState(); + ATGiftVapSvgaManager().pauseAnim(); + } + + @override + void dispose() { + vapController?.dispose(); + + _timer?.cancel(); + ATGiftVapSvgaManager().resumeAnim(); + super.dispose(); + } + + void _playAnim() async { + try { + vapController?.stop(); + await vapController?.setVapTagContent( + "tag1", + widget.rewardType == ATActivityRewardATPropsType.GOLD.name + ? ImageAssetContent("atu_images/general/at_icon_jb.png") + : ImageURLContent(widget.tagPath), + ); + await vapController?.playAsset( + "atu_images/room/anim/room_rocket_lv${widget.level}.mp4", + ); + + } catch (e) { + SmartDialog.dismiss(tag: "showRoomRocketAnimDialog"); + } + } + + void _initVapAnim() { + vapController?.setAnimListener( + onVideoStart: () { + _timer?.cancel(); + canClick = true; + _timer ??= Timer.periodic(Duration(seconds: 10), (timer) { + canClick = false; + }); + }, + onVideoComplete: () { + SmartDialog.dismiss(tag: "showRoomRocketAnimDialog"); + }, + onFailed: (code, type, msg) { + SmartDialog.dismiss(tag: "showRoomRocketAnimDialog"); + }, + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/room_banner_view.dart b/lib/chatvibe_ui/widgets/room/room_banner_view.dart new file mode 100644 index 0000000..835b61f --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/room_banner_view.dart @@ -0,0 +1,90 @@ +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_core/utilities/at_banner_utils.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_managers/app_general_manager.dart'; + +class RoomBannerView extends StatefulWidget { + @override + _RoomBannerViewState createState() => _RoomBannerViewState(); +} + +class _RoomBannerViewState extends State { + int _currentIndex = 0; + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + return _banner(ref); + }, + ); + } + + _banner(AppGeneralManager ref) { + return ref.roomBanners.isEmpty + ? Container() + : SizedBox( + width: 46.w, + child: Column( + children: [ + CarouselSlider( + options: CarouselOptions( + height: 46.w, + autoPlay: true, + // 启用自动播放 + enlargeCenterPage: false, + // 居中放大当前页面 + aspectRatio: 1 / 1, + // 宽高比 + enableInfiniteScroll: true, + // 启用无限循环 + autoPlayAnimationDuration: Duration(milliseconds: 800), + // 自动播放动画时长 + viewportFraction: 1, + // 视口分数 + onPageChanged: (index, reason) { + _currentIndex = index; + setState(() {}); + }, + ), + items: + ref.roomBanners.map((item) { + return GestureDetector( + child: netImage(url: item.smallCover ?? ""), + onTap: () { + print('ads:${item.toJson()}'); + ATBannerUtils.openBanner(item, context); + }, + ); + }).toList(), + ), + SingleChildScrollView( + scrollDirection:Axis.horizontal, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: + ref.roomBanners.asMap().entries.map((entry) { + return Container( + width: 4.0, + height: 4.0, + margin: EdgeInsetsDirectional.fromSTEB(2, 3, 2, 0), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: + _currentIndex == entry.key + ? Colors.blue + : Colors.white, + ), + ); + }).toList(), + ), + ), + ], + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/room_bottom_widget.dart b/lib/chatvibe_ui/widgets/room/room_bottom_widget.dart new file mode 100644 index 0000000..c73ae07 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/room_bottom_widget.dart @@ -0,0 +1,265 @@ +import 'package:agora_rtc_engine/agora_rtc_engine.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_menu_dialog.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_msg_input.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/utilities/at_lk_dialog_util.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import 'package:aslan/chatvibe_features/gift/gift_page.dart'; +import 'package:aslan/chatvibe_ui/widgets/msg/bottom_message_conversation_list_page.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/emoji/room_emoji_page.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/game/room_game_list_page.dart'; + +class RoomBottomWidget extends StatefulWidget { + @override + _RoomBottomWidgetState createState() => _RoomBottomWidgetState(); +} + +class _RoomBottomWidgetState extends State { + int roomMenuStime = 0; + + @override + Widget build(BuildContext context) { + return Stack( + alignment: Alignment.center, + children: [ + Container( + height: 55.w, + margin: EdgeInsetsDirectional.only(start: 15.w), + child: Row( + children: [ + Container( + alignment: Alignment.center, + width: 95.w, + padding: EdgeInsets.symmetric(vertical: 4.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_input.png", + ), + ), + ), + child: Row( + children: [ + SizedBox(width: 5.w), + Image.asset( + "atu_images/room/at_icon_room_input_t.png", + width: 22.w, + height: 22.w, + ), + ATDebounceWidget( + child: text( + ATAppLocalizations.of(context)!.sayHi, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + ), + onTap: () { + if (ATRoomUtils.touristCanMsg(context)) { + Navigator.push( + context, + PopRoute(child: RoomMsgInput()), + ); + } + }, + ), + ], + ), + ), + SizedBox(width: 10.w), + ATDebounceWidget( + child: Image.asset( + "atu_images/room/at_icon_emoji.png", + width: 30.w, + height: 30.w, + ), + onTap: () { + if (ATRoomUtils.touristCanMsg(context)) { + SmartDialog.show( + tag: "showRoomEmojiPage", + alignment: Alignment.bottomCenter, + debounce: true, + animationType: SmartAnimationType.fade, + maskColor: Colors.transparent, + clickMaskDismiss: true, + builder: (_) { + return RoomEmojiPage(); + }, + ); + } + }, + ), + Spacer(), + Consumer( + builder: (context, ref, child) { + return _mic(ref); + }, + ), + SizedBox(width: 10.w), + GestureDetector( + child: Image.asset( + "atu_images/room/at_icon_botton_game.png", + width: 30.w, + height: 30.w, + fit: BoxFit.contain, + ), + onTap: () { + showBottomInBottomDialog(context, RoomGameListPage()); + }, + ), + SizedBox(width: 10.w), + GestureDetector( + child: Image.asset( + "atu_images/room/at_icon_botton_menu.png", + width: 30.w, + height: 30.w, + fit: BoxFit.contain, + ), + onTap: () { + SmartDialog.show( + tag: "showRoomMenuDialog", + alignment: Alignment.bottomCenter, + debounce: true, + animationType: SmartAnimationType.fade, + maskColor: Colors.transparent, + clickMaskDismiss: true, + builder: (_) { + return RoomMenuDialog(roomMenuStime, (eTime) { + roomMenuStime = eTime; + }); + }, + ); + }, + ), + SizedBox(width: 15.w), + ], + ), + ), + PositionedDirectional( + bottom: 8.w, + child: ATDebounceWidget( + onTap: () { + SmartDialog.show( + tag: "showGiftControl", + alignment: Alignment.bottomCenter, + maskColor: Colors.transparent, + animationType: SmartAnimationType.fade, + clickMaskDismiss: true, + builder: (_) { + return GiftPage(); + }, + ); + }, + child: Image.asset( + "atu_images/room/at_icon_botton_gift.png", + width: 45.w, + height: 45.w, + fit: BoxFit.contain, + ), + ), + ), + ], + ); + } + + _mic(RtcProvider provider) { + ///默认不显示 + bool show = false; + + ///在麦上 + provider.roomWheatMap.forEach((k, v) { + if (v.user?.id == AccountStorage().getCurrentUser()?.userProfile?.id) { + show = true; + } + }); + + return Visibility( + visible: show, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + setState(() { + provider.isMic = !provider.isMic; + + ///没被禁麦才显示 + provider.roomWheatMap.forEach((k, v) { + if (v.user?.id == + AccountStorage().getCurrentUser()?.userProfile?.id && + !provider.roomWheatMap[k]!.micMute!) { + if (!provider.isMic) { + Provider.of( + context, + listen: false, + ).engine?.adjustRecordingSignalVolume(100); + Provider.of( + context, + listen: false, + ).engine?.setClientRole( + role: ClientRoleType.clientRoleBroadcaster, + ); + } else { + if (Provider.of( + context, + listen: false, + ).isMusicPlaying) { + Provider.of( + context, + listen: false, + ).engine?.adjustRecordingSignalVolume(0); + Provider.of( + context, + listen: false, + ).engine?.setClientRole( + role: ClientRoleType.clientRoleBroadcaster, + ); + Provider.of( + context, + listen: false, + ).engine?.muteLocalAudioStream(false); + } else { + Provider.of( + context, + listen: false, + ).engine?.setClientRole( + role: ClientRoleType.clientRoleAudience, + ); + Provider.of( + context, + listen: false, + ).engine?.muteLocalAudioStream(provider.isMic); + } + } + } + }); + }); + }, + child: Padding( + padding: EdgeInsets.only(right: 12.w), + child: Container( + width: 30.w, + height: 30.w, + alignment: Alignment.center, + // decoration: BoxDecoration( + // color: Colors.white.withOpacity(0.2), shape: BoxShape.circle + // ), + child: Image.asset( + "atu_images/room/${provider.isMic ? 'at_icon_botton_mic_close' : 'at_icon_botton_mic_open'}.png", + width: 30.w, + fit: BoxFit.fill, + gaplessPlayback: true, + ), + ), + ), + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/room_charm_page.dart b/lib/chatvibe_ui/widgets/room/room_charm_page.dart new file mode 100644 index 0000000..3551c44 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/room_charm_page.dart @@ -0,0 +1,177 @@ +import 'dart:ui' as ui; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/join_room_res.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; + +class RoomCharmPage extends StatefulWidget { + @override + _RoomCharmPageState createState() => _RoomCharmPageState(); +} + +class _RoomCharmPageState extends State { + JoinRoomRes? room; + + @override + void initState() { + super.initState(); + room = Provider.of(context!, listen: false).currenRoom; + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 15, sigmaY: 15), + child: Container( + height: 260.w, + decoration: BoxDecoration( + color: Colors.black54, + ), + child: Column( + children: [ + SizedBox(height: 6.w), + Row( + children: [ + SizedBox(width: 27.w), + Spacer(), + text( + ATAppLocalizations.of(context)!.charm, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + Spacer(), + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + color: Colors.transparent, + padding: EdgeInsets.all(3.w), + child: Icon( + Icons.close, + size: 22.w, + color: Colors.white, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showRoomCharm"); + }, + ), + SizedBox(width: 5.w), + ], + ), + SizedBox(height: 10.w), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(width: 15.w), + text( + ATAppLocalizations.of(context)!.gameRules, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + SizedBox(height: 10.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 15.w), + alignment: AlignmentDirectional.centerStart, + padding: EdgeInsets.symmetric( + horizontal: 10.w, + vertical: 10.w, + ), + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.circular(12), + ), + child: text( + ATAppLocalizations.of(context)!.charmGameRulesTips, + textColor: Colors.white54, + maxLines: 5, + fontWeight: FontWeight.w600, + fontSize: 14.sp, + ), + ), + SizedBox(height: 20.w), + GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + width: 130.w, + height: 45.w, + decoration: BoxDecoration( + color: + (room?.roomProfile?.roomSetting?.showHeartbeat ?? + false) + ? Colors.white38 + : ChatVibeTheme.primaryLight, + borderRadius: BorderRadius.circular(30.w), + ), + child: text( + (room?.roomProfile?.roomSetting?.showHeartbeat ?? false) + ? ATAppLocalizations.of(context)!.stop + : ATAppLocalizations.of(context)!.start, + fontWeight: FontWeight.bold, + fontSize: 18.sp, + ), + ), + onTap: () { + ChatRoomRepository() + .updateRoomSetting( + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.id ?? + "", + Provider.of(context, listen: false) + .currenRoom + ?.roomProfile + ?.roomProfile + ?.roomAccount ?? + "", + context, + showHeartbeat: + !((room + ?.roomProfile + ?.roomSetting + ?.showHeartbeat) ?? + false), + ) + .whenComplete(() { + Provider.of( + context, + listen: false, + ).loadRoomInfo( + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.id ?? + "", + ); + SmartDialog.dismiss(tag: "showRoomCharm"); + }); + }, + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/room_head_widget.dart b/lib/chatvibe_ui/widgets/room/room_head_widget.dart new file mode 100644 index 0000000..b762fd0 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/room_head_widget.dart @@ -0,0 +1,272 @@ +import 'dart:ui' as ui; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:marquee/marquee.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_lk_dialog_util.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_features/room/detail/room_detail_page.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/exit_min_room_page.dart'; +import '../../../chatvibe_features/room/rank/room_gift_rank_page.dart'; +import '../../../chatvibe_managers/room_manager.dart'; + +class RoomHeadWidget extends StatefulWidget { + @override + _RoomHeadWidgetState createState() => _RoomHeadWidgetState(); +} + +class _RoomHeadWidgetState extends State { + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, provider, child) { + return Row( + children: [ + Row( + children: [ + GestureDetector( + onTap: () { + showBottomInBottomDialog( + context!, + RoomDetailPage(provider.isFz()), + ); + }, + child: Container( + padding: EdgeInsets.symmetric( + vertical: 6.w, + horizontal: 12.w, + ), + child: Row( + children: [ + Stack( + alignment: Alignment.center, + children: [ + netImage( + url: + provider + .currenRoom + ?.roomProfile + ?.roomProfile + ?.roomCover ?? + "", + width: 28.w, + height: 28.w, + borderRadius: BorderRadius.all( + Radius.circular(8.w), + ), + ), + Selector( + selector: (c, p) => p.roomIsMute, + shouldRebuild: (prev, next) => prev != next, + builder: (_, isMute, __) { + return isMute + ? ClipOval( + child: Container( + color: Colors.black54, + padding: EdgeInsets.all(5.w), + child: Image.asset( + "atu_images/room/at_icon_mic_mute.png", + height: 14.w, + width: 14.w, + ), + ), + ) + : Container(); + }, + ), + ], + ), + SizedBox(width: 6.w), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(width: 10.w), + Container( + constraints: BoxConstraints( + maxWidth: 106.w, + maxHeight: 17.w, + ), + child: + (provider + .currenRoom + ?.roomProfile + ?.roomProfile + ?.roomName + ?.length ?? + 0) > + 10 + ? Marquee( + text: + provider + .currenRoom + ?.roomProfile + ?.roomProfile + ?.roomName ?? + "", + style: TextStyle( + fontSize: 13.sp, + color: Color(0xffffffff), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: + CrossAxisAlignment.start, + blankSpace: 40.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration( + seconds: 1, + ), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration( + milliseconds: 500, + ), + decelerationCurve: Curves.easeOut, + ) + : Text( + provider + .currenRoom + ?.roomProfile + ?.roomProfile + ?.roomName ?? + '', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13.sp, + color: Color(0xffffffff), + fontWeight: FontWeight.w600, + decoration: TextDecoration.none, + ), + ), + ), + + text( + "ID:${provider.currenRoom?.roomProfile?.userProfile?.getID()}", + fontSize: 13.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white70, + ), + ], + ), + SizedBox(width: 6.w), + provider.currenRoom?.roomProfile?.roomProfile?.userId != + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id + ? Selector( + selector: + (c, p) => + p.isFollowRoomRes?.followRoom ?? false, + shouldRebuild: (prev, next) => prev != next, + builder: (_, follow, __) { + return !follow + ? GestureDetector( + child: Image.asset( + "atu_images/room/at_icon_room_follow_no.png", + width: 26.w, + height: 26.w, + ), + onTap: () { + provider.followRoom(); + }, + ) + : Container(); + }, + ) + : Container(), + ], + ), + ), + ), + ], + ), + Spacer(), + _buildExperience(), + SizedBox(width: 5.w), + GestureDetector( + child: Image.asset( + "atu_images/room/at_icon_room_ext_min.png", + width: 32.w, + height: 32.w, + ), + onTap: () { + showCenterDialog( + context, + ExitMinRoomPage( + provider.isFz(), + provider.currenRoom?.roomProfile?.roomProfile?.id ?? "", + ), + barrierColor: Colors.black54, + ); + // provider.extRoom(); + }, + ), + SizedBox(width: 12.w), + ], + ); + }, + ); + } + + ///房间榜单入口 + _buildExperience() { + return Consumer( + builder: (context, ref, child) { + return GestureDetector( + child: Container( + width: 90.w, + height: 27.w, + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.circular(35.w), + ), + alignment: AlignmentDirectional.center, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + "atu_images/room/at_icon_room_contribute.png", + width: 18.w, + height: 18.w, + ), + SizedBox(width: 5.w), + text( + "${ref.roomContributeLevelRes?.thisWeekIntegral ?? 0}", + fontSize: 13.sp, + textColor: Colors.orangeAccent, + fontWeight: FontWeight.w600, + ), + Icon( + Icons.chevron_right, + size: 13.w, + color: Colors.orangeAccent, + ), + ], + ), + ), + onTap: () { + SmartDialog.show( + tag: "showRoomGiftRankPage", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return RoomGiftRankPage(); + }, + ); + }, + ); + }, + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/room_membership_fee_page.dart b/lib/chatvibe_ui/widgets/room/room_membership_fee_page.dart new file mode 100644 index 0000000..868ad86 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/room_membership_fee_page.dart @@ -0,0 +1,241 @@ +import 'dart:ui' as ui; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; + +class RoomMembershipFeePage extends StatefulWidget { + @override + _RoomMembershipFeePageState createState() => _RoomMembershipFeePageState(); +} + +class _RoomMembershipFeePageState extends State { + final TextEditingController _roomFeeController = TextEditingController(); + + @override + void initState() { + super.initState(); + _roomFeeController.text = + "${Provider.of(context, listen: false).currenRoom?.roomProfile?.roomSetting?.joinGolds}"; + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + backgroundColor: Colors.transparent, + body: Stack( + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + Navigator.pop(context); + }, + child: SizedBox( + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + ), + ), + Column( + children: [ + Spacer(), + ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.0), + topRight: Radius.circular(12.0), + ), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 15, sigmaY: 15), + child: Material( + color: Colors.black54, + child: Container( + width: ScreenUtil().screenWidth, + padding: EdgeInsets.all(15.w), + child: Stack( + children: [ + Column( + children: [ + text( + ATAppLocalizations.of(context)!.membershipFee, + fontSize: 16.sp, + textColor: Colors.white, + ), + SizedBox(height: 15.w), + text( + maxLines: 5, + ATAppLocalizations.of( + context, + )!.membershipFeeTips1, + fontSize: 14.sp, + textColor: Colors.white54, + ), + SizedBox(height: 15.w), + Container( + padding: EdgeInsets.only( + left: 12.w, + right: 12.w, + ), + alignment: Alignment.centerLeft, + height: 45.w, + width: 150.w, + decoration: BoxDecoration( + color: Colors.white30, + borderRadius: BorderRadius.all( + Radius.circular(8.w), + ), + ), + child: TextField( + controller: _roomFeeController, + textAlign: TextAlign.center, + maxLength: 5, + keyboardType: TextInputType.number, + decoration: InputDecoration( + hintStyle: TextStyle( + color: Colors.black45, + fontSize: 14.sp, + ), + contentPadding: EdgeInsets.only(top: 0.w), + counterText: '', + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + // enabledBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // focusedBorder: UnderlineInputBorder( + // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), + // prefixIcon: Padding(padding: EdgeInsets.all(8.w), child: Image.asset("images/login/at_icon_phone.png",width: 20.w, height: 20.w,fit: BoxFit.fill,),), + fillColor: Colors.black45, + ), + style: TextStyle( + fontSize: 15.sp, + color: Colors.white, + textBaseline: TextBaseline.alphabetic, + ), + ), + ), + SizedBox(height: 15.w), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + text( + ATAppLocalizations.of(context)!.freePrice, + fontSize: 14.sp, + textColor: Colors.white, + ), + Image.asset( + "atu_images/general/at_icon_jb.png", + width: 15.w, + height: 15.w, + ), + ], + ), + SizedBox(height: 15.w), + text( + maxLines: 5, + ATAppLocalizations.of( + context, + )!.membershipFeeTips2, + fontSize: 14.sp, + textColor: Colors.white54, + ), + SizedBox(height: 15.w), + GestureDetector( + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(35.w), + color: Colors.white30, + ), + height: 45.w, + width: 140.w, + child: text( + ATAppLocalizations.of(context)!.save, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ), + onTap: () { + if (_roomFeeController.text.isEmpty) { + _roomFeeController.text = "0"; + } + ChatRoomRepository() + .updateRoomSetting( + Provider.of( + context, + listen: false, + ) + .currenRoom + ?.roomProfile + ?.roomProfile + ?.id ?? + "", + Provider.of( + context, + listen: false, + ) + .currenRoom + ?.roomProfile + ?.roomProfile + ?.roomAccount ?? + "", + context, + joinGolds: _roomFeeController.text, + ) + .whenComplete(() { + Provider.of( + context, + listen: false, + ).loadRoomInfo( + Provider.of( + context, + listen: false, + ) + .currenRoom + ?.roomProfile + ?.roomProfile + ?.id ?? + "", + ); + Navigator.pop(context); + }); + }, + ), + ], + ), + GestureDetector( + child: Icon( + Icons.close, + size: 15.w, + color: Colors.white, + ), + onTap: () { + Navigator.pop(context); + }, + ), + ], + ), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/room_menu_dialog.dart b/lib/chatvibe_ui/widgets/room/room_menu_dialog.dart new file mode 100644 index 0000000..b156356 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/room_menu_dialog.dart @@ -0,0 +1,631 @@ +import 'dart:ui' as ui; +import 'package:aslan/chatvibe_ui/widgets/room/room_charm_page.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/constants/at_room_msg_type.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_msg_item.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/redpack/room_redenvelope_config_page.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/switch_model/room_mic_switch_page.dart'; +import 'package:aslan/main.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_core/utilities/at_pick_utils.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/join_room_res.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; +import '../../../chatvibe_core/constants/at_global_config.dart'; +import '../../../chatvibe_core/routes/at_fluro_navigator.dart'; +import '../../../chatvibe_core/utilities/at_gift_vap_svga_manager.dart'; +import '../../../chatvibe_data/models/enum/at_vip_type.dart'; +import '../../../chatvibe_data/sources/local/data_persistence.dart'; +import '../../../chatvibe_features/room/music/at_room_music_page.dart'; +import '../../../chatvibe_features/store/store_route.dart'; +import '../../components/at_debounce_widget.dart'; +import '../../components/at_tts.dart'; +import '../../components/text/at_text.dart'; + +class RoomMenuDialog extends StatefulWidget { + int roomMenuStime = 0; + Function(int eTime) callBack; + + RoomMenuDialog(this.roomMenuStime, this.callBack); + + @override + _RoomMenuDialogState createState() => _RoomMenuDialogState(); +} + +class _RoomMenuDialogState extends State { + List items1 = []; + List items2 = []; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + items1.clear(); + items1.add( + RoomMenu( + 0, + ATAppLocalizations.of(context)!.music, + "at_icon_room_music_tag.png", + ), + ); + items1.add( + RoomMenu( + 1, + ATAppLocalizations.of(context)!.redEnvelope, + "at_icon_room_redpack_tag.png", + ), + ); + items1.add( + RoomMenu( + 4, + ATAppLocalizations.of(context)!.luckNumber, + "at_icon_luck_number_tag.png", + ), + ); + items1.add( + RoomMenu(2, ATAppLocalizations.of(context)!.dice, "at_icon_dice_tag.png"), + ); + items1.add( + RoomMenu(3, ATAppLocalizations.of(context)!.rps, "at_icon_rps_tag.png"), + ); + + items2.clear(); + if (Provider.of(context, listen: false).isFz()) { + items2.add( + RoomMenu( + 0, + ATAppLocalizations.of(context)!.micManagement, + "at_icon_menu_mic_model_change.png", + ), + ); + } + + items2.add( + RoomMenu( + 1, + ATAppLocalizations.of(context)!.clearMessage, + "at_icon_room_msg_clear.png", + ), + ); + items2.add( + RoomMenu( + 2, + ATAppLocalizations.of(context)!.picture, + "at_icon_room_msg_pic.png", + ), + ); + items2.add( + RoomMenu( + 3, + ATAppLocalizations.of(context)!.shop, + "at_icon_room_menu_shop.png", + ), + ); + if (Provider.of(context, listen: false).isFz()) { + items2.add( + RoomMenu( + 4, + ATAppLocalizations.of(context)!.charm, + "at_icon_room_charm.png", + ), + ); + } + + items2.add( + RoomMenu( + 5, + ATAppLocalizations.of(context)!.sound2, + "atu_icon_room_sound_open.png", + ), + ); + items2.add( + RoomMenu( + 6, + ATAppLocalizations.of(context)!.giftEffect, + "at_icon_room_menu_gift_effect.png", + ), + ); + items2.add( + RoomMenu( + 7, + ATAppLocalizations.of(context)!.winFloat, + "at_icon_room_menu_float_win.png", + ), + ); + items2.add( + RoomMenu( + 8, + ATAppLocalizations.of(context)!.giftVibration, + "at_icon_room_menu_gift_vibration.png", + ), + ); + items2.add( + RoomMenu( + 9, + ATAppLocalizations.of(context)!.entryVehicleAnimation, + "at_icon_room_menu_entry_vehicle_animation.png", + ), + ); + return SafeArea( + top: false, + child: Stack( + alignment: Alignment.topCenter, + children: [ + Container( + height: 380.w, + decoration: BoxDecoration( + color: Color(0xff262533), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + ), + child: Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + SizedBox(height: 15.w), + Row( + children: [ + SizedBox(width: 20.w), + text( + ATAppLocalizations.of(context)!.entertainment, + fontWeight: FontWeight.bold, + textColor: Colors.white, + fontSize: 14.sp, + ), + ], + ), + SizedBox(height: 8.w), + GridView.builder( + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.only(bottom: 2.w), + shrinkWrap: true, + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, // 每行3个项目 + crossAxisSpacing: 2, + mainAxisSpacing: 2, + childAspectRatio: 1, + ), + itemBuilder: (c, i) { + return _buildItem1(items1[i], i); + }, + itemCount: items1.length, + ), + Row( + children: [ + SizedBox(width: 20.w), + text( + ATAppLocalizations.of(context)!.roomTools, + fontWeight: FontWeight.bold, + textColor: Colors.white, + fontSize: 14.sp, + ), + ], + ), + SizedBox(height: 8.w), + GridView.builder( + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.only(bottom: 2.w), + shrinkWrap: true, + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, // 每行3个项目 + crossAxisSpacing: 2, + mainAxisSpacing: 2, + childAspectRatio: 1, + ), + itemBuilder: (c, i) { + return _buildItem2(items2[i], i); + }, + itemCount: items2.length, + ), + ], + ), + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildItem1(RoomMenu item, int i) { + return GestureDetector( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset( + "atu_images/room/${item.icon}", + width: 45.w, + height: 45.w, + ), + SizedBox(height: 10.w), + text( + item.title, + fontSize: 9.sp, + textColor: Colors.white54, + fontWeight: FontWeight.w600, + letterSpacing: 0.1, + maxLines: 2, + textAlign: TextAlign.center, + ), + ], + ), + onTap: () async { + if (item.id == 0) { + SmartDialog.dismiss(tag: "showRoomMenuDialog"); + SmartDialog.show( + tag: "showRoomMusic", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return ATRoomMusicPage(); + }, + ); + } else if (item.id == 1) { + SmartDialog.dismiss(tag: "showRoomMenuDialog"); + SmartDialog.show( + tag: "showRedEnvelopeConfig", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return RoomRedenvelopeConfigPage(1); + }, + ); + } else if (item.id == 2) { + int sTime = DateTime.now().millisecondsSinceEpoch; + if (sTime - widget.roomMenuStime < 3000) { + ATTts.show( + ATAppLocalizations.of(context)!.operationsAreTooFrequent, + ); + return; + } + widget.callBack(sTime); + if (ATRoomUtils.touristCanMsg(context)) { + JoinRoomRes? room = + Provider.of(context, listen: false).currenRoom; + if (room != null) { + SmartDialog.dismiss(tag: "showRoomMenuDialog"); + num indexOnMic = Provider.of( + context, + listen: false, + ).userOnMaiInIndex( + AccountStorage().getCurrentUser()?.userProfile?.id ?? "", + ); + Msg msg = Msg( + groupId: room.roomProfile?.roomProfile?.roomAccount ?? "", + msg: "${ATRoomUtils.collectRandomInt(1, 6)}", + type: ATRoomMsgType.roomDice, + number: indexOnMic, + user: AccountStorage().getCurrentUser()?.userProfile, + ); + Provider.of( + context, + listen: false, + ).sendMsg(msg, addLocal: true); + Provider.of( + context, + listen: false, + ).starPlayEmoji(msg); + } + } + } else if (item.id == 3) { + int sTime = DateTime.now().millisecondsSinceEpoch; + if (sTime - widget.roomMenuStime < 3000) { + ATTts.show( + ATAppLocalizations.of(context)!.operationsAreTooFrequent, + ); + return; + } + widget.callBack(sTime); + if (ATRoomUtils.touristCanMsg(context)) { + JoinRoomRes? room = + Provider.of(context, listen: false).currenRoom; + if (room != null) { + SmartDialog.dismiss(tag: "showRoomMenuDialog"); + num indexOnMic = Provider.of( + context, + listen: false, + ).userOnMaiInIndex( + UserManager().getCurrentUser()?.userProfile?.id ?? "", + ); + Msg msg = Msg( + groupId: room.roomProfile?.roomProfile?.roomAccount ?? "", + msg: "${ATRoomUtils.collectRandomInt(1, 3)}", + type: ATRoomMsgType.roomRPS, + number: indexOnMic, + user: UserManager().getCurrentUser()?.userProfile, + ); + Provider.of( + context, + listen: false, + ).sendMsg(msg, addLocal: true); + Provider.of( + context, + listen: false, + ).starPlayEmoji(msg); + } + } + } else if (item.id == 4) { + int sTime = DateTime.now().millisecondsSinceEpoch; + if (sTime - widget.roomMenuStime < 3000) { + ATTts.show( + ATAppLocalizations.of(context)!.operationsAreTooFrequent, + ); + return; + } + widget.callBack(sTime); + if (ATRoomUtils.touristCanMsg(context)) { + JoinRoomRes? room = + Provider.of(context, listen: false).currenRoom; + if (room != null) { + SmartDialog.dismiss(tag: "showRoomMenuDialog"); + Provider.of(context, listen: false).sendMsg( + Msg( + groupId: room.roomProfile?.roomProfile?.roomAccount ?? "", + msg: "${ATRoomUtils.collectRandomInt(0, 999)}", + type: ATRoomMsgType.roomLuckNumber, + user: UserManager().getCurrentUser()?.userProfile, + ), + addLocal: true, + ); + } + } + } + }, + ); + } + + Widget _buildItem2(RoomMenu item, int i) { + return ATDebounceWidget( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Stack( + alignment: AlignmentDirectional.center, + children: [ + Image.asset( + "atu_images/room/${item.icon}", + width: 45.w, + height: 45.w, + ), + PositionedDirectional( + end: 0, + bottom: 0, + child: _buildSwitch(item), + ), + ], + ), + SizedBox(height: 10.w), + text( + item.title, + fontSize: 9.sp, + textColor: Colors.white54, + fontWeight: FontWeight.w600, + lineHeight: 1.1, + letterSpacing: 0.1, + maxLines: 2, + textAlign: TextAlign.center, + ), + ], + ), + onTap: () async { + if (item.id == 0) { + SmartDialog.dismiss(tag: "showRoomMenuDialog"); + SmartDialog.show( + tag: "showRoomMicSwitch", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + debounce: true, + builder: (_) { + return RoomMicSwitchPage(); + }, + ); + } else if (item.id == 1) { + Provider.of(context, listen: false).clearMessage(); + SmartDialog.dismiss(tag: "showRoomMenuDialog"); + } else if (item.id == 2) { + if (ATRoomUtils.touristCanMsg(context)) { + ATPickUtils.pickImageProcess(context, (bool success, String url) { + if (success) { + Provider.of(context, listen: false).sendMsg( + Msg( + groupId: + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.roomAccount, + msg: url, + type: ATRoomMsgType.image, + role: + Provider.of( + context, + listen: false, + ).currenRoom?.entrants?.roles ?? + "", + user: AccountStorage().getCurrentUser()?.userProfile, + ), + addLocal: true, + ); + } + SmartDialog.dismiss(tag: "showRoomMenuDialog"); + }, neeCrop: false); + } + } else if (item.id == 3) { + SmartDialog.dismiss(tag: "showRoomMenuDialog"); + ATNavigatorUtils.push( + navigatorKey.currentState!.context, + StoreRoute.list, + replace: false, + ); + } else if (item.id == 4) { + SmartDialog.dismiss(tag: "showRoomMenuDialog"); + SmartDialog.show( + tag: "showRoomCharm", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return RoomCharmPage(); + }, + ); + } else if (item.id == 5) { + // SmartDialog.dismiss(tag: "showRoomMenuDialog"); + Provider.of( + context, + listen: false, + ).muteAllRemote(); + } else if (item.id == 6) { + // SmartDialog.dismiss(tag: "showRoomMenuDialog"); + setState(() { + ATGlobalConfig.isGiftSpecialEffects = + !ATGlobalConfig.isGiftSpecialEffects; + }); + DataPersistence.setBool( + "${AccountStorage().getCurrentUser()?.userProfile?.account}-GiftSpecialEffects", + ATGlobalConfig.isGiftSpecialEffects, + ); + if (!ATGlobalConfig.isGiftSpecialEffects) { + ATGiftVapSvgaManager().clearTasks(); + } + } else if (item.id == 7) { + // SmartDialog.dismiss(tag: "showRoomMenuDialog"); + setState(() { + ATGlobalConfig.isFloatingAnimationInGlobal = + !ATGlobalConfig.isFloatingAnimationInGlobal; + }); + DataPersistence.setBool( + "${AccountStorage().getCurrentUser()?.userProfile?.account}-FloatingAnimationInGlobal", + ATGlobalConfig.isFloatingAnimationInGlobal, + ); + } else if (item.id == 8) { + // SmartDialog.dismiss(tag: "showRoomMenuDialog"); + setState(() { + ATGlobalConfig.isLuckGiftSpecialEffects = + !ATGlobalConfig.isLuckGiftSpecialEffects; + }); + DataPersistence.setBool( + "${AccountStorage().getCurrentUser()?.userProfile?.account}-LuckGiftSpecialEffects", + ATGlobalConfig.isLuckGiftSpecialEffects, + ); + if (!ATGlobalConfig.isLuckGiftSpecialEffects) { + ATGiftVapSvgaManager().clearTasks(); + Provider.of( + context, + listen: false, + ).cleanLuckGiftBackCoins(); + } + } else if (item.id == 9) { + // SmartDialog.dismiss(tag: "showRoomMenuDialog"); + var vip = AccountStorage().getCurrentUser()?.userProfile?.getVIP(); + if (vip?.name == ATVIPType.MARQUIS.name || + vip?.name == ATVIPType.DUKE.name || + vip?.name == ATVIPType.KING.name || + vip?.name == ATVIPType.EMPEROR.name) { + setState(() { + ATGlobalConfig.isEntryVehicleAnimation = + !ATGlobalConfig.isEntryVehicleAnimation; + }); + DataPersistence.setBool( + "${AccountStorage().getCurrentUser()?.userProfile?.account}-EntryVehicleAnimation", + ATGlobalConfig.isEntryVehicleAnimation, + ); + } else { + ATTts.show( + ATAppLocalizations.of(context)!.thisFeatureIsCurrentlyUnavailable, + ); + } + } + }, + ); + } + + _buildSwitch(RoomMenu item) { + if (item.id == 5) { + return Selector( + selector: (c, p) => p.roomIsMute, + shouldRebuild: (prev, next) => prev != next, + builder: (_, isMute, __) { + return Transform.translate( + offset: Offset(3.w, 8.w), + child: Image.asset( + isMute + ? "atu_images/general/at_icon_social_privilege_close.png" + : "atu_images/general/at_icon_social_privilege_open.png", + width: 20.w, + fit: BoxFit.fill, + ), + ); + }, + ); + } else if (item.id == 6) { + return Transform.translate( + offset: Offset(3.w, 8.w), + child: Image.asset( + ATGlobalConfig.isGiftSpecialEffects + ? "atu_images/general/at_icon_social_privilege_open.png" + : "atu_images/general/at_icon_social_privilege_close.png", + width: 20.w, + fit: BoxFit.fill, + ), + ); + } else if (item.id == 7) { + return Transform.translate( + offset: Offset(3.w, 8.w), + child: Image.asset( + ATGlobalConfig.isFloatingAnimationInGlobal + ? "atu_images/general/at_icon_social_privilege_open.png" + : "atu_images/general/at_icon_social_privilege_close.png", + width: 20.w, + fit: BoxFit.fill, + ), + ); + } else if (item.id == 8) { + return Transform.translate( + offset: Offset(3.w, 8.w), + child: Image.asset( + ATGlobalConfig.isLuckGiftSpecialEffects + ? "atu_images/general/at_icon_social_privilege_open.png" + : "atu_images/general/at_icon_social_privilege_close.png", + width: 20.w, + fit: BoxFit.fill, + ), + ); + } else if (item.id == 9) { + return Transform.translate( + offset: Offset(3.w, 8.w), + child: Image.asset( + ATGlobalConfig.isEntryVehicleAnimation + ? "atu_images/general/at_icon_social_privilege_open.png" + : "atu_images/general/at_icon_social_privilege_close.png", + width: 20.w, + fit: BoxFit.fill, + ), + ); + } + return Container(); + } +} + +class RoomMenu { + int id = 0; + + String title = ""; + String icon = ""; + + RoomMenu(this.id, this.title, this.icon); +} diff --git a/lib/chatvibe_ui/widgets/room/room_msg_input.dart b/lib/chatvibe_ui/widgets/room/room_msg_input.dart new file mode 100644 index 0000000..27fc2a6 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/room_msg_input.dart @@ -0,0 +1,212 @@ +import 'dart:ui' as ui; + +import 'package:extended_text_field/extended_text_field.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/constants/at_room_msg_type.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_domain/usecases/at_case.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_msg_item.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; + +///聊天输入框 +class RoomMsgInput extends StatefulWidget { + String? atTextContent; + + RoomMsgInput({this.atTextContent}); + + @override + _RoomMsgInputState createState() => _RoomMsgInputState(); +} + +class _RoomMsgInputState extends State { + bool showSend = false; + + FocusNode msgNode = FocusNode(); + + TextEditingController controller = TextEditingController(); + + @override + void initState() { + super.initState(); + if (widget.atTextContent != null) { + controller.value = TextEditingValue(text: widget.atTextContent ?? ""); + } + } + + @override + Widget build(BuildContext context) { + // FocusScope.of(context).requestFocus(msgNode); + return Scaffold( + backgroundColor: Colors.transparent, + body: Column( + children: [ + Expanded( + child: GestureDetector( + onTap: () { + Navigator.pop(context); + }, + ), + ), + float(context), + ], + ), + ); + } + + Widget float(BuildContext context) { + return Container( + decoration: BoxDecoration(color: Colors.black), + padding: EdgeInsets.symmetric(horizontal: 25.w, vertical: 12.w), + height: 56.0, + child: Container( + decoration: BoxDecoration( + color: Colors.white24, + borderRadius: BorderRadius.circular(height(5)), + ), + child: Row( + children: [ + Expanded( + child: ExtendedTextField( + controller: controller, + textDirection: + widget.atTextContent != null ? TextDirection.ltr : null, + specialTextSpanBuilder: AtTextSpanBuilder(), + focusNode: msgNode, + autofocus: true, + onSubmitted: (s) { + Navigator.pop(context); + if (controller.text.isNotEmpty) { + var currenRoom = + Provider.of( + context, + listen: false, + ).currenRoom; + if (currenRoom != null) { + Provider.of(context, listen: false).sendMsg( + Msg( + groupId: + currenRoom + .roomProfile + ?.roomProfile + ?.roomAccount ?? + "", + role: + Provider.of( + context, + listen: false, + ).currenRoom?.entrants?.roles ?? + "", + msg: controller.text, + type: ATRoomMsgType.text, + user: AccountStorage().getCurrentUser()?.userProfile, + ), + ); + } + } + }, + onChanged: (s) { + setState(() { + showSend = controller.text.isNotEmpty; + }); + }, + decoration: InputDecoration( + hintText: ATAppLocalizations.of(context)!.pleaseChatFfriendly, + fillColor: Colors.transparent, + hintStyle: TextStyle(color: Colors.white60, fontSize: sp(14)), + contentPadding: const EdgeInsets.symmetric(horizontal: 15), + counterText: '', + isDense: true, + filled: false, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + ), + style: TextStyle( + fontSize: ScreenUtil().setSp(14), + color: Colors.white, + ), + ), + ), + GestureDetector( + onTap: () { + Navigator.pop(context); + if (controller.text.isNotEmpty) { + var currenRoom = + Provider.of( + context, + listen: false, + ).currenRoom; + if (currenRoom != null) { + Provider.of(context, listen: false).sendMsg( + Msg( + groupId: + currenRoom.roomProfile?.roomProfile?.roomAccount ?? + "", + role: + Provider.of( + context, + listen: false, + ).currenRoom?.entrants?.roles ?? + "", + msg: controller.text, + type: ATRoomMsgType.text, + user: AccountStorage().getCurrentUser()?.userProfile, + ), + addLocal: true, + ); + } + } + }, + child: Container( + padding: EdgeInsets.all(4.w), + width: 30.w, + height: 30.w, + child: Image.asset("atu_images/room/at_icon_room_message_send.png"), + ), + ), + SizedBox(width: 3.w), + ], + ), + ), + ); + } +} + +class PopRoute extends PopupRoute { + final Duration _duration = Duration(milliseconds: 350); + Widget child; + + PopRoute({required this.child}); + + @override + Color? get barrierColor => null; + + @override + bool get barrierDismissible => true; + + @override + String? get barrierLabel => null; + + @override + Widget buildPage( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return child; + } + + @override + Duration get transitionDuration => _duration; +} diff --git a/lib/chatvibe_ui/widgets/room/room_msg_item.dart b/lib/chatvibe_ui/widgets/room/room_msg_item.dart new file mode 100644 index 0000000..e48b9aa --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/room_msg_item.dart @@ -0,0 +1,1837 @@ +import 'dart:convert'; +import 'package:extended_image/extended_image.dart' + show ExtendedNetworkImageProvider; +import 'package:extended_text/extended_text.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_ninepatch_image/flutter_ninepatch_image.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +import '../../../app_localizations.dart'; +import '../../../chatvibe_core/constants/at_app_colors.dart'; +import '../../../chatvibe_core/constants/at_global_config.dart'; +import '../../../chatvibe_core/constants/at_room_msg_type.dart'; +import '../../../chatvibe_core/constants/at_screen.dart'; +import '../../../chatvibe_core/routes/at_fluro_navigator.dart'; +import '../../../chatvibe_core/utilities/at_room_utils.dart'; +import '../../../chatvibe_data/models/enum/at_activity_reward_props_type.dart'; +import '../../../chatvibe_data/models/enum/at_props_type.dart'; +import '../../../chatvibe_data/models/enum/at_room_roles_type.dart'; +import '../../../chatvibe_data/sources/repositories/user_repository_impl.dart'; +import '../../../chatvibe_domain/models/res/gift_res.dart'; +import '../../../chatvibe_domain/models/res/login_res.dart'; +import '../../../chatvibe_domain/models/res/mic_res.dart'; +import '../../../chatvibe_domain/usecases/at_case.dart'; +import '../../../chatvibe_features/index/main_route.dart'; +import '../../components/at_compontent.dart'; +import '../../components/at_tts.dart'; +import '../../components/text/at_text.dart'; + +///消息item +class MsgItem extends StatefulWidget { + static final Map> roomUserFutureCache = + >{}; + static final Set roomUserRefreshInFlight = {}; + static final Set roomUserFullInfoLoaded = {}; + static final Map + badgeImageProviderCache = {}; + static final Map + chatBoxImageProviderCache = {}; + + static void clearRoomUserCaches({bool clearBadgeCache = false}) { + roomUserFutureCache.clear(); + roomUserRefreshInFlight.clear(); + roomUserFullInfoLoaded.clear(); + chatBoxImageProviderCache.clear(); + if (clearBadgeCache) { + badgeImageProviderCache.clear(); + } + } + + final Function(ChatVibeUserProfile? user) onClick; + final Msg msg; + MsgItem({Key? key, required this.msg, required this.onClick}) + : super(key: key); + + @override + _MsgItemState createState() => _MsgItemState(); +} + +class _MsgItemState extends State { + static const int _maxRoomUserFutureCacheSize = 256; + static const int _maxBadgeImageProviderCacheSize = 256; + static const int _maxChatBoxImageProviderCacheSize = 64; + static const Duration _badgeImageCacheMaxAge = Duration(days: 7); + static const Duration _chatBoxImageCacheMaxAge = Duration(days: 7); + + @override + Widget build(BuildContext context) { + //gift + // if (msg.type == Roomtype.gift.code) { + // return _buildGiftMsg(context); + // } + + ///系统提示 + if (widget.msg.type == ATRoomMsgType.systemTips) { + return _systemTipsRoomItem1(context); + } + + ///进入房间 + if (widget.msg.type == ATRoomMsgType.joinRoom) { + return _enterRoomItem1(context); + } + + ///礼物 + if (widget.msg.type == ATRoomMsgType.gift) { + return _buildGiftMsg1(context); + } + + ///火箭用户中奖 + if (widget.msg.type == ATRoomMsgType.rocketRewardUser) { + return _buildRocketRewardMsg1(context); + } + + ///幸运礼物 + if (widget.msg.type == ATRoomMsgType.gameLuckyGift) { + return _buildGameLuckyGiftMsg1(context); + } + + ///幸运礼物 + if (widget.msg.type == ATRoomMsgType.gameLuckyGift_5) { + return _buildGameLuckyGiftMsg_5(context); + } + + ///房间身份变动 + if (widget.msg.type == ATRoomMsgType.roomRoleChange) { + return _buildRoleChangeMsg1(context); + } + + ///踢出房间 + if (widget.msg.type == ATRoomMsgType.qcfj) { + widget.msg.msg = ATAppLocalizations.of(context)!.kickRoomTips; + } + + ///图片消息 + if (widget.msg.type == ATRoomMsgType.image) { + return _buildImageMsg1(context); + } + + ///掷骰子 + if (widget.msg.type == ATRoomMsgType.roomDice) { + return _buildDiceMsg1(context); + } + + ///石头剪刀布 + if (widget.msg.type == ATRoomMsgType.roomRPS) { + return _buildRPSMsg1(context); + } + + ///幸运数字 + if (widget.msg.type == ATRoomMsgType.roomLuckNumber) { + return _buildLuckNumberMsg1(context); + } + if (widget.msg.user != null) { + PropsResources? chatBox = widget.msg.user?.getChatBox(); + return Container( + alignment: AlignmentDirectional.topStart, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: width(5.w), + vertical: width(4), + ), + margin: EdgeInsets.symmetric( + horizontal: width(10), + ).copyWith(bottom: 6.w), + child: Column( + children: [ + _buildUserInfoLine(context), + SizedBox(height: 6.w), + Row( + children: [ + Flexible( + // 使用Flexible替代Expanded + child: GestureDetector( + child: + chatBox != null + ? Container( + constraints: BoxConstraints( + maxWidth: + ScreenUtil().screenWidth * 0.7, // 最大宽度约束 + ), + padding: EdgeInsets.symmetric(horizontal: 10.w), + margin: EdgeInsetsDirectional.only( + start: 34.w, + ).copyWith(bottom: 1.w), + child: NinePatchImage( + scale: 3, + alignment: Alignment.center, + imageProvider: _getChatBoxImageProvider( + chatBox.expand, + ), + sliceCachedKey: _buildNinePatchCacheKey( + chatBox.expand, + ), + child: ExtendedText( + widget.msg?.msg ?? '', + style: TextStyle( + fontSize: sp(13), + color: Color( + _msgColor(widget.msg.type ?? ""), + ), + fontWeight: FontWeight.w500, + ), + softWrap: true, // 自动换行 + specialTextSpanBuilder: AtTextSpanBuilder( + onTapCall: (userId) {}, + ), + ), + ), + ) + : Container( + constraints: BoxConstraints( + maxWidth: + ScreenUtil().screenWidth * 0.7, // 最大宽度约束 + ), + decoration: BoxDecoration( + color: Color(0xffB6A1DE).withOpacity(0.2), + borderRadius: BorderRadius.only( + topRight: Radius.circular(15.w), + bottomLeft: Radius.circular(15.w), + bottomRight: Radius.circular(15.w), + ), + ), + padding: EdgeInsets.symmetric( + horizontal: 10.w, + vertical: 4.w, + ), + margin: EdgeInsetsDirectional.only( + start: 54.w, + ).copyWith(bottom: 8.w), + child: ExtendedText( + widget.msg?.msg ?? '', + style: TextStyle( + fontSize: sp(13), + color: Color( + _msgColor(widget.msg.type ?? ""), + ), + fontWeight: FontWeight.w500, + ), + softWrap: true, // 自动换行 + specialTextSpanBuilder: AtTextSpanBuilder( + onTapCall: (userId) {}, + ), + ), + ), + onLongPress: () { + if (!(widget.msg?.msg ?? "").startsWith(AtText.flag)) { + Clipboard.setData( + ClipboardData(text: widget.msg?.msg ?? ''), + ); + ATTts.show("Copied"); + } + }, + ), + ), + ], + ), + ], + ), + ), + ); + } else { + return Container( + alignment: AlignmentDirectional.topStart, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: width(8.w), + vertical: width(4), + ), + margin: EdgeInsets.symmetric( + horizontal: width(10), + ).copyWith(bottom: 6.w), + decoration: BoxDecoration( + color: Color(0xffB6A1DE).withOpacity(0.2), + borderRadius: BorderRadius.all(Radius.circular(15.w)), + ), + child: Text( + widget.msg?.msg ?? '', + style: TextStyle( + fontSize: sp(13), + color: Color(_msgColor(widget.msg.type ?? "")), + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.left, + ), + ), + ); + } + } + + ///系统提示 + _systemTipsRoomItem1(BuildContext context) { + return Container( + alignment: AlignmentDirectional.topStart, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: width(8.w), + vertical: width(8), + ), + margin: EdgeInsets.symmetric( + horizontal: width(10), + ).copyWith(bottom: 8.w), + decoration: BoxDecoration( + color: Color(0xffB6A1DE).withOpacity(0.2), + borderRadius: BorderRadius.all(Radius.circular(15.w)), + ), + child: Text( + widget.msg?.msg ?? '', + style: TextStyle( + fontSize: sp(13), + height: 1.5.w, + color: Color(_msgColor(widget.msg.type ?? "")), + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.left, + ), + ), + ); + } + + ///火箭用户中奖 + _buildRocketRewardMsg1(BuildContext context) { + List spans = []; + + spans.add( + TextSpan( + text: "${widget.msg.user?.userNickname} ", + style: TextStyle( + fontSize: sp(12), + color: themeColor, + fontWeight: FontWeight.w500, + ), + recognizer: TapGestureRecognizer()..onTap = () {}, + ), + ); + spans.add( + TextSpan( + text: ATAppLocalizations.of(context)!.inRocket, + style: TextStyle( + fontSize: sp(12), + color: Colors.white, + fontWeight: FontWeight.w500, + ), + recognizer: TapGestureRecognizer()..onTap = () {}, + ), + ); + spans.add( + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Image.asset( + "images/room/at_icon_room_rocket_lv${widget.msg.number}.png", + width: 18.w, + ), + ), + ); + spans.add( + TextSpan( + text: ATAppLocalizations.of(context)!.obtain, + style: TextStyle( + fontSize: sp(12), + color: Colors.white, + fontWeight: FontWeight.w500, + ), + recognizer: TapGestureRecognizer()..onTap = () {}, + ), + ); + spans.add( + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: + widget.msg.role == ATActivityRewardATPropsType.GOLD.name + ? Image.asset("atu_images/general/at_icon_jb.png", width: 18.w) + : netImage(url: widget.msg.msg ?? "", width: 18.w), + ), + ); + Widget text = Text.rich( + TextSpan(children: spans), + textAlign: TextAlign.left, + strutStyle: StrutStyle( + height: 1.8, // 行高倍数 + fontWeight: FontWeight.w500, + forceStrutHeight: true, // 强制应用行高 + ), + ); + return Container( + alignment: AlignmentDirectional.topStart, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.w), + margin: EdgeInsets.only(left: 34.w).copyWith(bottom: 8.w), + decoration: BoxDecoration( + color: Color(0xffB6A1DE).withOpacity(0.2), + borderRadius: BorderRadius.all(Radius.circular(15.w)), + ), + child: text, + ), + ); + } + + ///幸运礼物 + _buildGameLuckyGiftMsg1(BuildContext context) { + List spans = []; + + spans.add( + TextSpan( + text: "${widget.msg.user?.userNickname} ", + style: TextStyle( + fontSize: sp(12), + color: themeColor, + fontWeight: FontWeight.w500, + ), + recognizer: + TapGestureRecognizer() + ..onTap = () { + widget.onClick(widget.msg.user); + }, + ), + ); + spans.add( + TextSpan( + text: ATAppLocalizations.of(context)!.sendTo, + style: TextStyle( + fontSize: sp(12), + color: Colors.white, + fontWeight: FontWeight.w500, + ), + recognizer: TapGestureRecognizer()..onTap = () {}, + ), + ); + spans.add( + TextSpan( + text: " ${widget.msg.toUser?.userNickname} ", + style: TextStyle( + fontSize: sp(12), + color: themeColor, + fontWeight: FontWeight.w500, + ), + recognizer: + TapGestureRecognizer() + ..onTap = () { + widget.onClick(widget.msg.toUser); + }, + ), + ); + ATGlobalConfig.lang == "ar" + ? spans.add( + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Image.asset( + "atu_images/general/at_icon_jb.png", + width: 18.w, + ), + ), + ) + : spans.add( + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: netImage(url: widget.msg.gift?.giftPhoto ?? "", width: 18.w), + ), + ); + spans.add( + TextSpan( + text: " ${ATAppLocalizations.of(context)!.obtain}", + style: TextStyle( + fontSize: sp(12), + color: themeColor, + fontWeight: FontWeight.w500, + ), + recognizer: TapGestureRecognizer()..onTap = () {}, + ), + ); + spans.add( + TextSpan( + text: " ${widget.msg.awardAmount}", + style: TextStyle( + fontSize: sp(12), + color: themeColor, + fontWeight: FontWeight.w500, + ), + recognizer: TapGestureRecognizer()..onTap = () {}, + ), + ); + ATGlobalConfig.lang == "ar" + ? spans.add( + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: netImage(url: widget.msg.gift?.giftPhoto ?? "", width: 18.w), + ), + ) + : spans.add( + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Image.asset( + "atu_images/general/at_icon_jb.png", + width: 18.w, + ), + ), + ); + Widget text = Text.rich( + TextSpan(children: spans), + textAlign: TextAlign.left, + strutStyle: StrutStyle( + height: 1.3, // 行高倍数 + fontWeight: FontWeight.w500, + forceStrutHeight: true, // 强制应用行高 + ), + ); + return Container( + alignment: AlignmentDirectional.topStart, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.w), + margin: EdgeInsets.only(left: 34.w).copyWith(bottom: 8.w), + decoration: BoxDecoration( + color: Color(0xffB6A1DE).withOpacity(0.2), + borderRadius: BorderRadius.all(Radius.circular(15.w)), + ), + child: text, + ), + ); + } + + ///幸运礼物中奖5倍以及以上 + _buildGameLuckyGiftMsg_5(BuildContext context) { + List spans = []; + spans.add( + TextSpan( + text: "${widget.msg.user?.userNickname} ", + style: TextStyle( + fontSize: sp(12), + color: Color(0xffFEF129), + fontWeight: FontWeight.w500, + ), + recognizer: + TapGestureRecognizer() + ..onTap = () { + widget.onClick(widget.msg.user); + }, + ), + ); + spans.add( + TextSpan( + text: ATAppLocalizations.of(context)!.get, + style: TextStyle( + fontSize: sp(12), + color: Colors.white, + fontWeight: FontWeight.w500, + ), + recognizer: TapGestureRecognizer()..onTap = () {}, + ), + ); + spans.add( + TextSpan( + text: " ${widget.msg.awardAmount} ", + style: TextStyle( + fontSize: sp(12), + color: Color(0xffFEF129), + fontWeight: FontWeight.w500, + ), + recognizer: TapGestureRecognizer()..onTap = () {}, + ), + ); + spans.add( + TextSpan( + text: ATAppLocalizations.of(context)!.coins3, + style: TextStyle( + fontSize: sp(12), + color: Color(0xffFEF129), + fontWeight: FontWeight.w500, + ), + recognizer: TapGestureRecognizer()..onTap = () {}, + ), + ); + spans.add( + TextSpan( + text: " ${ATAppLocalizations.of(context)!.receivedFromALuckyGift}", + style: TextStyle( + fontSize: sp(12), + color: Colors.white, + fontWeight: FontWeight.w500, + ), + recognizer: TapGestureRecognizer()..onTap = () {}, + ), + ); + Widget text = Text.rich( + TextSpan(children: spans), + textAlign: TextAlign.left, + strutStyle: StrutStyle( + height: 1.2, // 行高倍数 + fontWeight: FontWeight.w500, + forceStrutHeight: true, // 强制应用行高 + ), + ); + return Container( + alignment: AlignmentDirectional.topStart, + child: Container( + constraints: BoxConstraints(minHeight: 68.w), + padding: EdgeInsets.symmetric(vertical: 5.w), + margin: EdgeInsets.only(left: 34.w).copyWith(bottom: 8.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/room/at_icon_luck_gift_msg_n_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Row( + children: [ + SizedBox(width: 10.w), + Expanded(child: text), + Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_luck_gift_msg_n_ball.png", + ), + fit: BoxFit.fill, + ), + ), + alignment: AlignmentDirectional.center, + width: 55.w, + height: 55.w, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 10.w), + buildNumForGame(widget.msg.msg ?? "0", size: 18.w), + SizedBox(height: 3.w), + ATGlobalConfig.lang == "ar" + ? Image.asset( + "atu_images/room/at_icon_times_text_ar.png", + height: 12.w, + ) + : Image.asset( + "atu_images/room/at_icon_times_text_en.png", + height: 10.w, + ), + ], + ), + ), + SizedBox(width: 15.w), + ], + ), + ), + ); + } + + Widget _buildGiftMsg1(BuildContext context) { + PropsResources? chatBox = widget.msg.user?.getChatBox(); + return Container( + alignment: AlignmentDirectional.topStart, + padding: EdgeInsets.symmetric(horizontal: width(5.w), vertical: width(4)), + margin: EdgeInsets.symmetric(horizontal: width(10)).copyWith(bottom: 8.w), + child: Column( + children: [ + _buildUserInfoLine(context), + SizedBox(height: 6.w), + Row( + children: [ + Flexible( + // 使用Flexible替代Expanded + child: + chatBox != null + ? Container( + constraints: BoxConstraints( + maxWidth: ScreenUtil().screenWidth * 0.7, // 最大宽度约束 + ), + padding: EdgeInsets.symmetric(horizontal: 10.w), + margin: EdgeInsetsDirectional.only( + start: 34.w, + ).copyWith(bottom: 1.w), + child: NinePatchImage( + scale: 3, + alignment: Alignment.center, + imageProvider: _getChatBoxImageProvider( + chatBox.expand, + ), + sliceCachedKey: _buildNinePatchCacheKey( + chatBox.expand, + ), + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: + ATAppLocalizations.of(context)!.sendTo, + style: TextStyle( + fontSize: 13.sp, + color: Colors.white, + ), + ), + TextSpan(text: " "), + TextSpan( + text: widget.msg.toUser?.userNickname ?? "", + style: TextStyle( + fontSize: 13.sp, + color: themeColor, + ), + recognizer: + TapGestureRecognizer()..onTap = () {}, + ), + TextSpan(text: " "), + WidgetSpan( + child: netImage( + url: widget.msg.gift?.giftPhoto ?? "", + width: 22.w, + height: 22.w, + ), + ), + TextSpan(text: " "), + TextSpan( + text: "x${widget.msg.number}", + style: TextStyle( + fontSize: 13.sp, + color: Colors.white, + ), + recognizer: + TapGestureRecognizer()..onTap = () {}, + ), + ], + ), + textAlign: TextAlign.left, + ), + ), + ) + : Container( + constraints: BoxConstraints( + maxWidth: ScreenUtil().screenWidth * 0.7, // 最大宽度约束 + ), + decoration: BoxDecoration( + color: Color(0xffB6A1DE).withOpacity(0.2), + borderRadius: BorderRadius.only( + topRight: Radius.circular(15.w), + bottomLeft: Radius.circular(15.w), + bottomRight: Radius.circular(15.w), + ), + ), + padding: EdgeInsets.symmetric( + horizontal: 10.w, + vertical: 4.w, + ), + margin: EdgeInsetsDirectional.only( + start: 54.w, + ).copyWith(bottom: 8.w), + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: ATAppLocalizations.of(context)!.sendTo, + style: TextStyle( + fontSize: 13.sp, + color: Colors.white, + ), + ), + TextSpan(text: " "), + TextSpan( + text: widget.msg.toUser?.userNickname ?? "", + style: TextStyle( + fontSize: 13.sp, + color: themeColor, + ), + recognizer: + TapGestureRecognizer()..onTap = () {}, + ), + TextSpan(text: " "), + WidgetSpan( + child: netImage( + url: widget.msg.gift?.giftPhoto ?? "", + width: 22.w, + height: 22.w, + ), + ), + TextSpan(text: " "), + TextSpan( + text: "x${widget.msg.number}", + style: TextStyle( + fontSize: 13.sp, + color: Colors.white, + ), + recognizer: + TapGestureRecognizer()..onTap = () {}, + ), + ], + ), + textAlign: TextAlign.left, + ), + ), + ), + ], + ), + ], + ), + ); + } + + ///进入房间 + _enterRoomItem1(BuildContext context) { + List spans = []; + if (widget.msg.role != null && widget.msg.role != 0) { + spans.add( + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Padding( + padding: EdgeInsets.only(right: 3.w), + child: msgRoleTag(widget.msg.role ?? "", width: 18.w), + ), + ), + ); + } + spans.add( + TextSpan( + text: "${widget.msg?.user?.userNickname ?? ''} ", + style: TextStyle( + fontSize: sp(13), + color: _roleNameColor(widget.msg.role ?? ""), + fontWeight: FontWeight.w500, + ), + recognizer: TapGestureRecognizer()..onTap = () {}, + ), + ); + spans.add( + TextSpan( + text: ATAppLocalizations.of(context)!.joinRoomTips, + style: TextStyle( + fontSize: sp(13), + color: Color(_msgColor(widget.msg.type ?? "")), + fontWeight: FontWeight.w500, + ), + ), + ); + Widget text = Text.rich( + TextSpan(children: spans), + textAlign: TextAlign.left, + ); + return Container( + alignment: AlignmentDirectional.topStart, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: width(8.w), + vertical: width(4), + ), + margin: EdgeInsets.symmetric( + horizontal: width(10), + ).copyWith(bottom: 8.w), + child: text, + ), + ); + } + + Widget _buildMedals(List wearBadge) { + return wearBadge.isNotEmpty + ? Expanded( + child: SingleChildScrollView( + child: SizedBox( + height: 25.w, + child: ListView.separated( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: wearBadge.length, + itemBuilder: (context, index) { + return netImage( + width: 25.w, + height: 25.w, + url: wearBadge[index].selectUrl ?? "", + ); + }, + separatorBuilder: (BuildContext context, int index) { + return SizedBox(width: 5.w); + }, + ), + ), + ), + ) + : Container(); + } + + String _buildNinePatchCacheKey(String? url) { + final String normalizedUrl = (url ?? "").trim(); + if (normalizedUrl.isEmpty) { + return ""; + } + // Reuse the parsed 9-patch slices for the same chat box background so + // rapid room message updates don't repeatedly rebuild bubble geometry. + return "$normalizedUrl|scale:3|v3"; + } + + int _msgColor(String type) { + switch (type) { + case ATRoomMsgType.joinRoom: + case ATRoomMsgType.systemTips: + case ATRoomMsgType.welcome: + return 0xFFFFFFFF; + case ATRoomMsgType.gift: + return 0xFFFFFFFF; + case ATRoomMsgType.shangMai: + case ATRoomMsgType.xiaMai: + case ATRoomMsgType.qcfj: + case ATRoomMsgType.killXiaMai: + return 0xFFFFFFFF; + break; + // case type.text: + // return _mapTextColor(); + // break; + default: + return 0xFFFFFFFF; + } + } + + _roleNameColor(String role) { + if (ATRoomRolesType.HOMEOWNER.name == role) { + return Color(0xff2AC0F2); + } else if (ATRoomRolesType.ADMIN.name == role) { + return Color(0xffF9A024); + } else if (ATRoomRolesType.MEMBER.name == role) { + return Color(0xff28ED77); + } else { + return Colors.white; + } + } + + Widget _buildRoleChangeMsg1(BuildContext context) { + var text = ""; + if (ATRoomRolesType.ADMIN.name == widget.msg.msg) { + text = ATAppLocalizations.of(context)!.adminByHomeowner; + } else if (ATRoomRolesType.MEMBER.name == widget.msg.msg) { + text = ATAppLocalizations.of(context)!.memberByHomeowner; + } else { + text = ATAppLocalizations.of(context)!.touristByHomeowner; + } + List spans = []; + if (widget.msg.toUser != null) { + spans.add( + TextSpan( + text: "${widget.msg.toUser?.userNickname ?? ''} ", + style: TextStyle( + fontSize: sp(13), + color: Color(0xff2AC0F2), + fontWeight: FontWeight.w500, + ), + recognizer: TapGestureRecognizer()..onTap = () {}, + ), + ); + } + spans.add( + TextSpan( + text: text, + style: TextStyle( + fontSize: sp(13), + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ); + Widget textView = Text.rich( + TextSpan(children: spans), + textAlign: TextAlign.left, + ); + return Container( + alignment: AlignmentDirectional.topStart, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: width(8.w), + vertical: width(8), + ), + margin: EdgeInsets.symmetric( + horizontal: width(10), + ).copyWith(bottom: 8.w), + decoration: BoxDecoration( + color: Color(0xffB6A1DE).withOpacity(0.2), + borderRadius: BorderRadius.all(Radius.circular(15.w)), + ), + child: textView, + ), + ); + } + + Widget _buildImageMsg1(BuildContext context) { + PropsResources? chatBox = widget.msg.user?.getChatBox(); + return Container( + alignment: AlignmentDirectional.topStart, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: width(5.w), + vertical: width(4), + ), + margin: EdgeInsets.symmetric( + horizontal: width(10), + ).copyWith(bottom: 6.w), + child: Column( + children: [ + _buildUserInfoLine(context), + SizedBox(height: 6.w), + Row( + children: [ + Flexible( + // 使用Flexible替代Expanded + child: GestureDetector( + child: + chatBox != null + ? Container( + constraints: BoxConstraints( + maxWidth: + ScreenUtil().screenWidth * 0.5, // 最大宽度约束 + ), + padding: EdgeInsets.symmetric(horizontal: 10.w), + margin: EdgeInsetsDirectional.only( + start: 34.w, + ).copyWith(bottom: 1.w), + child: NinePatchImage( + scale: 3, + alignment: Alignment.center, + imageProvider: _getChatBoxImageProvider( + chatBox.expand, + ), + sliceCachedKey: _buildNinePatchCacheKey( + chatBox.expand, + ), + child: netImage(url: widget.msg.msg ?? ""), + ), + ) + : Container( + constraints: BoxConstraints( + maxWidth: + ScreenUtil().screenWidth * 0.5, // 最大宽度约束 + ), + decoration: BoxDecoration( + color: Color(0xffB6A1DE).withOpacity(0.2), + borderRadius: BorderRadius.only( + topRight: Radius.circular(15.w), + bottomLeft: Radius.circular(15.w), + bottomRight: Radius.circular(15.w), + ), + ), + padding: EdgeInsets.symmetric( + horizontal: 10.w, + vertical: 4.w, + ), + margin: EdgeInsetsDirectional.only( + start: 54.w, + ).copyWith(bottom: 8.w), + child: netImage(url: widget.msg.msg ?? ""), + ), + onTap: () { + String encodedUrls = Uri.encodeComponent( + jsonEncode([widget.msg.msg]), + ); + ATNavigatorUtils.push( + context, + "${MainRoute.imagePreview}?imageUrls=$encodedUrls&initialIndex=0", + ); + }, + ), + ), + ], + ), + ], + ), + ), + ); + } + + Widget _buildDiceMsg1(BuildContext context) { + PropsResources? chatBox = widget.msg.user?.getChatBox(); + return Container( + alignment: AlignmentDirectional.topStart, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: width(5.w), + vertical: width(4), + ), + margin: EdgeInsets.symmetric( + horizontal: width(10), + ).copyWith(bottom: 6.w), + child: Column( + children: [ + _buildUserInfoLine(context), + SizedBox(height: 6.w), + Row( + children: [ + Flexible( + // 使用Flexible替代Expanded + child: GestureDetector( + child: + chatBox != null + ? Container( + constraints: BoxConstraints( + maxWidth: + ScreenUtil().screenWidth * 0.5, // 最大宽度约束 + ), + padding: EdgeInsets.symmetric(horizontal: 10.w), + margin: EdgeInsetsDirectional.only( + start: 34.w, + ).copyWith(bottom: 1.w), + child: NinePatchImage( + scale: 3, + alignment: Alignment.center, + imageProvider: _getChatBoxImageProvider( + chatBox.expand, + ), + sliceCachedKey: _buildNinePatchCacheKey( + chatBox.expand, + ), + child: + (widget.msg.hasPlayedAnimation ?? false) + ? Image.asset( + "atu_images/room/at_icon_dice_${widget.msg.msg}.png", + height: 35.w, + ) + : FutureBuilder( + future: Future.delayed( + Duration(milliseconds: 2000), + ), + // 传入Future + builder: ( + BuildContext context, + AsyncSnapshot snapshot, + ) { + // 根据snapshot的状态来构建UI + if (snapshot.connectionState == + ConnectionState.done) { + widget.msg.hasPlayedAnimation = + true; + return Image.asset( + "atu_images/room/at_icon_dice_${widget.msg.msg}.png", + height: 35.w, + ); + } else { + return Image.asset( + "atu_images/room/at_icon_dice_animl.webp", + height: 35.w, + ); + } + }, + ), + ), + ) + : Container( + constraints: BoxConstraints( + maxWidth: + ScreenUtil().screenWidth * 0.5, // 最大宽度约束 + ), + decoration: BoxDecoration( + color: Color(0xffB6A1DE).withOpacity(0.2), + borderRadius: BorderRadius.only( + topRight: Radius.circular(15.w), + bottomLeft: Radius.circular(15.w), + bottomRight: Radius.circular(15.w), + ), + ), + padding: EdgeInsets.symmetric( + horizontal: 10.w, + vertical: 4.w, + ), + margin: EdgeInsetsDirectional.only( + start: 54.w, + ).copyWith(bottom: 8.w), + child: + (widget.msg.hasPlayedAnimation ?? false) + ? Image.asset( + "atu_images/room/at_icon_dice_${widget.msg.msg}.png", + height: 35.w, + ) + : FutureBuilder( + future: Future.delayed( + Duration(milliseconds: 2000), + ), + // 传入Future + builder: ( + BuildContext context, + AsyncSnapshot snapshot, + ) { + // 根据snapshot的状态来构建UI + if (snapshot.connectionState == + ConnectionState.done) { + widget.msg.hasPlayedAnimation = + true; + return Image.asset( + "atu_images/room/at_icon_dice_${widget.msg.msg}.png", + height: 35.w, + ); + } else { + return Image.asset( + "atu_images/room/at_icon_dice_animl.webp", + height: 35.w, + ); + } + }, + ), + ), + onTap: () {}, + ), + ), + ], + ), + ], + ), + ), + ); + } + + Widget _buildRPSMsg1(BuildContext context) { + PropsResources? chatBox = widget.msg.user?.getChatBox(); + return Container( + alignment: AlignmentDirectional.topStart, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: width(5.w), + vertical: width(4), + ), + margin: EdgeInsets.symmetric( + horizontal: width(10), + ).copyWith(bottom: 6.w), + child: Column( + children: [ + _buildUserInfoLine(context), + SizedBox(height: 6.w), + Row( + children: [ + Flexible( + // 使用Flexible替代Expanded + child: GestureDetector( + child: + chatBox != null + ? Container( + constraints: BoxConstraints( + maxWidth: + ScreenUtil().screenWidth * 0.5, // 最大宽度约束 + ), + padding: EdgeInsets.symmetric(horizontal: 10.w), + margin: EdgeInsetsDirectional.only( + start: 34.w, + ).copyWith(bottom: 1.w), + child: NinePatchImage( + scale: 3, + alignment: Alignment.center, + imageProvider: _getChatBoxImageProvider( + chatBox.expand, + ), + sliceCachedKey: _buildNinePatchCacheKey( + chatBox.expand, + ), + child: + (widget.msg.hasPlayedAnimation ?? false) + ? Image.asset( + "atu_images/room/at_icon_rps_${widget.msg.msg}.png", + height: 35.w, + ) + : FutureBuilder( + future: Future.delayed( + Duration(milliseconds: 2000), + ), + // 传入Future + builder: ( + BuildContext context, + AsyncSnapshot snapshot, + ) { + // 根据snapshot的状态来构建UI + if (snapshot.connectionState == + ConnectionState.done) { + widget.msg.hasPlayedAnimation = + true; + return Image.asset( + "atu_images/room/at_icon_rps_${widget.msg.msg}.png", + height: 35.w, + ); + } else { + return Image.asset( + "atu_images/room/at_icon_rps_animal.webp", + height: 35.w, + ); + } + }, + ), + ), + ) + : Container( + constraints: BoxConstraints( + maxWidth: + ScreenUtil().screenWidth * 0.5, // 最大宽度约束 + ), + decoration: BoxDecoration( + color: Color(0xffB6A1DE).withOpacity(0.2), + borderRadius: BorderRadius.only( + topRight: Radius.circular(15.w), + bottomLeft: Radius.circular(15.w), + bottomRight: Radius.circular(15.w), + ), + ), + padding: EdgeInsets.symmetric( + horizontal: 10.w, + vertical: 4.w, + ), + margin: EdgeInsetsDirectional.only( + start: 54.w, + ).copyWith(bottom: 8.w), + child: + (widget.msg.hasPlayedAnimation ?? false) + ? Image.asset( + "atu_images/room/at_icon_rps_${widget.msg.msg}.png", + height: 35.w, + ) + : FutureBuilder( + future: Future.delayed( + Duration(milliseconds: 2000), + ), + // 传入Future + builder: ( + BuildContext context, + AsyncSnapshot snapshot, + ) { + // 根据snapshot的状态来构建UI + if (snapshot.connectionState == + ConnectionState.done) { + widget.msg.hasPlayedAnimation = + true; + return Image.asset( + "atu_images/room/at_icon_rps_${widget.msg.msg}.png", + height: 35.w, + ); + } else { + return Image.asset( + "atu_images/room/at_icon_rps_animal.webp", + height: 35.w, + ); + } + }, + ), + ), + onTap: () {}, + ), + ), + ], + ), + ], + ), + ), + ); + } + + Widget _buildLuckNumberMsg1(BuildContext context) { + PropsResources? chatBox = widget.msg.user?.getChatBox(); + return Container( + alignment: AlignmentDirectional.topStart, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: width(5.w), + vertical: width(4), + ), + margin: EdgeInsets.symmetric( + horizontal: width(10), + ).copyWith(bottom: 6.w), + child: Column( + children: [ + _buildUserInfoLine(context), + SizedBox(height: 6.w), + Row( + children: [ + Flexible( + // 使用Flexible替代Expanded + child: GestureDetector( + child: + chatBox != null + ? Container( + constraints: BoxConstraints( + maxWidth: + ScreenUtil().screenWidth * 0.7, // 最大宽度约束 + ), + padding: EdgeInsets.symmetric(horizontal: 10.w), + margin: EdgeInsetsDirectional.only( + start: 34.w, + ).copyWith(bottom: 1.w), + child: NinePatchImage( + scale: 3, + alignment: Alignment.center, + imageProvider: _getChatBoxImageProvider( + chatBox.expand, + ), + sliceCachedKey: _buildNinePatchCacheKey( + chatBox.expand, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + ATGlobalConfig.lang == "ar" + ? Image.asset( + "atu_images/room/at_icon_luck_num_text_ar.png", + height: 20.w, + ) + : Image.asset( + "atu_images/room/at_icon_luck_num_text_en.png", + height: 20.w, + ), + buildNumForRoomLuckNum( + widget.msg.msg ?? "", + size: 20.w, + ), + ], + ), + ), + ) + : Container( + constraints: BoxConstraints( + maxWidth: + ScreenUtil().screenWidth * 0.7, // 最大宽度约束 + ), + decoration: BoxDecoration( + color: Color(0xffB6A1DE).withOpacity(0.2), + borderRadius: BorderRadius.only( + topRight: Radius.circular(15.w), + bottomLeft: Radius.circular(15.w), + bottomRight: Radius.circular(15.w), + ), + ), + padding: EdgeInsets.symmetric( + horizontal: 10.w, + vertical: 4.w, + ), + margin: EdgeInsetsDirectional.only( + start: 54.w, + ).copyWith(bottom: 8.w), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + ATGlobalConfig.lang == "ar" + ? Image.asset( + "atu_images/room/at_icon_luck_num_text_ar.png", + height: 20.w, + ) + : Image.asset( + "atu_images/room/at_icon_luck_num_text_en.png", + height: 20.w, + ), + buildNumForRoomLuckNum( + widget.msg.msg ?? "", + size: 20.w, + ), + ], + ), + ), + onTap: () {}, + ), + ), + ], + ), + ], + ), + ), + ); + } + + _buildUserInfoLine(BuildContext context) { + final String userId = widget.msg.user?.id ?? ""; + if (userId.isEmpty) { + return const SizedBox.shrink(); + } + final bool shouldRefreshUserInfo = widget.msg.needUpDataUserInfo ?? false; + if (shouldRefreshUserInfo) { + ATRoomUtils.roomUsersMap.remove(userId); + MsgItem.roomUserFutureCache.remove(userId); + MsgItem.roomUserFullInfoLoaded.remove(userId); + MsgItem.roomUserRefreshInFlight.add(userId); + widget.msg.needUpDataUserInfo = false; + } + final ChatVibeUserProfile? msgUser = widget.msg.user; + ChatVibeUserProfile? cachedUser = ATRoomUtils.roomUsersMap[userId]; + if (cachedUser != null && MsgItem.roomUserFullInfoLoaded.contains(userId)) { + return _buildNewUserInfoLine(context, cachedUser); + } + final ChatVibeUserProfile? fallbackUser = cachedUser ?? msgUser; + return FutureBuilder( + future: _getOrLoadRoomUserInfo(userId), + builder: (ct, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.hasData) { + ChatVibeUserProfile user = snapshot.data!; + ATRoomUtils.roomUsersMap[user.id ?? ""] = user; + MsgItem.roomUserFullInfoLoaded.add(userId); + MsgItem.roomUserRefreshInFlight.remove(userId); + return _buildNewUserInfoLine(context, user); + } + if (snapshot.hasError) { + MsgItem.roomUserFutureCache.remove(userId); + MsgItem.roomUserRefreshInFlight.remove(userId); + } + } + if (fallbackUser != null) { + return _buildNewUserInfoLine(context, fallbackUser); + } + final PropsResources? activeVip = _getActiveVip(widget.msg.user); + return Row( + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + widget.onClick(widget.msg.user); + }, + child: Stack( + alignment: AlignmentDirectional.bottomEnd, + children: [ + head( + url: widget.msg.user?.userAvatar ?? "", + width: 48.w, + // headdress: msg.user?.getHeaddress()?.sourceUrl, + ), + Positioned( + child: msgRoleTag(widget.msg.role ?? "", width: 14.w), + bottom: 2.w, + right: 2.w, + ), + ], + ), + ), + SizedBox(width: 3.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + getWealthLevel( + widget.msg.user?.wealthLevel ?? 0, + width: 48.w, + height: 23.w, + fontSize: 10.sp, + ), + SizedBox(width: 3.w), + getUserLevel( + widget.msg.user?.charmLevel ?? 0, + width: 48.w, + height: 23.w, + fontSize: 10.sp, + ), + SizedBox(width: 3.w), + chatvibeNickNameText( + maxWidth: 120.w, + fontWeight: FontWeight.w500, + widget.msg.user?.userNickname ?? "", + fontSize: 13.sp, + type: activeVip?.name ?? "", + needScroll: + (widget.msg.user?.userNickname?.characters.length ?? + 0) > + 10, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + activeVip != null + ? _buildStableBadgeImage( + url: activeVip.cover ?? "", + width: 25.w, + height: 25.w, + ) + : Container(), + _buildMedals( + (widget.msg.user?.wearBadge?.where((item) { + return item.use ?? false; + }).toList() ?? + []), + ), + ], + ), + ], + ), + ), + ], + ); + }, + ); + } + + PropsResources? _getActiveVip(ChatVibeUserProfile? user) { + final List? useProps = user?.useProps; + if (useProps == null || useProps.isEmpty) { + return null; + } + final int now = DateTime.now().millisecondsSinceEpoch; + PropsResources? activeVip; + PropsResources? fallbackVipWithoutExpire; + int latestExpireTime = 0; + for (final UseProps useProp in useProps) { + if (useProp.propsResources?.type != ATPropsType.NOBLE_VIP.name) { + continue; + } + final int? parsedExpire = int.tryParse(useProp.expireTime ?? ""); + if (parsedExpire == null || parsedExpire <= 0) { + fallbackVipWithoutExpire ??= useProp.propsResources; + continue; + } + final int expireTime = parsedExpire; + if (expireTime > now && expireTime >= latestExpireTime) { + latestExpireTime = expireTime; + activeVip = useProp.propsResources; + } + } + return activeVip ?? fallbackVipWithoutExpire; + } + + Widget _buildStableBadgeImage({ + required String? url, + required double width, + required double height, + }) { + final String normalizedUrl = (url ?? "").trim(); + if (normalizedUrl.isEmpty) { + return const SizedBox.shrink(); + } + return RepaintBoundary( + child: Image( + image: _getBadgeImageProvider(normalizedUrl), + width: width, + height: height, + fit: BoxFit.contain, + gaplessPlayback: true, + filterQuality: FilterQuality.low, + errorBuilder: (context, error, stackTrace) { + return const SizedBox.shrink(); + }, + ), + ); + } + + ExtendedNetworkImageProvider _getChatBoxImageProvider(String? url) { + final String normalizedUrl = (url ?? "").trim(); + final ExtendedNetworkImageProvider? cached = + MsgItem.chatBoxImageProviderCache[normalizedUrl]; + if (cached != null) { + return cached; + } + if (MsgItem.chatBoxImageProviderCache.length >= + _maxChatBoxImageProviderCacheSize) { + MsgItem.chatBoxImageProviderCache.remove( + MsgItem.chatBoxImageProviderCache.keys.first, + ); + } + final ExtendedNetworkImageProvider provider = ExtendedNetworkImageProvider( + normalizedUrl, + cache: true, + cacheMaxAge: _chatBoxImageCacheMaxAge, + ); + MsgItem.chatBoxImageProviderCache[normalizedUrl] = provider; + return provider; + } + + ExtendedNetworkImageProvider _getBadgeImageProvider(String? url) { + final String normalizedUrl = (url ?? "").trim(); + final ExtendedNetworkImageProvider? cached = + MsgItem.badgeImageProviderCache[normalizedUrl]; + if (cached != null) { + return cached; + } + if (MsgItem.badgeImageProviderCache.length >= + _maxBadgeImageProviderCacheSize) { + MsgItem.badgeImageProviderCache.remove( + MsgItem.badgeImageProviderCache.keys.first, + ); + } + final ExtendedNetworkImageProvider provider = ExtendedNetworkImageProvider( + normalizedUrl, + cache: true, + cacheMaxAge: _badgeImageCacheMaxAge, + ); + MsgItem.badgeImageProviderCache[normalizedUrl] = provider; + return provider; + } + + Future _getOrLoadRoomUserInfo(String userId) { + final Future? cached = + MsgItem.roomUserFutureCache[userId]; + if (cached != null) { + return cached; + } + if (MsgItem.roomUserFutureCache.length >= _maxRoomUserFutureCacheSize) { + MsgItem.roomUserFutureCache.remove( + MsgItem.roomUserFutureCache.keys.first, + ); + } + final Future future = AccountRepository().loadUserInfo( + userId, + ); + MsgItem.roomUserFutureCache[userId] = future; + return future; + } + + _buildNewUserInfoLine(BuildContext context, ChatVibeUserProfile user) { + final PropsResources? activeVip = _getActiveVip(user); + return Row( + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + widget.onClick(user); + }, + child: Stack( + alignment: AlignmentDirectional.bottomEnd, + children: [ + head( + url: user.userAvatar ?? "", + width: 48.w, + // headdress: msg.user?.getHeaddress()?.sourceUrl, + ), + Positioned( + child: msgRoleTag(widget.msg.role ?? "", width: 14.w), + bottom: 2.w, + right: 2.w, + ), + ], + ), + ), + SizedBox(width: 3.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + getWealthLevel( + user.wealthLevel ?? 0, + width: 48.w, + height: 23.w, + fontSize: 10.sp, + ), + SizedBox(width: 3.w), + getUserLevel( + user.charmLevel ?? 0, + width: 48.w, + height: 23.w, + fontSize: 10.sp, + ), + SizedBox(width: 3.w), + chatvibeNickNameText( + maxWidth: 120.w, + fontWeight: FontWeight.w500, + user.userNickname ?? "", + fontSize: 13.sp, + type: activeVip?.name ?? "", + needScroll: + (user.userNickname?.characters.length ?? 0) > 10, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + activeVip != null + ? _buildStableBadgeImage( + url: activeVip.cover ?? "", + width: 25.w, + height: 25.w, + ) + : Container(), + _buildMedals( + user.wearBadge?.where((item) { + return item.use ?? false; + }).toList() ?? + [], + ), + ], + ), + ], + ), + ), + ], + ); + } +} + +///消息 +class Msg { + String? groupId; + String? msg; + MicRes? userWheat; + String? type; + ChatVibeUserProfile? user; + String? role = ""; + ChatVibeGiftRes? gift; + + //礼物个数 + num? number; + + num? awardAmount; + + // 送礼给谁 + ChatVibeUserProfile? toUser; + int? time = 0; + + //动画是否已经播放(骰子、石头剪刀布、随机数字) + bool? hasPlayedAnimation = false; + + bool? needUpDataUserInfo = false; + + Msg({ + required this.groupId, + required this.msg, + required this.type, + this.user, + this.toUser, + this.userWheat, + this.role = "", + this.gift, + this.number, + this.awardAmount, + this.hasPlayedAnimation, + this.needUpDataUserInfo, + }) { + time = DateTime.now().millisecondsSinceEpoch; + } + + Msg.fromJson(dynamic json) { + groupId = json['groupId']; + msg = json['msg']; + type = json['type']; + role = json['role']; + time = json['time']; + number = json['number']; + awardAmount = json['awardAmount']; + hasPlayedAnimation = json['hasPlayedAnimation']; + needUpDataUserInfo = json['needUpDataUserInfo']; + userWheat = + json['userWheat'] != null ? MicRes.fromJson(json['userWheat']) : null; + user = + json['user'] != null + ? ChatVibeUserProfile.fromJson(json['user']) + : null; + toUser = + json['toUser'] != null + ? ChatVibeUserProfile.fromJson(json['toUser']) + : null; + + gift = json['gift'] != null ? ChatVibeGiftRes.fromJson(json['gift']) : null; + } + + Map toJson() { + final map = {}; + map['groupId'] = groupId; + map['msg'] = msg; + map['type'] = type; + map['role'] = role; + map['time'] = time; + map['awardAmount'] = awardAmount; + map['number'] = number; + map['hasPlayedAnimation'] = hasPlayedAnimation; + map['needUpDataUserInfo'] = needUpDataUserInfo; + if (userWheat != null) { + map['userWheat'] = userWheat?.toJson(); + } + if (user != null) { + map['user'] = user?.toJson(); + } + if (toUser != null) { + map['toUser'] = toUser?.toJson(); + } + if (gift != null) { + map['gift'] = gift?.toJson(); + } + return map; + } +} diff --git a/lib/chatvibe_ui/widgets/room/room_online_user_widget.dart b/lib/chatvibe_ui/widgets/room/room_online_user_widget.dart new file mode 100644 index 0000000..e2f4c98 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/room_online_user_widget.dart @@ -0,0 +1,108 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_lk_dialog_util.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; + +import '../../../chatvibe_features/room/online/at_room_online_page.dart'; + +class RoomOnlineUserWidget extends StatefulWidget { + @override + _RoomOnlineUserWidgetState createState() => _RoomOnlineUserWidgetState(); +} + +class _RoomOnlineUserWidgetState extends State { + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + if (ref.playingLudoGame != null && !ref.isMinLudoGame) { + return Container(); + } + return Row( + children: [ + SizedBox(width: 15.w), + GestureDetector( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 3.w, vertical: 3.w), + decoration: BoxDecoration( + color: Colors.white12, + borderRadius: BorderRadius.all(Radius.circular(25.w)), + ), + child: Row( + children: [ + SizedBox(width: 3.w), + Image.asset( + "atu_images/room/at_icon_online_peple.png", + width: 12.w, + height: 12.sp, + ), + SizedBox(width: 3.w), + text("${ref.onlineUsers.length}", fontSize: 9.sp), + Icon( + Icons.chevron_right_outlined, + size: 10.w, + color: Colors.white, + ), + ], + ), + ), + onTap: () { + showBottomInBottomDialog( + context, + ATRoomOnlinePage( + roomId: ref.currenRoom?.roomProfile?.roomProfile?.id, + ), + ); + }, + ), + SizedBox(width: 5.w), + ref.onlineUsers.isNotEmpty + ? Expanded( + child: SizedBox( + height: 25.w, + child: Stack( + clipBehavior: Clip.none, + children: [ + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.min, + children: List.generate(ref.onlineUsers.length, ( + index, + ) { + return Transform.translate( + offset: Offset(-3.w * index, 0), + child: Padding( + padding: EdgeInsets.only(right: 0.w), + child: netImage( + url: + ref.onlineUsers[index].userAvatar ?? "", + width: 23.w, + height: 23.w, + shape: BoxShape.circle, + border: Border.all( + color: Colors.white, + width: 1.w, + ), + ), + ), + ); + }), + ), + ), + ], + ), + ), + ) + : Container(), + ], + ); + }, + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/room_play_widget.dart b/lib/chatvibe_ui/widgets/room/room_play_widget.dart new file mode 100644 index 0000000..ae62f7f --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/room_play_widget.dart @@ -0,0 +1,255 @@ +import 'package:aslan/chatvibe_ui/widgets/room/redpack/room_redenvelope_list_page.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_banner_view.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_task_page.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import '../../../chatvibe_features/index/main_route.dart'; +import '../../../chatvibe_features/room/music/control/at_room_music_control_page.dart'; +import '../../../chatvibe_managers/audio_manager.dart'; +import '../../../chatvibe_managers/rtm_manager.dart'; + +class RoomPlayWidget extends StatefulWidget { + @override + _RoomPlayWidgetState createState() => _RoomPlayWidgetState(); +} + +class _RoomPlayWidgetState extends State { + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + alignment: Alignment.bottomCenter, + child: SingleChildScrollView( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + children: [ + Selector( + selector: + (_, provider) => + provider.currentMusicMode != + null, + shouldRebuild: + (prev, next) => prev != next, + builder: (context, hasMusicMode, _) { + return RepaintBoundary( + child: + hasMusicMode + ? GestureDetector( + child: Container( + margin: + EdgeInsets.symmetric( + vertical: 5.w, + ), + child: Image.asset( + "atu_images/room/at_icon_room_music_tag.png", + width: 45.w, + height: 45.w, + ), + ), + onTap: () { + SmartDialog.show( + tag: + "showRoomMusicControl", + alignment: + Alignment + .center, + debounce: true, + animationType: + SmartAnimationType + .fade, + clickMaskDismiss: + true, + builder: (_) { + return ATRoomMusicControlPage(); + }, + ); + }, + ) + : Container(), + ); + }, + ), + GestureDetector( + child: Selector( + selector: (_, p) => p.roomTaskClaimableCount, + shouldRebuild: (prev, next) => prev != next, + builder: (context, claimableCount, _) { + return RepaintBoundary( + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + Container( + margin: EdgeInsets.symmetric( + vertical: 5.w, + ), + child: Image.asset( + "atu_images/room/at_icon_room_task.png", + width: 45.w, + height: 45.w, + ), + ), + PositionedDirectional( + bottom: 8.w, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + "atu_images/room/at_icon_room_task_tag.png", + width: 10.w, + height: 10.w, + ), + SizedBox(width: 2.w), + text( + "$claimableCount", + fontSize: 9.w, + textColor: Colors.white, + ), + ], + ), + ), + ], + ), + ); + }, + ), + onTap: () { + SmartDialog.show( + tag: "showRoomTaskDialog", + alignment: Alignment.bottomCenter, + animationType: SmartAnimationType.fade, + builder: (_) { + return RoomTaskPage(); + }, + onDismiss: () { + ref.getRoomTaskClaimableCount(); + }, + ); + }, + ), + + Selector( + selector: + (_, p) => + p.redPacketList.isNotEmpty, + shouldRebuild: + (prev, next) => prev != next, + builder: (context, hasRedPacket, _) { + return RepaintBoundary( + child: + hasRedPacket + ? GestureDetector( + child: Container( + margin: + EdgeInsets.symmetric( + vertical: 5.w, + ), + child: Image.asset( + "atu_images/room/at_icon_room_redpack_tag.png", + width: 45.w, + height: 45.w, + ), + ), + onTap: () { + SmartDialog.show( + tag: + "showRoomRedenvelopeList", + alignment: + Alignment + .center, + animationType: + SmartAnimationType + .fade, + builder: (_) { + return RoomRedenvelopeListPage(); + }, + ); + }, + ) + : Container(), + ); + }, + ), + ATDebounceWidget( + child: Selector( + selector: (c, p) => p.allUnReadCount, + shouldRebuild: (prev, next) => prev != next, + builder: (_, allUnReadCount, __) { + return Container( + margin: EdgeInsets.symmetric( + vertical: 5.w, + ), + child: + allUnReadCount > 0 + ? Badge( + backgroundColor: Colors.red, + label: text( + "${allUnReadCount > 99 ? "99+" : allUnReadCount}", + fontSize: 9.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + alignment: + AlignmentDirectional.topEnd, + child: Image.asset( + "atu_images/room/at_icon_botton_message.png", + width: 45.w, + height: 45.w, + fit: BoxFit.contain, + ), + ) + : Image.asset( + "atu_images/room/at_icon_botton_message.png", + width: 45.w, + height: 45.w, + fit: BoxFit.contain, + ), + ); + }, + ), + onTap: () { + ATNavigatorUtils.push( + context, + "${MainRoute.message}?isFromRoom=true", + ); + }, + ), + Container( + height: 56.w, + margin: EdgeInsets.symmetric(vertical: 5.w), + child: RepaintBoundary(child: RoomBannerView()), + ), + SizedBox(height: 10.w), + ], + ), + SizedBox(width: 10.w), + ], + ), + ), + ), + ], + ), + ), + ], + ); + }, + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/room_special_effects_management_page.dart b/lib/chatvibe_ui/widgets/room/room_special_effects_management_page.dart new file mode 100644 index 0000000..4dc5f96 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/room_special_effects_management_page.dart @@ -0,0 +1,272 @@ +import 'dart:ui' as ui; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_core/utilities/at_gift_vap_svga_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/join_room_res.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; + +import '../../../chatvibe_data/models/enum/at_vip_type.dart'; +import '../../components/at_debounce_widget.dart'; +import '../../components/at_tts.dart'; +import '../../components/text/at_text.dart'; + +class RoomSpecialEffectsManagementPage extends StatefulWidget { + @override + _RoomSpecialEffectsManagementPageState createState() => + _RoomSpecialEffectsManagementPageState(); +} + +class _RoomSpecialEffectsManagementPageState + extends State { + JoinRoomRes? room; + + @override + void initState() { + super.initState(); + room = Provider.of(context, listen: false).currenRoom; + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 15, sigmaY: 15), + child: Container( + height: 350.w, + color: Colors.black54, + child: Column( + children: [ + SizedBox(height: 10.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + ATAppLocalizations.of(context)!.specialEffectsManagement, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + SizedBox(height: 15.w), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(width: 15.w), + text( + ATAppLocalizations.of(context)!.giftSpecialEffects, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + Spacer(), + ATDebounceWidget( + child: Image.asset( + ATGlobalConfig.isGiftSpecialEffects + ? "atu_images/general/at_icon_switch_on.png" + : "atu_images/general/at_icon_switch_off.png", + width: 40.w, + height: 20.w, + ), + onTap: () { + setState(() { + ATGlobalConfig.isGiftSpecialEffects = + !ATGlobalConfig.isGiftSpecialEffects; + }); + DataPersistence.setBool( + "${AccountStorage().getCurrentUser()?.userProfile?.account}-GiftSpecialEffects", + ATGlobalConfig.isGiftSpecialEffects, + ); + if (!ATGlobalConfig.isGiftSpecialEffects) { + ATGiftVapSvgaManager().clearTasks(); + } + }, + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 15.w), + Divider( + color: Colors.grey, + endIndent: 15.w, + indent: 15.w, + thickness: 0.2, + ), + SizedBox(height: 15.w), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(width: 15.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text( + ATAppLocalizations.of(context)!.entryVehicleAnimation, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + text( + ATAppLocalizations.of( + context, + )!.entryVehicleAnimation2, + fontSize: 10.sp, + maxLines: 2, + fontWeight: FontWeight.w600, + textColor: Colors.white54, + ), + ], + ), + ), + ATDebounceWidget( + child: Image.asset( + ATGlobalConfig.isEntryVehicleAnimation + ? "atu_images/general/at_icon_switch_on.png" + : "atu_images/general/at_icon_switch_off.png", + width: 40.w, + height: 20.w, + ), + onTap: () { + var vip = + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.getVIP(); + if (vip?.name == ATVIPType.DUKE.name || + vip?.name == ATVIPType.KING.name) { + setState(() { + ATGlobalConfig.isEntryVehicleAnimation = + !ATGlobalConfig.isEntryVehicleAnimation; + }); + DataPersistence.setBool( + "${AccountStorage().getCurrentUser()?.userProfile?.account}-EntryVehicleAnimation", + ATGlobalConfig.isEntryVehicleAnimation, + ); + } else { + ATTts.show( + ATAppLocalizations.of( + context, + )!.thisFeatureIsCurrentlyUnavailable, + ); + } + }, + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 15.w), + Divider( + color: Colors.grey, + endIndent: 15.w, + indent: 15.w, + thickness: 0.2, + ), + SizedBox(height: 15.w), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(width: 15.w), + text( + ATAppLocalizations.of(context)!.floatingAnimationInGlobal, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + Spacer(), + ATDebounceWidget( + child: Image.asset( + ATGlobalConfig.isFloatingAnimationInGlobal + ? "atu_images/general/at_icon_switch_on.png" + : "atu_images/general/at_icon_switch_off.png", + width: 40.w, + height: 20.w, + ), + onTap: () { + setState(() { + ATGlobalConfig.isFloatingAnimationInGlobal = + !ATGlobalConfig.isFloatingAnimationInGlobal; + }); + DataPersistence.setBool( + "${AccountStorage().getCurrentUser()?.userProfile?.account}-FloatingAnimationInGlobal", + ATGlobalConfig.isFloatingAnimationInGlobal, + ); + }, + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 15.w), + Divider( + color: Colors.grey, + endIndent: 15.w, + indent: 15.w, + thickness: 0.2, + ), + SizedBox(height: 15.w), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(width: 15.w), + text( + ATAppLocalizations.of(context)!.luckGiftSpecialEffects, + fontSize: 14.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + Spacer(), + ATDebounceWidget( + child: Image.asset( + ATGlobalConfig.isLuckGiftSpecialEffects + ? "atu_images/general/at_icon_switch_on.png" + : "atu_images/general/at_icon_switch_off.png", + width: 40.w, + height: 20.w, + ), + onTap: () { + setState(() { + ATGlobalConfig.isLuckGiftSpecialEffects = + !ATGlobalConfig.isLuckGiftSpecialEffects; + }); + DataPersistence.setBool( + "${AccountStorage().getCurrentUser()?.userProfile?.account}-LuckGiftSpecialEffects", + ATGlobalConfig.isLuckGiftSpecialEffects, + ); + if (!ATGlobalConfig.isLuckGiftSpecialEffects) { + ATGiftVapSvgaManager().clearTasks(); + Provider.of(context, listen: false).cleanLuckGiftBackCoins(); + } + }, + ), + SizedBox(width: 10.w), + ], + ), + SizedBox(height: 15.w), + Divider( + color: Colors.grey, + endIndent: 15.w, + indent: 15.w, + thickness: 0.2, + ), + SizedBox(height: 10.w), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/room_task_page.dart b/lib/chatvibe_ui/widgets/room/room_task_page.dart new file mode 100644 index 0000000..e6d85c4 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/room_task_page.dart @@ -0,0 +1,818 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:marquee/marquee.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/room_reward_countdown_timer.dart'; + +import '../../../chatvibe_data/models/enum/at_task_code_type.dart'; +import '../../../chatvibe_data/models/enum/at_vip_type.dart'; +import '../../../chatvibe_domain/models/res/at_room_task_list_res.dart'; +import '../../components/at_compontent.dart'; +import '../../components/at_debounce_widget.dart'; +import '../../components/at_tts.dart'; +import '../../components/text/at_text.dart'; + +class RoomTaskPage extends StatefulWidget { + const RoomTaskPage({super.key}); + + @override + _RoomTaskPageState createState() => _RoomTaskPageState(); +} + +class _RoomTaskPageState extends State { + int type = 0; + int taskCategory = 1; + ATRoomTaskListRes? roomTaskListRes; + bool isLoding = true; + + //折扣 + double disCount = 1.0; + + @override + void initState() { + super.initState(); + if (AccountStorage().getVIP()?.name == ATVIPType.VISCOUNT.name) { + // disCount = 1.05; + } else if (AccountStorage().getVIP()?.name == ATVIPType.EARL.name) { + // disCount = 1.07; + } else if (AccountStorage().getVIP()?.name == ATVIPType.MARQUIS.name) { + disCount = 1.10; + } else if (AccountStorage().getVIP()?.name == ATVIPType.DUKE.name) { + disCount = 1.15; + } else if (AccountStorage().getVIP()?.name == ATVIPType.KING.name) { + disCount = 1.20; + } else if (AccountStorage().getVIP()?.name == ATVIPType.EMPEROR.name) { + disCount = 1.25; + } + _loadData(); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + Container( + height: ScreenUtil().screenHeight * 0.85, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/room/at_icon_room_task_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + SizedBox(height: 125.w), + Row( + children: [ + Spacer(), + GestureDetector( + child: Image.asset( + "atu_images/room/at_icon_room_reward_help.png", + height: 23.w, + width: 23.w, + ), + onTap: () { + SmartDialog.show( + tag: "showRoomTaskRuleDialog", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return Container( + height: 340.w, + padding: EdgeInsets.symmetric(horizontal: 28.w), + width: ScreenUtil().screenWidth * 0.9, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_task_rule_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + SizedBox(height: 56.w), + text( + ATAppLocalizations.of( + context, + )!.dailyCoinBonanzaRules, + fontSize: 15.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + SizedBox(height: 35.w), + Expanded( + child: SingleChildScrollView( + child: text( + maxLines: 25, + ATAppLocalizations.of( + context, + )!.dailyCoinBonanzaRulesDetail, + textColor: Colors.white, + fontSize: 13.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + SizedBox(height: 25.w), + ], + ), + ); + }, + ); + }, + ), + SizedBox(width: 30.w), + ], + ), + SizedBox(height: 15.w), + Directionality( + textDirection: TextDirection.ltr, + child: RoomRewardCountdownTimer( + key: UniqueKey(), + targetDate: DateTime.fromMillisecondsSinceEpoch( + roomTaskListRes?.countdown ?? 0, + ), + ), + ), + SizedBox(height: 15.w), + _buildTaskTabs(), + SizedBox(height: 15.w), + Expanded( + child: ListView.separated( + itemCount: roomTaskListRes?.tasks?.length ?? 0, // 列表项数量 + padding: EdgeInsets.only( + bottom: 15.w, + left: 20.w, + right: 20.w, + ), + itemBuilder: (context, index) { + return _buildTaskItem( + index, + roomTaskListRes?.tasks?[index], + ); + }, + separatorBuilder: (BuildContext context, int index) { + return SizedBox(height: 10.w); + }, + ), + ), + ], + ), + ), + isLoding + ? Container( + color: Colors.transparent, + child: Center( + child: Container( + decoration: BoxDecoration( + color: Colors.black26, + borderRadius: BorderRadius.circular(8), + ), + width: 55.w, + height: 55.w, + child: CupertinoActivityIndicator(), + ), + ), + ) + : Container(), + ], + ), + ); + } + + void _loadData() { + setState(() { + isLoding = true; + }); + ChatRoomRepository() + .roomTaskList(taskCategory) + .then((res) { + roomTaskListRes = res; + isLoding = false; + setState(() {}); + }) + .catchError((e) { + setState(() { + isLoding = true; + }); + }); + } + + Widget _buildTaskTabs() { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 22.w), + child: Directionality( + textDirection: TextDirection.ltr, + child: Container( + height: 58.w, + decoration: BoxDecoration( + color: Color(0xFF1B1A22), + borderRadius: BorderRadius.circular(4.w), + border: Border.all(color: Color(0xFF9A6A34), width: 1.w), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.22), + blurRadius: 10.w, + offset: Offset(0, 4.w), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(3.w), + child: LayoutBuilder( + builder: (context, constraints) { + final double selectedWidth = constraints.maxWidth * 0.56; + return Stack( + children: [ + Positioned.fill( + child: IgnorePointer( + child: + type == 0 + ? Align( + alignment: Alignment.centerLeft, + child: SizedBox( + width: selectedWidth, + child: ClipPath( + clipper: _RoomTaskSelectedTabClipper( + isLeft: true, + ), + child: _buildSelectedTaskTabBackground(), + ), + ), + ) + : Align( + alignment: Alignment.centerRight, + child: SizedBox( + width: selectedWidth, + child: ClipPath( + clipper: _RoomTaskSelectedTabClipper( + isLeft: false, + ), + child: _buildSelectedTaskTabBackground(), + ), + ), + ), + ), + ), + Row( + children: [ + Expanded( + child: _buildTaskTabItem( + label: + ATAppLocalizations.of(context)!.personalTasks, + selected: type == 0, + onTap: () => _changeTaskTab(0), + margin: EdgeInsets.only(right: 5.w), + textAlign: TextAlign.center, + ), + ), + Expanded( + child: _buildTaskTabItem( + label: + ATAppLocalizations.of(context)!.roomOwnerTasks, + selected: type == 1, + onTap: () => _changeTaskTab(1), + margin: EdgeInsets.only(left: 5.w), + textAlign: TextAlign.center, + ), + ), + ], + ), + ], + ); + }, + ), + ), + ), + ), + ); + } + + Widget _buildSelectedTaskTabBackground() { + return Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [Color(0xFFF3D6AE), Color(0xFFBE8A55)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + ); + } + + Widget _buildTaskTabItem({ + required String label, + required bool selected, + required VoidCallback onTap, + required EdgeInsets margin, + required TextAlign textAlign, + }) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: onTap, + child: Container( + margin: margin, + alignment: Alignment.center, + padding: EdgeInsets.symmetric(horizontal: 10.w), + child: text( + label, + maxLines: 1, + textAlign: textAlign, + fontStyle: FontStyle.italic, + fontSize: 14.sp, + letterSpacing: 0.5, + fontWeight: FontWeight.w700, + textColor: selected ? Color(0xFF2E1C10) : Color(0xFFE9C48B), + ), + ), + ); + } + + void _changeTaskTab(int nextType) { + if (type == nextType) { + return; + } + setState(() { + type = nextType; + }); + taskCategory = nextType == 0 ? 1 : 2; + _loadData(); + } + + Widget _buildTaskItem(int index, Tasks? task) { + return Container( + height: 105.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_task_list_item_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Row( + children: [ + SizedBox(width: 18.w), + netImage(url: task?.iconUrl ?? "", height: 35.w, width: 35.w), + SizedBox(width: 3.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 20.w), + Container( + constraints: BoxConstraints(maxWidth: 180.w), + height: 17.w, + child: + "${_getTaskName(task?.taskCode ?? "")}".length > 25 + ? Marquee( + text: _getTaskName(task?.taskCode ?? ""), + style: TextStyle( + fontSize: 12.sp, + height: 1.11, + color: Colors.white, + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + ), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.start, + blankSpace: 40.0, + velocity: 40.0, + pauseAfterRound: Duration(seconds: 1), + accelerationDuration: Duration(seconds: 1), + accelerationCurve: Curves.easeOut, + decelerationDuration: Duration(milliseconds: 550), + decelerationCurve: Curves.easeOut, + ) + : Text( + _getTaskName(task?.taskCode ?? ""), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12.sp, + color: Colors.white, + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + ), + ), + ), + SizedBox(height: 8.w), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Container( + padding: EdgeInsetsDirectional.only(end: 18.w), + child: Stack( + children: [ + Container( + margin: EdgeInsets.only(top: 3.w), + decoration: BoxDecoration( + color: Colors.white24, + borderRadius: BorderRadius.circular(8.w), + ), + height: 8.w, + width: _getProgressWidth(task), + ), + Container( + margin: EdgeInsets.only(top: 3.w), + height: 8.w, + width: _getCurrenProgress(task), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [Color(0xffDD8000), Color(0xffFFFFFF)], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + borderRadius: BorderRadius.circular(8.w), + ), + ), + Container( + margin: EdgeInsetsDirectional.only(start: 3.w), + width: _getProgressWidth(task), + child: Row( + children: + task?.tiers + ?.map( + (e) => Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.end, + children: [ + Image.asset( + "atu_images/general/at_icon_jb.png", + width: 15.w, + height: 15.w, + ), + Transform.translate( + offset: Offset( + ATGlobalConfig.lang == "ar" + ? -10.w + : 10.w, + 0, + ), + child: text( + "${_getTargetValue(e.targetValue ?? 0)} ${_getTargetUnit(task.taskCode ?? "")}", + fontSize: 9.sp, + textColor: Colors.white, + ), + ), + Transform.translate( + offset: Offset( + ATGlobalConfig.lang == "ar" + ? -(_translateRewardValue( + "+${e.rewardValue}" + .length, + )) + : _translateRewardValue( + "+${e.rewardValue}" + .length, + ), + 0, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.end, + children: [ + disCount > 1 + ? text( + "+${e.rewardValue}", + fontSize: 9.sp, + textColor: Color( + 0xffFFB627, + ), + decoration: + TextDecoration + .lineThrough, + lineHeight: 0.95, + ) + : Container(), + text( + "+${((e.rewardValue ?? 0) * disCount + 1e-9).floor()}", + fontSize: 9.sp, + lineHeight: 0.95, + textColor: Color( + 0xffFFB627, + ), + ), + ], + ), + ), + ], + ), + ), + ) + .toList() ?? + [], + ), + ), + ], + ), + ), + ), + ], + ), + ), + SizedBox(width: 8.w), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: 10.w), + _buildReciverTaskBtn(task), + text( + "(${task?.currentValue ?? 0}/${task?.tiers?.last.targetValue ?? 0})", + fontSize: 12.sp, + fontWeight: FontWeight.w600, + textColor: Colors.white, + ), + ], + ), + SizedBox(width: 12.w), + ], + ), + ); + } + + _buildReciverTaskBtn(Tasks? task) { + /// 0 go,1 claim,2 completed + int state = 0; + if (task?.currentValue == task?.tiers?.last.targetValue) { + ///进度条跑完了 + for (int index = 0; index < (task?.tiers ?? []).length; index++) { + Tiers? tier = task?.tiers?[index]; + if ((tier?.isCompleted ?? false) && !(tier?.isClaimed ?? false)) { + ///已经完成没有领取 + state = 1; + break; + } else { + state = 2; + } + } + } else { + for (int index = 0; index < (task?.tiers ?? []).length; index++) { + Tiers? tier = task?.tiers?[index]; + if ((tier?.isCompleted ?? false) && !(tier?.isClaimed ?? false)) { + ///已经完成没有领取 + state = 1; + break; + } else { + state = 0; + } + } + } + + if (state == 0) { + return Container( + width: 72.w, + height: 28.w, + alignment: Alignment.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_task_list_item_go_btn.png", + ), + fit: BoxFit.fill, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + text( + ATAppLocalizations.of(context)!.go, + textColor: Colors.white, + fontSize: 13.sp, + fontWeight: FontWeight.w600, + ), + Icon( + ATGlobalConfig.lang == "ar" + ? Icons.keyboard_arrow_left + : Icons.keyboard_arrow_right, + size: 16.w, + color: Colors.white, + ), + ], + ), + ); + } else if (state == 1) { + return ATDebounceWidget( + child: Container( + width: 72.w, + height: 28.w, + alignment: Alignment.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_task_list_item_act_btn.png", + ), + fit: BoxFit.fill, + ), + ), + child: text( + ATAppLocalizations.of(context)!.claim, + textColor: Colors.white, + fontSize: 13.sp, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + _claim(task); + }, + ); + } else if (state == 2) { + return Container( + width: 72.w, + height: 28.w, + alignment: Alignment.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_task_list_item_complete_btn.png", + ), + fit: BoxFit.fill, + ), + ), + child: text( + ATAppLocalizations.of(context)!.complete, + textColor: Colors.white, + fontSize: 13.sp, + fontWeight: FontWeight.w600, + ), + ); + } + } + + _translateRewardValue(int lenght) { + double ws = 0; + if (lenght == 3) { + ws = 1.w; + } else if (lenght == 4) { + ws = 4.w; + } else if (lenght == 5) { + ws = 5.w; + } else if (lenght == 6) { + ws = 8.w; + } else if (lenght == 7) { + ws = 10.w; + }else if (lenght == 8) { + ws = 12.w; + } + return ws; + } + + _getTargetValue(int targetValue) { + if (targetValue >= 1000000) { + return _formatCompactTargetValue(targetValue / 1000000, 'M'); + } else if (targetValue >= 1000) { + return _formatCompactTargetValue(targetValue / 1000, 'K'); + } + return targetValue.toString(); + } + + String _formatCompactTargetValue(double value, String suffix) { + final bool hasDecimal = value.truncateToDouble() != value; + final String formatted = value.toStringAsFixed(hasDecimal ? 1 : 0); + return '$formatted$suffix'; + } + + _getTaskName(String taskCode) { + if (taskCode == ATTaskCodeType.PERSONAL_GAME_CONSUME.name) { + return ATAppLocalizations.of(context)!.taskNamePersonalGameConsume; + } else if (taskCode == ATTaskCodeType.PERSONAL_MIC_IN_ROOM.name) { + return ATAppLocalizations.of(context)!.taskNamePersonalMicInRoom; + } else if (taskCode == ATTaskCodeType.PERSONAL_ACTIVE_IN_ROOM.name) { + return ATAppLocalizations.of(context)!.taskNamePersonalActiveInRoom; + } else if (taskCode == ATTaskCodeType.ROOM_OWNER_MIC_TIME.name) { + return ATAppLocalizations.of(context)!.taskNameRoomOwnerMicTime; + } else if (taskCode == ATTaskCodeType.PERSONAL_LUCKY_GIFT_GOLD.name) { + return ATAppLocalizations.of(context)!.taskNamePersonalLuckyGiftGold; + } else if (taskCode == ATTaskCodeType.PERSONAL_MAGIC_GIFT_GOLD.name) { + return ATAppLocalizations.of(context)!.taskNamePersonalMagicGiftGold; + } else if (taskCode == ATTaskCodeType.ROOM_OWNER_SEND_GIFT_GOLD.name) { + return ATAppLocalizations.of(context)!.taskNameRoomOwnerSendGiftGold; + } else if (taskCode == ATTaskCodeType.ROOM_USER_SEND_GIFT_GOLD.name) { + return ATAppLocalizations.of(context)!.taskNameRoomUserSendGiftGold; + } else if (taskCode == ATTaskCodeType.ROOM_OWNER_INVITE_MIC.name) { + return ATAppLocalizations.of(context)!.taskNameRoomOwnerInviteMic; + } else if (taskCode == ATTaskCodeType.ROOM_ONLINE_USER_COUNT.name) { + return ATAppLocalizations.of(context)!.taskNameRoomOnlineUserCount; + } else if (taskCode == ATTaskCodeType.PERSONAL_SEND_GIFT.name) { + return ATAppLocalizations.of(context)!.taskNamePersonalSendGift; + } else if (taskCode == ATTaskCodeType.ROOM_MIC_USER_30MIN.name) { + return ATAppLocalizations.of(context)!.taskNameRoomMicUser30Min; + } else if (taskCode == ATTaskCodeType.ROOM_MIC_USER_60MIN.name) { + return ATAppLocalizations.of(context)!.taskNameRoomMicUser60Min; + } else if (taskCode == ATTaskCodeType.ROOM_MIC_USER_120MIN.name) { + return ATAppLocalizations.of(context)!.taskNameRoomMicUser120Min; + } else if (taskCode == ATTaskCodeType.ROOM_OWNER_SEND_GIFT_USER.name) { + return ATAppLocalizations.of(context)!.taskNameRoomOwnerSendGiftUser; + } else if (taskCode == ATTaskCodeType.ROOM_OWNER_SEND_RED_PACKET.name) { + return ATAppLocalizations.of(context)!.taskNameRoomOwnerSendRedPacket; + } else if (taskCode == ATTaskCodeType.ROOM_NEW_MEMBER.name) { + return ATAppLocalizations.of(context)!.taskNameRoomNewMember; + } + } + + _getTargetUnit(String taskCode) { + if (taskCode == ATTaskCodeType.PERSONAL_GAME_CONSUME.name || + taskCode == ATTaskCodeType.PERSONAL_LUCKY_GIFT_GOLD.name || + taskCode == ATTaskCodeType.PERSONAL_MAGIC_GIFT_GOLD.name || + taskCode == ATTaskCodeType.ROOM_OWNER_SEND_GIFT_GOLD.name || + taskCode == ATTaskCodeType.ROOM_USER_SEND_GIFT_GOLD.name) { + return "Coins"; + } else if (taskCode == ATTaskCodeType.ROOM_OWNER_MIC_TIME.name || + taskCode == ATTaskCodeType.PERSONAL_MIC_IN_ROOM.name || + taskCode == ATTaskCodeType.PERSONAL_ACTIVE_IN_ROOM.name) { + return "Min"; + } else if (taskCode == ATTaskCodeType.ROOM_OWNER_INVITE_MIC.name || + taskCode == ATTaskCodeType.ROOM_ONLINE_USER_COUNT.name || + taskCode == ATTaskCodeType.PERSONAL_SEND_GIFT.name || + taskCode == ATTaskCodeType.ROOM_MIC_USER_30MIN.name || + taskCode == ATTaskCodeType.ROOM_MIC_USER_60MIN.name || + taskCode == ATTaskCodeType.ROOM_MIC_USER_120MIN.name || + taskCode == ATTaskCodeType.ROOM_OWNER_SEND_GIFT_USER.name || + taskCode == ATTaskCodeType.ROOM_OWNER_SEND_RED_PACKET.name || + taskCode == ATTaskCodeType.ROOM_NEW_MEMBER.name) { + return "Member"; + } + return ""; + } + + _getProgressWidth(Tasks? task) { + if ((task?.tiers?.length ?? 0) < 4) { + return ScreenUtil().screenWidth * 0.50; + } else { + return ScreenUtil().screenWidth * 0.50 / 3 * (task?.tiers?.length ?? 0); + } + } + + void _claim(Tasks? task) { + ChatRoomRepository() + .roomTaskClaim(task?.taskCode ?? "") + .then((res) { + ATTts.show(ATAppLocalizations.of(context)!.receiveSucc); + _loadData(); + }) + .catchError((e) { + _loadData(); + }); + } + + _getCurrenProgress(Tasks? task) { + int currentValue = task?.currentValue ?? 0; + var tiers = task?.tiers ?? []; + + if (tiers.isEmpty) return 0.0; + + // 假设 tiers 已按 targetValue 升序排列 + int completedCount = 0; + + for (var tier in tiers) { + if (currentValue >= (tier.targetValue ?? 0)) { + completedCount++; + } else { + break; + } + } + // 计算进度:完成的层级数 / 总层级数 * 总宽度 + num w = _getProgressWidth(task); + return (w / tiers.length) * completedCount; + } +} + +class _RoomTaskSelectedTabClipper extends CustomClipper { + final bool isLeft; + + _RoomTaskSelectedTabClipper({required this.isLeft}); + + @override + Path getClip(Size size) { + final double cutWidth = 40.w; + final Path path = Path(); + if (isLeft) { + path.moveTo(0, 0); + path.lineTo(size.width - cutWidth, 0); + path.lineTo(size.width, size.height); + path.lineTo(0, size.height); + } else { + path.moveTo(cutWidth, 0); + path.lineTo(size.width, 0); + path.lineTo(size.width, size.height); + path.lineTo(0, size.height); + } + path.close(); + return path; + } + + @override + bool shouldReclip(covariant _RoomTaskSelectedTabClipper oldClipper) { + return oldClipper.isLeft != isLeft; + } +} diff --git a/lib/chatvibe_ui/widgets/room/room_user_card_setting.dart b/lib/chatvibe_ui/widgets/room/room_user_card_setting.dart new file mode 100644 index 0000000..7c31496 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/room_user_card_setting.dart @@ -0,0 +1,170 @@ +import 'dart:ui' as ui; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/set_room_role_page.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_core/constants/at_screen.dart'; +import 'package:aslan/chatvibe_core/utilities/at_lk_dialog_util.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_user_card_res.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; + +import '../../../chatvibe_data/models/enum/at_room_roles_type.dart'; + +class RoomUserCardSetting extends StatefulWidget { + String? roomId; + RoomUserCardRes? userCardInfo; + + RoomUserCardSetting({super.key, this.roomId, this.userCardInfo}); + + @override + _RoomUserCardSettingState createState() => _RoomUserCardSettingState(); +} + +class _RoomUserCardSettingState extends State { + bool isOnMai = false; + bool micMute = false; + + @override + void initState() { + super.initState(); + isOnMai = + Provider.of( + context, + listen: false, + ).userOnMaiInIndex(widget.userCardInfo?.userProfile?.id ?? "") != + -1; + } + + @override + Widget build(BuildContext context) { + List list = []; + list.add(SizedBox(height: 10.w)); + if (isOnMai) { + if (Provider.of(context, listen: false).isFz()) { + ///操作的人是房主 + list.add( + _item(ATAppLocalizations.of(context)!.removeTheMic, null, () { + Navigator.of(context).pop(); + Provider.of( + context, + listen: false, + ).killXiaMai(widget.userCardInfo?.userProfile?.id ?? ""); + }), + ); + } else { + if (widget.userCardInfo?.roomRole != ATRoomRolesType.ADMIN.name && + widget.userCardInfo?.roomRole != ATRoomRolesType.HOMEOWNER.name) { + list.add( + _item(ATAppLocalizations.of(context)!.removeTheMic, null, () { + Navigator.of(context).pop(); + Provider.of( + context, + listen: false, + ).killXiaMai(widget.userCardInfo?.userProfile?.id ?? ""); + }), + ); + } + } + } + if (Provider.of(context, listen: false).isFz()) { + list.add( + _item(ATAppLocalizations.of(context)!.setUpAnIdentity, null, () { + Navigator.of(context).pop(); + showCenterDialog(context, SetRoomRolePage(widget.userCardInfo)); + }), + ); + } + if (widget.userCardInfo?.roomRole != ATRoomRolesType.ADMIN.name && + widget.userCardInfo?.roomRole != ATRoomRolesType.HOMEOWNER.name) { + list.add( + _item(ATAppLocalizations.of(context)!.kickedOutOfRoom, null, () { + Navigator.of(context).pop(); + SmartDialog.show( + tag: "showKickedOutOfRoomDialog", + maskColor: Colors.black26, + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + clickMaskDismiss: true, + builder: (_) { + return KickedOutOfRoomDialog( + roomId: widget.roomId ?? "", + userId: widget.userCardInfo?.userProfile?.id ?? "", + ); + }, + ); + }), + ); + } + list.add( + _item(ATAppLocalizations.of(context)!.cancel, null, () { + Navigator.of(context).pop(); + }), + ); + list.add(SizedBox(height: 10.w)); + return SafeArea( + child: Container( + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topRight: Radius.circular(15.w), + topLeft: Radius.circular(15.w), + ), + color: Colors.white, + //gradient: getLinearGradient([0xff5E12A8, 0xff000000], opacity: 0.9), + ), + child: Column(children: list), + ), + ); + } + + Widget _item(String msg, String? icon, Function onClick) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + onClick(); + }, + child: Column( + children: [ + Container( + decoration: + msg == ATAppLocalizations.of(context)!.cancel + ? BoxDecoration( + color: Color(0xffF2F2F2), + borderRadius: BorderRadius.all(Radius.circular(32.w)), + ) + : null, + width: ScreenUtil().screenWidth * 0.7, + padding: EdgeInsets.symmetric(vertical: 10.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + icon != null + ? Image.asset(icon, height: 18.w, width: 18.w) + : Container(), + SizedBox(width: 5.w), + Container( + alignment: Alignment.center, + child: Text( + msg, + style: TextStyle( + fontSize: sp(16), + color: msg == 'Cancel' ? Color(0xffB1B1B1) : Colors.black, + fontWeight: FontWeight.w400, + decoration: TextDecoration.none, + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/seamless_auto_scroll.dart b/lib/chatvibe_ui/widgets/room/seamless_auto_scroll.dart new file mode 100644 index 0000000..24e73e7 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/seamless_auto_scroll.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class SeamlessAutoScroll extends StatefulWidget { + final List items; + final double itemHeight; + final Duration scrollInterval; + + const SeamlessAutoScroll({ + Key? key, + required this.items, + this.itemHeight = 120.0, + this.scrollInterval = const Duration(seconds: 2), + }) : super(key: key); + + @override + _AutoScrollListState createState() => _AutoScrollListState(); +} + +class _AutoScrollListState extends State { + final ScrollController _scrollController = ScrollController(); + Timer? _timer; + int _currentIndex = 0; + + @override + void initState() { + super.initState(); + _startAutoScroll(); + } + + @override + void dispose() { + _timer?.cancel(); + _scrollController.dispose(); + super.dispose(); + } + + void _startAutoScroll() { + _timer = Timer.periodic(widget.scrollInterval, (timer) { + if (_currentIndex < widget.items.length) { + // 滚动到下一个项目 + _scrollController.animateTo( + _currentIndex * widget.itemHeight, + duration: const Duration(milliseconds: 550), + curve: Curves.easeIn, + ); + _currentIndex++; + } else { + // 滚动到底部后回到顶部重新开始 + _currentIndex = 0; + _scrollController.jumpTo(0); + } + }); + } + + @override + Widget build(BuildContext context) { + return Container( + height: 110.w, // 固定容器高度 + decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)), + child: ListView.builder( + controller: _scrollController, + physics: const NeverScrollableScrollPhysics(), // 禁用手动滚动 + itemCount: widget.items.length, + itemBuilder: (context, index) { + return Container( + height: widget.itemHeight, + alignment: Alignment.centerLeft, + child: widget.items[index], + ); + }, + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/seat/room_seat_widget.dart b/lib/chatvibe_ui/widgets/room/seat/room_seat_widget.dart new file mode 100644 index 0000000..3304f56 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/seat/room_seat_widget.dart @@ -0,0 +1,886 @@ +import 'dart:async'; +import 'dart:math' as math; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_features/room/seat/seat_item.dart'; +import 'package:aslan/chatvibe_features/webview/game_bs_webview_page.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/lucky_gift_tag_widget.dart'; +import 'package:aslan/chatvibe_ui/widgets/headdress/headdress_widget.dart'; + +import '../../../../chatvibe_data/models/enum/at_game_mode_type.dart'; +import '../../../../chatvibe_domain/models/res/login_res.dart'; +import '../../../../chatvibe_domain/models/res/mic_res.dart'; + +class RoomSeatWidget extends StatefulWidget { + const RoomSeatWidget({super.key}); + + @override + _RoomSeatWidgetState createState() => _RoomSeatWidgetState(); +} + +class _RoomSeatWidgetState extends State { + static const Duration _cpHeartLoopInterval = Duration(milliseconds: 3000); + static const Duration _cpHeartReplyDelay = Duration(milliseconds: 360); + static const int _cpHeartBurstCount = 2; + static const Duration _cpHeartBurstStagger = Duration(milliseconds: 140); + + final GlobalKey _stackKey = GlobalKey(); + final List<_CpHeartFlightData> _activeFlights = <_CpHeartFlightData>[]; + + Timer? _cpHeartLoopTimer; + int _nextFlightId = 0; + int _lastLayoutDigest = -1; + List<_CpSeatPair> _cpAdjacentPairs = const <_CpSeatPair>[]; + List<_CpSeatPair> _cpRemotePairs = const <_CpSeatPair>[]; + Map _seatCenters = const {}; + Map _seatSizes = const {}; + + @override + void dispose() { + _cpHeartLoopTimer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + final bool isGaming = ref.playingLudoGame != null && !ref.isMinLudoGame; + _scheduleCpHeartRefresh(_buildCpLayoutDigest(ref, isGaming), ref); + if (isGaming) { + return Stack( + key: _stackKey, + alignment: Alignment.center, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 5.w), + ref.roomWheatMap.isNotEmpty + ? Container( + padding: EdgeInsets.symmetric(horizontal: 15.w), + height: 45.w, + child: Center( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + physics: const ClampingScrollPhysics(), + child: Row( + spacing: 15.w, + mainAxisSize: MainAxisSize.min, // 宽度自适应内容 + children: List.generate( + ref.roomWheatMap.length, + (index) => + SeatItem(index: index, isGameModel: true), + ), + ), + ), + ), + ) + : Container(), + + RepaintBoundary( + child: GameBsWebviewPage( + url: ref.playingLudoGame?.gameInfo?.gameCode ?? "", + height: 860.w, + width: ScreenUtil().screenWidth, + gameMode: ATGameModeType.CHAT_ROOM.name, + gameId: ref.playingLudoGame?.gameInfo?.id ?? "", + ), + ), + SizedBox(height: 5.w), + !ref.isMinLudoGame + ? Row( + children: [ + Spacer(), + ATDebounceWidget( + child: Image.asset( + "atu_images/room/at_icon_room_game_min.png", + width: 20.w, + height: 20.w, + ), + onTap: () { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of( + context, + )!.returnToVoiceChat, + btnText: + ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + ref.minOrShowLudoGame(true); + }, + ); + }, + ); + }, + ), + SizedBox(width: 8.w), + ref.isFz() + ? ATDebounceWidget( + child: Image.asset( + "atu_images/room/at_icon_room_game_close.png", + width: 20.w, + height: 20.w, + ), + onTap: () { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: + ATAppLocalizations.of( + context, + )!.tips, + msg: + ATAppLocalizations.of( + context, + )!.exitGameMode, + btnText: + ATAppLocalizations.of( + context, + )!.confirm, + onEnsure: () { + ref.gameLudoDisband(); + }, + ); + }, + ); + }, + ) + : Container(), + ref.isFz() ? SizedBox(width: 8.w) : Container(), + ], + ) + : Container(), + ], + ), + LuckyGiftTagWidget(), + Positioned.fill( + child: IgnorePointer(child: _buildCpAdjacencyOverlay()), + ), + Positioned.fill( + child: IgnorePointer(child: _buildCpHeartOverlay()), + ), + ], + ); + } + return Stack( + key: _stackKey, + children: [ + ref.roomWheatMap.length == 5 + ? _buildSeat5() + : (ref.roomWheatMap.length == 10 + ? _buildSeat10() + : (ref.roomWheatMap.length == 15 + ? _buildSeat15() + : (ref.roomWheatMap.length == 20 + ? _buildSeat20() + : Container(height: 180.w)))), + Positioned.fill( + child: IgnorePointer(child: _buildCpAdjacencyOverlay()), + ), + Positioned.fill( + child: IgnorePointer(child: _buildCpHeartOverlay()), + ), + ], + ); + }, + ); + } + + int _buildCpLayoutDigest(RtcProvider rtcProvider, bool isGaming) { + int digest = rtcProvider.roomWheatMap.length; + for (final entry in rtcProvider.roomWheatMap.entries) { + final MicRes mic = entry.value; + final String userId = mic.user?.id ?? ''; + digest = Object.hash(digest, entry.key, userId); + for (final cp in mic.user?.cpList ?? const []) { + digest = Object.hash( + digest, + cp.cpUserId ?? '', + cp.micEffectLevel ?? 0, + cp.hasEffectLevel == true ? 1 : 0, + ); + } + } + return Object.hash(digest, isGaming ? 1 : 0); + } + + void _scheduleCpHeartRefresh(int digest, RtcProvider rtcProvider) { + if (_lastLayoutDigest == digest) { + return; + } + _lastLayoutDigest = digest; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) { + return; + } + _refreshCpHeartState(rtcProvider); + }); + } + + void _refreshCpHeartState(RtcProvider rtcProvider) { + final RenderBox? stackBox = + _stackKey.currentContext?.findRenderObject() as RenderBox?; + if (stackBox == null || !stackBox.hasSize) { + return; + } + final Map nextCenters = {}; + final Map nextSizes = {}; + for (final entry in rtcProvider.seatGlobalKeyMap.entries) { + final int index = entry.key.toInt(); + final BuildContext? seatContext = entry.value.currentContext; + final RenderBox? seatBox = seatContext?.findRenderObject() as RenderBox?; + if (seatBox == null || !seatBox.hasSize) { + continue; + } + final Offset topLeft = seatBox.localToGlobal( + Offset.zero, + ancestor: stackBox, + ); + nextSizes[index] = seatBox.size; + nextCenters[index] = + topLeft + Offset(seatBox.size.width / 2, seatBox.size.height / 2); + } + final List<_CpSeatPair> nextPairs = _collectCpSeatPairs(rtcProvider); + final _CpPairGroups nextGroups = _splitCpPairsByDistance( + nextPairs, + nextCenters, + ); + final bool pairChanged = + !_samePairs(_cpAdjacentPairs, nextGroups.adjacent) || + !_samePairs(_cpRemotePairs, nextGroups.remote); + _seatCenters = nextCenters; + _seatSizes = nextSizes; + _cpAdjacentPairs = nextGroups.adjacent; + _cpRemotePairs = nextGroups.remote; + if (pairChanged) { + setState(() { + _activeFlights.clear(); + }); + } + _configureCpHeartLoop(); + } + + List<_CpSeatPair> _collectCpSeatPairs(RtcProvider rtcProvider) { + final List<_CpSeatPair> pairs = <_CpSeatPair>[]; + final Set seen = {}; + for (final entry in rtcProvider.roomWheatMap.entries) { + final int fromIndex = entry.key.toInt(); + final ChatVibeUserProfile? user = entry.value.user; + final String fromUserId = user?.id ?? ''; + if (fromUserId.isEmpty) { + continue; + } + for (final CPRes cp in user?.cpList ?? const []) { + final String toUserId = cp.cpUserId ?? ''; + if (toUserId.isEmpty) { + continue; + } + final MapEntry? targetEntry = rtcProvider + .roomWheatMap + .entries + .cast?>() + .firstWhere( + (candidate) => candidate?.value.user?.id == toUserId, + orElse: () => null, + ); + if (targetEntry == null) { + continue; + } + final ChatVibeUserProfile? targetUser = targetEntry.value.user; + final int? micEffectLevel = _resolveMutualCpLevel(user, targetUser); + if (micEffectLevel == null) { + continue; + } + final int toIndex = targetEntry.key.toInt(); + final int start = math.min(fromIndex, toIndex); + final int end = math.max(fromIndex, toIndex); + final String pairKey = '$start-$end'; + if (!seen.add(pairKey)) { + continue; + } + pairs.add( + _CpSeatPair( + fromIndex: start, + toIndex: end, + micEffectLevel: micEffectLevel, + hasNonMicEffect: _resolveMutualNonMicEffectEnabled( + user, + targetUser, + ), + ), + ); + } + } + return pairs; + } + + int? _resolveMutualCpLevel( + ChatVibeUserProfile? userA, + ChatVibeUserProfile? userB, + ) { + final String userAId = userA?.id ?? ''; + final String userBId = userB?.id ?? ''; + if (userAId.isEmpty || userBId.isEmpty) { + return null; + } + final CPRes? aRelation = (userA?.cpList ?? const []) + .cast() + .firstWhere((cp) => cp?.cpUserId == userBId, orElse: () => null); + final CPRes? bRelation = (userB?.cpList ?? const []) + .cast() + .firstWhere((cp) => cp?.cpUserId == userAId, orElse: () => null); + if (aRelation == null || bRelation == null) { + return null; + } + final int aLevel = (aRelation.micEffectLevel ?? 1).toInt(); + final int bLevel = (bRelation.micEffectLevel ?? 1).toInt(); + return math.max(aLevel, bLevel).clamp(1, 3); + } + + bool _resolveMutualNonMicEffectEnabled( + ChatVibeUserProfile? userA, + ChatVibeUserProfile? userB, + ) { + final String userAId = userA?.id ?? ''; + final String userBId = userB?.id ?? ''; + if (userAId.isEmpty || userBId.isEmpty) { + return false; + } + final CPRes? aRelation = (userA?.cpList ?? const []) + .cast() + .firstWhere((cp) => cp?.cpUserId == userBId, orElse: () => null); + final CPRes? bRelation = (userB?.cpList ?? const []) + .cast() + .firstWhere((cp) => cp?.cpUserId == userAId, orElse: () => null); + return aRelation?.hasEffectLevel == true || + bRelation?.hasEffectLevel == true; + } + + String _cpAdjacencyResourceForLevel(int micEffectLevel) { + switch (micEffectLevel) { + case 1: + return "atu_images/room/at_icon_cp_mic_anm_lv_1.svga"; + case 3: + return "atu_images/room/at_icon_cp_mic_anm_lv_3.svga"; + case 2: + default: + return "atu_images/room/at_icon_cp_mic_anm_lv_2.svga"; + } + } + + bool _samePairs(List<_CpSeatPair> left, List<_CpSeatPair> right) { + if (left.length != right.length) { + return false; + } + for (int i = 0; i < left.length; i++) { + if (left[i] != right[i]) { + return false; + } + } + return true; + } + + _CpPairGroups _splitCpPairsByDistance( + List<_CpSeatPair> pairs, + Map seatCenters, + ) { + if (pairs.isEmpty || seatCenters.length < 2) { + return const _CpPairGroups( + adjacent: <_CpSeatPair>[], + remote: <_CpSeatPair>[], + ); + } + final List occupiedCenters = seatCenters.values.toList(); + double minSeatDistance = double.infinity; + for (int i = 0; i < occupiedCenters.length; i++) { + for (int j = i + 1; j < occupiedCenters.length; j++) { + final double distance = + (occupiedCenters[i] - occupiedCenters[j]).distance; + if (distance < minSeatDistance) { + minSeatDistance = distance; + } + } + } + if (!minSeatDistance.isFinite) { + return _CpPairGroups(adjacent: pairs, remote: const <_CpSeatPair>[]); + } + final double adjacentThreshold = minSeatDistance * 1.18; + final List<_CpSeatPair> adjacent = <_CpSeatPair>[]; + final List<_CpSeatPair> remote = <_CpSeatPair>[]; + for (final _CpSeatPair pair in pairs) { + final Offset? from = seatCenters[pair.fromIndex]; + final Offset? to = seatCenters[pair.toIndex]; + if (from == null || to == null) { + continue; + } + final double distance = (from - to).distance; + if (distance <= adjacentThreshold) { + adjacent.add(pair); + } else if (pair.hasNonMicEffect) { + remote.add(pair); + } + } + return _CpPairGroups(adjacent: adjacent, remote: remote); + } + + void _configureCpHeartLoop() { + _cpHeartLoopTimer?.cancel(); + if (_cpRemotePairs.isEmpty || _seatCenters.isEmpty) { + if (mounted && _activeFlights.isNotEmpty) { + setState(() { + _activeFlights.clear(); + }); + } + return; + } + _emitCpHeartFlights(); + _cpHeartLoopTimer = Timer.periodic(_cpHeartLoopInterval, (_) { + if (!mounted) { + return; + } + _emitCpHeartFlights(); + }); + } + + void _emitCpHeartFlights() { + for (final _CpSeatPair pair in _cpRemotePairs) { + _emitHeartBurst(pair.fromIndex, pair.toIndex); + Timer(_cpHeartReplyDelay, () { + if (!mounted || !_cpRemotePairs.contains(pair)) { + return; + } + _emitHeartBurst(pair.toIndex, pair.fromIndex); + }); + } + } + + void _emitHeartBurst(int fromIndex, int toIndex) { + for (int i = 0; i < _cpHeartBurstCount; i++) { + final Duration delay = Duration( + milliseconds: _cpHeartBurstStagger.inMilliseconds * i, + ); + if (delay == Duration.zero) { + _addHeartFlight(fromIndex, toIndex, variant: i); + continue; + } + Timer(delay, () { + if (!mounted) { + return; + } + _addHeartFlight(fromIndex, toIndex, variant: i); + }); + } + } + + void _addHeartFlight(int fromIndex, int toIndex, {int variant = 0}) { + final Offset? from = _seatCenters[fromIndex]; + final Offset? to = _seatCenters[toIndex]; + if (from == null || to == null) { + return; + } + final double distance = (to - from).distance; + final double direction = from.dx <= to.dx ? 1 : -1; + final double arcHeight = math.max( + 20.w, + distance * (0.10 + (variant % 3) * 0.025), + ); + final double controlOffsetX = + direction * ((variant.isEven ? 1 : -1) * (8.w + (variant * 3.w))); + final double startYOffset = (variant.isEven ? -1 : 1) * (variant * 1.5.w); + setState(() { + _activeFlights.add( + _CpHeartFlightData( + id: _nextFlightId++, + from: from.translate(0, startYOffset), + to: to, + arcHeight: arcHeight, + controlOffsetX: controlOffsetX, + size: 15.w + ((variant % 3) * 2.w), + duration: Duration(milliseconds: 760 + (variant * 70)), + rotationAmplitude: 0.12 + (variant * 0.025), + ), + ); + }); + } + + void _removeHeartFlight(int id) { + if (!mounted) { + return; + } + setState(() { + _activeFlights.removeWhere((flight) => flight.id == id); + }); + } + + Widget _buildCpHeartOverlay() { + if (_activeFlights.isEmpty) { + return const SizedBox.shrink(); + } + return Stack( + children: + _activeFlights + .map( + (flight) => _CpHeartFlightWidget( + key: ValueKey(flight.id), + data: flight, + onCompleted: () => _removeHeartFlight(flight.id), + ), + ) + .toList(), + ); + } + + Widget _buildCpAdjacencyOverlay() { + if (_cpAdjacentPairs.isEmpty) { + return const SizedBox.shrink(); + } + return Stack( + children: + _cpAdjacentPairs.map((pair) { + final Offset? from = _seatCenters[pair.fromIndex]; + final Offset? to = _seatCenters[pair.toIndex]; + final Size? fromSize = _seatSizes[pair.fromIndex]; + final Size? toSize = _seatSizes[pair.toIndex]; + if (from == null || + to == null || + fromSize == null || + toSize == null) { + return const SizedBox.shrink(); + } + const double width = 94; + const double height = 62; + final double bottomLine = math.max( + from.dy + fromSize.height / 2, + to.dy + toSize.height / 2, + ); + final double left = ((from.dx + to.dx) / 2) - width.w / 2; + final double top = bottomLine - (height.w * 0.58); + return Positioned( + key: ValueKey( + 'cp-adj-${pair.fromIndex}-${pair.toIndex}-${pair.micEffectLevel}', + ), + left: left, + top: top, + child: SizedBox( + width: width.w, + height: height.w, + child: SVGAHeadwearWidget( + resource: _cpAdjacencyResourceForLevel(pair.micEffectLevel), + width: width.w, + height: height.w, + loops: 0, + clearsAfterStop: false, + ), + ), + ); + }).toList(), + ); + } + + _buildSeat5() { + return Container( + margin: EdgeInsets.only(top: 3.w, left: 10.w, right: 10.w), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + Expanded(flex: 1, child: SeatItem(index: 0)), + Expanded(flex: 1, child: SeatItem(index: 1)), + Expanded(flex: 1, child: SeatItem(index: 2)), + Expanded(flex: 1, child: SeatItem(index: 3)), + Expanded(flex: 1, child: SeatItem(index: 4)), + ], + ), + ], + ), + ); + } + + _buildSeat10() { + return Container( + margin: EdgeInsets.only(top: 3.w, left: 10.w, right: 10.w), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + Expanded(flex: 1, child: SeatItem(index: 0)), + Expanded(flex: 1, child: SeatItem(index: 1)), + Expanded(flex: 1, child: SeatItem(index: 2)), + Expanded(flex: 1, child: SeatItem(index: 3)), + Expanded(flex: 1, child: SeatItem(index: 4)), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(flex: 1, child: SeatItem(index: 5)), + Expanded(flex: 1, child: SeatItem(index: 6)), + Expanded(flex: 1, child: SeatItem(index: 7)), + Expanded(flex: 1, child: SeatItem(index: 8)), + Expanded(flex: 1, child: SeatItem(index: 9)), + ], + ), + ], + ), + ); + } + + _buildSeat15() { + return Container( + margin: EdgeInsets.only(top: 3.w, left: 10.w, right: 10.w), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + Expanded(flex: 1, child: SeatItem(index: 0)), + Expanded(flex: 1, child: SeatItem(index: 1)), + Expanded(flex: 1, child: SeatItem(index: 2)), + Expanded(flex: 1, child: SeatItem(index: 3)), + Expanded(flex: 1, child: SeatItem(index: 4)), + ], + ), + Row( + children: [ + Expanded(flex: 1, child: SeatItem(index: 5)), + Expanded(flex: 1, child: SeatItem(index: 6)), + Expanded(flex: 1, child: SeatItem(index: 7)), + Expanded(flex: 1, child: SeatItem(index: 8)), + Expanded(flex: 1, child: SeatItem(index: 9)), + ], + ), + Row( + children: [ + Expanded(flex: 1, child: SeatItem(index: 10)), + Expanded(flex: 1, child: SeatItem(index: 11)), + Expanded(flex: 1, child: SeatItem(index: 12)), + Expanded(flex: 1, child: SeatItem(index: 13)), + Expanded(flex: 1, child: SeatItem(index: 14)), + ], + ), + ], + ), + ); + } + + _buildSeat20() { + return Container( + margin: EdgeInsets.only(top: 3.w, left: 10.w, right: 10.w), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + Expanded(flex: 1, child: SeatItem(index: 0)), + Expanded(flex: 1, child: SeatItem(index: 1)), + Expanded(flex: 1, child: SeatItem(index: 2)), + Expanded(flex: 1, child: SeatItem(index: 3)), + Expanded(flex: 1, child: SeatItem(index: 4)), + ], + ), + Row( + children: [ + Expanded(flex: 1, child: SeatItem(index: 5)), + Expanded(flex: 1, child: SeatItem(index: 6)), + Expanded(flex: 1, child: SeatItem(index: 7)), + Expanded(flex: 1, child: SeatItem(index: 8)), + Expanded(flex: 1, child: SeatItem(index: 9)), + ], + ), + Row( + children: [ + Expanded(flex: 1, child: SeatItem(index: 10)), + Expanded(flex: 1, child: SeatItem(index: 11)), + Expanded(flex: 1, child: SeatItem(index: 12)), + Expanded(flex: 1, child: SeatItem(index: 13)), + Expanded(flex: 1, child: SeatItem(index: 14)), + ], + ), + Row( + children: [ + Expanded(flex: 1, child: SeatItem(index: 15)), + Expanded(flex: 1, child: SeatItem(index: 16)), + Expanded(flex: 1, child: SeatItem(index: 17)), + Expanded(flex: 1, child: SeatItem(index: 18)), + Expanded(flex: 1, child: SeatItem(index: 19)), + ], + ), + ], + ), + ); + } +} + +class _CpSeatPair { + final int fromIndex; + final int toIndex; + final int micEffectLevel; + final bool hasNonMicEffect; + + const _CpSeatPair({ + required this.fromIndex, + required this.toIndex, + required this.micEffectLevel, + required this.hasNonMicEffect, + }); + + @override + bool operator ==(Object other) { + return other is _CpSeatPair && + other.fromIndex == fromIndex && + other.toIndex == toIndex && + other.micEffectLevel == micEffectLevel && + other.hasNonMicEffect == hasNonMicEffect; + } + + @override + int get hashCode => + Object.hash(fromIndex, toIndex, micEffectLevel, hasNonMicEffect); +} + +class _CpPairGroups { + final List<_CpSeatPair> adjacent; + final List<_CpSeatPair> remote; + + const _CpPairGroups({required this.adjacent, required this.remote}); +} + +class _CpHeartFlightData { + final int id; + final Offset from; + final Offset to; + final double arcHeight; + final double controlOffsetX; + final double size; + final Duration duration; + final double rotationAmplitude; + + const _CpHeartFlightData({ + required this.id, + required this.from, + required this.to, + required this.arcHeight, + required this.controlOffsetX, + required this.size, + required this.duration, + required this.rotationAmplitude, + }); +} + +class _CpHeartFlightWidget extends StatefulWidget { + final _CpHeartFlightData data; + final VoidCallback onCompleted; + + const _CpHeartFlightWidget({ + super.key, + required this.data, + required this.onCompleted, + }); + + @override + State<_CpHeartFlightWidget> createState() => _CpHeartFlightWidgetState(); +} + +class _CpHeartFlightWidgetState extends State<_CpHeartFlightWidget> + with SingleTickerProviderStateMixin { + late final AnimationController _controller; + late final Animation _animation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: widget.data.duration, + ); + _animation = CurvedAnimation( + parent: _controller, + curve: Curves.easeOutCubic, + ); + _controller.addStatusListener((status) { + if (status == AnimationStatus.completed) { + widget.onCompleted(); + } + }); + _controller.forward(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Offset _bezierPoint(double t) { + final Offset start = widget.data.from; + final Offset end = widget.data.to; + final Offset mid = Offset( + (start.dx + end.dx) / 2 + widget.data.controlOffsetX, + math.min(start.dy, end.dy) - widget.data.arcHeight, + ); + final double oneMinusT = 1 - t; + return Offset( + oneMinusT * oneMinusT * start.dx + + 2 * oneMinusT * t * mid.dx + + t * t * end.dx, + oneMinusT * oneMinusT * start.dy + + 2 * oneMinusT * t * mid.dy + + t * t * end.dy, + ); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _animation, + builder: (context, child) { + final double t = _animation.value; + final Offset point = _bezierPoint(t); + final double scale = + t < 0.35 ? 0.62 + (t * 1.05) : 1.0 - ((t - 0.35) * 0.18); + final double opacity = + t < 0.12 ? t / 0.12 : (t > 0.72 ? 1 - ((t - 0.72) / 0.28) : 1.0); + final double rotation = + math.sin(t * math.pi) * widget.data.rotationAmplitude; + return Positioned( + left: point.dx - widget.data.size / 2, + top: point.dy - widget.data.size / 2, + child: Opacity( + opacity: opacity.clamp(0.0, 1.0), + child: Transform.rotate( + angle: rotation, + child: Transform.scale(scale: scale, child: child), + ), + ), + ); + }, + child: Image.asset( + "atu_images/room/at_icon_cp_heart_ap.png", + width: widget.data.size, + height: widget.data.size, + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/set_room_role_page.dart b/lib/chatvibe_ui/widgets/room/set_room_role_page.dart new file mode 100644 index 0000000..4584225 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/set_room_role_page.dart @@ -0,0 +1,238 @@ +import 'dart:ui' as ui; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_core/constants/at_room_msg_type.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/room_msg_item.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/room_user_card_res.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_managers/rtm_manager.dart'; + +import '../../../chatvibe_data/models/enum/at_room_roles_type.dart'; +import '../../components/at_tts.dart'; +import '../../components/text/at_text.dart'; + +class SetRoomRolePage extends StatefulWidget { + RoomUserCardRes? userCardInfo; + + SetRoomRolePage(this.userCardInfo); + + @override + _SetRoomRolePageState createState() => _SetRoomRolePageState(); +} + +class _SetRoomRolePageState extends State { + String roomId = ""; + String roomAccount = ""; + String optTips = ""; + RtmProvider? rtmProvider; + + @override + void initState() { + super.initState(); + roomId = + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.id ?? + ""; + rtmProvider = Provider.of(context, listen: false); + roomAccount = + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.roomAccount ?? + ""; + } + + @override + Widget build(BuildContext context) { + optTips = ATAppLocalizations.of(context)!.operationSuccessful; + return ClipRect( + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 15, sigmaY: 15), + child: Container( + width: ScreenUtil().screenWidth * 0.75, + padding: EdgeInsets.symmetric(horizontal: 10.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(12.w)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 18.w), + Row( + children: [ + Expanded( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (widget.userCardInfo?.roomRole == + ATRoomRolesType.ADMIN.name) { + ATTts.show( + ATAppLocalizations.of( + context, + )!.alreadyAnAdministrator, + ); + return; + } + Navigator.of(context).pop(); + ChatRoomRepository() + .changeRoomRole( + roomId, + ATRoomRolesType.ADMIN.name, + widget.userCardInfo?.userProfile?.id ?? "", + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id ?? + "", + "OTHER_CHANGE", + ) + .whenComplete(() { + ATTts.show(optTips); + Msg msg = Msg( + groupId: roomAccount, + msg: 'ADMIN', + toUser: widget.userCardInfo?.userProfile, + type: ATRoomMsgType.roomRoleChange, + ); + rtmProvider?.sendMsg(msg, addLocal: true); + }); + }, + child: Column( + children: [ + Image.asset( + "atu_images/room/at_icon_room_gly.png", + width: 30.w, + height: 30.w, + ), + SizedBox(height: 10.w), + text( + ATAppLocalizations.of(context)!.admin, + textColor: Color(0xffB1B1B1), + fontSize: 13.sp, + ), + ], + ), + ), + ), + Expanded( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + child: Column( + children: [ + Image.asset( + "atu_images/room/at_icon_room_hy.png", + width: 30.w, + height: 30.w, + ), + SizedBox(height: 10.w), + text( + ATAppLocalizations.of(context)!.member, + textColor: Color(0xffB1B1B1), + fontSize: 13.sp, + ), + ], + ), + onTap: () { + if (widget.userCardInfo?.roomRole == + ATRoomRolesType.MEMBER.name) { + ATTts.show( + ATAppLocalizations.of(context)!.alreadyAnMember, + ); + return; + } + Navigator.of(context).pop(); + ChatRoomRepository() + .changeRoomRole( + roomId, + ATRoomRolesType.MEMBER.name, + widget.userCardInfo?.userProfile?.id ?? "", + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id ?? + "", + "OTHER_CHANGE", + ) + .whenComplete(() { + ATTts.show(optTips); + Msg msg = Msg( + groupId: roomAccount, + msg: 'MEMBER', + toUser: widget.userCardInfo?.userProfile, + type: ATRoomMsgType.roomRoleChange, + ); + rtmProvider?.sendMsg(msg, addLocal: true); + }); + }, + ), + ), + Expanded( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + child: Column( + children: [ + Image.asset( + "atu_images/room/at_icon_room_guest.png", + width: 30.w, + height: 30.w, + ), + SizedBox(height: 10.w), + text( + ATAppLocalizations.of(context)!.guest, + textColor: Color(0xffB1B1B1), + fontSize: 13.sp, + ), + ], + ), + onTap: () { + if (widget.userCardInfo?.roomRole == + ATRoomRolesType.TOURIST.name) { + ATTts.show( + ATAppLocalizations.of(context)!.alreadyAnTourist, + ); + return; + } + Navigator.of(context).pop(); + ChatRoomRepository() + .changeRoomRole( + roomId, + ATRoomRolesType.TOURIST.name, + widget.userCardInfo?.userProfile?.id ?? "", + AccountStorage() + .getCurrentUser() + ?.userProfile + ?.id ?? + "", + "OTHER_CHANGE", + ) + .whenComplete(() { + ATTts.show(optTips); + Msg msg = Msg( + groupId: roomAccount, + msg: 'TOURIST', + toUser: widget.userCardInfo?.userProfile, + type: ATRoomMsgType.roomRoleChange, + ); + rtmProvider?.sendMsg(msg, addLocal: true); + }); + }, + ), + ), + ], + ), + SizedBox(height: 20.w), + ], + ), + ), + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/switch_model/room_mic_switch_page.dart b/lib/chatvibe_ui/widgets/room/switch_model/room_mic_switch_page.dart new file mode 100644 index 0000000..8fcb9dd --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/switch_model/room_mic_switch_page.dart @@ -0,0 +1,93 @@ +import 'dart:ui' as ui; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/switch_model/room_seat_number_page.dart'; + +import 'package:aslan/app_localizations.dart'; + +class RoomMicSwitchPage extends StatefulWidget { + @override + _RoomMicSwitchPageState createState() => _RoomMicSwitchPageState(); +} + +class _RoomMicSwitchPageState extends State + with SingleTickerProviderStateMixin { + TabController? _tabController; + final List _pages = []; + final List _tabs = []; + + @override + void initState() { + super.initState(); + _pages.add(RoomSeatNumberPage()); + // _pages.add(RoomSeatThemePage()); + _tabController = TabController(length: _pages.length, vsync: this); + } + + @override + Widget build(BuildContext context) { + _tabs.clear(); + _tabs.add(Tab(text: ATAppLocalizations.of(context)!.numberOfMic)); + // _tabs.add(Tab(text: ATAppLocalizations.of(context)!.micTheme)); + return SafeArea( + top: false, + child: Stack( + alignment: Alignment.topCenter, + children: [ + Container( + height: 490.w, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + ), + child: Column( + children: [ + TabBar( + tabAlignment: TabAlignment.center, + labelPadding: EdgeInsets.symmetric(horizontal: 18.w), + labelColor: Colors.black, + isScrollable: true, + unselectedLabelColor: Colors.white54, + labelStyle: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.bold, + ), + unselectedLabelStyle: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + indicator: BoxDecoration(), + dividerColor: Colors.transparent, + controller: _tabController, + tabs: _tabs, + ), + Expanded( + child: TabBarView( + controller: _tabController, + children: _pages, + ), + ), + ], + ), + ), + Positioned( + left: 12.w, + top: 10.w, + child: GestureDetector( + child: Icon(Icons.close, size: 20.w, color: Colors.white), + onTap: () { + SmartDialog.dismiss(tag: "showRoomMicSwitch"); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/switch_model/room_seat_number_page.dart b/lib/chatvibe_ui/widgets/room/switch_model/room_seat_number_page.dart new file mode 100644 index 0000000..3a338a7 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/switch_model/room_seat_number_page.dart @@ -0,0 +1,416 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:provider/provider.dart'; + +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/join_room_res.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; + +class RoomSeatNumberPage extends StatefulWidget { + @override + _RoomSeatNumberPageState createState() => _RoomSeatNumberPageState(); +} + +class _RoomSeatNumberPageState extends State { + num mikeSize = 10; + + @override + void initState() { + super.initState(); + mikeSize = + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomSetting?.mikeSize ?? + 10; + } + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.symmetric(horizontal: 8.w, vertical: 5.w), + child: Column( + children: [ + SizedBox(height: 5.w), + Row( + spacing: 8.w, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + image: DecorationImage( + image: AssetImage("atu_images/room/at_icon_seat_num_bg.png"), + fit: BoxFit.cover, + ), + ), + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + GestureDetector( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12.w), + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.circular(12.w), + border: Border.all( + color: + mikeSize == 5 + ? ChatVibeTheme.primaryLight + : Colors.transparent, + width: 2.w, + ), + ), + child: Stack( + children: [ + Column( + spacing: 5.w, + children: [ + SizedBox(height: 5.w), + Image.asset( + "atu_images/room/at_icon_room_mic_model_5.png", + height: 25.w, + ), + Image.asset( + "atu_images/room/at_icon_room_mic_model_5.png", + height: 25.w, + color: Colors.transparent, + ), + Image.asset( + "atu_images/room/at_icon_room_mic_model_5.png", + height: 25.w, + color: Colors.transparent, + ), + text( + textColor: Colors.white, + ATAppLocalizations.of( + context, + )!.classicMic("5"), + fontWeight: FontWeight.w600, + fontSize: 12.sp, + ), + SizedBox(height: 5.w), + ], + ), + ], + ), + ), + onTap: () { + mikeSize = 5; + setState(() {}); + }, + ), + ], + ), + ), + ), + + Expanded( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + image: DecorationImage( + image: AssetImage("atu_images/room/at_icon_seat_num_bg.png"), + fit: BoxFit.cover, + ), + ), + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + GestureDetector( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12.w), + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.circular(12.w), + border: Border.all( + color: + mikeSize == 10 + ? ChatVibeTheme.primaryLight + : Colors.transparent, + width: 2.w, + ), + ), + child: Stack( + children: [ + Column( + spacing: 5.w, + children: [ + SizedBox(height: 5.w), + Image.asset( + "atu_images/room/at_icon_room_mic_model_5.png", + height: 25.w, + ), + Image.asset( + "atu_images/room/at_icon_room_mic_model_5.png", + height: 25.w, + ), + Image.asset( + "atu_images/room/at_icon_room_mic_model_5.png", + height: 25.w, + color: Colors.transparent, + ), + text( + textColor: Colors.white, + ATAppLocalizations.of( + context, + )!.classicMic("10"), + fontWeight: FontWeight.w600, + fontSize: 12.sp, + ), + SizedBox(height: 5.w), + ], + ), + ], + ), + ), + onTap: () { + mikeSize = 10; + setState(() {}); + }, + ), + ], + ), + ), + ), + ], + ), + SizedBox(height: 15.w), + Row( + spacing: 8.w, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + image: DecorationImage( + image: AssetImage("atu_images/room/at_icon_seat_num_bg.png"), + fit: BoxFit.cover, + ), + ), + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + GestureDetector( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12.w), + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.circular(12.w), + border: Border.all( + color: + mikeSize == 15 + ? ChatVibeTheme.primaryLight + : Colors.transparent, + width: 2.w, + ), + ), + child: Stack( + children: [ + Column( + spacing: 5.w, + children: [ + SizedBox(height: 5.w), + Image.asset( + "atu_images/room/at_icon_room_mic_model_5.png", + height: 25.w, + ), + Image.asset( + "atu_images/room/at_icon_room_mic_model_5.png", + height: 25.w, + ), + Image.asset( + "atu_images/room/at_icon_room_mic_model_5.png", + height: 25.w, + ), + Image.asset( + "atu_images/room/at_icon_room_mic_model_5.png", + height: 25.w, + color: Colors.transparent, + ), + text( + textColor: Colors.white, + ATAppLocalizations.of( + context, + )!.classicMic("15"), + fontWeight: FontWeight.w600, + fontSize: 12.sp, + ), + SizedBox(height: 5.w), + ], + ), + ], + ), + ), + onTap: () { + mikeSize = 15; + setState(() {}); + }, + ), + ], + ), + ), + ), + Expanded( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.w), + image: DecorationImage( + image: AssetImage("atu_images/room/at_icon_seat_num_bg.png"), + fit: BoxFit.cover, + ), + ), + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + GestureDetector( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12.w), + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.circular(12.w), + border: Border.all( + color: + mikeSize == 20 + ? ChatVibeTheme.primaryLight + : Colors.transparent, + width: 2.w, + ), + ), + child: Stack( + children: [ + Column( + spacing: 5.w, + children: [ + SizedBox(height: 5.w), + Image.asset( + "atu_images/room/at_icon_room_mic_model_5.png", + height: 25.w, + ), + Image.asset( + "atu_images/room/at_icon_room_mic_model_5.png", + height: 25.w, + ), + Image.asset( + "atu_images/room/at_icon_room_mic_model_5.png", + height: 25.w, + ), + Image.asset( + "atu_images/room/at_icon_room_mic_model_5.png", + height: 25.w, + ), + text( + textColor: Colors.white, + ATAppLocalizations.of( + context, + )!.classicMic("20"), + fontWeight: FontWeight.w600, + fontSize: 12.sp, + ), + SizedBox(height: 5.w), + ], + ), + ], + ), + ), + onTap: () { + mikeSize = 20; + setState(() {}); + }, + ), + ], + ), + ), + ), + ], + ), + Spacer(), + GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + margin: EdgeInsets.only(bottom: 20.w), + height: 45.w, + width: 180.w, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Color(0xffFEB219), Color(0xffFF9326)], + ), + borderRadius: BorderRadius.circular(25.w), + ), + child: text( + ATAppLocalizations.of(context)!.save, + textColor: Colors.white, + fontSize: 16.sp, + fontWeight: FontWeight.bold, + ), + ), + onTap: () { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: + ATAppLocalizations.of(context)!.confirmSwitchMicModelTips, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + ATLoadingManager.exhibitOperation(); + JoinRoomRes? room = + Provider.of( + context, + listen: false, + ).currenRoom; + if (room != null) { + ChatRoomRepository() + .updateRoomSetting( + room.roomProfile?.roomProfile?.id ?? "", + room.roomProfile?.roomProfile?.roomAccount ?? "", + context, + mikeSize: mikeSize, + ) + .then((value) { + var roomSetting = room.roomProfile?.roomSetting; + Provider.of( + context, + listen: false, + ).updateRoomSetting( + roomSetting!.copyWith(mikeSize: mikeSize), + ); + ATTts.show( + ATAppLocalizations.of( + context, + )!.operationSuccessful, + ); + ATLoadingManager.veilRoutine(); + SmartDialog.dismiss(tag: "showRoomMicSwitch"); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } + }, + ); + }, + ); + }, + ), + ], + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room/switch_model/room_seat_theme_page.dart b/lib/chatvibe_ui/widgets/room/switch_model/room_seat_theme_page.dart new file mode 100644 index 0000000..2fbe002 --- /dev/null +++ b/lib/chatvibe_ui/widgets/room/switch_model/room_seat_theme_page.dart @@ -0,0 +1,548 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/main.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/chatvibe_ui/components/dialog/dialog_base.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/room_repository_imp.dart'; +import 'package:aslan/chatvibe_domain/models/res/join_room_res.dart'; +import 'package:aslan/chatvibe_managers/rtc_manager.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +import '../../../../chatvibe_data/models/enum/at_room_special_mike_type.dart'; +import '../../../../chatvibe_data/models/enum/at_vip_type.dart'; + +class RoomSeatThemePage extends StatefulWidget { + @override + _RoomSeatThemePageState createState() => _RoomSeatThemePageState(); +} + +class _RoomSeatThemePageState extends State { + String specialMikeType = ""; + + @override + void initState() { + super.initState(); + specialMikeType = + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomSetting?.roomSpecialMikeType ?? + ""; + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 8.w, vertical: 5.w), + child: Column( + children: [ + SizedBox(height: 5.w), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Stack( + alignment: AlignmentDirectional.topEnd, + children: [ + GestureDetector( + child: Container( + height: 110.w, + width: 172.w, + padding: EdgeInsets.symmetric(horizontal: 12.w), + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.circular(5), + border: Border.all( + color: + specialMikeType.isEmpty || + (specialMikeType != + ATRoomSpecialMikeType + .TYPE_VIP3 + .name && + specialMikeType != + ATRoomSpecialMikeType + .TYPE_VIP4 + .name && + specialMikeType != + ATRoomSpecialMikeType + .TYPE_VIP5 + .name && + specialMikeType != + ATRoomSpecialMikeType + .TYPE_VIP6 + .name) + ? ChatVibeTheme.primaryLight + : Colors.transparent, + width: 1.w, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + "atu_images/room/at_icon_room_free_seat.png", + width: 45.w, + height: 45.w, + ), + SizedBox(width: 15.w), + Image.asset( + "atu_images/room/at_icon_room_free_sonic.png", + width: 45.w, + height: 45.w, + ), + ], + ), + SizedBox(height: 5.w), + text( + ATAppLocalizations.of(context)!.free, + fontWeight: FontWeight.w600, + fontSize: 12.sp, + ), + ], + ), + ), + onTap: () { + specialMikeType = ""; + setState(() {}); + }, + ), + specialMikeType.isEmpty || + (specialMikeType != + ATRoomSpecialMikeType.TYPE_VIP3.name && + specialMikeType != + ATRoomSpecialMikeType.TYPE_VIP4.name && + specialMikeType != + ATRoomSpecialMikeType.TYPE_VIP5.name && + specialMikeType != + ATRoomSpecialMikeType.TYPE_VIP6.name) + ? Image.asset( + "atu_images/room/at_icon_room_switch_mic_model_check.png", + width: 15.w, + height: 15.w, + ) + : Container(), + ], + ), + Stack( + alignment: AlignmentDirectional.topEnd, + children: [ + GestureDetector( + child: Container( + height: 110.w, + width: 172.w, + padding: EdgeInsets.symmetric(horizontal: 12.w), + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.circular(5), + border: Border.all( + color: + specialMikeType == + ATRoomSpecialMikeType.TYPE_VIP3.name + ? ChatVibeTheme.primaryLight + : Colors.transparent, + width: 1.w, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + "atu_images/room/at_icon_room_vip3_seat.png", + width: 45.w, + height: 45.w, + ), + SizedBox(width: 15.w), + Image.asset( + "atu_images/room/at_icon_room_vip3_sonic.png", + width: 45.w, + height: 45.w, + ), + ], + ), + SizedBox(height: 5.w), + text( + ATAppLocalizations.of(context)!.vip3, + fontWeight: FontWeight.w600, + fontSize: 12.sp, + ), + ], + ), + ), + onTap: () { + specialMikeType = ATRoomSpecialMikeType.TYPE_VIP3.name; + setState(() {}); + }, + ), + specialMikeType == ATRoomSpecialMikeType.TYPE_VIP3.name + ? Image.asset( + "atu_images/room/at_icon_room_switch_mic_model_check.png", + width: 15.w, + height: 15.w, + ) + : Container(), + ], + ), + ], + ), + SizedBox(height: 15.w), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Stack( + alignment: AlignmentDirectional.topEnd, + children: [ + GestureDetector( + child: Container( + height: 110.w, + width: 172.w, + padding: EdgeInsets.symmetric(horizontal: 12.w), + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.circular(5), + border: Border.all( + color: + specialMikeType == + ATRoomSpecialMikeType.TYPE_VIP4.name + ? ChatVibeTheme.primaryLight + : Colors.transparent, + width: 1.w, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + "atu_images/room/at_icon_room_vip4_seat.png", + width: 45.w, + height: 45.w, + ), + SizedBox(width: 15.w), + Image.asset( + "atu_images/room/at_icon_room_vip4_sonic.png", + width: 45.w, + height: 45.w, + ), + ], + ), + SizedBox(height: 5.w), + text( + ATAppLocalizations.of(context)!.vip4, + fontWeight: FontWeight.w600, + fontSize: 12.sp, + ), + ], + ), + ), + onTap: () { + specialMikeType = ATRoomSpecialMikeType.TYPE_VIP4.name; + setState(() {}); + }, + ), + specialMikeType == ATRoomSpecialMikeType.TYPE_VIP4.name + ? Image.asset( + "atu_images/room/at_icon_room_switch_mic_model_check.png", + width: 15.w, + height: 15.w, + ) + : Container(), + ], + ), + Stack( + alignment: AlignmentDirectional.topEnd, + children: [ + GestureDetector( + child: Container( + height: 110.w, + width: 172.w, + padding: EdgeInsets.symmetric(horizontal: 12.w), + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.circular(5), + border: Border.all( + color: + specialMikeType == + ATRoomSpecialMikeType.TYPE_VIP5.name + ? ChatVibeTheme.primaryLight + : Colors.transparent, + width: 1.w, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + "atu_images/room/at_icon_room_vip5_seat.png", + width: 45.w, + height: 45.w, + ), + SizedBox(width: 15.w), + Image.asset( + "atu_images/room/at_icon_room_vip5_sonic.png", + width: 45.w, + height: 45.w, + ), + ], + ), + SizedBox(height: 5.w), + text( + ATAppLocalizations.of(context)!.vip5, + fontWeight: FontWeight.w600, + fontSize: 12.sp, + ), + ], + ), + ), + onTap: () { + specialMikeType = ATRoomSpecialMikeType.TYPE_VIP5.name; + setState(() {}); + }, + ), + specialMikeType == ATRoomSpecialMikeType.TYPE_VIP5.name + ? Image.asset( + "atu_images/room/at_icon_room_switch_mic_model_check.png", + width: 15.w, + height: 15.w, + ) + : Container(), + ], + ), + ], + ), + SizedBox(height: 15.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Stack( + alignment: AlignmentDirectional.topEnd, + children: [ + GestureDetector( + child: Container( + height: 110.w, + width: 172.w, + padding: EdgeInsets.symmetric(horizontal: 12.w), + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.circular(5), + border: Border.all( + color: + specialMikeType == + ATRoomSpecialMikeType.TYPE_VIP6.name + ? ChatVibeTheme.primaryLight + : Colors.transparent, + width: 1.w, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + "atu_images/room/at_icon_room_vip6_seat.png", + width: 45.w, + height: 45.w, + ), + SizedBox(width: 15.w), + Image.asset( + "atu_images/room/at_icon_room_vip6_sonic.png", + width: 45.w, + height: 45.w, + ), + ], + ), + SizedBox(height: 5.w), + text( + ATAppLocalizations.of(context)!.vip6, + fontWeight: FontWeight.w600, + fontSize: 12.sp, + ), + ], + ), + ), + onTap: () { + specialMikeType = ATRoomSpecialMikeType.TYPE_VIP6.name; + setState(() {}); + }, + ), + specialMikeType == ATRoomSpecialMikeType.TYPE_VIP6.name + ? Image.asset( + "atu_images/room/at_icon_room_switch_mic_model_check.png", + width: 15.w, + height: 15.w, + ) + : Container(), + ], + ), + ], + ), + SizedBox(height: 35.w), + GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + margin: EdgeInsets.only(bottom: 20.w), + height: 45.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_switch_mic_model_btn.png", + ), + ), + ), + child: text( + ATAppLocalizations.of(context)!.save, + textColor: Colors.white, + fontSize: 16.sp, + fontWeight: FontWeight.bold, + ), + ), + onTap: () { + if (specialMikeType.isNotEmpty) { + if (AccountStorage().getVIP()?.name == ATVIPType.MARQUIS.name) { + if (specialMikeType == ATRoomSpecialMikeType.TYPE_VIP3.name) { + _save(); + } else { + _showGoVipDialog(); + } + } else if (AccountStorage().getVIP()?.name == + ATVIPType.DUKE.name) { + if (specialMikeType == ATRoomSpecialMikeType.TYPE_VIP3.name || + specialMikeType == ATRoomSpecialMikeType.TYPE_VIP4.name) { + _save(); + } else { + _showGoVipDialog(); + } + } else if (AccountStorage().getVIP()?.name == + ATVIPType.KING.name) { + if (specialMikeType == ATRoomSpecialMikeType.TYPE_VIP3.name || + specialMikeType == ATRoomSpecialMikeType.TYPE_VIP4.name || + specialMikeType == ATRoomSpecialMikeType.TYPE_VIP5.name) { + _save(); + } else { + _showGoVipDialog(); + } + } else if (AccountStorage().getVIP()?.name == + ATVIPType.EMPEROR.name) { + if (specialMikeType == ATRoomSpecialMikeType.TYPE_VIP3.name || + specialMikeType == ATRoomSpecialMikeType.TYPE_VIP4.name || + specialMikeType == ATRoomSpecialMikeType.TYPE_VIP5.name || + specialMikeType == ATRoomSpecialMikeType.TYPE_VIP6.name) { + _save(); + } else { + _showGoVipDialog(); + } + } else { + _showGoVipDialog(); + ATTts.show( + ATAppLocalizations.of( + context, + )!.pleaseUpgradeYourVipLevelFirst, + ); + } + } else { + //普通样式 + _save(); + } + }, + ), + ], + ), + ), + ); + } + + void _save() { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.confirmSwitchMicThemeTips, + btnText: ATAppLocalizations.of(context)!.confirm, + onEnsure: () { + ATLoadingManager.exhibitOperation(); + JoinRoomRes? room = + Provider.of(context, listen: false).currenRoom; + if (room != null) { + ChatRoomRepository() + .updateRoomSetting( + room.roomProfile?.roomProfile?.id ?? "", + room.roomProfile?.roomProfile?.roomAccount ?? "", + context, + roomSpecialMikeType: specialMikeType, + ) + .then((value) { + var roomSetting = room.roomProfile?.roomSetting; + Provider.of( + context, + listen: false, + ).updateRoomSetting( + roomSetting!.copyWith( + roomSpecialMikeType: specialMikeType, + ), + ); + ATLoadingManager.veilRoutine(); + ATTts.show(ATAppLocalizations.of(context)!.operationSuccessful); + SmartDialog.dismiss(tag: "showRoomMicSwitch"); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } + }, + ); + }, + ); + } + + void _showGoVipDialog() { + SmartDialog.show( + tag: "showConfirmDialog", + alignment: Alignment.center, + debounce: true, + animationType: SmartAnimationType.fade, + builder: (_) { + return MsgDialog( + title: ATAppLocalizations.of(context)!.tips, + msg: ATAppLocalizations.of(context)!.vipUseThisThemeTips, + btnText: ATAppLocalizations.of(context)!.buy, + onEnsure: () { + SmartDialog.dismiss(tag: "showRoomMicSwitch"); + ATNavigatorUtils.push( + navigatorKey.currentState!.context, + MainRoute.vipList, + replace: false, + ); + }, + ); + }, + ); + } +} diff --git a/lib/chatvibe_ui/widgets/room_reward_countdown_timer.dart b/lib/chatvibe_ui/widgets/room_reward_countdown_timer.dart new file mode 100644 index 0000000..9056f1d --- /dev/null +++ b/lib/chatvibe_ui/widgets/room_reward_countdown_timer.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; + +class RoomRewardCountdownTimer extends StatefulWidget { + final DateTime targetDate; + + const RoomRewardCountdownTimer({super.key, required this.targetDate}); + + @override + State createState() => + _RoomRewardCountdownTimerState(); +} + +class _RoomRewardCountdownTimerState extends State { + Timer? _timer; + + // 剩余时间 + Duration _remainingTime = Duration.zero; + + @override + void initState() { + super.initState(); + _startTimer(); + } + + void _startTimer() { + _timer = Timer.periodic(Duration(seconds: 1), (timer) { + setState(() { + _remainingTime = widget.targetDate.difference(DateTime.now()); + // 如果倒计时结束 + if (_remainingTime.isNegative) { + _remainingTime = Duration.zero; + _timer?.cancel(); + } + }); + }); + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + // 格式化数字为两位数 + String _formatNumber(int number) { + return number.toString().padLeft(2, '0'); + } + + @override + Widget build(BuildContext context) { + // 计算天、时、分、秒 + // int days = _remainingTime.inDays; + int hours = _remainingTime.inHours.remainder(24); + int minutes = _remainingTime.inMinutes.remainder(60); + int seconds = _remainingTime.inSeconds.remainder(60); + return Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(width: 50.w), + // _TimeBox( + // value: days < 10 ? "$days" : _formatNumber(days), + // label: 'D', + // ), + // SizedBox(width: 3.w,), + _TimeBox(value: _formatNumber(hours), label: ''), + _buildMaohao(), + _TimeBox(value: _formatNumber(minutes), label: ''), + _buildMaohao(), + _TimeBox(value: _formatNumber(seconds), label: ''), + SizedBox(width: 50.w), + ], + ), + ); + } + + _buildMaohao(){ + return text( + ":", + fontSize: 23.sp, + fontWeight: FontWeight.bold, + textColor: Colors.white, + ); + } +} + +class _TimeBox extends StatelessWidget { + final String value; + final String label; + + const _TimeBox({required this.value, required this.label}); + + @override + Widget build(BuildContext context) { + return Container( + width: 72.w, + height: 38.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_reward_countdown_item_bg.png", + ), + fit: BoxFit.fill, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + text( + value, + fontSize: 20.sp, + fontWeight: FontWeight.bold, + textColor: Color(0xffFAE3A4), + ), + text( + label, + fontSize: 20.sp, + fontWeight: FontWeight.bold, + textColor: Color(0xffFAE3A4), + ), + ], + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/sgin/recive_award_sgin_page.dart b/lib/chatvibe_ui/widgets/sgin/recive_award_sgin_page.dart new file mode 100644 index 0000000..2a9910f --- /dev/null +++ b/lib/chatvibe_ui/widgets/sgin/recive_award_sgin_page.dart @@ -0,0 +1,247 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_core/utilities/at_room_utils.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_sign_in_res.dart'; +import 'package:aslan/main.dart'; +import 'package:aslan/chatvibe_core/routes/at_fluro_navigator.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; +import 'package:aslan/chatvibe_domain/models/res/task_checkIn_latest_vip_record_res.dart'; +import 'package:aslan/chatvibe_features/index/main_route.dart'; + +import '../../../chatvibe_data/models/enum/at_activity_reward_props_type.dart'; + +///签到奖励 +class ReciveAwardSginPage extends StatefulWidget { + ActivityRewardProps? awd; + int resNum; + bool fromHome = false; + + @override + _ReciveAwardSginPageState createState() => _ReciveAwardSginPageState(); + + ReciveAwardSginPage(this.awd, this.resNum, this.fromHome); +} + +class _ReciveAwardSginPageState extends State { + List taskCheckInLatestVipRecordRes = []; + + @override + void initState() { + super.initState(); + AccountRepository() + .taskCheckInLatestVipRecord() + .then((result) { + taskCheckInLatestVipRecordRes = result; + setState(() {}); + }) + .catchError((e) {}); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + child: Container( + height: 400.w, + margin: EdgeInsets.symmetric(horizontal: 15.w), + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/index/at_icon_sgin_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 110.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 15.w, + children: [ + Container( + width: 90.w, + height: 115.w, + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + Image.asset("atu_images/index/at_icon_sgin_item_bg1.png"), + widget.awd?.type == ATActivityRewardATPropsType.GOLD.name + ? Image.asset( + "atu_images/general/at_icon_jb.png", + width: 36.w, + height: 36.w, + ) + : netImage( + url: widget.awd?.cover ?? "", + width: 48.w, + height: 48.w, + ), + Positioned( + top: 2, + right: 13.w, + child: text( + "${widget.resNum}", + fontSize: 11.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + PositionedDirectional( + bottom: 3.w, + child: + widget.awd?.type == + ATActivityRewardATPropsType.PROPS.name + ? Row( + children: [ + text( + "${widget.awd?.quantity} ${ATAppLocalizations.of(context)!.day}", + fontSize: 12.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + ], + ) + : Row( + children: [ + SizedBox(width: 2.w), + text( + "${ATAppLocalizations.of(context)!.coins4}*${widget.awd?.amount}", + fontSize: 12.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + ], + ), + ), + ], + ), + ), + taskCheckInLatestVipRecordRes.isNotEmpty + ? Container( + width: 90.w, + height: 115.w, + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + Image.asset( + getVipBg( + taskCheckInLatestVipRecordRes.first.vipLevel ?? 0, + ), + ), + Image.asset( + "atu_images/general/at_icon_jb3.png", + width: 48.w, + height: 48.w, + ), + + PositionedDirectional( + bottom: 3.w, + child: Row( + children: [ + SizedBox(width: 2.w), + text( + "${ATAppLocalizations.of(context)!.coins4}*${taskCheckInLatestVipRecordRes.first.loginRewardsAmount?.toInt() ?? 0}", + fontSize: 12.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + ], + ), + ), + ], + ), + ) + : Container(), + ], + ), + SizedBox(height: 10.w), + taskCheckInLatestVipRecordRes.isNotEmpty + ? Container( + child: text( + "${ATAppLocalizations.of(context)!.currentVip}${taskCheckInLatestVipRecordRes.first.vipLevel ?? 0}:", + fontSize: 16.sp, + fontWeight: FontWeight.w600, + textColor: Color(0xffFFBD13), + ), + ) + : Container(), + SizedBox(height: 5.w), + taskCheckInLatestVipRecordRes.isNotEmpty + ? Container( + alignment: Alignment.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/index/at_icon_sgin_awd_vip_bg2.png", + ), + fit: BoxFit.fill, + ), + ), + height: 28.w, + child: text( + "${taskCheckInLatestVipRecordRes.first.loginRewardsAmount?.toInt() ?? 0}${ATAppLocalizations.of(context)!.coins4}/${ATAppLocalizations.of(context)!.day}", + fontSize: 16.sp, + fontWeight: FontWeight.w400, + textColor: Color(0xffFFBD13), + ), + ) + : Container(), + Spacer(), + ATDebounceWidget( + child: Container( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(width: 10.w), + Expanded( + child: text( + ATAppLocalizations.of( + context, + )!.forMoreRewardsPleaseCheckTheTaskCenter, + fontSize: 14.sp, + maxLines: 2, + fontWeight: FontWeight.w600, + textColor: Color(0xffFFBD13), + ), + ), + Icon( + Icons.keyboard_arrow_right_rounded, + color: Color(0xffFFBD13), + size: 18.w, + ), + SizedBox(width: 10.w), + ], + ), + ), + onTap: () { + ATRoomUtils.closeAllDialogs(); + if (widget.fromHome) { + ATNavigatorUtils.push( + navigatorKey.currentContext!, + MainRoute.taskList, + replace: false, + ); + } + }, + ), + SizedBox(height: 25.w), + ], + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showSginListAward"); + }, + ); + } + + String getVipBg(num vipLevel) { + return "atu_images/index/at_icon_sgin_awd_vip_${vipLevel}_bg.png"; + } +} diff --git a/lib/chatvibe_ui/widgets/sgin/sgin_page.dart b/lib/chatvibe_ui/widgets/sgin/sgin_page.dart new file mode 100644 index 0000000..09228dd --- /dev/null +++ b/lib/chatvibe_ui/widgets/sgin/sgin_page.dart @@ -0,0 +1,338 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_ui/widgets/sgin/recive_award_sgin_page.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/user_repository_impl.dart'; + +import '../../../chatvibe_data/models/enum/at_activity_reward_props_type.dart'; +import '../../../chatvibe_domain/models/res/at_sign_in_res.dart'; + +///签到页面 +class SginPage extends StatefulWidget { + bool fromHome = false; + + SginPage(this.fromHome); + + @override + _SginPageState createState() => _SginPageState(); +} + +class _SginPageState extends State { + ATSignInRes? res; + + //是否已经签到 + bool isSgined = true; + + @override + void initState() { + super.initState(); + sginListAward(); + AccountRepository() + .checkInToday() + .then((result) { + isSgined = result; + setState(() {}); + }) + .catchError((e) {}); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + child: Container( + height: 465.w, + margin: EdgeInsets.symmetric(horizontal: 15.w), + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/index/at_icon_sgin_bg.png"), + fit: BoxFit.fill, + ), + ), + child: + res != null + ? Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 105.w), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(width: 15.w), + _buildItme( + 1, + res?.rewards?[0].propsGroup?.activityRewardProps?[0], + res?.days, + ), + _buildItme( + 2, + res?.rewards?[0].propsGroup?.activityRewardProps?[1], + res?.days, + ), + _buildItme( + 3, + res?.rewards?[0].propsGroup?.activityRewardProps?[2], + res?.days, + ), + _buildItme( + 4, + res?.rewards?[0].propsGroup?.activityRewardProps?[3], + res?.days, + ), + SizedBox(width: 15.w), + ], + ), + SizedBox(height: 10.w), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(width: 15.w), + _buildItme( + 5, + res?.rewards?[0].propsGroup?.activityRewardProps?[4], + res?.days, + ), + _buildItme( + 6, + res?.rewards?[0].propsGroup?.activityRewardProps?[5], + res?.days, + ), + _buildItme( + 7, + res?.rewards?[0].propsGroup?.activityRewardProps?[6], + res?.days, + ), + SizedBox(width: 15.w), + ], + ), + SizedBox(height: 10.w), + GestureDetector( + child: Container( + width: 200.w, + height: 55.w, + alignment: AlignmentDirectional.center, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + isSgined + ? "atu_images/index/at_icon_signedin_bg.png" + : "atu_images/index/at_icon_sgin_rec_bg.png", + ), + ), + ), + child: text( + isSgined + ? ATAppLocalizations.of(context)!.signedin + : ATAppLocalizations.of(context)!.receive, + fontWeight: FontWeight.bold, + fontSize: 18.sp, + textColor: Colors.white, + ), + ), + onTap: () { + if (isSgined) { + return; + } + receive(); + }, + ), + SizedBox(height: 5.w), + Container( + margin: EdgeInsets.symmetric(horizontal: 30.w), + child: text( + ATAppLocalizations.of(context)!.sginTips, + maxLines: 4, + textColor: Color(0xffAAAAAA), + lineHeight: 1.2, + letterSpacing: 0.1, + fontSize: 12.sp, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(height: 10.w), + ], + ) + : Container(), + ), + onTap: () { + SmartDialog.dismiss(tag: "showSginListAward"); + }, + ); + } + + _buildItme(int tagDay, ActivityRewardProps? reward, num? days) { + return tagDay == 7 + ? Column( + children: [ + SizedBox( + width: 147.w, + height: 85.w, + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + Image.asset("atu_images/index/at_icon_sgin_item_day7_bg1.png"), + tagDay < (days ?? 0) + 1 + ? Image.asset("atu_images/index/at_icon_sgin_item_day7_bg2.png") + : Container(), + reward?.type == ATActivityRewardATPropsType.GOLD.name + ? Image.asset( + "atu_images/general/at_icon_jb.png", + width: 42.w, + height: 42.w, + ) + : netImage( + url: reward?.cover ?? "", + width: 42.w, + height: 42.w, + ), + Positioned( + top: 0, + right: 10.w, + child: text( + "$tagDay", + fontSize: 9.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + PositionedDirectional( + bottom: 0, + child: + reward?.type == ATActivityRewardATPropsType.PROPS.name + ? Row( + children: [ + text( + "${reward?.quantity} ${ATAppLocalizations.of(context)!.day}", + fontSize: 12.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + ], + ) + : Row( + children: [ + SizedBox(width: 2.w), + text( + "${ATAppLocalizations.of(context)!.coins4}*${reward?.amount}", + fontSize: 12.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + ], + ), + ), + days == tagDay + ? Image.asset("atu_images/index/at_icon_sgin_item_day7_bg3.png") + : Container(), + ], + ), + ), + ], + ) + : Column( + children: [ + Container( + width: 70.w, + height: 85.w, + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + Image.asset("atu_images/index/at_icon_sgin_item_bg3.png"), + days == tagDay - 1 + ? Image.asset("atu_images/index/at_icon_sgin_item_bg1.png") + : Container(), + reward?.type == ATActivityRewardATPropsType.GOLD.name + ? Image.asset( + "atu_images/general/at_icon_jb.png", + width: 42.w, + height: 42.w, + ) + : netImage( + url: reward?.cover ?? "", + width: 42.w, + height: 42.w, + ), + Positioned( + top: 0, + right: 11.w, + child: text( + "$tagDay", + fontSize: 9.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + PositionedDirectional( + bottom: 0, + child: + reward?.type == ATActivityRewardATPropsType.PROPS.name + ? Row( + children: [ + text( + "${reward?.quantity} ${ATAppLocalizations.of(context)!.day}", + fontSize: 12.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + ], + ) + : Row( + children: [ + SizedBox(width: 2.w), + text( + "${ATAppLocalizations.of(context)!.coins4}*${reward?.amount}", + fontSize: 12.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + ], + ), + ), + tagDay < (days ?? 0) + 1 + ? Image.asset("atu_images/index/at_icon_sgin_item_bg2.png") + : Container(), + ], + ), + ), + ], + ); + } + + void sginListAward() async { + res = await AccountRepository().sginListAward(); + setState(() {}); + } + + ///签到 + void receive() { + ATLoadingManager.exhibitOperation(); + String ruleId = res?.rewards?[0].rule?.id ?? ""; + String resourceGroupId = res?.rewards?[0].propsGroup?.id ?? ""; + AccountRepository() + .checkInReceive(ruleId, resourceGroupId) + .then((resNum) { + ATLoadingManager.veilRoutine(); + ATTts.show(ATAppLocalizations.of(context)!.checkInSuccessful); + SmartDialog.dismiss(tag: "showSginListAward"); + ActivityRewardProps? awd = + res?.rewards?[0].propsGroup?.activityRewardProps![resNum - 1]; + SmartDialog.show( + tag: "showReciveAwardSgin", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return ReciveAwardSginPage(awd, resNum, widget.fromHome); + }, + ); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + } +} diff --git a/lib/chatvibe_ui/widgets/share/share_page.dart b/lib/chatvibe_ui/widgets/share/share_page.dart new file mode 100644 index 0000000..0a979c8 --- /dev/null +++ b/lib/chatvibe_ui/widgets/share/share_page.dart @@ -0,0 +1,160 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_core/constants/at_global_config.dart'; +import 'package:aslan/chatvibe_core/utilities/at_share_utils.dart'; + +class SharePage extends StatefulWidget { + String shareText; + + SharePage(this.shareText); + + @override + _SharePageState createState() => _SharePageState(); +} + +class _SharePageState extends State { + @override + Widget build(BuildContext context) { + return SafeArea( + child: Container( + padding: EdgeInsets.all(16.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + topRight: Radius.circular(12.w), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text( + ATAppLocalizations.of(context)!.shareTo, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + textColor: Colors.black, + ), + SizedBox(height: 15.w), + Row( + children: [ + Expanded( + child: ATDebounceWidget( + child: Column( + children: [ + Image.asset( + "atu_images/general/at_icon_share_link.png", + width: 40.w, + height: 40.w, + ), + text( + ATAppLocalizations.of(context)!.copyLink, + maxLines: 2, + fontWeight: FontWeight.w600, + textColor: Colors.black38, + fontSize: 14.sp, + ), + ], + ), + onTap: () { + Clipboard.setData( + ClipboardData( + text: + "${widget.shareText}${Platform.isAndroid ? ATGlobalConfig.appDownloadUrlGoogle : ATGlobalConfig.appDownloadUrlGoogle}", + ), + ); + ATTts.show(ATAppLocalizations.of(context)!.copiedToClipboard); + }, + ), + ), + Expanded( + child: ATDebounceWidget( + child: Column( + children: [ + Image.asset( + "atu_images/general/at_icon_share_facebook.png", + width: 40.w, + height: 40.w, + ), + text( + ATAppLocalizations.of(context)!.faceBook, + maxLines: 2, + fontWeight: FontWeight.w600, + textColor: Colors.black38, + fontSize: 14.sp, + ), + ], + ), + onTap: () { + ATSharingHelper.shareTextToFacebook( + "${widget.shareText}${Platform.isAndroid ? ATGlobalConfig.appDownloadUrlGoogle : ATGlobalConfig.appDownloadUrlGoogle}", + ); + }, + ), + ), + Expanded( + child: ATDebounceWidget( + child: Column( + children: [ + Image.asset( + "atu_images/general/at_icon_share_whatsapp.png", + width: 40.w, + height: 40.w, + ), + text( + ATAppLocalizations.of(context)!.whatsApp, + maxLines: 2, + fontWeight: FontWeight.w600, + textColor: Colors.black38, + fontSize: 14.sp, + ), + ], + ), + onTap: () { + ATSharingHelper.shareTextToWhatsApp( + "${widget.shareText}${Platform.isAndroid ? ATGlobalConfig.appDownloadUrlGoogle : ATGlobalConfig.appDownloadUrlGoogle}", + ); + }, + ), + ), + Expanded( + child: ATDebounceWidget( + child: Column( + children: [ + Image.asset( + "atu_images/general/at_icon_share_snapchat.png", + width: 40.w, + height: 40.w, + ), + text( + ATAppLocalizations.of(context)!.snapChat, + maxLines: 2, + fontWeight: FontWeight.w600, + textColor: Colors.black38, + fontSize: 14.sp, + ), + ], + ), + onTap: () { + ATSharingHelper.shareToSnapchatDirectlyMethod( + "${widget.shareText}${Platform.isAndroid ? ATGlobalConfig.appDownloadUrlGoogle : ATGlobalConfig.appDownloadUrlGoogle}", + ); + }, + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/store/props_store_chatbox_detail_dialog.dart b/lib/chatvibe_ui/widgets/store/props_store_chatbox_detail_dialog.dart new file mode 100644 index 0000000..1bede05 --- /dev/null +++ b/lib/chatvibe_ui/widgets/store/props_store_chatbox_detail_dialog.dart @@ -0,0 +1,239 @@ +import 'package:extended_image/extended_image.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_ninepatch_image/flutter_ninepatch_image.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/store_list_res.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/store_repository_imp.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import '../../../chatvibe_core/constants/at_global_config.dart'; +import '../../../chatvibe_core/routes/at_fluro_navigator.dart'; +import '../../../chatvibe_data/models/enum/at_currency_type.dart'; +import '../../../chatvibe_data/models/enum/at_props_type.dart'; +import '../../../chatvibe_features/wallet/wallet_route.dart'; +import '../../../main.dart'; +import '../../components/at_debounce_widget.dart'; +import '../../components/at_compontent.dart'; + +class PropsStoreChatboxDetailDialog extends StatefulWidget { + StoreListRes res; + + //折扣 + double disCount = 1; + + PropsStoreChatboxDetailDialog(this.res, this.disCount); + + @override + _PropsStoreChatboxDetailDialogState createState() => + _PropsStoreChatboxDetailDialogState(); +} + +class _PropsStoreChatboxDetailDialogState + extends State { + PropsPrices? curentPropsPrice; + int currentIndex = 0; + + @override + void initState() { + super.initState(); + currentIndex = 0; + curentPropsPrice = widget.res.propsPrices![currentIndex]; + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: Container( + height: 330.w, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(10.w)), + ), + child: Column( + children: [ + SizedBox(height: 20.w), + netImage( + url: + widget.res.propsResources?.cover ?? "", + height: 95.w, + ), + SizedBox(height: 25.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Spacer(), + Image.asset( + "atu_images/general/at_icon_jb.png", + width: 15.w, + height: 15.w, + ), + widget.disCount < 1 + ? text( + "${curentPropsPrice?.amount}", + textColor: Color(0xffFF9500), + fontWeight: FontWeight.w600, + decoration: TextDecoration.lineThrough, + ) + : Container(), + widget.disCount < 1 ? SizedBox(width: 5.w) : Container(), + text( + "${((curentPropsPrice?.amount ?? 0) * widget.disCount).toInt()}", + textColor: Color(0xffFF9500), + fontSize: 13.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 15.w), + ], + ), + SizedBox(height: 10.w), + SingleChildScrollView( + child: SizedBox( + height: 28.w, + child: ListView.separated( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: widget.res.propsPrices?.length ?? 0, + itemBuilder: (context, index) { + return GestureDetector( + child: Container( + width: 110.w, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5.w), + color: + index == currentIndex + ? Color(0xffFFF4D9) + : Colors.transparent, + border: Border.all( + color: Color(0xffFF9500), + width: 1.w, + ), + ), + child: text( + "${widget.res.propsPrices?[index].days} ${ATAppLocalizations.of(context)!.day}", + textColor: Color(0xffFF9500), + ), + ), + onTap: () { + currentIndex = index; + curentPropsPrice = widget.res.propsPrices?[index]; + setState(() {}); + }, + ); + }, + separatorBuilder: (BuildContext context, int index) { + return SizedBox(width: 5.w); + }, + ), + ), + ), + SizedBox(height: 20.w), + Container( + color: Colors.black12, + width: ScreenUtil().screenWidth, + height: 0.5.w, + ), + SizedBox(height: 20.w), + Container( + alignment: Alignment.center, + child: Row( + children: [ + Consumer( + builder: (context, ref, child) { + return GestureDetector( + child: Row( + children: [ + SizedBox(width: 10.w), + Image.asset( + ATGlobalConfig.businessLogicStrategy + .getStorePageGoldIcon(), + width: 20.w, + height: 20.w, + ), + SizedBox(width: 5.w), + text( + "${ref.myBalance}", + fontSize: 13.sp, + textColor: Colors.black, + ), + Icon( + Icons.chevron_right, + size: 20.w, + color: Colors.black, + ), + ], + ), + onTap: () { + SmartDialog.dismiss(tag: "showPropsDetail"); + ATNavigatorUtils.push( + navigatorKey.currentState!.context, + WalletRoute.recharge, + replace: false, + ); + }, + ); + }, + ), + Spacer(), + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 130.w, + padding: EdgeInsets.symmetric(vertical: 10.w), + decoration: BoxDecoration( + color: ChatVibeTheme.primaryLight, + borderRadius: BorderRadius.circular(35.w), + ), + child: text( + ATAppLocalizations.of(context)!.purchase, + fontSize: 16.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + ATLoadingManager.exhibitOperation(); + StoreRepositoryImp() + .storePurchasing( + widget.res.id ?? "", + ATPropsType.AVATAR_FRAME.name, + ATCurrencyType.GOLD.name, + "${curentPropsPrice?.days}", + ) + .then((value) { + SmartDialog.dismiss(tag: "showPropsDetail"); + ATLoadingManager.veilRoutine(); + ATTts.show( + ATAppLocalizations.of( + context, + )!.purchaseIsSuccessful, + ); + Provider.of( + context, + listen: false, + ).updateBalance(value); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + }, + ), + SizedBox(width: 10.w,) + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/store/props_store_headdress_detail_dialog.dart b/lib/chatvibe_ui/widgets/store/props_store_headdress_detail_dialog.dart new file mode 100644 index 0000000..32eab71 --- /dev/null +++ b/lib/chatvibe_ui/widgets/store/props_store_headdress_detail_dialog.dart @@ -0,0 +1,244 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/components/at_debounce_widget.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/store_list_res.dart'; +import 'package:aslan/main.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_data/sources/local/user_manager.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/store_repository_imp.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import '../../../chatvibe_core/constants/at_global_config.dart'; +import '../../../chatvibe_core/routes/at_fluro_navigator.dart'; +import '../../../chatvibe_data/models/enum/at_currency_type.dart'; +import '../../../chatvibe_data/models/enum/at_props_type.dart'; +import '../../../chatvibe_features/wallet/wallet_route.dart'; + +class PropsStoreHeaddressDetailDialog extends StatefulWidget { + StoreListRes res; + + //折扣 + double disCount = 1; + + PropsStoreHeaddressDetailDialog(this.res, this.disCount); + + @override + _PropsStoreHeaddressDetailDialogState createState() => + _PropsStoreHeaddressDetailDialogState(); +} + +class _PropsStoreHeaddressDetailDialogState + extends State { + PropsPrices? curentPropsPrice; + int currentIndex = 0; + + @override + void initState() { + super.initState(); + currentIndex = 0; + curentPropsPrice = widget.res.propsPrices![currentIndex]; + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: Container( + height: 330.w, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(10.w)), + ), + child: Column( + children: [ + SizedBox(height: 20.w), + head( + url: + AccountStorage().getCurrentUser()?.userProfile?.userAvatar ?? + "", + width: 95.w, + height: 95.w, + headdress: + widget.res.propsResources?.sourceUrl ?? + "" + "", + ), + SizedBox(height: 25.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Spacer(), + Image.asset( + "atu_images/general/at_icon_jb.png", + width: 15.w, + height: 15.w, + ), + widget.disCount < 1 + ? text( + "${curentPropsPrice?.amount}", + textColor: Color(0xffFF9500), + fontWeight: FontWeight.w600, + decoration: TextDecoration.lineThrough, + ) + : Container(), + widget.disCount < 1 ? SizedBox(width: 5.w) : Container(), + text( + "${((curentPropsPrice?.amount ?? 0) * widget.disCount).toInt()}", + textColor: Color(0xffFF9500), + fontSize: 13.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 15.w), + ], + ), + SizedBox(height: 10.w), + SingleChildScrollView( + child: SizedBox( + height: 28.w, + child: ListView.separated( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: widget.res.propsPrices?.length ?? 0, + itemBuilder: (context, index) { + return GestureDetector( + child: Container( + width: 110.w, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5.w), + color: + index == currentIndex + ? Color(0xffFFF4D9) + : Colors.transparent, + border: Border.all( + color: Color(0xffFF9500), + width: 1.w, + ), + ), + child: text( + "${widget.res.propsPrices?[index].days} ${ATAppLocalizations.of(context)!.day}", + textColor: Color(0xffFF9500), + ), + ), + onTap: () { + currentIndex = index; + curentPropsPrice = widget.res.propsPrices?[index]; + setState(() {}); + }, + ); + }, + separatorBuilder: (BuildContext context, int index) { + return SizedBox(width: 5.w); + }, + ), + ), + ), + SizedBox(height: 20.w), + Container( + color: Colors.black12, + width: ScreenUtil().screenWidth, + height: 0.5.w, + ), + SizedBox(height: 20.w), + Container( + alignment: Alignment.center, + child: Row( + children: [ + Consumer( + builder: (context, ref, child) { + return GestureDetector( + child: Row( + children: [ + SizedBox(width: 10.w), + Image.asset( + ATGlobalConfig.businessLogicStrategy + .getStorePageGoldIcon(), + width: 20.w, + height: 20.w, + ), + SizedBox(width: 5.w), + text( + "${ref.myBalance}", + fontSize: 13.sp, + textColor: Colors.black, + ), + Icon( + Icons.chevron_right, + size: 20.w, + color: Colors.black, + ), + ], + ), + onTap: () { + SmartDialog.dismiss(tag: "showPropsDetail"); + ATNavigatorUtils.push( + navigatorKey.currentState!.context, + WalletRoute.recharge, + replace: false, + ); + }, + ); + }, + ), + Spacer(), + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 130.w, + padding: EdgeInsets.symmetric(vertical: 10.w), + decoration: BoxDecoration( + color: ChatVibeTheme.primaryLight, + borderRadius: BorderRadius.circular(35.w), + ), + child: text( + ATAppLocalizations.of(context)!.purchase, + fontSize: 16.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + ATLoadingManager.exhibitOperation(); + StoreRepositoryImp() + .storePurchasing( + widget.res.id ?? "", + ATPropsType.AVATAR_FRAME.name, + ATCurrencyType.GOLD.name, + "${curentPropsPrice?.days}", + ) + .then((value) { + SmartDialog.dismiss(tag: "showPropsDetail"); + ATLoadingManager.veilRoutine(); + ATTts.show( + ATAppLocalizations.of( + context, + )!.purchaseIsSuccessful, + ); + Provider.of( + context, + listen: false, + ).updateBalance(value); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + }, + ), + SizedBox(width: 10.w,) + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/store/props_store_mountains_detail_dialog.dart b/lib/chatvibe_ui/widgets/store/props_store_mountains_detail_dialog.dart new file mode 100644 index 0000000..7cc3238 --- /dev/null +++ b/lib/chatvibe_ui/widgets/store/props_store_mountains_detail_dialog.dart @@ -0,0 +1,244 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/store_list_res.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/store_repository_imp.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import '../../../chatvibe_core/constants/at_global_config.dart'; +import '../../../chatvibe_core/routes/at_fluro_navigator.dart'; +import '../../../chatvibe_data/models/enum/at_currency_type.dart'; +import '../../../chatvibe_data/models/enum/at_props_type.dart'; +import '../../../chatvibe_data/sources/local/user_manager.dart'; +import '../../../chatvibe_features/wallet/wallet_route.dart'; +import '../../../main.dart'; +import '../../components/at_debounce_widget.dart'; + +class PropsStoreMountainsDetailDialog extends StatefulWidget { + StoreListRes res; + + //折扣 + double disCount = 1; + + PropsStoreMountainsDetailDialog(this.res, this.disCount); + + @override + _PropsStoreMountainsDetailDialogState createState() => + _PropsStoreMountainsDetailDialogState(); +} + +class _PropsStoreMountainsDetailDialogState + extends State { + PropsPrices? curentPropsPrice; + int currentIndex = 0; + + @override + void initState() { + super.initState(); + currentIndex = 0; + curentPropsPrice = widget.res.propsPrices![currentIndex]; + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: Container( + height: 330.w, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(10.w)), + ), + child: Column( + children: [ + SizedBox(height: 20.w), + netImage( + url: + widget.res.propsResources?.cover ?? "", + width: 95.w, + height: 95.w, + ), + SizedBox(height: 25.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Spacer(), + Image.asset( + "atu_images/general/at_icon_jb.png", + width: 15.w, + height: 15.w, + ), + widget.disCount < 1 + ? text( + "${curentPropsPrice?.amount}", + textColor: Color(0xffFF9500), + fontWeight: FontWeight.w600, + decoration: TextDecoration.lineThrough, + ) + : Container(), + widget.disCount < 1 ? SizedBox(width: 5.w) : Container(), + text( + "${((curentPropsPrice?.amount ?? 0) * widget.disCount).toInt()}", + textColor: Color(0xffFF9500), + fontSize: 13.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 15.w), + ], + ), + SizedBox(height: 10.w), + SingleChildScrollView( + child: SizedBox( + height: 28.w, + child: ListView.separated( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: widget.res.propsPrices?.length ?? 0, + itemBuilder: (context, index) { + return GestureDetector( + child: Container( + width: 110.w, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5.w), + color: + index == currentIndex + ? Color(0xffFFF4D9) + : Colors.transparent, + border: Border.all( + color: Color(0xffFF9500), + width: 1.w, + ), + ), + child: text( + "${widget.res.propsPrices?[index].days} ${ATAppLocalizations.of(context)!.day}", + textColor: Color(0xffFF9500), + ), + ), + onTap: () { + currentIndex = index; + curentPropsPrice = widget.res.propsPrices?[index]; + setState(() {}); + }, + ); + }, + separatorBuilder: (BuildContext context, int index) { + return SizedBox(width: 5.w); + }, + ), + ), + ), + SizedBox(height: 20.w), + Container( + color: Colors.black12, + width: ScreenUtil().screenWidth, + height: 0.5.w, + ), + SizedBox(height: 20.w), + Container( + alignment: Alignment.center, + child: Row( + children: [ + Consumer( + builder: (context, ref, child) { + return GestureDetector( + child: Row( + children: [ + SizedBox(width: 10.w), + Image.asset( + ATGlobalConfig.businessLogicStrategy + .getStorePageGoldIcon(), + width: 20.w, + height: 20.w, + ), + SizedBox(width: 5.w), + text( + "${ref.myBalance}", + fontSize: 13.sp, + textColor: Colors.black, + ), + Icon( + Icons.chevron_right, + size: 20.w, + color: Colors.black, + ), + ], + ), + onTap: () { + SmartDialog.dismiss(tag: "showPropsDetail"); + ATNavigatorUtils.push( + navigatorKey.currentState!.context, + WalletRoute.recharge, + replace: false, + ); + }, + ); + }, + ), + Spacer(), + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 130.w, + padding: EdgeInsets.symmetric(vertical: 10.w), + decoration: BoxDecoration( + color: ChatVibeTheme.primaryLight, + borderRadius: BorderRadius.circular(35.w), + ), + child: text( + ATAppLocalizations.of(context)!.purchase, + fontSize: 16.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + ATLoadingManager.exhibitOperation(); + StoreRepositoryImp() + .storePurchasing( + widget.res.id ?? "", + ATPropsType.AVATAR_FRAME.name, + ATCurrencyType.GOLD.name, + "${curentPropsPrice?.days}", + ) + .then((value) { + SmartDialog.dismiss(tag: "showPropsDetail"); + ATLoadingManager.veilRoutine(); + ATTts.show( + ATAppLocalizations.of( + context, + )!.purchaseIsSuccessful, + ); + Provider.of( + context, + listen: false, + ).updateBalance(value); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + }, + ), + SizedBox(width: 10.w,) + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/store/props_store_theme_detail_dialog.dart b/lib/chatvibe_ui/widgets/store/props_store_theme_detail_dialog.dart new file mode 100644 index 0000000..90708a9 --- /dev/null +++ b/lib/chatvibe_ui/widgets/store/props_store_theme_detail_dialog.dart @@ -0,0 +1,244 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_core/utilities/at_loading_manager.dart'; +import 'package:aslan/chatvibe_domain/models/res/store_list_res.dart'; +import 'package:provider/provider.dart'; +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/at_compontent.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/components/at_tts.dart'; +import 'package:aslan/chatvibe_data/sources/repositories/store_repository_imp.dart'; +import 'package:aslan/chatvibe_managers/user_profile_manager.dart'; +import '../../../chatvibe_core/constants/at_global_config.dart'; +import '../../../chatvibe_core/routes/at_fluro_navigator.dart'; +import '../../../chatvibe_data/models/enum/at_currency_type.dart'; +import '../../../chatvibe_data/models/enum/at_props_type.dart'; +import '../../../chatvibe_features/wallet/wallet_route.dart'; +import '../../../main.dart'; +import '../../components/at_debounce_widget.dart'; + +class PropsStoreThemeDetailDialog extends StatefulWidget { + StoreListRes res; + + //折扣 + double disCount = 1; + + PropsStoreThemeDetailDialog(this.res, this.disCount); + + @override + _PropsStoreThemeDetailDialogState createState() => + _PropsStoreThemeDetailDialogState(); +} + +class _PropsStoreThemeDetailDialogState + extends State { + PropsPrices? curentPropsPrice; + int currentIndex = 0; + + @override + void initState() { + super.initState(); + currentIndex = 0; + curentPropsPrice = widget.res.propsPrices![currentIndex]; + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + child: Container( + height: 330.w, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(10.w)), + ), + child: Column( + children: [ + SizedBox(height: 20.w), + netImage( + url: + widget.res.propsResources?.cover ?? "", + borderRadius: BorderRadius.circular(10.w), + width: 95.w, + height: 95.w, + ), + SizedBox(height: 25.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Spacer(), + Image.asset( + "atu_images/general/at_icon_jb.png", + width: 15.w, + height: 15.w, + ), + widget.disCount < 1 + ? text( + "${curentPropsPrice?.amount}", + textColor: Color(0xffFF9500), + fontWeight: FontWeight.w600, + decoration: TextDecoration.lineThrough, + ) + : Container(), + widget.disCount < 1 ? SizedBox(width: 5.w) : Container(), + text( + "${((curentPropsPrice?.amount ?? 0) * widget.disCount).toInt()}", + textColor: Color(0xffFF9500), + fontSize: 13.sp, + fontWeight: FontWeight.w600, + ), + SizedBox(width: 15.w), + ], + ), + SizedBox(height: 10.w), + SingleChildScrollView( + child: SizedBox( + height: 28.w, + child: ListView.separated( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: widget.res.propsPrices?.length ?? 0, + itemBuilder: (context, index) { + return GestureDetector( + child: Container( + width: 85.w, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5.w), + color: + index == currentIndex + ? Color(0xffFFF4D9) + : Colors.transparent, + border: Border.all( + color: Color(0xffFF9500), + width: 1.w, + ), + ), + child: text( + "${widget.res.propsPrices?[index].days} ${ATAppLocalizations.of(context)!.day}", + textColor: Color(0xffFF9500), + ), + ), + onTap: () { + currentIndex = index; + curentPropsPrice = widget.res.propsPrices?[index]; + setState(() {}); + }, + ); + }, + separatorBuilder: (BuildContext context, int index) { + return SizedBox(width: 5.w); + }, + ), + ), + ), + SizedBox(height: 20.w), + Container( + color: Colors.black12, + width: ScreenUtil().screenWidth, + height: 0.5.w, + ), + SizedBox(height: 20.w), + Container( + alignment: Alignment.center, + child: Row( + children: [ + Consumer( + builder: (context, ref, child) { + return GestureDetector( + child: Row( + children: [ + SizedBox(width: 10.w), + Image.asset( + ATGlobalConfig.businessLogicStrategy + .getStorePageGoldIcon(), + width: 20.w, + height: 20.w, + ), + SizedBox(width: 5.w), + text( + "${ref.myBalance}", + fontSize: 13.sp, + textColor: Colors.black, + ), + Icon( + Icons.chevron_right, + size: 20.w, + color: Colors.black, + ), + ], + ), + onTap: () { + SmartDialog.dismiss(tag: "showPropsDetail"); + ATNavigatorUtils.push( + navigatorKey.currentState!.context, + WalletRoute.recharge, + replace: false, + ); + }, + ); + }, + ), + Spacer(), + ATDebounceWidget( + child: Container( + alignment: Alignment.center, + width: 130.w, + padding: EdgeInsets.symmetric(vertical: 10.w), + decoration: BoxDecoration( + color: ChatVibeTheme.primaryLight, + borderRadius: BorderRadius.circular(35.w), + ), + child: text( + ATAppLocalizations.of(context)!.purchase, + fontSize: 16.sp, + textColor: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + ATLoadingManager.exhibitOperation(); + StoreRepositoryImp() + .storePurchasing( + widget.res.id ?? "", + ATPropsType.AVATAR_FRAME.name, + ATCurrencyType.GOLD.name, + "${curentPropsPrice?.days}", + ) + .then((value) { + SmartDialog.dismiss(tag: "showPropsDetail"); + ATLoadingManager.veilRoutine(); + ATTts.show( + ATAppLocalizations.of( + context, + )!.purchaseIsSuccessful, + ); + Provider.of( + context, + listen: false, + ).updateBalance(value); + }) + .catchError((e) { + ATLoadingManager.veilRoutine(); + }); + }, + ), + SizedBox(width: 10.w,) + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/chatvibe_ui/widgets/task/at_task_list_dialog.dart b/lib/chatvibe_ui/widgets/task/at_task_list_dialog.dart new file mode 100644 index 0000000..6ab4af4 --- /dev/null +++ b/lib/chatvibe_ui/widgets/task/at_task_list_dialog.dart @@ -0,0 +1,673 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:provider/provider.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; + +import '../../../app_localizations.dart'; +import '../../../chatvibe_core/constants/at_global_config.dart'; +import '../../../chatvibe_core/routes/at_fluro_navigator.dart'; +import '../../../chatvibe_core/utilities/at_date_utils.dart'; +import '../../../chatvibe_core/utilities/at_room_utils.dart'; +import '../../../chatvibe_data/models/enum/at_vip_type.dart'; +import '../../../chatvibe_data/sources/local/data_persistence.dart'; +import '../../../chatvibe_data/sources/local/user_manager.dart'; +import '../../../chatvibe_data/sources/repositories/user_repository_impl.dart'; +import '../../../chatvibe_domain/models/res/at_task_list_res.dart'; +import '../../../chatvibe_domain/models/res/my_room_res.dart'; +import '../../../chatvibe_domain/models/res/room_res.dart'; +import '../../../chatvibe_features/index/main_route.dart'; +import '../../../chatvibe_features/store/store_route.dart'; +import '../../../chatvibe_managers/app_general_manager.dart'; +import '../../../chatvibe_managers/room_manager.dart'; +import '../../../main.dart'; +import '../../components/at_compontent.dart'; +import '../../components/at_debounce_widget.dart'; +import '../../components/at_tts.dart'; +import '../../components/text/at_text.dart'; + +class ATTaskListDialog extends StatefulWidget { + @override + _ATTaskListDialogState createState() => _ATTaskListDialogState(); +} + +class _ATTaskListDialogState extends State + with SingleTickerProviderStateMixin { + final RefreshController _refreshController = RefreshController( + initialRefresh: false, + ); + + bool isLoading = true; + + //折扣 + double disCount = 1; + List growthTasks = []; + List dailyTasks = []; + + @override + void initState() { + super.initState(); + if (AccountStorage().getVIP()?.name == ATVIPType.VISCOUNT.name) { + disCount = 1.05; + } else if (AccountStorage().getVIP()?.name == ATVIPType.EARL.name) { + disCount = 1.07; + } else if (AccountStorage().getVIP()?.name == ATVIPType.MARQUIS.name) { + disCount = 1.10; + } else if (AccountStorage().getVIP()?.name == ATVIPType.DUKE.name) { + disCount = 1.15; + } else if (AccountStorage().getVIP()?.name == ATVIPType.KING.name) { + disCount = 1.20; + } else if (AccountStorage().getVIP()?.name == ATVIPType.EMPEROR.name) { + disCount = 1.25; + } + disCount = 1; + loadData(); + DataPersistence.setBool( + "IndexShowTaskListDialog${ATMDateUtils.formatDateTime(DateTime.now())}", + true, + ); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("images/index/icon_task_list_dialog_bg.png"), + fit: BoxFit.fill, + ), + ), + height: ScreenUtil().screenHeight * 0.65, + padding: EdgeInsets.symmetric(horizontal: 8.w), + margin: EdgeInsets.symmetric(horizontal: 12.w), + child: + isLoading + ? Center( + child: CupertinoActivityIndicator(color: Colors.black12), + ) + : SmartRefresher( + enablePullDown: true, + enablePullUp: false, + controller: _refreshController, + onRefresh: () { + loadData(); + }, + onLoading: () {}, + child: Column( + children: [ + SizedBox(height: 100.w), + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + Container( + width: ScreenUtil().screenWidth, + margin: EdgeInsets.symmetric( + horizontal: 2.w, + vertical: 15.w, + ), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Row( + children: [ + SizedBox(width: 15.w), + Expanded( + child: text( + ATAppLocalizations.of( + context, + )!.dailyTasksTips, + fontSize: 10.sp, + maxLines: 2, + textColor: Colors.black38, + ), + ), + SizedBox(width: 15.w), + ], + ), + Container( + margin: EdgeInsets.symmetric( + vertical: 8.w, + horizontal: 10.w, + ), + child: text( + ATAppLocalizations.of( + context, + )!.dailyTasks, + textColor: Colors.black, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ), + ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: dailyTasks.length, + // 列表项数量 + padding: EdgeInsets.only(bottom: 5.w), + itemBuilder: (context, index) { + return _buildTaskItem( + dailyTasks[index], + index, + ); + }, + separatorBuilder: ( + BuildContext context, + int index, + ) { + return SizedBox(height: 10.w); + }, + ), + ], + ), + ), + Container( + width: ScreenUtil().screenWidth, + margin: EdgeInsets.symmetric(horizontal: 2.w), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.symmetric( + vertical: 8.w, + horizontal: 10.w, + ), + child: text( + ATAppLocalizations.of( + context, + )!.improvementTasks, + textColor: Colors.black, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ), + ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: growthTasks.length, + // 列表项数量 + padding: EdgeInsets.only(bottom: 15.w), + itemBuilder: (context, index) { + return _buildTaskItem( + growthTasks[index], + index, + ); + }, + separatorBuilder: ( + BuildContext context, + int index, + ) { + return SizedBox(height: 10.w); + }, + ), + ], + ), + ), + SizedBox(height: 10.w), + ], + ), + ), + ), + SizedBox(height: 15.w), + ], + ), + ), + ), + SizedBox(height: 5.w), + ATDebounceWidget( + child: Container( + padding: EdgeInsets.all(10.w), + child: Image.asset( + "images/general/icon_clear_c.png", + width: 20.w, + height: 20.w, + color: Colors.white, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showTaskListDialog"); + }, + ), + ], + ); + } + + _buildTaskItem(ATTaskListRes task, int index) { + return Container( + decoration: BoxDecoration( + color: Color(0xffF2DDA8), + borderRadius: BorderRadius.all(Radius.circular(12.w)), + ), + margin: EdgeInsets.symmetric(horizontal: 10.w), + padding: EdgeInsets.symmetric(horizontal: 2.w, vertical: 10.w), + child: Row( + children: [ + netImage(url: task.taskIcon ?? "", width: 45.w), + SizedBox(width: 5.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + constraints: BoxConstraints(maxWidth: 160.w), + child: text( + "${task.taskDesc}", + maxLines: 2, + fontWeight: FontWeight.w600, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPagePrimaryTextColor(), + fontSize: 14.sp, + ), + ), + SizedBox(width: 3.w), + text( + "(${task.completedValue ?? 0}/${task.targetValue ?? 0})", + fontWeight: FontWeight.w600, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPagePrimaryTextColor(), + fontSize: 14.sp, + ), + ], + ), + SizedBox(height: 3.w), + task.conditionType == "GOLD-CUSTOMIZE" + ? Row( + children: [ + Image.asset( + ATGlobalConfig.businessLogicStrategy + .getTaskPageGoldIcon(), + width: 18.w, + ), + SizedBox(width: 2.w), + disCount > 1 + ? text( + "${task.conditionValue?.split("-").first}", + fontSize: 14.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPageGoldTextColor(), + fontWeight: FontWeight.w600, + decoration: TextDecoration.lineThrough, + ) + : Container(), + disCount > 1 ? SizedBox(width: 3.w) : Container(), + text( + "${(int.parse((task.conditionValue ?? "0-").split("-").first) * disCount).toInt()}", + fontSize: 14.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPageGoldTextColor(), + fontWeight: FontWeight.w600, + ), + SizedBox(width: 4.w), + Image.asset( + ATGlobalConfig.businessLogicStrategy + .getTaskPageExpIcon(), + width: 20.w, + ), + SizedBox(width: 4.w), + + disCount > 1 + ? text( + "${task.conditionValue?.split("-").last}", + fontSize: 14.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPageExpTextColor(), + fontWeight: FontWeight.w600, + decoration: TextDecoration.lineThrough, + ) + : Container(), + disCount > 1 ? SizedBox(width: 3.w) : Container(), + text( + "${(int.parse((task.conditionValue ?? "-0").split("-").last) * disCount).toInt()}", + fontSize: 14.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPageExpTextColor(), + fontWeight: FontWeight.w600, + ), + ], + ) + : (task.conditionType == "CUSTOMIZE" + ? Row( + children: [ + Image.asset( + ATGlobalConfig.businessLogicStrategy + .getTaskPageExpIcon(), + width: 20.w, + ), + SizedBox(width: 4.w), + disCount > 1 + ? text( + "${task.conditionValue}", + fontSize: 14.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPageExpTextColor(), + decoration: TextDecoration.lineThrough, + ) + : Container(), + disCount > 1 ? SizedBox(width: 3.w) : Container(), + text( + "${(int.parse(task.conditionValue ?? "0") * disCount).toInt()}", + fontSize: 14.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPageExpTextColor(), + fontWeight: FontWeight.w600, + ), + ], + ) + : (task.taskId == 11 + ? Row( + children: [ + text( + "Gift Bag x1", + fontSize: 14.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPageThemeColor(), + fontWeight: FontWeight.w600, + ), + ], + ) + : Container())), + ], + ), + Spacer(), + SizedBox(width: 3.w), + task.taskStatus == 0 + ? GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + width: 60.w, + height: 30.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_task_list_item_go_btn.png", + ), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + text( + ATAppLocalizations.of(context)!.go, + fontSize: 13.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPageThemeLightColor(), + ), + SizedBox(width: 2.w), + Icon( + Icons.arrow_forward_ios, + color: + ATGlobalConfig.businessLogicStrategy + .getTaskPageThemeLightColor(), + size: 12.w, + ), + ], + ), + ), + onTap: () { + goTask(task.taskId ?? 0); + }, + ) + : (task.isRewardCollected == 0 + ? GestureDetector( + child: Container( + alignment: AlignmentDirectional.center, + width: 60.w, + height: 30.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_task_list_item_act_btn.png", + ), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + text( + ATAppLocalizations.of(context)!.receive, + fontSize: 13.sp, + textColor: + ATGlobalConfig.businessLogicStrategy + .getTaskPageButtonTextColor(), + ), + SizedBox(width: 2.w), + ], + ), + ), + onTap: () { + receiveReward(task.taskId ?? 0); + }, + ) + : Container( + alignment: AlignmentDirectional.center, + width: 60.w, + height: 30.w, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "atu_images/room/at_icon_room_task_list_item_complete_btn.png", + ), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + text( + ATAppLocalizations.of(context)!.done, + fontSize: 13.sp, + textColor: Color(0xff888888), + ), + SizedBox(width: 2.w), + ], + ), + )), + SizedBox(width: 3.w), + ], + ), + ); + } + + void loadData() { + AccountRepository() + .tasks() + .then((result) { + dailyTasks.clear(); + growthTasks.clear(); + for (var task in result) { + if (task.taskType == 1) { + //每日任务 + dailyTasks.add(task); + } else if (task.taskType == 2) { + //成长任务 + growthTasks.add(task); + } + } + _refreshController.refreshCompleted(); + _refreshController.loadComplete(); + setState(() {}); + }) + .catchError((e) { + _refreshController.loadNoData(); + _refreshController.refreshCompleted(); + setState(() {}); + }); + } + + void goTask(num taskId) { + if (taskId == 1) { + //去房间上麦 + goMyRoom(false, false, false); + } else if (taskId == 2) { + //去房间送礼 + goMyRoom(false, false, true); + } else if (taskId == 3) { + //去商店 + SmartDialog.dismiss(tag: "showTaskListDialog"); + ATNavigatorUtils.push(navigatorKey.currentState!.context, StoreRoute.list); + } else if (taskId == 4) { + //去玩游戏 + goMyRoom(true, false, false); + } else if (taskId == 5) { + //创建房间 + SmartDialog.dismiss(tag: "showTaskListDialog"); + _openMyDrawer(true); + } else if (taskId == 6) { + //加好友 + goHotGame(false, false); + } else if (taskId == 7) { + //去玩游戏 + goHotGame(true, false); + } else if (taskId == 8) { + //去赠送幸运礼物 + goMyRoom(false, true, false); + } else if (taskId == 9) { + goHotGame(false, false); + } else if (taskId == 10) { + //加入房间 + goHotGame(false, false); + } else if (taskId == 11) { + //去邀请用户注册 + if (ATGlobalConfig.isReview) { + return; + } + SmartDialog.dismiss(tag: "showTaskListDialog"); + ATNavigatorUtils.push( + navigatorKey.currentState!.context, + "${MainRoute.webViewPage}?url=${Uri.encodeComponent(ATGlobalConfig.inviteNewUserUrl)}&showTitle='false'", + replace: false, + ); + } + } + + ///领取奖励 + void receiveReward(num taskId) { + if (taskId == 11) { + SmartDialog.show( + tag: "showReciverInvitationReward", + alignment: Alignment.center, + maskColor: Colors.transparent, + animationType: SmartAnimationType.fade, + builder: (_) { + return Center( + child: Stack( + alignment: AlignmentDirectional.bottomCenter, + children: [ + SizedBox( + height: 320.w, + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage( + "images/index/icon_invitation_bg.png", + ), + ), + ), + ), + ), + GestureDetector( + behavior: HitTestBehavior.opaque, + child: Container( + height: 38.w, + width: 120.w, + color: Colors.transparent, + margin: EdgeInsets.only(bottom: 15.w), + ), + onTap: () { + AccountRepository() + .taskReward(taskId) + .then((result) { + if (result) { + ATTts.show( + ATAppLocalizations.of(context)!.receiveSucc, + ); + SmartDialog.dismiss( + tag: "showReciverInvitationReward", + ); + loadData(); + } + }) + .catchError((e) { + ATTts.show(e); + }); + }, + ), + ], + ), + ); + }, + ); + } else { + AccountRepository() + .taskReward(taskId) + .then((result) { + if (result) { + ATTts.show(ATAppLocalizations.of(context)!.receiveSucc); + loadData(); + } + }) + .catchError((e) { + ATTts.show(e); + }); + } + } + + void goHotGame(bool openGameDialog, bool openGiftDialogToLuckyGift) { + SmartDialog.dismiss(tag: "showTaskListDialog"); + ChatVibeRoomRes? hotRoom = + Provider.of(context, listen: false).hotRoom; + if (hotRoom != null) { + ATRoomUtils.goRoom( + hotRoom.id ?? "", + navigatorKey.currentState!.context, + openGameDialog: openGameDialog, + openGiftDialogToLuckyGift: openGiftDialogToLuckyGift, + ); + } + } + + void goMyRoom( + bool openGameDialog, + bool openGiftDialogToLuckyGift, + bool openGiftDialogToGift, + ) { + SmartDialog.dismiss(tag: "showTaskListDialog"); + MyRoomRes? myRoom = + Provider.of(context, listen: false).myRoom; + if (myRoom != null) { + ATRoomUtils.goRoom( + myRoom.id ?? "", + navigatorKey.currentState!.context, + openGameDialog: openGameDialog, + openGiftDialogToLuckyGift: openGiftDialogToLuckyGift, + openGiftDialogToGift: openGiftDialogToGift, + ); + } else { + _openMyDrawer(true); + } + } + + void _openMyDrawer(bool highlightMyRoom) { + // SmartDialog.dismiss(tag: "showTaskListDialog"); + // // Wait for pop transition, then switch tab on existing IndexPage instance. + // Future.delayed(const Duration(milliseconds: 250), () { + // eventBus.fire(OpenDrawerEvent(highlightMyRoom: highlightMyRoom)); + // }); + } +} diff --git a/lib/chatvibe_ui/widgets/test.dart b/lib/chatvibe_ui/widgets/test.dart new file mode 100644 index 0000000..6b006c7 --- /dev/null +++ b/lib/chatvibe_ui/widgets/test.dart @@ -0,0 +1,326 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'dart:math'; +import 'dart:async'; + +import 'package:aslan/chatvibe_ui/widgets/headdress/headdress_widget.dart'; +import 'package:aslan/chatvibe_ui/widgets/room/anim/room_entrance_widget.dart'; + +// 红包数据模型 +class RedPacket { + double x; // x坐标 + double y; // y坐标 + double speed; // 下落速度 + double size; // 大小 + double rotation; // 旋转角度 + double rotationSpeed; // 旋转速度 + Color color; // 红包颜色 + bool isCollected; // 是否被收集 + String id; // 唯一标识符 + + RedPacket({ + required this.x, + required this.y, + required this.speed, + required this.size, + required this.rotation, + required this.rotationSpeed, + required this.color, + this.isCollected = false, + required this.id, + }); +} + +class RedPacketRainPage extends StatefulWidget { + @override + _RedPacketRainPageState createState() => _RedPacketRainPageState(); +} + +class _RedPacketRainPageState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + final Random _random = Random(); + final List _redPackets = []; + final int _maxPackets = 50; + DateTime? _startTime; + int _durationSeconds = 30; + int _collectedCount = 0; + + final List _redColors = [ + Colors.red[400]!, + Colors.red[500]!, + Colors.red[600]!, + Colors.red[700]!, + Colors.red[800]!, + Colors.red[900]!, + ]; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: Duration(milliseconds: 350), + )..addListener(_updateRain); + + _startRain(); + + Future.delayed(Duration(seconds: 3),(){ + RoomEntranceQueueManager().addToQueue(AnimationQueueItem(resource: "atu_images/room/entrance/test3.svga",dynamicData: {"textReplacements":{"02":"3123123"}})); + }); + } + + void _startRain() { + _startTime = DateTime.now(); + _collectedCount = 0; + _redPackets.clear(); + _controller.repeat(); + + Timer(Duration(seconds: _durationSeconds), () { + if (mounted) { + _controller.stop(); + } + }); + } + + void _updateRain() { + if (!mounted) return; + + final screenWidth = MediaQuery.of(context).size.width; + final screenHeight = MediaQuery.of(context).size.height; + + // 随机生成新红包 + if (_redPackets.length < _maxPackets && _random.nextDouble() < 0.3) { + _createNewPacket(screenWidth, screenHeight); + } + + // 更新现有红包位置 + for (int i = _redPackets.length - 1; i >= 0; i--) { + final packet = _redPackets[i]; + + if (!packet.isCollected) { + packet.y += packet.speed; + packet.rotation += packet.rotationSpeed; + + if (packet.y > screenHeight + 100) { + _redPackets.removeAt(i); + continue; + } + } + } + + setState(() {}); + } + + void _createNewPacket(double screenWidth, double screenHeight) { + _redPackets.add( + RedPacket( + x: _random.nextDouble() * screenWidth, + y: -50, + speed: 2 + _random.nextDouble() * 3, + size: 30 + _random.nextDouble() * 20, + rotation: _random.nextDouble() * 2 * pi, + rotationSpeed: (_random.nextDouble() - 0.5) * 0.1, + color: _redColors[_random.nextInt(_redColors.length)], + id: '${DateTime.now().millisecondsSinceEpoch}_${_random.nextInt(1000)}', + ), + ); + } + + void _collectPacket(String packetId) { + final packetIndex = _redPackets.indexWhere( + (packet) => packet.id == packetId, + ); + + if (packetIndex != -1 && !_redPackets[packetIndex].isCollected) { + setState(() { + _redPackets[packetIndex].isCollected = true; + _collectedCount++; + }); + + // 收集动画效果 + Future.delayed(Duration(milliseconds: 350), () { + if (mounted) { + setState(() { + _redPackets.removeWhere((packet) => packet.id == packetId); + }); + } + }); + } + } + + @override + void dispose() { + _controller.dispose(); + _redPackets.clear(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final remainingTime = + _startTime != null + ? max( + 0, + _durationSeconds - + DateTime.now().difference(_startTime!).inSeconds, + ) + : _durationSeconds; + + return Scaffold( + backgroundColor: Colors.black, + body: RoomEntranceWidget( + height: 90.w, + ), + ); + } + + void _handleTap(Offset position) { + // 从后往前遍历,这样点击检测会优先处理最上层的红包(最后绘制的) + for (int i = _redPackets.length - 1; i >= 0; i--) { + final packet = _redPackets[i]; + if (!packet.isCollected && _isPointInRedPacket(position, packet)) { + _collectPacket(packet.id); + break; // 只处理最上层的一个红包 + } + } + } + + bool _isPointInRedPacket(Offset point, RedPacket packet) { + // 简化的点击区域检测 - 圆形区域 + final distance = sqrt( + pow(point.dx - packet.x, 2) + pow(point.dy - packet.y, 2), + ); + return distance < packet.size; + } + + Widget _buildInfoCard(String title, String value) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Colors.red), + ), + child: Column( + children: [ + Text(title, style: TextStyle(color: Colors.white, fontSize: 10)), + Text( + value, + style: TextStyle( + color: Colors.red, + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ); + } +} + +class RedPacketPainter extends CustomPainter { + final List redPackets; + + RedPacketPainter({required this.redPackets}); + + @override + void paint(Canvas canvas, Size size) { + final textPainter = TextPainter(textDirection: TextDirection.ltr); + + for (final packet in redPackets) { + if (packet.isCollected) { + _drawCollectedPacket(canvas, packet); + } else { + _drawRedPacket(canvas, packet, textPainter); + } + } + } + + void _drawRedPacket( + Canvas canvas, + RedPacket packet, + TextPainter textPainter, + ) { + canvas.save(); + canvas.translate(packet.x, packet.y); + canvas.rotate(packet.rotation); + + // 绘制红包主体 + final packetPaint = + Paint() + ..color = packet.color + ..style = PaintingStyle.fill; + + final rect = Rect.fromCenter( + center: Offset.zero, + width: packet.size, + height: packet.size * 1.5, + ); + + final rrect = RRect.fromRectAndRadius(rect, Radius.circular(8)); + canvas.drawRRect(rrect, packetPaint); + + // 红包中间的黄色条纹 + final stripePaint = + Paint() + ..color = Colors.yellow[700]! + ..style = PaintingStyle.fill; + + final stripeRect = Rect.fromCenter( + center: Offset.zero, + width: packet.size * 0.8, + height: packet.size * 0.2, + ); + canvas.drawRRect( + RRect.fromRectAndRadius(stripeRect, Radius.circular(4)), + stripePaint, + ); + + // 红包文字 + textPainter.text = TextSpan( + text: '福', + style: TextStyle( + color: Colors.yellow[700], + fontSize: packet.size * 0.4, + fontWeight: FontWeight.bold, + ), + ); + textPainter.layout(); + textPainter.paint( + canvas, + Offset(-textPainter.width / 2, -textPainter.height / 2), + ); + + canvas.restore(); + } + + void _drawCollectedPacket(Canvas canvas, RedPacket packet) { + final progress = (DateTime.now().millisecondsSinceEpoch % 300) / 300; + final scale = 1.0 - progress; + final opacity = 1.0 - progress; + + canvas.save(); + canvas.translate(packet.x, packet.y); + canvas.scale(scale); + + final packetPaint = + Paint() + ..color = packet.color.withOpacity(opacity) + ..style = PaintingStyle.fill; + + final rect = Rect.fromCenter( + center: Offset.zero, + width: packet.size, + height: packet.size * 1.5, + ); + + final rrect = RRect.fromRectAndRadius(rect, Radius.circular(8)); + canvas.drawRRect(rrect, packetPaint); + + canvas.restore(); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} diff --git a/lib/chatvibe_ui/widgets/update/app_version_update_page.dart b/lib/chatvibe_ui/widgets/update/app_version_update_page.dart new file mode 100644 index 0000000..e5a9178 --- /dev/null +++ b/lib/chatvibe_ui/widgets/update/app_version_update_page.dart @@ -0,0 +1,123 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:store_redirect/store_redirect.dart'; + +import 'package:aslan/app_localizations.dart'; +import 'package:aslan/chatvibe_ui/components/text/at_text.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; +import 'package:aslan/chatvibe_domain/models/res/at_version_manage_latest_res.dart'; + +class AppVersionUpdatePage extends StatefulWidget { + ATVersionManageLatestRes result; + + AppVersionUpdatePage(this.result); + + @override + _AppVersionUpdatePageState createState() => _AppVersionUpdatePageState(); +} + +class _AppVersionUpdatePageState extends State { + @override + Widget build(BuildContext context) { + return Container( + height: 350.w, + margin: EdgeInsets.symmetric(horizontal: 25.w), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("atu_images/general/at_icon_app_update_bg.png"), + fit: BoxFit.fill, + ), + ), + child: Column( + children: [ + SizedBox(height: 145.w), + text( + ATAppLocalizations.of(context)!.importantReminder, + fontWeight: FontWeight.bold, + fontSize: 17.sp, + textColor: Colors.black, + ), + SizedBox(height: 15.w), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(width: 15.w), + Expanded( + child: text( + ATAppLocalizations.of( + context, + )!.appUpdateTip("${widget.result.version}"), + fontSize: 14.sp, + maxLines: 5, + textColor: Colors.black54, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(width: 15.w), + ], + ), + SizedBox(height: 45.w), + Row( + children: [ + SizedBox(width: 25.w), + widget.result.forceUpdate == true + ? Container() + : GestureDetector( + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular(25.w), + border: Border.all(color: ChatVibeTheme.primaryColor, width: 1.w), + ), + width: 85.w, + height: 40.w, + child: text( + ATAppLocalizations.of(context)!.skip2, + textColor: ChatVibeTheme.primaryColor, + fontWeight: FontWeight.w600, + fontSize: 15.sp, + ), + ), + onTap: () { + SmartDialog.dismiss(tag: "showUpdateDialog"); + }, + ), + widget.result.forceUpdate == true + ? Container() + : SizedBox(width: 25.w), + Expanded( + child: GestureDetector( + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(25.w), + gradient: LinearGradient( + colors: [Color(0xffC670FF), Color(0xff7726FF)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + height: 40.w, + child: text( + ATAppLocalizations.of(context)!.updateNow, + textColor: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 15.sp, + ), + ), + onTap: () { + StoreRedirect.redirect(); + }, + ), + ), + SizedBox(width: 25.w), + ], + ), + ], + ), + ); + } +} diff --git a/lib/config/pickImage.dart b/lib/config/pickImage.dart new file mode 100644 index 0000000..017a788 --- /dev/null +++ b/lib/config/pickImage.dart @@ -0,0 +1,107 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:image_picker/image_picker.dart'; + +import '../chatvibe_core/utilities/at_path_utils.dart'; +import '../chatvibe_ui/components/at_tts.dart'; + +class ImagePick { + static final ImagePicker _picker = ImagePicker(); + + /// 选择图片和视频 + static Future?> pickPicAndVideoFromGallery( + BuildContext context, + ) async { + try { + // 调用 pickImage 方法,source 设置为 gallery 即可调用相册 + final XFile? pickedFile = await _picker.pickMedia( + // 以下为可选参数,用于控制输出图像 + maxWidth: 1800, // 限制图片最大宽度 + maxHeight: 1800, // 限制图片最大高度 + imageQuality: 85, // 图片压缩质量 (0-100) + ); + if (pickedFile != null) { + if (!ATPathUtils.fileTypeIsPicAndMp(pickedFile.path)) { + ATTts.show( "Please select atu_images in .jpg, .jpeg, .png format."); + return null; + } + return [File(pickedFile.path)]; + } + return null; + } catch (e) { + // 处理异常,例如权限被拒绝或选择器被取消 + print("图片选择出错: $e"); + // 可以在这里添加用户友好的提示信息 + return null; + } + } + + /// 从相册选择多张图片 + static Future?> pickMultipleFromGallery( + BuildContext context, + ) async { + try { + final List pickedFiles = await _picker.pickMultiImage( + maxWidth: 1800, + maxHeight: 1800, + imageQuality: 85, + limit: 9, + ); + if (pickedFiles.isNotEmpty) { + return pickedFiles + .where((file) { + return file.mimeType?.startsWith('image/') == true; + }) + .map((xFile) => File(xFile.path)) + .toList(); + } + return null; + } catch (e) { + print("多图选择出错: $e"); + return null; + } + } + + /// 从相册选择单张图片 + static Future pickSingleFromGallery(BuildContext context) async { + try { + final XFile? pickedFile = await _picker.pickImage( + source: ImageSource.gallery, + maxWidth: 1800, + maxHeight: 1800, + imageQuality: 85, + ); + if (pickedFile != null) { + if (!ATPathUtils.fileTypeIsPic(pickedFile.path)) { + ATTts.show( "Please select atu_images in .jpg, .jpeg, .png format."); + return null; + } + return File(pickedFile.path); + } + return null; + } catch (e) { + print("图片选择出错: $e"); + return null; + } + } + + /// 使用相机拍摄照片 + static Future pickFromCamera(BuildContext context) async { + try { + final XFile? pickedFile = await _picker.pickImage( + source: ImageSource.camera, + maxWidth: 1800, + maxHeight: 1800, + imageQuality: 85, + ); + if (pickedFile != null) { + return File(pickedFile.path); + } + return null; + } catch (e) { + print("相机拍照出错: $e"); + return null; + } + } +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..dab7fa8 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,388 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:ui'; + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +import 'package:fluro/fluro.dart' as fluro; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:aslan/chatvibe_core/utilities/at_keybord_util.dart'; +import 'package:aslan/chatvibe_data/sources/local/at_foreground_service_helper.dart'; +import 'package:aslan/chatvibe_data/sources/local/data_persistence.dart'; +import 'package:provider/provider.dart'; +import 'package:provider/single_child_widget.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; + +import 'app_localizations.dart'; +import 'chatvibe_core/config/app_config.dart'; +import 'chatvibe_core/constants/at_global_config.dart'; +import 'chatvibe_core/constants/at_screen.dart'; +import 'chatvibe_core/routes/at_routes.dart'; +import 'chatvibe_core/routes/at_lk_application.dart'; +import 'chatvibe_core/utilities/at_deep_link_handler.dart'; +import 'chatvibe_core/utilities/at_deviceId_utils.dart'; +import 'chatvibe_features/splash/splash_page.dart'; +import 'chatvibe_managers/app_general_manager.dart'; +import 'chatvibe_managers/apple_payment_manager.dart'; +import 'chatvibe_managers/audio_manager.dart'; +import 'chatvibe_managers/authentication_manager.dart'; +import 'chatvibe_managers/dynamic_content_manager.dart'; +import 'chatvibe_managers/gift_animation_manager.dart'; +import 'chatvibe_managers/gift_system_manager.dart'; +import 'chatvibe_managers/google_payment_manager.dart'; +import 'chatvibe_managers/localization_manager.dart'; +import 'chatvibe_managers/room_manager.dart'; +import 'chatvibe_managers/rtc_manager.dart'; +import 'chatvibe_managers/rtm_manager.dart'; +import 'chatvibe_managers/shop_manager.dart'; +import 'chatvibe_managers/theme_manager.dart'; +import 'chatvibe_managers/user_profile_manager.dart'; +import 'chatvibe_ui/theme/chatvibe_theme.dart'; + + +void main() async { + + // 初始化应用配置 + AppConfig.initialize(); + + + // 2. 使用 runZonedGuarded 创建一个隔离区域来捕获所有异步错误 + runZonedGuarded( + () async { + // 1. 确保 Widget 绑定初始化 + WidgetsFlutterBinding.ensureInitialized(); + // 性能优化:启用手势重采样 + GestureBinding.instance.resamplingEnabled = true; + // 阶段1:初始化绝对必要的服务(不阻塞UI) + await _initializeEssentialServices(); + // 使用重试机制初始化存储 + await _initializeStorageWithRetry(); + // 立即运行应用,不等待非必要服务初始化 + runApp(const MyAppWithProviders()); + + // 阶段2:在应用启动后初始化非必要服务 + WidgetsBinding.instance.addPostFrameCallback((_) { + _initializeNonEssentialServices(); + }); + }, + // 9. runZonedGuarded 的错误回调(这是捕获异步错误的主要方式) + (error, stackTrace) { + // 这是捕获 runZonedGuarded 区域内所有未捕获异步错误的地方 + FirebaseCrashlytics.instance.recordError(error, stackTrace, fatal: true); + debugPrint('Zoned Error: $error\nStack: $stackTrace'); + }, + ); +} + +/// 初始化绝对必要的服务 +Future _initializeEssentialServices() async { + // 设置屏幕方向 + await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); + + // 设置状态栏样式 + if (Platform.isAndroid) { + SystemChrome.setSystemUIOverlayStyle( + const SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + statusBarIconBrightness: Brightness.dark, + ), + ); + } + + // 初始化键盘监听 + ATKeybordUtil.initOperation(); + + // 初始化 Firebase(必需) + try { + await Firebase.initializeApp(); + debugPrint('Firebase initialized successfully'); + } catch (e, stackTrace) { + debugPrint('Firebase initialization failed: $e\n$stackTrace'); + // 即使 Firebase 初始化失败,也继续运行应用 + // 记录错误到控制台,但不崩溃 + } + + // 设置错误处理 + FlutterError.onError = (details) { + FirebaseCrashlytics.instance.recordFlutterFatalError(details); + FlutterError.presentError(details); + }; + + // 设置顶级的异步错误处理器 + PlatformDispatcher.instance.onError = (error, stack) { + FirebaseCrashlytics.instance.recordError(error, stack, fatal: true); + return true; + }; + + // 启用 Crashlytics + await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true); +} + +/// 初始化非必要服务(延迟执行) +Future _initializeNonEssentialServices() async { + try { + // 初始化其他非必要服务 + ATForegroundServiceHelper().initialize(); + + debugPrint('Non-essential services initialized successfully'); + } catch (e) { + debugPrint('Non-essential services initialization failed: $e'); + // 不阻塞应用,记录错误即可 + } +} + +/// 带重试机制的存储初始化 +Future _initializeStorageWithRetry() async { + const maxRetries = 3; + + for (int i = 0; i < maxRetries; i++) { + try { + await DataPersistence.initialize(); + return; // 成功则退出 + } catch (e) { + debugPrint('DataPersistence initialization attempt ${i + 1} failed: $e'); + + if (i == maxRetries - 1) { + // 最后一次尝试也失败,记录错误但不崩溃 + debugPrint('DataPersistence initialization failed after $maxRetries attempts: $e'); + return; // 不重新抛出异常,继续运行 + } + + // 等待一段时间后重试 + await Future.delayed(Duration(milliseconds: 500 * (i + 1))); + } + } +} + +/// 带有所有Provider的根组件 +class MyAppWithProviders extends StatelessWidget { + const MyAppWithProviders({super.key}); + + @override + Widget build(BuildContext context) { + return MultiProvider(providers: _buildProviders(), child: const MyApp()); + } + + /// 创建Provider列表 - 优化为懒加载 + List _buildProviders() { + return [ + // 认证相关Provider - 保持非懒加载 + ChangeNotifierProvider( + lazy: false, + create: (context) => ChatVibeAuthenticationManager(), + ), + ChangeNotifierProvider( + lazy: false, + create: (context) => ChatVibeUserProfileManager(), + ), + + // 其他Provider改为懒加载,使用时才初始化 + ChangeNotifierProvider( + lazy: true, + create: (context) => AppGeneralManager(), + ), + ChangeNotifierProvider( + lazy: true, + create: (context) => ChatVibeRoomManager(), + ), + ChangeNotifierProvider( + lazy: true, + create: (context) => RealTimeCommunicationManager(), + ), + ChangeNotifierProvider( + lazy: true, + create: (context) => RealTimeMessagingManager(), + ), + ChangeNotifierProvider( + lazy: true, + create: (context) => ChatVibeGiftSystemManager(), + ), + ChangeNotifierProvider( + lazy: true, + create: (context) => GiftAnimationManager(), + ), + ChangeNotifierProvider( + lazy: true, + create: (context) => LocalizationManager(), + ), + ChangeNotifierProvider( + lazy: true, + create: (context) => ThemeManager(), + ), + ChangeNotifierProvider( + lazy: true, + create: (context) => GooglePaymentManager(), + ), + ChangeNotifierProvider( + lazy: true, + create: (context) => ApplePaymentManager(), + ), + ChangeNotifierProvider( + lazy: true, + create: (context) => ShopManager(), + ), + ChangeNotifierProvider( + lazy: true, + create: (context) => AudioManager(), + ), + ChangeNotifierProvider( + lazy: true, + create: (context) => ChatVibeDynamicContentManager(), + ), + ]; + } +} + +final GlobalKey navigatorKey = GlobalKey(); +final RouteObserver routeObserver = RouteObserver(); + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + late fluro.FluroRouter _router; + final ATDeepLinkHandler _deepLinkHandler = ATDeepLinkHandler(); + + @override + void initState() { + super.initState(); + // 延迟初始化路由 + _initializeRouter(); + _initDeepLinking(); + // 延迟初始化设备信息 + WidgetsBinding.instance.addPostFrameCallback((_) { + ATDeviceIdUtils.initDeviceinfo(); + }); + } + + @override + dispose() { + _deepLinkHandler.dispose(); + super.dispose(); + } + + Future _initDeepLinking() async { + // 初始化,并传递一个回调函数用于处理链接 + await _deepLinkHandler.initDeepLinks( + onLinkReceived: (Uri uri) { + // 当收到链接时,无论应用在哪个页面,都可以进行路由 + _handleDeepLink(uri); + }, + ); + } + + void _handleDeepLink(Uri uri) { + // 这是处理链接的核心路由逻辑 + print('App 根层收到链接: $uri'); + String path = uri.path; + String id = uri.queryParameters['id'] ?? ''; + } + + void _initializeRouter() { + _router = fluro.FluroRouter(); + ATRoutes.configureRoutes(_router); + ATLkApplication.router = _router; + } + + @override + Widget build(BuildContext context) { + return _buildAppContent(); + } + + Widget _buildAppContent() { + return Consumer( + builder: (context, localeProvider, child) { + // 只有在LocaleProvider可用时才设置语言 + ATGlobalConfig.lang = localeProvider.locale?.languageCode ?? "en"; + return Consumer( + builder: (context, themeManager, child) { + return ScreenUtilInit( + designSize: Size(ATScreen.designWidth, ATScreen.designHeight), + splitScreenMode: false, + minTextAdapt: true, + builder: (context, child) { + return AnnotatedRegion( + value: SystemUiOverlayStyle.dark, + child: RefreshConfiguration( + headerBuilder: () => MaterialClassicHeader(color: ChatVibeTheme.primaryColor), + footerBuilder: + () => CustomFooter( + builder: (BuildContext context, LoadStatus? mode) { + Widget body; + if (mode == LoadStatus.idle) { + body = Text( + ATAppLocalizations.of(context)!.pullToLoadMore, + ); + } else if (mode == LoadStatus.loading) { + body = CupertinoActivityIndicator(); + } else if (mode == LoadStatus.failed) { + body = Text( + ATAppLocalizations.of( + context, + )!.loadingFailedClickToRetry, + ); + } else if (mode == LoadStatus.canLoading) { + body = Text( + ATAppLocalizations.of(context)!.releaseToLoadMore, + ); + } else { + body = Text( + "", + style: TextStyle( + fontSize: 12.sp, + color: Color(0xff999999), + ), + ); + } + return Container( + height: + kTextTabBarHeight + ScreenUtil().statusBarHeight, + child: Center(child: body), + ); + }, + ), + child: MaterialApp( + title: 'Aslan', + locale: Provider.of(context).locale, + localizationsDelegates: [ + ATAppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + supportedLocales: [ + const Locale('en', ''), + const Locale('zh', ''), + const Locale('ar', ''), + const Locale('tr', ''), + const Locale('bn', ''), + ], + navigatorKey: navigatorKey, + debugShowCheckedModeBanner: false, + onGenerateRoute: ATLkApplication.router.generator, + theme: themeManager.currentTheme, + home: SplashPage(), + builder: FlutterSmartDialog.init(), + navigatorObservers: [routeObserver], + ), + ), + ); + }, + ); + }, + ); + }, + ); + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/CHANGELOG.md b/local_packages/flutter_foreground_task-9.1.0/CHANGELOG.md new file mode 100644 index 0000000..8d8c4aa --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/CHANGELOG.md @@ -0,0 +1,540 @@ +## 9.1.0 + +* [**FEAT**] Support manual foregroundServiceType via serviceTypes in startService + +## 9.0.0 + +* [**CHORE**] Bump minimum supported SDK version to `Flutter 3.22/Dart 3.4` +* [**CHORE**] Bump `kotlin_version(1.7.10 -> 1.9.10)`, `gradle(7.3.0 -> 8.6.0)` for Android 15 +* [**FEAT**] Add `isTimeout` param to the onDestroy callback +* [**FIX**] Fix "null object" error [#332](https://github.com/Dev-hwang/flutter_foreground_task/issues/332) +* [**FIX**] Fix "Reply already submitted" error [#330](https://github.com/Dev-hwang/flutter_foreground_task/issues/330) +* [**FIX**] Prevent crash by catching exceptions during foreground service start +* Check [migration_documentation](./documentation/migration_documentation.md) for changes + +## 8.17.0 + +* [**FEAT**] Allow `onNotificationPressed` to trigger without `SYSTEM_ALERT_WINDOW` permission + - Do not use the `launchApp` function inside the `onNotificationPressed` callback + - Instead, use the `notificationInitialRoute` option of the `startService` or `updateService` functions + +## 8.16.0 + +* [**BREAKING**] Change `ServiceRequestResult` class to `sealed class` for improved code readability +* [**BREAKING**] Change method for customizing notification icon +* [**FEAT**] Add `copyWith` function for models reuse +* Check [migration_documentation](./documentation/migration_documentation.md) for changes + +## 8.14.0 + +* [**FEAT**] Support quickboot for HTC devices + +## 8.13.0 + +* [**CHORE**] Downgrade iOS minimumVersion from `13.0` to `12.0` + +## 8.12.0 + +* [**FEAT**] Add `setOnlyAlertOnce` option to AndroidNotificationOptions [pr-#287](https://github.com/Dev-hwang/flutter_foreground_task/pull/287) +* [**CHANGE**] Change notification channel importance from `DEFAULT` to `LOW` + +## 8.11.0 + +* [**FEAT**] Allow sending `Set` collection using `FlutterForegroundTask.sendDataToMain` + +## 8.10.4 + +* [**FIX**] Fixed an issue where main function was called repeatedly when there was no callback to start +* [**FIX**] Handle exceptions that occur when service component cannot be found + +## 8.10.2 + +* [**FIX**] Fixed an issue where exception was thrown when flutterEngine failed to start [#282](https://github.com/Dev-hwang/flutter_foreground_task/issues/282) +* [**FIX**] Fixed an issue with flutterEngine not being destroyed properly + +## 8.10.0 + +* [**BREAKING**] Change `onStart`, `onDestroy` callback return type from `void` to `Future` + - The onRepeatEvent callback is called when the onStart asynchronous operation has finished + - Now you can access network, database, and other plugins in onDestroy callback [#276](https://github.com/Dev-hwang/flutter_foreground_task/issues/276) + - Check [migration_documentation](./documentation/migration_documentation.md) for changes + +## 8.9.0 + +* [**CHANGE**] Ignore `autoRunOnBoot` option when service is stopped by developer +* [**CHANGE**] Ignore `autoRunOnBoot` option when android:stopWithTask is set to true +* [**FEAT**] Add TaskStarter to check who started the task [#276](https://github.com/Dev-hwang/flutter_foreground_task/issues/276) + - Add `starter` parameter to `onStart` callback of TaskHandler + - `.developer`: The task has been started by the developer (startService, restartService, updateService) + - `.system`: The task has been started by the system (reboot, app-updates, AlarmManager-restart) +* [**FEAT-iOS**] Allow background app refresh + - ~~Bump iOS minimumVersion to 13.0~~ + - You need to add `BGTaskSchedulerPermittedIdentifiers` key in `ios/Runner/info.plist` file + - Check [Getting started-iOS](https://pub.dev/packages/flutter_foreground_task#baby_chick-ios) for more details + +## 8.8.1+1 + +* [**DOCS**] Update example to see two-way communication flow + +## 8.8.1 + +* [**FIX**] Fix issue related to `openIgnoreBatteryOptimizationSettings` [#275](https://github.com/Dev-hwang/flutter_foreground_task/issues/275) + +## 8.8.0 + +* [**REMOVE**] Remove notification permission request code in startService + - Notification permission request is required before starting the service + - Check step-4 of [readme](https://github.com/Dev-hwang/flutter_foreground_task?tab=readme-ov-file#hatched_chick-step-by-step) +* [**FIX-Android**] Fix issue with calling onRepeatEvent without waiting for repeat delay after onStart + - If use the `eventAction: ForegroundTaskEventAction.repeat(millis)` + - flow: `onStart` - (millis) - `onRepeatEvent` - (millis) - `onRepeatEvent`.. + +## 8.7.0 + +* [**FEAT**] Allow permission settings page to be opened even if permission is granted + - openIgnoreBatteryOptimizationSettings() + - openSystemAlertWindowSettings() + - openAlarmsAndRemindersSettings() + +## 8.6.0 + +* [**BREAKING**] Change the way to set the task intervals (for increase scalability) + - Remove `interval`, `isOnceEvent` option in ForegroundTaskOptions model + - Add `eventAction` option with ForegroundTaskEventAction constructor + - Check [migration_documentation](./documentation/migration_documentation.md) for changes + +## 8.5.0 + +* [**FEAT**] Add `openAlarmsAndRemindersSettings` utility + - This utility allows the Android OS to immediately restart service in doze mode. + - required `android.permission.SCHEDULE_EXACT_ALARM` permission. + - When you call this function, will be gone to the settings page. So you need to explain to the user why set it. + - Check [utility_documentation](./documentation/utility_documentation.md) for more details. +* [**FEAT**] Add `canScheduleExactAlarms` utility + - Returns whether the `android.permission.SCHEDULE_EXACT_ALARM` permission is granted. + - In some cases, permission is granted automatically. Don't worry :) + +## 8.3.1 + +* [**FIX**] Fixed an issue where Map collection could not be cast in onReceiveData [#258](https://github.com/Dev-hwang/flutter_foreground_task/issues/258) + +## 8.3.0 + +* [**FEAT**] Add showBadge option to AndroidNotificationOptions. +* [**DEPRECATED**] Deprecated AndroidNotificationOptions.id. Use startService(serviceId) instead. +* [**DOCS**] Separate models, utility, migration documentation. + +## 8.2.0 + +* [**CHORE-AOS**] Down Android minSdkVersion to 21 + +## 8.1.0 + +* [**FIX-AOS**] Fixed an issue where notification text was not visible on Android 7.1 and below +* [**FEAT-iOS**] Implement onNotificationButtonPressed +* [**FEAT-iOS**] Implement onNotificationPressed +* [**FEAT-iOS**] Implement onNotificationDismissed +* [**FEAT-iOS**] Implement notification permission request and check function + - Request notification permission in the `Example._requestPermissions` function + +## 8.0.0 + +* [**BREAKING**] Redesign the communication method between TaskHandler and UI + - Fixed an issue where `SendPort.send` does not work when calling `receivePort` getter function after app restart [#244](https://github.com/Dev-hwang/flutter_foreground_task/issues/244) + - Allow task data to be listened on multiple pages + - Check [migration_documentation](./documentation/migration_documentation.md) for changes + +## 7.5.2 + +* [**FIX**] Fixed an issue that caused poor performance and delays in TaskHandler +* [**FIX-iOS**] Fixed an issue where onDestroy was not called when an app was closed in recent apps + +## 7.5.0+1 + +* [**DOCS**] Update readme + - Update documentation on how to pass timestamp to UI isolate + - Update `WithForegroundTask` documentation comments + +## 7.5.0 + +* [**FEAT**] Support notificationText with multiple lines [#182](https://github.com/Dev-hwang/flutter_foreground_task/issues/182) +* [**REMOVE**] Remove `WillStartForegroundTask` widget + - This widget is designed to start a service when the app is minimized. + - This widget had an ambiguous purpose, it caused a lot of problems because the widget controlled the service. + - These issues have led me to remove this widget. Please implement it yourself if necessary. + +## 7.4.3 + +* [**FEAT-Recommended**] Improve restart alarm that occurs when the service terminates abnormally + +## 7.4.2 + +* [**FIX**] Fixed an issue where notification events were handled on both when using plugin in multiple apps [#137](https://github.com/Dev-hwang/flutter_foreground_task/issues/137) +* [**FIX**] Fixed an issue where the service did not restart when Android OS forcibly terminated the service [#223](https://github.com/Dev-hwang/flutter_foreground_task/issues/223) + +## 7.4.0 + +* [**FEAT**] Add result class to handle service request errors + - The return value of service request functions changed from boolean to `ServiceRequestResult` +* [**FEAT**] Add function to send data to TaskHandler + - You can send data from UI to TaskHandler using `FlutterForegroundTask.sendData` function + +## 7.2.0 + +* [**FEAT**] Add ability to restart service when app is deleted from recent app list + - To restart service on Android 12+, you must allow the `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` permission +* [**REMOVE**] Remove `isSticky` from AndroidNotificationOptions + - The isSticky option is automatically set by the service based on the Manifest android:stopWithTask + +## 7.1.0 + +* [**FEAT-Recommended**] Allow updateService to be processed without service restart + +## 7.0.0 + +* [**CHORE**] Updates minimum supported SDK version to `Flutter 3.10` / `Dart 3.0` +* [**BREAKING**] Change timezone(local > UTC) of timestamp in TaskHandler callback +* [**BREAKING**] Remove `iconData`, `buttons` from AndroidNotificationOptions + - Can use this options in the startService function + - Check [migration_documentation](./documentation/migration_documentation.md) for changes +* [**FEAT**] Add ability to update notification icon and buttons for Android platform +* [**FEAT**] Add `onNotificationDismissed` callback for Android 14 + +## 6.5.0 + +* [**CHORE**] Bump Android minSdkVersion to 23 +* [**FIX**] Fixed an issue where notification actions did not work on Android 14 + +## 6.4.0 + +* [**REMOVE**] Remove `foregroundServiceType` option +* [**FEAT**] Add `autoRunOnMyPackageReplaced` option + +## 6.3.0 + +* [**FEAT**] ~~Add option to allow multiple foregroundServiceType~~ +* [**DOCS**] Update readme + +## 6.2.0 + +* [**FEAT**] Support AGP 8 +* [**FEAT**] ~~Add `foregroundServiceType` option to specify foreground service type on Android 14 and higher~~ + +## 6.1.3 + +* [**DOCS**] Update readme [#192](https://github.com/Dev-hwang/flutter_foreground_task/issues/192) +* [**CHORE**] Remove platform dependency +* [**CHORE**] Update dependencies + +## 6.1.2 + +* [**FIX**] Fix issue where SecurityException occurred when registering runtime receiver [#175](https://github.com/Dev-hwang/flutter_foreground_task/issues/175) + +## 6.1.1 + +* [**TEST**] Add assertions to service options + +## 6.1.0 + +* [**BREAKING**] Remove future return of TaskHandler callback function +* [**FIX**] Fix issue where isRunningService is not updated after calling onDestroy +* [**FIX**] Fix storage data not syncing between isolates +* [**FIX**] Fix the service could not be started when the notification channel information is empty +* [**CHORE**] Upgrade dependencies - shared_preferences + +## 6.0.0+1 + +* [**REFACTOR**] Move required permissions on the Android platform inside the plugin + +## 6.0.0 + +* [**BREAKING**] Rename the callback function to clarify what information the event provides + - Rename `onButtonPressed` to `onNotificationButtonPressed` + - Rename `onEvent` to `onRepeatEvent` +* [**FEAT**] Add textColor option to NotificationButton +* [**FEAT**] Add ability to change task options while service is running + +## 5.2.1 + +* [**FIX**] Fix issue where service could not be started in the background + +## 5.2.0 + +* [**FEAT**] Add id option to AndroidNotificationOptions +* [**FEAT**] The WillStartForegroundTask widget supports receiving data + +## 5.0.0 + +* [**CHORE**] Update dependency constraints to `sdk: '>=2.18.0 <4.0.0'` `flutter: '>=3.3.0'` +* [**FEAT**] Add notification permission request func for Android 13 + - `FlutterForegroundTask.checkNotificationPermission()` + - `FlutterForegroundTask.requestNotificationPermission()` +* [**DOCS**] Update documentation to the latest version +* [**FIX**] Fix service not starting when notification permission is denied + +## 4.2.0 + +* [**FEAT**] Add notification permission request func for Android 13 + - According to the [official documentation](https://developer.android.com/develop/ui/views/notifications/notification-permission), starting with Android 13 and higher, you need to request notification permission to expose foreground service notifications. + - In this version, notification permission requests occur when the `startService` function is called. + - Add `` permission to your `AndroidManifest.xml` file. + +## 4.1.0 + +* [**CHORE**] Bump Android coroutines version to 1.6.4 +* [**CHANGE**] Change the way get receivePort from asynchronous to synchronous [#128](https://github.com/Dev-hwang/flutter_foreground_task/issues/128) + - Can register and get receivePort without starting the service. + - From now on, register receivePort before starting the service. Please check the readme and example. +* [**FIX**] Fix issue where the results of the service start and stop functions did not match the service status + +## 4.0.1 + +* [**FIX**] Fix mounted error [#133](https://github.com/Dev-hwang/flutter_foreground_task/issues/133) + +## 4.0.0 + +* [**CHORE**] Bump Android Gradle version to 7.1.2 +* [**CHORE**] Update minimum Flutter version to 3.0.0 [#130](https://github.com/Dev-hwang/flutter_foreground_task/issues/130) [#131](https://github.com/Dev-hwang/flutter_foreground_task/issues/131) +* [**DOCS**] Update readme [#125](https://github.com/Dev-hwang/flutter_foreground_task/issues/125) + +## 3.10.0 + +* [**FEAT**] Add `isOnceEvent` option to `ForegroundTaskOptions.class`. +* [**DOCS**] Add `entry-point` pragma. +* [**REFACTOR**] Refactor code using plugin_platform_interface. + +## 3.9.0 + +* [**FEAT**] Add `allowWakeLock` option to `ForegroundTaskOptions.class`. +* [**FEAT**] Add `forceOpen` option to `openSystemAlertWindowSettings()`. + +## 3.8.2 + +* Fix issue with SharedPreferences won't provide updated data from main isolate while running on background. Thanks @h2210316651 + +## 3.8.1 + +* Fix issue where sendPort returned null when restartService called. + +## 3.8.0 + +* Upgrade Coroutine library. +* Upgrade shared_preferences plugin. +* Separate the SendPort registration code from the foreground service related functions. + - Register a SendPort object only when the user needs a ReceivePort. + - Please see [this page](https://github.com/Dev-hwang/flutter_foreground_task/blob/1cfc23160eb352fbfa74f7dfbe34ff714a83fffc/example/lib/main.dart#L147) for a modified example. +* [**FEAT**] Add `isAppOnForeground` function. + - Returns whether the app is in the foreground. + - This can be used when you want to perform some function when the app is in the foreground. +* [**FEAT**] Add `setOnLockScreenVisibility` function. Thanks @Techno-Disaster + - Toggles lockScreen visibility. + - If set to true, launchApp can be run from the lockscreen. + +## 3.7.3 + +* [[#61](https://github.com/Dev-hwang/flutter_foreground_task/issues/61)] Add code to prevent ForegroundServiceStartNotAllowedException. +* [[#78](https://github.com/Dev-hwang/flutter_foreground_task/issues/78)] Fix compilation errors for Flutter 3.0.0 + +## 3.7.2 + +* [[#62](https://github.com/Dev-hwang/flutter_foreground_task/issues/62)] Fix issues with SharedPreferences. + +## 3.7.1 + +* Add SYSTEM_ALERT_WINDOW permission request function. +* Provide a way to use notification press handler on Android 10+. + - https://developer.android.com/guide/components/activities/background-starts + - You can use the SYSTEM_ALERT_WINDOW permission to work around the above Restrictions. + +## 3.7.0 + +* Add notification press handler for Android platform. +* Add sendPort parameter to onDestroy function of TaskHandler. +* Add receivePort getter function. +* Clarify the meaning of the dev message. +* Example updates. + +## 3.6.0 + +* Upgrade Flutter SDK minimum version to 2.8.1 +* Upgrade shared_preferences: ^2.0.13 +* Add `backgroundColor` option for AndroidNotificationOptions. +* Add `getAllData` function. +* Fixed the problem that data not related to the service is deleted when clearAllData() is called. +* Fixed the problem that the notification button did not work when using a specific button id. + +## 3.5.5 + +* Downgrade Android minSdkVersion to 21. + +## 3.5.4 + +* [[#42](https://github.com/Dev-hwang/flutter_foreground_task/issues/42)] Only minimize app on pop when there is no route to pop. + +## 3.5.3 + +* Upgrade shared_preferences: ^2.0.11 + +## 3.5.2 + +* [**iOS**] Fixed an issue where notifications not related to the service were removed. +* [**iOS**] Improved compatibility with other plugins that use notifications. + - Additional settings are required, so please check the Readme-Getting started. + +## 3.5.1 + +* Add process exit code to prevent memory leak. +* Fix dart formatting issues. + +## 3.5.0 + +* Upgrade shared_preferences: ^2.0.9 +* Can now add action buttons to Android notification. + +## 3.4.1 + +* [**Bug**] Fixed an issue where lockMode(wakeLock, wifiLock) was not properly released when the service was forcibly shutdown. +* [**Bug**] Fixed an issue where foreground service notification UX was delayed on Android version 12. + +## 3.4.0 + +* Add wakeLock to keep the CPU active in the background. + - Need to add `android.permission.WAKE_LOCK` permission to `AndroidManifest.xml` file. +* Add wifiLock to keep the Wi-Fi radio awake in the background. + - Enable or disable can be set with `allowWifiLock` of `ForegroundTaskOptions` class. + +## 3.3.0 + +* Add `requestIgnoreBatteryOptimization` function. +* Change onWillStart type from `ValueGetter` to `AsyncValueGetter`. + +## 3.2.3 + +* [**Bug**] Fixed an issue where IllegalArgumentException was thrown when starting the service on Android version 11 and higher. +* Bump Android minSdkVersion to 23. +* Bump Android compileSdkVersion to 31. + +## 3.2.2 + +* [**Bug**] Fixed an issue where RemoteServiceException occurred intermittently. + +## 3.2.1 + +* [**iOS**] Fixed an issue where all data stored in `UserDefaults.standard` was removed when the stopService function was called. + +## 3.2.0 + +* Add `restartService` function. You can now restart the service to get the new `ReceivePort`. +* Improve service-related function code. A return value has been added to check if the function was properly requested. + +## 3.1.0 + +* Upgrade shared_preferences: ^2.0.8 +* Add `required keyword` to parameters of saveData func. +* Add `isSticky` notification option for Android. + +## 3.0.0 + +* [**BREAKING**] The way you start the foreground service and register tasks has changed. Check [readme](https://pub.dev/packages/flutter_foreground_task#how-to-use) for more information. +* [**BREAKING**] Change function name from `start` to `startService`. +* [**BREAKING**] Change function name from `update` to `updateService`. +* [**BREAKING**] Change function name from `stop` to `stopService`. +* [**BREAKING**] Change function name from `isRunningTask` to `isRunningService`. +* Added functions for data management. +* Fixed an issue where notifications were not removed when the service was stopped. +* Fixed incorrect documentation. + +## 2.2.1 + +* Fixed playSound option not working properly in the background. + +## 2.2.0 + +* Implement background task on iOS platform. Please check Readme as setup is required. +* Implement a service restart function to deal with unexpected errors. +* Remove `notification_options.dart`. +* Add `android_notification_options.dart`. +* Add `ios_notification_options.dart`. +* Change the `playSound` default value from `true` to `false`. + +## 2.1.0 + +* Fixed duplicate call to startForegroundTask function. +* Optimize android native code. +* Add `sendPort` parameter to TaskCallback. + +## 2.0.5+1 + +* Update README.md + +## 2.0.5 + +* Fix `callbackHandle` type casting error. + +## 2.0.4 + +* Add utility methods related to battery optimization. +* Add `showWhen` option. +* Add `visibility` option. +* Migrate example to null safety. + +## 2.0.3 + +* Add `autoRunOnBoot` field to `ForegroundTaskOptions`. Check the readme for more details. + +## 2.0.2 + +* Add `onDestroy` to clean up used resources in callback functions. + +## 2.0.1 + +* Change the notification icon setting method. +* Improved the code so that the notification icon is displayed properly even when using the resource shrinker. + +## 2.0.0 + +* [**BREAKING**] Remove singleton `instance` of `FlutterForegroundTask`. +* [**BREAKING**] `TaskCallback` return type changed from `void` to `Future`. +* [**BREAKING**] All functions of `FlutterForegroundTask` are applied as static. +* [**BREAKING**] The way foreground task are registered has changed. See the readme for details. +* Add `printDevLog` option. +* Update README.md +* Update Example. + +## 1.0.9 + +* Add `icon` field to `NotificationOptions`. +* Change the model's `toMap` function name to `toJson`. + +## 1.0.8 + +* Add `FlutterForegroundTask.instance.update()` function. +* Update README.md + +## 1.0.7 + +* Fix incorrect comments and documents. +* Add `enableVibration` notification options. +* Add `playSound` notification options. + +## 1.0.5 + +* Fix an issue where `RemoteServiceException` occurs. + +## 1.0.4 + +* Add `WillStartForegroundTask` widget. + +## 1.0.3 + +* Fix incorrect comments and documents. +* Add `channelImportance` notification options. +* Add `priority` notification options. + +## 1.0.1 + +* Add `WithForegroundTask` widget. + +## 1.0.0 + +* Initial release. diff --git a/local_packages/flutter_foreground_task-9.1.0/LICENSE b/local_packages/flutter_foreground_task-9.1.0/LICENSE new file mode 100644 index 0000000..0bc6bc8 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Dev-hwang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/local_packages/flutter_foreground_task-9.1.0/README.md b/local_packages/flutter_foreground_task-9.1.0/README.md new file mode 100644 index 0000000..90c89a7 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/README.md @@ -0,0 +1,616 @@ +This plugin is used to implement a foreground service on the Android platform. + +

+pub +build +MIT license +

+ +## Features + +* Can perform repetitive tasks with the foreground service. +* Supports two-way communication between the foreground service and UI(main isolate). +* Provides a widget that minimize the app without closing it when the user presses the soft back button. +* Provides useful utilities that can use while performing tasks. +* Provides an option to automatically resume the foreground service on boot. + +## Support version + +- Flutter: `3.22.0+` +- Dart: `3.4.0+` +- Android: `5.0+ (minSdkVersion: 21)` +- iOS: `12.0+` + +## Getting started + +To use this plugin, add `flutter_foreground_task` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/). For example: + +```yaml +dependencies: + flutter_foreground_task: ^9.1.0 +``` + +After adding the plugin to your flutter project, we need to declare the platform-specific permissions ans service to use for this plugin to work properly. + +### :baby_chick: Android + +This plugin requires `Kotlin version 1.9.10+` and `Gradle version 8.6.0+`. Please refer to the migration documentation for more details. + +- [project/settings.gradle](https://github.com/Dev-hwang/flutter_foreground_task/blob/master/example/android/settings.gradle) +- [project/gradle-wrapper.properties](https://github.com/Dev-hwang/flutter_foreground_task/blob/master/example/android/gradle/wrapper/gradle-wrapper.properties) +- [app/build.gradle](https://github.com/Dev-hwang/flutter_foreground_task/blob/master/example/android/app/build.gradle) +- [migration_documentation](https://github.com/Dev-hwang/flutter_foreground_task/blob/master/documentation/migration_documentation.md) + +Open the `AndroidManifest.xml` file and declare the service tag inside the `` tag as follows. +If you want the foreground service to run only when the app is running, add `android:stopWithTask="true"`. +As mentioned in the Android guidelines, to start a FG service on Android 14+, you must declare `android:foregroundServiceType`. + +* [`camera`](https://developer.android.com/about/versions/14/changes/fgs-types-required#camera) +* [`connectedDevice`](https://developer.android.com/about/versions/14/changes/fgs-types-required#connected-device) +* [`dataSync`](https://developer.android.com/about/versions/14/changes/fgs-types-required#data-sync) +* [`health`](https://developer.android.com/about/versions/14/changes/fgs-types-required#health) +* [`location`](https://developer.android.com/about/versions/14/changes/fgs-types-required#location) +* [`mediaPlayback`](https://developer.android.com/about/versions/14/changes/fgs-types-required#media) +* [`mediaProjection`](https://developer.android.com/about/versions/14/changes/fgs-types-required#media-projection) +* [`microphone`](https://developer.android.com/about/versions/14/changes/fgs-types-required#microphone) +* [`phoneCall`](https://developer.android.com/about/versions/14/changes/fgs-types-required#phone-call) +* [`remoteMessaging`](https://developer.android.com/about/versions/14/changes/fgs-types-required#remote-messaging) +* [`shortService`](https://developer.android.com/about/versions/14/changes/fgs-types-required#short-service) +* [`specialUse`](https://developer.android.com/about/versions/14/changes/fgs-types-required#special-use) +* [`systemExempted`](https://developer.android.com/about/versions/14/changes/fgs-types-required#system-exempted) + +``` + + + + + + + + + + + +``` + +> [!CAUTION] +> Check [runtime requirements](https://developer.android.com/about/versions/14/changes/fgs-types-required#system-runtime-checks) before starting the service. If this requirement is not met, the foreground service cannot be started. + +> [!CAUTION] +> Android 15 introduces a new timeout behavior to `dataSync` for apps targeting Android 15 (API level 35) or higher. +> The system permits an app's `dataSync` services to run for a total of 6 hours in a 24-hour period. +> However, if the user brings the app to the foreground, the timer resets and the app has 6 hours available. +> +> There are new restrictions on `BOOT_COMPLETED(autoRunOnBoot)` broadcast receivers launching foreground services. +> `BOOT_COMPLETED` receivers are not allowed to launch the following types of foreground services: +> - [dataSync](https://developer.android.com/develop/background-work/services/fg-service-types#data-sync) +> - [camera](https://developer.android.com/develop/background-work/services/fg-service-types#camera) +> - [mediaPlayback](https://developer.android.com/develop/background-work/services/fg-service-types#media) +> - [phoneCall](https://developer.android.com/develop/background-work/services/fg-service-types#phone-call) +> - [microphone](https://developer.android.com/about/versions/14/changes/fgs-types-required#microphone) +> +> You can find how to test this behavior and more details at this [link](https://developer.android.com/about/versions/15/behavior-changes-15#fgs-hardening). + +### :baby_chick: iOS + +You can also run `flutter_foreground_task` on the iOS platform. However, it has the following limitations. + +* If you force close an app in recent apps, the task will be destroyed immediately. +* The task cannot be started automatically on boot like Android OS. +* The task runs in the background for approximately 30 seconds every 15 minutes. This may take longer than 15 minutes due to iOS limitations. + +**Info.plist**: + +Add the key below to `ios/Runner/info.plist` file so that the task can run in the background. + +```text +BGTaskSchedulerPermittedIdentifiers + + com.pravera.flutter_foreground_task.refresh + +UIBackgroundModes + + fetch + +``` + +**Objective-C**: + +To use this plugin developed in Swift in a project using Objective-C, you need to add a bridge header. +If there is no `ios/Runner/Runner-Bridging-Header.h` file in your project, check this [page](https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/importing_objective-c_into_swift). + +Open the `ios/Runner/AppDelegate.swift` file and add the commented code. + +```objc +#import "AppDelegate.h" +#import "GeneratedPluginRegistrant.h" + +// this +#import + +// this +void registerPlugins(NSObject* registry) { + [GeneratedPluginRegistrant registerWithRegistry:registry]; +} + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + + // this + [FlutterForegroundTaskPlugin setPluginRegistrantCallback:registerPlugins]; + if (@available(iOS 10.0, *)) { + [UNUserNotificationCenter currentNotificationCenter].delegate = (id) self; + } + + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +@end +``` + +**Swift**: + +Declare the import statement below in the `ios/Runner/Runner-Bridging-Header.h` file. + +```objc +#import +``` + +Open the `ios/Runner/AppDelegate.swift` file and add the commented code. + +```swift +import UIKit +import Flutter + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + + // this + SwiftFlutterForegroundTaskPlugin.setPluginRegistrantCallback { registry in + GeneratedPluginRegistrant.register(with: registry) + } + if #available(iOS 10.0, *) { + UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate + } + + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} +``` + +## How to use + +### :hatched_chick: step by step + +1. Initialize port for communication between TaskHandler and UI. + +```dart +void main() { + // Initialize port for communication between TaskHandler and UI. + FlutterForegroundTask.initCommunicationPort(); + runApp(const ExampleApp()); +} +``` + +2. Write a `TaskHandler` and a `callback` to request starting a TaskHandler. + +```dart +// The callback function should always be a top-level or static function. +@pragma('vm:entry-point') +void startCallback() { + FlutterForegroundTask.setTaskHandler(MyTaskHandler()); +} + +class MyTaskHandler extends TaskHandler { + // Called when the task is started. + @override + Future onStart(DateTime timestamp, TaskStarter starter) async { + print('onStart(starter: ${starter.name})'); + } + + // Called based on the eventAction set in ForegroundTaskOptions. + @override + void onRepeatEvent(DateTime timestamp) { + // Send data to main isolate. + final Map data = { + "timestampMillis": timestamp.millisecondsSinceEpoch, + }; + FlutterForegroundTask.sendDataToMain(data); + } + + // Called when the task is destroyed. + @override + Future onDestroy(DateTime timestamp, bool isTimeout) async { + print('onDestroy(isTimeout: $isTimeout)'); + } + + // Called when data is sent using `FlutterForegroundTask.sendDataToTask`. + @override + void onReceiveData(Object data) { + print('onReceiveData: $data'); + } + + // Called when the notification button is pressed. + @override + void onNotificationButtonPressed(String id) { + print('onNotificationButtonPressed: $id'); + } + + // Called when the notification itself is pressed. + @override + void onNotificationPressed() { + print('onNotificationPressed'); + } + + // Called when the notification itself is dismissed. + @override + void onNotificationDismissed() { + print('onNotificationDismissed'); + } +} +``` + +3. Add a callback to receive data sent from the TaskHandler. If the screen or controller is disposed, be sure to call the `removeTaskDataCallback` function. + +```dart +void _onReceiveTaskData(Object data) { + if (data is Map) { + final dynamic timestampMillis = data["timestampMillis"]; + if (timestampMillis != null) { + final DateTime timestamp = + DateTime.fromMillisecondsSinceEpoch(timestampMillis, isUtc: true); + print('timestamp: ${timestamp.toString()}'); + } + } +} + +@override +void initState() { + super.initState(); + // Add a callback to receive data sent from the TaskHandler. + FlutterForegroundTask.addTaskDataCallback(_onReceiveTaskData); +} + +@override +void dispose() { + // Remove a callback to receive data sent from the TaskHandler. + FlutterForegroundTask.removeTaskDataCallback(_onReceiveTaskData); + super.dispose(); +} +``` + +4. Request permissions and initialize the service. + +```dart +Future _requestPermissions() async { + // Android 13+, you need to allow notification permission to display foreground service notification. + // + // iOS: If you need notification, ask for permission. + final NotificationPermission notificationPermission = + await FlutterForegroundTask.checkNotificationPermission(); + if (notificationPermission != NotificationPermission.granted) { + await FlutterForegroundTask.requestNotificationPermission(); + } + + if (Platform.isAndroid) { + // Android 12+, there are restrictions on starting a foreground service. + // + // To restart the service on device reboot or unexpected problem, you need to allow below permission. + if (!await FlutterForegroundTask.isIgnoringBatteryOptimizations) { + // This function requires `android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` permission. + await FlutterForegroundTask.requestIgnoreBatteryOptimization(); + } + + // Use this utility only if you provide services that require long-term survival, + // such as exact alarm service, healthcare service, or Bluetooth communication. + // + // This utility requires the "android.permission.SCHEDULE_EXACT_ALARM" permission. + // Using this permission may make app distribution difficult due to Google policy. + if (!await FlutterForegroundTask.canScheduleExactAlarms) { + // When you call this function, will be gone to the settings page. + // So you need to explain to the user why set it. + await FlutterForegroundTask.openAlarmsAndRemindersSettings(); + } + } +} + +void _initService() { + FlutterForegroundTask.init( + androidNotificationOptions: AndroidNotificationOptions( + channelId: 'foreground_service', + channelName: 'Foreground Service Notification', + channelDescription: + 'This notification appears when the foreground service is running.', + onlyAlertOnce: true, + ), + iosNotificationOptions: const IOSNotificationOptions( + showNotification: false, + playSound: false, + ), + foregroundTaskOptions: ForegroundTaskOptions( + eventAction: ForegroundTaskEventAction.repeat(5000), + autoRunOnBoot: true, + autoRunOnMyPackageReplaced: true, + allowWakeLock: true, + allowWifiLock: true, + ), + ); +} + +@override +void initState() { + super.initState(); + // Add a callback to receive data sent from the TaskHandler. + FlutterForegroundTask.addTaskDataCallback(_onReceiveTaskData); + + WidgetsBinding.instance.addPostFrameCallback((_) { + // Request permissions and initialize the service. + _requestPermissions(); + _initService(); + }); +} +``` + +5. Use `FlutterForegroundTask.startService` to start the service. `startService` provides the following options: +* `serviceId`: The unique ID that identifies the service. +* `notificationTitle`: The title to display in the notification. +* `notificationText`: The text to display in the notification. +* `notificationIcon`: The icon to display in the notification. Go to [this page](./documentation/customize_notification_icon.md) to customize. +* `notificationButtons`: The buttons to display in the notification. (can add 0~3 buttons) +* `notificationInitialRoute`: Initial route to be used when the app is launched via a notification. Works the same as the `launchApp` utility. +* `callback`: A top-level function that calls the setTaskHandler function. + +```dart +Future _startService() async { + if (await FlutterForegroundTask.isRunningService) { + return FlutterForegroundTask.restartService(); + } else { + return FlutterForegroundTask.startService( + // You can manually specify the foregroundServiceType for the service + // to be started, as shown in the comment below. + // serviceTypes: [ + // ForegroundServiceTypes.dataSync, + // ForegroundServiceTypes.remoteMessaging, + // ], + serviceId: 256, + notificationTitle: 'Foreground Service is running', + notificationText: 'Tap to return to the app', + notificationIcon: null, + notificationButtons: [ + const NotificationButton(id: 'btn_hello', text: 'hello'), + ], + notificationInitialRoute: '/', + callback: startCallback, + ); + } +} +``` + +> [!NOTE] +> iOS Platform, `notificationButtons` is not displayed directly in notification. +> When the user slides down the notification, the button is displayed, so you need to guide the user on how to use it. +> https://developer.apple.com/documentation/usernotifications/declaring-your-actionable-notification-types + +6. Use `FlutterForegroundTask.updateService` to update the service. The options are the same as the start function. + +```dart +final ForegroundTaskOptions defaultTaskOptions = ForegroundTaskOptions( + eventAction: ForegroundTaskEventAction.repeat(5000), + autoRunOnBoot: true, + autoRunOnMyPackageReplaced: true, + allowWakeLock: true, + allowWifiLock: true, +); + +@pragma('vm:entry-point') +void startCallback() { + FlutterForegroundTask.setTaskHandler(FirstTaskHandler()); +} + +class FirstTaskHandler extends TaskHandler { + int _count = 0; + + @override + Future onStart(DateTime timestamp, TaskStarter starter) async { + // some code + } + + @override + void onRepeatEvent(DateTime timestamp) { + _count++; + + if (_count == 10) { + FlutterForegroundTask.updateService( + foregroundTaskOptions: defaultTaskOptions.copyWith( + eventAction: ForegroundTaskEventAction.repeat(1000), + ), + callback: updateCallback, + ); + return; + } + + FlutterForegroundTask.updateService( + notificationTitle: 'Hello FirstTaskHandler :)', + notificationText: timestamp.toString(), + ); + + // Send data to main isolate. + final Map data = { + "timestampMillis": timestamp.millisecondsSinceEpoch, + }; + FlutterForegroundTask.sendDataToMain(data); + } + + @override + Future onDestroy(DateTime timestamp, bool isTimeout) async { + // some code + } +} + +@pragma('vm:entry-point') +void updateCallback() { + FlutterForegroundTask.setTaskHandler(SecondTaskHandler()); +} + +class SecondTaskHandler extends TaskHandler { + @override + Future onStart(DateTime timestamp, TaskStarter starter) async { + // some code + } + + @override + void onRepeatEvent(DateTime timestamp) { + FlutterForegroundTask.updateService( + notificationTitle: 'Hello SecondTaskHandler :)', + notificationText: timestamp.toString(), + ); + + // Send data to main isolate. + final Map data = { + "timestampMillis": timestamp.millisecondsSinceEpoch, + }; + FlutterForegroundTask.sendDataToMain(data); + } + + @override + Future onDestroy(DateTime timestamp, bool isTimeout) async { + // some code + } +} +``` + +7. If you no longer use the service, call `FlutterForegroundTask.stopService`. + +```dart +Future _stopService() { + return FlutterForegroundTask.stopService(); +} +``` + +### :hatched_chick: deepening + +This plugin supports two-way communication between TaskHandler and UI(main isolate). + +The send function can only send primitive type(int, double, bool), String, Collection(Map, List) provided by Flutter. + +If you want to send a custom object, send it in String format using jsonEncode and jsonDecode. + +JSON and serialization >> https://docs.flutter.dev/data-and-backend/serialization/json + +```dart +// TaskHandler +@override +Future onStart(DateTime timestamp, TaskStarter starter) async { + // TaskHandler -> Main(UI) + FlutterForegroundTask.sendDataToMain(Object); +} + +// Main(UI) +void _onReceiveTaskData(Object data) { + print('onReceiveTaskData: $data'); +} +``` + +```dart +// Main(UI) +void _sendDataToTask() { + // Main(UI) -> TaskHandler + // + // The Map collection can only be sent in json format, such as Map. + FlutterForegroundTask.sendDataToTask(Object); +} + +// TaskHandler +@override +void onReceiveData(Object data) { + print('onReceiveData: $data'); + + // You can cast it to any type you want using the Collection.cast function. + if (data is List) { + final List intList = data.cast(); + } +} +``` + +And there are some functions for storing and managing data that are only used in this plugin. + +```dart +void function() async { + await FlutterForegroundTask.getData(key: String); + await FlutterForegroundTask.getAllData(); + await FlutterForegroundTask.saveData(key: String, value: Object); + await FlutterForegroundTask.removeData(key: String); + await FlutterForegroundTask.clearAllData(); +} +``` + +If the plugin you want to use provides a stream, use it like this: + +```dart +class MyTaskHandler extends TaskHandler { + StreamSubscription? _streamSubscription; + + @override + Future onStart(DateTime timestamp, TaskStarter starter) async { + _streamSubscription = FlLocation.getLocationStream().listen((location) { + final String message = '${location.latitude}, ${location.longitude}'; + FlutterForegroundTask.updateService(notificationText: message); + + // Send data to main isolate. + final String locationJson = jsonEncode(location.toJson()); + FlutterForegroundTask.sendDataToMain(locationJson); + }); + } + + @override + void onRepeatEvent(DateTime timestamp) { + // not use + } + + @override + Future onDestroy(DateTime timestamp, bool isTimeout) async { + _streamSubscription?.cancel(); + _streamSubscription = null; + } +} +``` + +### :hatched_chick: other example + +#### [`internal_plugin_service`](https://github.com/Dev-hwang/flutter_foreground_task_example/tree/main/internal_plugin_service) +An example of using the platform channel in project with `flutter_foreground_task`. + +#### [`location_service`](https://github.com/Dev-hwang/flutter_foreground_task_example/tree/main/location_service) +An example of a background location service implementation using `flutter_foreground_task` and `fl_location`. + +#### [`record_service`](https://github.com/Dev-hwang/flutter_foreground_task_example/tree/main/record_service) +An example of a voice record service implementation using `flutter_foreground_task` and `record`. + +#### [`geofencing_service`](https://github.com/Dev-hwang/flutter_foreground_task_example/tree/main/geofencing_service) +An example of a background geofencing service implementation using `flutter_foreground_task` and `geofencing_api`. + +#### [`pedometer_service`](https://github.com/Dev-hwang/flutter_foreground_task_example/tree/main/pedometer_service) +An example of a pedometer service implementation using `flutter_foreground_task` and `pedometer`. + +## More Documentation + +Go [here](./documentation/models_documentation.md) to learn about the `models` provided by this plugin. + +Go [here](./documentation/utility_documentation.md) to learn about the `utility` provided by this plugin. + +Go [here](./documentation/migration_documentation.md) to `migrate` to the new version. + +## Support + +If you find any bugs or issues while using the plugin, please register an issues on [GitHub](https://github.com/Dev-hwang/flutter_foreground_task/issues). You can also contact us at . diff --git a/local_packages/flutter_foreground_task-9.1.0/analysis_options.yaml b/local_packages/flutter_foreground_task-9.1.0/analysis_options.yaml new file mode 100644 index 0000000..1f65fcc --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/analysis_options.yaml @@ -0,0 +1,8 @@ +include: package:flutter_lints/flutter.yaml + +linter: + rules: + avoid_print: false + avoid_function_literals_in_foreach_calls: false + constant_identifier_names: false + prefer_void_to_null: false diff --git a/local_packages/flutter_foreground_task-9.1.0/android/build.gradle b/local_packages/flutter_foreground_task-9.1.0/android/build.gradle new file mode 100644 index 0000000..bafdc15 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/build.gradle @@ -0,0 +1,73 @@ +group 'com.pravera.flutter_foreground_task' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '2.1.0' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:8.6.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + // Conditional for compatibility with AGP <4.2. + if (project.android.hasProperty("namespace")) { + namespace 'com.pravera.flutter_foreground_task' + } + + compileSdk 36 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11 + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + test.java.srcDirs += 'src/test/kotlin' + } + + defaultConfig { + minSdkVersion 21 + } + + dependencies { + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0' + implementation 'androidx.core:core-ktx:1.15.0' + + testImplementation 'org.jetbrains.kotlin:kotlin-test' + testImplementation 'org.mockito:mockito-core:5.0.0' + } + + testOptions { + unitTests.all { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen { false } + showStandardStreams = true + } + } + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/gradle.properties b/local_packages/flutter_foreground_task-9.1.0/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/local_packages/flutter_foreground_task-9.1.0/android/gradle/wrapper/gradle-wrapper.properties b/local_packages/flutter_foreground_task-9.1.0/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c85cfe --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip diff --git a/local_packages/flutter_foreground_task-9.1.0/android/settings.gradle b/local_packages/flutter_foreground_task-9.1.0/android/settings.gradle new file mode 100644 index 0000000..e458267 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'flutter_foreground_task' diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/AndroidManifest.xml b/local_packages/flutter_foreground_task-9.1.0/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1b13a38 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/FlutterForegroundTaskLifecycleListener.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/FlutterForegroundTaskLifecycleListener.kt new file mode 100644 index 0000000..643e8a1 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/FlutterForegroundTaskLifecycleListener.kt @@ -0,0 +1,31 @@ +package com.pravera.flutter_foreground_task + +import io.flutter.embedding.engine.FlutterEngine + +/** A listener that can listen to the task lifecycle events. */ +interface FlutterForegroundTaskLifecycleListener { + /** + * Each time a task starts, a new FlutterEngine is created. + * + * This is called before [onTaskStart], + * Initialize the service you want to use in the task. (like PlatformChannel initialization) + */ + fun onEngineCreate(flutterEngine: FlutterEngine?) + + /** Called when the task is started. */ + fun onTaskStart(starter: FlutterForegroundTaskStarter) + + /** Called based on the eventAction set in ForegroundTaskOptions. */ + fun onTaskRepeatEvent() + + /** Called when the task is destroyed. */ + fun onTaskDestroy() + + /** + * If one task is finished or replaced by another, the FlutterEngine is destroyed. + * + * This is called after [onTaskDestroy], + * where dispose the service that was initialized in [onEngineCreate]. + */ + fun onEngineWillDestroy() +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/FlutterForegroundTaskPlugin.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/FlutterForegroundTaskPlugin.kt new file mode 100644 index 0000000..af10d83 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/FlutterForegroundTaskPlugin.kt @@ -0,0 +1,80 @@ +package com.pravera.flutter_foreground_task + +import android.content.Intent +import com.pravera.flutter_foreground_task.service.ForegroundService +import com.pravera.flutter_foreground_task.service.ForegroundServiceManager +import com.pravera.flutter_foreground_task.service.NotificationPermissionManager +import com.pravera.flutter_foreground_task.service.ServiceProvider +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.PluginRegistry.NewIntentListener + +/** FlutterForegroundTaskPlugin */ +class FlutterForegroundTaskPlugin : FlutterPlugin, ActivityAware, ServiceProvider, NewIntentListener { + companion object { + fun addTaskLifecycleListener(listener: FlutterForegroundTaskLifecycleListener) { + ForegroundService.addTaskLifecycleListener(listener) + } + + fun removeTaskLifecycleListener(listener: FlutterForegroundTaskLifecycleListener) { + ForegroundService.removeTaskLifecycleListener(listener) + } + } + + private lateinit var notificationPermissionManager: NotificationPermissionManager + private lateinit var foregroundServiceManager: ForegroundServiceManager + + private var activityBinding: ActivityPluginBinding? = null + private lateinit var methodCallHandler: MethodCallHandlerImpl + + override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { + notificationPermissionManager = NotificationPermissionManager() + foregroundServiceManager = ForegroundServiceManager() + + methodCallHandler = MethodCallHandlerImpl(binding.applicationContext, this) + methodCallHandler.init(binding.binaryMessenger) + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + if (::methodCallHandler.isInitialized) { + methodCallHandler.dispose() + } + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + methodCallHandler.setActivity(binding.activity) + binding.addRequestPermissionsResultListener(notificationPermissionManager) + binding.addActivityResultListener(methodCallHandler) + binding.addOnNewIntentListener(this) + activityBinding = binding + + val intent = binding.activity.intent + ForegroundService.handleNotificationContentIntent(intent) + } + + override fun onDetachedFromActivityForConfigChanges() { + onDetachedFromActivity() + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + onAttachedToActivity(binding) + } + + override fun onDetachedFromActivity() { + activityBinding?.removeRequestPermissionsResultListener(notificationPermissionManager) + activityBinding?.removeActivityResultListener(methodCallHandler) + activityBinding?.removeOnNewIntentListener(this) + activityBinding = null + methodCallHandler.setActivity(null) + } + + override fun onNewIntent(intent: Intent): Boolean { + ForegroundService.handleNotificationContentIntent(intent) + return true + } + + override fun getNotificationPermissionManager() = notificationPermissionManager + + override fun getForegroundServiceManager() = foregroundServiceManager +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/FlutterForegroundTaskPluginChannel.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/FlutterForegroundTaskPluginChannel.kt new file mode 100644 index 0000000..1ba7c82 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/FlutterForegroundTaskPluginChannel.kt @@ -0,0 +1,11 @@ +package com.pravera.flutter_foreground_task + +import android.app.Activity +import io.flutter.plugin.common.BinaryMessenger + +/** FlutterForegroundTaskPluginChannel */ +interface FlutterForegroundTaskPluginChannel { + fun init(messenger: BinaryMessenger) + fun setActivity(activity: Activity?) + fun dispose() +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/FlutterForegroundTaskStarter.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/FlutterForegroundTaskStarter.kt new file mode 100644 index 0000000..2efcab9 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/FlutterForegroundTaskStarter.kt @@ -0,0 +1,6 @@ +package com.pravera.flutter_foreground_task + +enum class FlutterForegroundTaskStarter { + DEVELOPER, + SYSTEM +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/MethodCallHandlerImpl.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/MethodCallHandlerImpl.kt new file mode 100644 index 0000000..f8e8efd --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/MethodCallHandlerImpl.kt @@ -0,0 +1,204 @@ +package com.pravera.flutter_foreground_task + +import android.app.Activity +import android.content.Context +import android.content.Intent + +import com.pravera.flutter_foreground_task.errors.ActivityNotAttachedException +import com.pravera.flutter_foreground_task.models.NotificationPermission +import com.pravera.flutter_foreground_task.service.NotificationPermissionCallback +import com.pravera.flutter_foreground_task.service.ServiceProvider +import com.pravera.flutter_foreground_task.utils.ErrorHandleUtils +import com.pravera.flutter_foreground_task.utils.PluginUtils + +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.PluginRegistry +import java.util.UUID +import kotlin.Exception + +/** MethodCallHandlerImpl */ +class MethodCallHandlerImpl(private val context: Context, private val provider: ServiceProvider) : + MethodChannel.MethodCallHandler, + FlutterForegroundTaskPluginChannel, + PluginRegistry.ActivityResultListener { + private lateinit var channel: MethodChannel + + private var activity: Activity? = null + private var methodCodes: MutableMap = mutableMapOf() + private var methodResults: MutableMap = mutableMapOf() + + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + val args = call.arguments + try { + when (call.method) { + "checkNotificationPermission" -> { + checkActivityNull().let { + val status = provider.getNotificationPermissionManager().checkPermission(it) + result.success(status.ordinal) + } + } + + "requestNotificationPermission" -> { + checkActivityNull().let { + val callback = object : NotificationPermissionCallback { + override fun onResult(permissionStatus: NotificationPermission) { + result.success(permissionStatus.ordinal) + } + + override fun onError(exception: Exception) { + ErrorHandleUtils.handleMethodCallError(result, exception) + } + } + provider.getNotificationPermissionManager().requestPermission(it, callback) + } + } + + "startService" -> { + provider.getForegroundServiceManager().start(context, args) + result.success(true) + } + + "restartService" -> { + provider.getForegroundServiceManager().restart(context) + result.success(true) + } + + "updateService" -> { + provider.getForegroundServiceManager().update(context, args) + result.success(true) + } + + "stopService" -> { + provider.getForegroundServiceManager().stop(context) + result.success(true) + } + + "sendData" -> provider.getForegroundServiceManager().sendData(args) + + "isRunningService" -> + result.success(provider.getForegroundServiceManager().isRunningService()) + + "attachedActivity" -> result.success(activity != null) + + "minimizeApp" -> { + checkActivityNull().let { + PluginUtils.minimizeApp(it) + } + } + + "launchApp" -> { + if (args is String?) { + PluginUtils.launchApp(context, args) + } + } + + "isAppOnForeground" -> result.success(PluginUtils.isAppOnForeground(context)) + + "setOnLockScreenVisibility" -> { + checkActivityNull().let { + if (args is Boolean) { + PluginUtils.setOnLockScreenVisibility(it, args) + } + } + } + + "wakeUpScreen" -> PluginUtils.wakeUpScreen(context) + + "isIgnoringBatteryOptimizations" -> + result.success(PluginUtils.isIgnoringBatteryOptimizations(context)) + + "openIgnoreBatteryOptimizationSettings" -> { + checkActivityNull().let { + val requestCode = UUID.randomUUID().hashCode() and 0xFFFF + methodCodes[requestCode] = RequestCode.OPEN_IGNORE_BATTERY_OPTIMIZATION_SETTINGS + methodResults[requestCode] = result + PluginUtils.openIgnoreBatteryOptimizationSettings(it, requestCode) + } + } + + "requestIgnoreBatteryOptimization" -> { + checkActivityNull().let { + val requestCode = UUID.randomUUID().hashCode() and 0xFFFF + methodCodes[requestCode] = RequestCode.REQUEST_IGNORE_BATTERY_OPTIMIZATION + methodResults[requestCode] = result + PluginUtils.requestIgnoreBatteryOptimization(it, requestCode) + } + } + + "canDrawOverlays" -> result.success(PluginUtils.canDrawOverlays(context)) + + "openSystemAlertWindowSettings" -> { + checkActivityNull().let { + val requestCode = UUID.randomUUID().hashCode() and 0xFFFF + methodCodes[requestCode] = RequestCode.OPEN_SYSTEM_ALERT_WINDOW_SETTINGS + methodResults[requestCode] = result + PluginUtils.openSystemAlertWindowSettings(it, requestCode) + } + } + + "canScheduleExactAlarms" -> + result.success(PluginUtils.canScheduleExactAlarms(context)) + + "openAlarmsAndRemindersSettings" -> { + checkActivityNull().let { + val requestCode = UUID.randomUUID().hashCode() and 0xFFFF + methodCodes[requestCode] = RequestCode.OPEN_ALARMS_AND_REMINDER_SETTINGS + methodResults[requestCode] = result + PluginUtils.openAlarmsAndRemindersSettings(it, requestCode) + } + } + + else -> result.notImplemented() + } + } catch (e: Exception) { + ErrorHandleUtils.handleMethodCallError(result, e) + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { + val methodCode = methodCodes[requestCode] + val methodResult = methodResults[requestCode] + methodCodes.remove(requestCode) + methodResults.remove(requestCode) + + if (methodCode == null || methodResult == null) { + return true + } + + when (methodCode) { + RequestCode.OPEN_IGNORE_BATTERY_OPTIMIZATION_SETTINGS -> + methodResult.success(PluginUtils.isIgnoringBatteryOptimizations(context)) + RequestCode.REQUEST_IGNORE_BATTERY_OPTIMIZATION -> + methodResult.success(PluginUtils.isIgnoringBatteryOptimizations(context)) + RequestCode.OPEN_SYSTEM_ALERT_WINDOW_SETTINGS -> + methodResult.success(PluginUtils.canDrawOverlays(context)) + RequestCode.OPEN_ALARMS_AND_REMINDER_SETTINGS -> + methodResult.success(PluginUtils.canScheduleExactAlarms(context)) + } + return true + } + + override fun init(messenger: BinaryMessenger) { + channel = MethodChannel(messenger, "flutter_foreground_task/methods") + channel.setMethodCallHandler(this) + } + + override fun setActivity(activity: Activity?) { + this.activity = activity + } + + override fun dispose() { + if (::channel.isInitialized) { + channel.setMethodCallHandler(null) + } + } + + private fun checkActivityNull(): Activity { + if (activity == null) { + throw ActivityNotAttachedException() + } + return activity!! + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/PreferencesKey.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/PreferencesKey.kt new file mode 100644 index 0000000..c399100 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/PreferencesKey.kt @@ -0,0 +1,58 @@ +package com.pravera.flutter_foreground_task + +/** + * Key values for data stored in SharedPreferences. + * + * @author Dev-hwang + * @version 1.0 + */ +object PreferencesKey { + private const val prefix = "com.pravera.flutter_foreground_task.prefs." + + // permissions + const val NOTIFICATION_PERMISSION_STATUS_PREFS = prefix + "NOTIFICATION_PERMISSION_STATUS" + + // service status + const val FOREGROUND_SERVICE_STATUS_PREFS = prefix + "FOREGROUND_SERVICE_STATUS" + const val FOREGROUND_SERVICE_ACTION = "foregroundServiceAction" + + // service types + const val FOREGROUND_SERVICE_TYPES_PREFS = prefix + "FOREGROUND_SERVICE_TYPES" + const val FOREGROUND_SERVICE_TYPES = "serviceTypes" + + // notification options + const val NOTIFICATION_OPTIONS_PREFS = prefix + "NOTIFICATION_OPTIONS" + const val SERVICE_ID = "serviceId" + const val NOTIFICATION_ID = "notificationId" + const val NOTIFICATION_CHANNEL_ID = "notificationChannelId" + const val NOTIFICATION_CHANNEL_NAME = "notificationChannelName" + const val NOTIFICATION_CHANNEL_DESC = "notificationChannelDescription" + const val NOTIFICATION_CHANNEL_IMPORTANCE = "notificationChannelImportance" + const val NOTIFICATION_PRIORITY = "notificationPriority" + const val ENABLE_VIBRATION = "enableVibration" + const val PLAY_SOUND = "playSound" + const val SHOW_WHEN = "showWhen" + const val SHOW_BADGE = "showBadge" + const val ONLY_ALERT_ONCE = "onlyAlertOnce" + const val VISIBILITY = "visibility" + + // notification content + const val NOTIFICATION_CONTENT_TITLE = "notificationContentTitle" + const val NOTIFICATION_CONTENT_TEXT = "notificationContentText" + const val NOTIFICATION_CONTENT_ICON = "icon" + const val NOTIFICATION_CONTENT_BUTTONS = "buttons" + const val NOTIFICATION_INITIAL_ROUTE = "initialRoute" + + // task options + const val FOREGROUND_TASK_OPTIONS_PREFS = prefix + "FOREGROUND_TASK_OPTIONS" + const val TASK_EVENT_ACTION = "taskEventAction" // new + const val INTERVAL = "interval" // deprecated + const val IS_ONCE_EVENT = "isOnceEvent" // deprecated + const val AUTO_RUN_ON_BOOT = "autoRunOnBoot" + const val AUTO_RUN_ON_MY_PACKAGE_REPLACED = "autoRunOnMyPackageReplaced" + const val ALLOW_WAKE_LOCK = "allowWakeLock" + const val ALLOW_WIFI_LOCK = "allowWifiLock" + + // task data + const val CALLBACK_HANDLE = "callbackHandle" +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/RequestCode.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/RequestCode.kt new file mode 100644 index 0000000..4c67f84 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/RequestCode.kt @@ -0,0 +1,17 @@ +package com.pravera.flutter_foreground_task + +object RequestCode { + // permissions + const val REQUEST_NOTIFICATION_PERMISSION = 100 + + // utils + const val REQUEST_IGNORE_BATTERY_OPTIMIZATION = 200 + const val OPEN_IGNORE_BATTERY_OPTIMIZATION_SETTINGS = 201 + const val OPEN_SYSTEM_ALERT_WINDOW_SETTINGS = 202 + const val OPEN_ALARMS_AND_REMINDER_SETTINGS = 203 + + // service + const val SET_RESTART_SERVICE_ALARM = 300 + const val NOTIFICATION_PRESSED = 301 + const val NOTIFICATION_DISMISSED = 302 +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/errors/ActivityNotAttachedException.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/errors/ActivityNotAttachedException.kt new file mode 100644 index 0000000..4583545 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/errors/ActivityNotAttachedException.kt @@ -0,0 +1,4 @@ +package com.pravera.flutter_foreground_task.errors + +class ActivityNotAttachedException : + Exception("Cannot call method because Activity is not attached to FlutterEngine.") diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/errors/NotSupportedException.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/errors/NotSupportedException.kt new file mode 100644 index 0000000..62852ef --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/errors/NotSupportedException.kt @@ -0,0 +1,3 @@ +package com.pravera.flutter_foreground_task.errors + +class NotSupportedException(message: String) : Exception(message) diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/errors/PermissionRequestCancelledException.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/errors/PermissionRequestCancelledException.kt new file mode 100644 index 0000000..d5be500 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/errors/PermissionRequestCancelledException.kt @@ -0,0 +1,4 @@ +package com.pravera.flutter_foreground_task.errors + +class PermissionRequestCancelledException : + Exception("The permission request dialog was closed or the request was cancelled.") diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/errors/ServiceAlreadyStartedException.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/errors/ServiceAlreadyStartedException.kt new file mode 100644 index 0000000..18194bc --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/errors/ServiceAlreadyStartedException.kt @@ -0,0 +1,3 @@ +package com.pravera.flutter_foreground_task.errors + +class ServiceAlreadyStartedException : Exception("The service has already started.") diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/errors/ServiceNotStartedException.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/errors/ServiceNotStartedException.kt new file mode 100644 index 0000000..e772cc1 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/errors/ServiceNotStartedException.kt @@ -0,0 +1,3 @@ +package com.pravera.flutter_foreground_task.errors + +class ServiceNotStartedException : Exception("The service is not started.") diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundServiceAction.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundServiceAction.kt new file mode 100644 index 0000000..cfcf37c --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundServiceAction.kt @@ -0,0 +1,19 @@ +package com.pravera.flutter_foreground_task.models + +/** + * Intent action for foreground service control. + * + * @author Dev-hwang + * @version 1.0 + */ +object ForegroundServiceAction { + private const val prefix = "com.pravera.flutter_foreground_task.action." + + const val API_START = prefix + "api_start" + const val API_RESTART = prefix + "api_restart" + const val API_UPDATE = prefix + "api_update" + const val API_STOP = prefix + "api_stop" + + const val REBOOT = prefix + "reboot" + const val RESTART = prefix + "restart" +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundServiceStatus.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundServiceStatus.kt new file mode 100644 index 0000000..6db18e1 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundServiceStatus.kt @@ -0,0 +1,32 @@ +package com.pravera.flutter_foreground_task.models + +import android.content.Context +import com.pravera.flutter_foreground_task.PreferencesKey as PrefsKey + +data class ForegroundServiceStatus(val action: String) { + companion object { + fun getData(context: Context): ForegroundServiceStatus { + val prefs = context.getSharedPreferences( + PrefsKey.FOREGROUND_SERVICE_STATUS_PREFS, Context.MODE_PRIVATE) + + val action = prefs.getString(PrefsKey.FOREGROUND_SERVICE_ACTION, null) + ?: ForegroundServiceAction.API_STOP + + return ForegroundServiceStatus(action = action) + } + + fun setData(context: Context, action: String) { + val prefs = context.getSharedPreferences( + PrefsKey.FOREGROUND_SERVICE_STATUS_PREFS, Context.MODE_PRIVATE) + + with(prefs.edit()) { + putString(PrefsKey.FOREGROUND_SERVICE_ACTION, action) + commit() + } + } + } + + fun isCorrectlyStopped(): Boolean { + return action == ForegroundServiceAction.API_STOP + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundServiceTypes.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundServiceTypes.kt new file mode 100644 index 0000000..315bca0 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundServiceTypes.kt @@ -0,0 +1,77 @@ +package com.pravera.flutter_foreground_task.models + +import android.content.Context +import android.content.pm.ServiceInfo +import android.os.Build +import com.pravera.flutter_foreground_task.PreferencesKey + +data class ForegroundServiceTypes(val value: Int) { + companion object { + fun getData(context: Context): ForegroundServiceTypes { + val prefs = context.getSharedPreferences( + PreferencesKey.FOREGROUND_SERVICE_TYPES_PREFS, Context.MODE_PRIVATE) + + val value = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + prefs.getInt(PreferencesKey.FOREGROUND_SERVICE_TYPES, ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST) + } else { + prefs.getInt(PreferencesKey.FOREGROUND_SERVICE_TYPES, 0) // none + } + + return ForegroundServiceTypes(value = value) + } + + fun setData(context: Context, map: Map<*, *>?) { + val prefs = context.getSharedPreferences( + PreferencesKey.FOREGROUND_SERVICE_TYPES_PREFS, Context.MODE_PRIVATE) + + var value = 0 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val serviceTypes = map?.get(PreferencesKey.FOREGROUND_SERVICE_TYPES) as? List<*> + if (serviceTypes != null) { + for (serviceType in serviceTypes) { + getForegroundServiceTypeFlag(serviceType)?.let { + value = value or it + } + } + } + } + + // not none type + if (value > 0) { + with(prefs.edit()) { + putInt(PreferencesKey.FOREGROUND_SERVICE_TYPES, value) + commit() + } + } + } + + fun clearData(context: Context) { + val prefs = context.getSharedPreferences( + PreferencesKey.FOREGROUND_SERVICE_TYPES_PREFS, Context.MODE_PRIVATE) + + with(prefs.edit()) { + clear() + commit() + } + } + + private fun getForegroundServiceTypeFlag(type: Any?): Int? { + return when (type) { + 0 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA else null + 1 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE else null + 2 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC else null + 3 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH else null + 4 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION else null + 5 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK else null + 6 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION else null + 7 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE else null + 8 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL else null + 9 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING else null + 10 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE else null + 11 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE else null + 12 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED else null + else -> null + } + } + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundTaskData.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundTaskData.kt new file mode 100644 index 0000000..9dd28f3 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundTaskData.kt @@ -0,0 +1,56 @@ +package com.pravera.flutter_foreground_task.models + +import android.content.Context +import com.pravera.flutter_foreground_task.PreferencesKey as PrefsKey + +data class ForegroundTaskData(val callbackHandle: Long?) { + companion object { + fun getData(context: Context): ForegroundTaskData { + val prefs = context.getSharedPreferences( + PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS, Context.MODE_PRIVATE) + + val callbackHandle = if (prefs.contains(PrefsKey.CALLBACK_HANDLE)) { + prefs.getLong(PrefsKey.CALLBACK_HANDLE, 0L) + } else { + null + } + + return ForegroundTaskData(callbackHandle = callbackHandle) + } + + fun setData(context: Context, map: Map<*, *>?) { + val prefs = context.getSharedPreferences( + PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS, Context.MODE_PRIVATE) + + val callbackHandle = "${map?.get(PrefsKey.CALLBACK_HANDLE)}".toLongOrNull() + + with(prefs.edit()) { + remove(PrefsKey.CALLBACK_HANDLE) + callbackHandle?.let { putLong(PrefsKey.CALLBACK_HANDLE, it) } + commit() + } + } + + fun updateData(context: Context, map: Map<*, *>?) { + val prefs = context.getSharedPreferences( + PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS, Context.MODE_PRIVATE) + + val callbackHandle = "${map?.get(PrefsKey.CALLBACK_HANDLE)}".toLongOrNull() + + with(prefs.edit()) { + callbackHandle?.let { putLong(PrefsKey.CALLBACK_HANDLE, it) } + commit() + } + } + + fun clearData(context: Context) { + val prefs = context.getSharedPreferences( + PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS, Context.MODE_PRIVATE) + + with(prefs.edit()) { + clear() + commit() + } + } + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundTaskEventAction.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundTaskEventAction.kt new file mode 100644 index 0000000..edab44d --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundTaskEventAction.kt @@ -0,0 +1,45 @@ +package com.pravera.flutter_foreground_task.models + +import org.json.JSONObject +import java.util.Objects + +data class ForegroundTaskEventAction( + val type: ForegroundTaskEventType, + val interval: Long +) { + companion object { + private const val TASK_EVENT_TYPE_KEY = "taskEventType" + private const val TASK_EVENT_INTERVAL_KEY = "taskEventInterval" + + fun fromJsonString(jsonString: String): ForegroundTaskEventAction { + val jsonObj = JSONObject(jsonString) + + val type: ForegroundTaskEventType = if (jsonObj.isNull(TASK_EVENT_TYPE_KEY)) { + ForegroundTaskEventType.NOTHING + } else { + val value = jsonObj.getInt(TASK_EVENT_TYPE_KEY) + ForegroundTaskEventType.fromValue(value) + } + + val interval: Long = if (jsonObj.isNull(TASK_EVENT_INTERVAL_KEY)) { + 5000L + } else { + val value = jsonObj.getInt(TASK_EVENT_INTERVAL_KEY) + value.toLong() + } + + return ForegroundTaskEventAction(type = type, interval = interval) + } + } + + override fun equals(other: Any?): Boolean { + if (other == null || other !is ForegroundTaskEventAction) { + return false + } + return this.type.value == other.type.value && this.interval == other.interval + } + + override fun hashCode(): Int { + return Objects.hash(type.value, interval) + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundTaskEventType.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundTaskEventType.kt new file mode 100644 index 0000000..184fbdc --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundTaskEventType.kt @@ -0,0 +1,12 @@ +package com.pravera.flutter_foreground_task.models + +enum class ForegroundTaskEventType(val value: Int) { + NOTHING(1), + ONCE(2), + REPEAT(3); + + companion object { + fun fromValue(value: Int) = + ForegroundTaskEventType.values().firstOrNull { it.value == value } ?: NOTHING + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundTaskOptions.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundTaskOptions.kt new file mode 100644 index 0000000..01191a8 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/ForegroundTaskOptions.kt @@ -0,0 +1,107 @@ +package com.pravera.flutter_foreground_task.models + +import android.content.Context +import org.json.JSONObject +import com.pravera.flutter_foreground_task.PreferencesKey as PrefsKey + +data class ForegroundTaskOptions( + val eventAction: ForegroundTaskEventAction, + val autoRunOnBoot: Boolean, + val autoRunOnMyPackageReplaced: Boolean, + val allowWakeLock: Boolean, + val allowWifiLock: Boolean +) { + companion object { + fun getData(context: Context): ForegroundTaskOptions { + val prefs = context.getSharedPreferences( + PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS, Context.MODE_PRIVATE) + + val eventActionJsonString = prefs.getString(PrefsKey.TASK_EVENT_ACTION, null) + val eventAction: ForegroundTaskEventAction = if (eventActionJsonString != null) { + ForegroundTaskEventAction.fromJsonString(eventActionJsonString) + } else { + // for deprecated api + val oldIsOnceEvent = prefs.getBoolean(PrefsKey.IS_ONCE_EVENT, false) + val oldInterval = prefs.getLong(PrefsKey.INTERVAL, 5000L) + if (oldIsOnceEvent) { + ForegroundTaskEventAction(ForegroundTaskEventType.ONCE, oldInterval) + } else { + ForegroundTaskEventAction(ForegroundTaskEventType.REPEAT, oldInterval) + } + } + + val autoRunOnBoot = prefs.getBoolean(PrefsKey.AUTO_RUN_ON_BOOT, false) + val autoRunOnMyPackageReplaced = prefs.getBoolean(PrefsKey.AUTO_RUN_ON_MY_PACKAGE_REPLACED, false) + val allowWakeLock = prefs.getBoolean(PrefsKey.ALLOW_WAKE_LOCK, true) + val allowWifiLock = prefs.getBoolean(PrefsKey.ALLOW_WIFI_LOCK, false) + + return ForegroundTaskOptions( + eventAction = eventAction, + autoRunOnBoot = autoRunOnBoot, + autoRunOnMyPackageReplaced = autoRunOnMyPackageReplaced, + allowWakeLock = allowWakeLock, + allowWifiLock = allowWifiLock + ) + } + + fun setData(context: Context, map: Map<*, *>?) { + val prefs = context.getSharedPreferences( + PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS, Context.MODE_PRIVATE) + + val eventActionJson = map?.get(PrefsKey.TASK_EVENT_ACTION) as? Map<*, *> + var eventActionJsonString: String? = null + if (eventActionJson != null) { + eventActionJsonString = JSONObject(eventActionJson).toString() + } + + val autoRunOnBoot = map?.get(PrefsKey.AUTO_RUN_ON_BOOT) as? Boolean ?: false + val autoRunOnMyPackageReplaced = map?.get(PrefsKey.AUTO_RUN_ON_MY_PACKAGE_REPLACED) as? Boolean ?: false + val allowWakeLock = map?.get(PrefsKey.ALLOW_WAKE_LOCK) as? Boolean ?: true + val allowWifiLock = map?.get(PrefsKey.ALLOW_WIFI_LOCK) as? Boolean ?: false + + with(prefs.edit()) { + putString(PrefsKey.TASK_EVENT_ACTION, eventActionJsonString) + putBoolean(PrefsKey.AUTO_RUN_ON_BOOT, autoRunOnBoot) + putBoolean(PrefsKey.AUTO_RUN_ON_MY_PACKAGE_REPLACED, autoRunOnMyPackageReplaced) + putBoolean(PrefsKey.ALLOW_WAKE_LOCK, allowWakeLock) + putBoolean(PrefsKey.ALLOW_WIFI_LOCK, allowWifiLock) + commit() + } + } + + fun updateData(context: Context, map: Map<*, *>?) { + val prefs = context.getSharedPreferences( + PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS, Context.MODE_PRIVATE) + + val eventActionJson = map?.get(PrefsKey.TASK_EVENT_ACTION) as? Map<*, *> + var eventActionJsonString: String? = null + if (eventActionJson != null) { + eventActionJsonString = JSONObject(eventActionJson).toString() + } + + val autoRunOnBoot = map?.get(PrefsKey.AUTO_RUN_ON_BOOT) as? Boolean + val autoRunOnMyPackageReplaced = map?.get(PrefsKey.AUTO_RUN_ON_MY_PACKAGE_REPLACED) as? Boolean + val allowWakeLock = map?.get(PrefsKey.ALLOW_WAKE_LOCK) as? Boolean + val allowWifiLock = map?.get(PrefsKey.ALLOW_WIFI_LOCK) as? Boolean + + with(prefs.edit()) { + eventActionJsonString?.let { putString(PrefsKey.TASK_EVENT_ACTION, it) } + autoRunOnBoot?.let { putBoolean(PrefsKey.AUTO_RUN_ON_BOOT, it) } + autoRunOnMyPackageReplaced?.let { putBoolean(PrefsKey.AUTO_RUN_ON_MY_PACKAGE_REPLACED, it) } + allowWakeLock?.let { putBoolean(PrefsKey.ALLOW_WAKE_LOCK, it) } + allowWifiLock?.let { putBoolean(PrefsKey.ALLOW_WIFI_LOCK, it) } + commit() + } + } + + fun clearData(context: Context) { + val prefs = context.getSharedPreferences( + PrefsKey.FOREGROUND_TASK_OPTIONS_PREFS, Context.MODE_PRIVATE) + + with(prefs.edit()) { + clear() + commit() + } + } + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/NotificationButton.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/NotificationButton.kt new file mode 100644 index 0000000..0bd9270 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/NotificationButton.kt @@ -0,0 +1,49 @@ +package com.pravera.flutter_foreground_task.models + +import org.json.JSONObject +import java.util.Objects + +data class NotificationButton( + val id: String, + val text: String, + val textColorRgb: String? +) { + companion object { + private const val ID_KEY = "id" + private const val TEXT_KEY = "text" + private const val TEXT_COLOR_RGB_KEY = "textColorRgb" + + fun fromJSONObject(jsonObj: JSONObject): NotificationButton { + val id: String = if (jsonObj.isNull(ID_KEY)) { + "unknown" + } else { + jsonObj.getString(ID_KEY) + } + + val text: String = if (jsonObj.isNull(TEXT_KEY)) { + "" + } else { + jsonObj.getString(TEXT_KEY) + } + + val textColorRgb: String? = if (jsonObj.isNull(TEXT_COLOR_RGB_KEY)) { + null + } else { + jsonObj.getString(TEXT_COLOR_RGB_KEY) + } + + return NotificationButton(id = id, text = text, textColorRgb = textColorRgb) + } + } + + override fun equals(other: Any?): Boolean { + if (other == null || other !is NotificationButton) { + return false + } + return this.id == other.id && this.text == other.text && this.textColorRgb == other.textColorRgb + } + + override fun hashCode(): Int { + return Objects.hash(id, text, textColorRgb) + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/NotificationContent.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/NotificationContent.kt new file mode 100644 index 0000000..abb5729 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/NotificationContent.kt @@ -0,0 +1,122 @@ +package com.pravera.flutter_foreground_task.models + +import android.content.Context +import com.pravera.flutter_foreground_task.PreferencesKey as PrefsKey +import org.json.JSONArray +import org.json.JSONObject + +data class NotificationContent( + val title: String, + val text: String, + val icon: NotificationIcon?, + val buttons: List, + val initialRoute: String? +) { + companion object { + fun getData(context: Context): NotificationContent { + val prefs = context.getSharedPreferences( + PrefsKey.NOTIFICATION_OPTIONS_PREFS, Context.MODE_PRIVATE) + + val title = prefs.getString(PrefsKey.NOTIFICATION_CONTENT_TITLE, null) ?: "" + val text = prefs.getString(PrefsKey.NOTIFICATION_CONTENT_TEXT, null) ?: "" + + val iconJsonString = prefs.getString(PrefsKey.NOTIFICATION_CONTENT_ICON, null) + var icon: NotificationIcon? = null + if (iconJsonString != null) { + icon = NotificationIcon.fromJsonString(iconJsonString) + } + + val buttonsJsonString = prefs.getString(PrefsKey.NOTIFICATION_CONTENT_BUTTONS, null) + val buttons: MutableList = mutableListOf() + if (buttonsJsonString != null) { + val buttonsJsonArr = JSONArray(buttonsJsonString) + for (i in 0 until buttonsJsonArr.length()) { + val buttonJsonObj = buttonsJsonArr.getJSONObject(i) + buttons.add(NotificationButton.fromJSONObject(buttonJsonObj)) + } + } + + val initialRoute = prefs.getString(PrefsKey.NOTIFICATION_INITIAL_ROUTE, null) + + return NotificationContent( + title = title, + text = text, + icon = icon, + buttons = buttons, + initialRoute = initialRoute + ) + } + + fun setData(context: Context, map: Map<*, *>?) { + val prefs = context.getSharedPreferences( + PrefsKey.NOTIFICATION_OPTIONS_PREFS, Context.MODE_PRIVATE) + + val title = map?.get(PrefsKey.NOTIFICATION_CONTENT_TITLE) as? String ?: "" + val text = map?.get(PrefsKey.NOTIFICATION_CONTENT_TEXT) as? String ?: "" + + val iconJson = map?.get(PrefsKey.NOTIFICATION_CONTENT_ICON) as? Map<*, *> + var iconJsonString: String? = null + if (iconJson != null) { + iconJsonString = JSONObject(iconJson).toString() + } + + val buttonsJson = map?.get(PrefsKey.NOTIFICATION_CONTENT_BUTTONS) as? List<*> + var buttonsJsonString: String? = null + if (buttonsJson != null) { + buttonsJsonString = JSONArray(buttonsJson).toString() + } + + val initialRoute = map?.get(PrefsKey.NOTIFICATION_INITIAL_ROUTE) as? String + + with(prefs.edit()) { + putString(PrefsKey.NOTIFICATION_CONTENT_TITLE, title) + putString(PrefsKey.NOTIFICATION_CONTENT_TEXT, text) + putString(PrefsKey.NOTIFICATION_CONTENT_ICON, iconJsonString) + putString(PrefsKey.NOTIFICATION_CONTENT_BUTTONS, buttonsJsonString) + putString(PrefsKey.NOTIFICATION_INITIAL_ROUTE, initialRoute) + commit() + } + } + + fun updateData(context: Context, map: Map<*, *>?) { + val prefs = context.getSharedPreferences( + PrefsKey.NOTIFICATION_OPTIONS_PREFS, Context.MODE_PRIVATE) + + val title = map?.get(PrefsKey.NOTIFICATION_CONTENT_TITLE) as? String + val text = map?.get(PrefsKey.NOTIFICATION_CONTENT_TEXT) as? String + + val iconJson = map?.get(PrefsKey.NOTIFICATION_CONTENT_ICON) as? Map<*, *> + var iconJsonString: String? = null + if (iconJson != null) { + iconJsonString = JSONObject(iconJson).toString() + } + + val buttonsJson = map?.get(PrefsKey.NOTIFICATION_CONTENT_BUTTONS) as? List<*> + var buttonsJsonString: String? = null + if (buttonsJson != null) { + buttonsJsonString = JSONArray(buttonsJson).toString() + } + + val initialRoute = map?.get(PrefsKey.NOTIFICATION_INITIAL_ROUTE) as? String + + with(prefs.edit()) { + title?.let { putString(PrefsKey.NOTIFICATION_CONTENT_TITLE, it) } + text?.let { putString(PrefsKey.NOTIFICATION_CONTENT_TEXT, it) } + iconJsonString?.let { putString(PrefsKey.NOTIFICATION_CONTENT_ICON, it) } + buttonsJsonString?.let { putString(PrefsKey.NOTIFICATION_CONTENT_BUTTONS, it) } + initialRoute?.let { putString(PrefsKey.NOTIFICATION_INITIAL_ROUTE, it) } + commit() + } + } + + fun clearData(context: Context) { + val prefs = context.getSharedPreferences( + PrefsKey.NOTIFICATION_OPTIONS_PREFS, Context.MODE_PRIVATE) + + with(prefs.edit()) { + clear() + commit() + } + } + } +} \ No newline at end of file diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/NotificationIcon.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/NotificationIcon.kt new file mode 100644 index 0000000..67b3527 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/NotificationIcon.kt @@ -0,0 +1,34 @@ +package com.pravera.flutter_foreground_task.models + +import org.json.JSONObject + +data class NotificationIcon( + val metaDataName: String, + val backgroundColorRgb: String? +) { + companion object { + private const val META_DATA_NAME_KEY = "metaDataName" + private const val BACKGROUND_COLOR_RGB_KEY = "backgroundColorRgb" + + fun fromJsonString(jsonString: String): NotificationIcon { + val jsonObj = JSONObject(jsonString) + + val metaDataName: String = if (jsonObj.isNull(META_DATA_NAME_KEY)) { + "" + } else { + jsonObj.getString(META_DATA_NAME_KEY) + } + + val backgroundColorRgb: String? = if (jsonObj.isNull(BACKGROUND_COLOR_RGB_KEY)) { + null + } else { + jsonObj.getString(BACKGROUND_COLOR_RGB_KEY) + } + + return NotificationIcon( + metaDataName = metaDataName, + backgroundColorRgb = backgroundColorRgb + ) + } + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/NotificationOptions.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/NotificationOptions.kt new file mode 100644 index 0000000..e086335 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/NotificationOptions.kt @@ -0,0 +1,100 @@ +package com.pravera.flutter_foreground_task.models + +import android.content.Context +import com.pravera.flutter_foreground_task.PreferencesKey as PrefsKey + +data class NotificationOptions( + val serviceId: Int, + val channelId: String, + val channelName: String, + val channelDescription: String?, + val channelImportance: Int, + val priority: Int, + val enableVibration: Boolean, + val playSound: Boolean, + val showWhen: Boolean, + val showBadge: Boolean, + val onlyAlertOnce: Boolean, + val visibility: Int +) { + companion object { + fun getData(context: Context): NotificationOptions { + val prefs = context.getSharedPreferences( + PrefsKey.NOTIFICATION_OPTIONS_PREFS, Context.MODE_PRIVATE) + + val serviceId = prefs.getInt(PrefsKey.SERVICE_ID, prefs.getInt(PrefsKey.NOTIFICATION_ID, 1000)) + val channelId = prefs.getString(PrefsKey.NOTIFICATION_CHANNEL_ID, null) ?: "foreground_service" + val channelName = prefs.getString(PrefsKey.NOTIFICATION_CHANNEL_NAME, null) ?: "Foreground Service" + val channelDesc = prefs.getString(PrefsKey.NOTIFICATION_CHANNEL_DESC, null) + val channelImportance = prefs.getInt(PrefsKey.NOTIFICATION_CHANNEL_IMPORTANCE, 2) + val priority = prefs.getInt(PrefsKey.NOTIFICATION_PRIORITY, -1) + val enableVibration = prefs.getBoolean(PrefsKey.ENABLE_VIBRATION, false) + val playSound = prefs.getBoolean(PrefsKey.PLAY_SOUND, false) + val showWhen = prefs.getBoolean(PrefsKey.SHOW_WHEN, false) + val showBadge = prefs.getBoolean(PrefsKey.SHOW_BADGE, false) + val onlyAlertOnce = prefs.getBoolean(PrefsKey.ONLY_ALERT_ONCE, false) + val visibility = prefs.getInt(PrefsKey.VISIBILITY, 1) + + return NotificationOptions( + serviceId = serviceId, + channelId = channelId, + channelName = channelName, + channelDescription = channelDesc, + channelImportance = channelImportance, + priority = priority, + enableVibration = enableVibration, + playSound = playSound, + showWhen = showWhen, + showBadge = showBadge, + onlyAlertOnce = onlyAlertOnce, + visibility = visibility + ) + } + + fun setData(context: Context, map: Map<*, *>?) { + val prefs = context.getSharedPreferences( + PrefsKey.NOTIFICATION_OPTIONS_PREFS, Context.MODE_PRIVATE) + + val serviceId = map?.get(PrefsKey.SERVICE_ID) as? Int + ?: map?.get(PrefsKey.NOTIFICATION_ID) as? Int + ?: 1000 + val channelId = map?.get(PrefsKey.NOTIFICATION_CHANNEL_ID) as? String + val channelName = map?.get(PrefsKey.NOTIFICATION_CHANNEL_NAME) as? String + val channelDesc = map?.get(PrefsKey.NOTIFICATION_CHANNEL_DESC) as? String + val channelImportance = map?.get(PrefsKey.NOTIFICATION_CHANNEL_IMPORTANCE) as? Int ?: 2 + val priority = map?.get(PrefsKey.NOTIFICATION_PRIORITY) as? Int ?: -1 + val enableVibration = map?.get(PrefsKey.ENABLE_VIBRATION) as? Boolean ?: false + val playSound = map?.get(PrefsKey.PLAY_SOUND) as? Boolean ?: false + val showWhen = map?.get(PrefsKey.SHOW_WHEN) as? Boolean ?: false + val showBadge = map?.get(PrefsKey.SHOW_BADGE) as? Boolean ?: false + val onlyAlertOnce = map?.get(PrefsKey.ONLY_ALERT_ONCE) as? Boolean ?: false + val visibility = map?.get(PrefsKey.VISIBILITY) as? Int ?: 1 + + with(prefs.edit()) { + putInt(PrefsKey.SERVICE_ID, serviceId) + putString(PrefsKey.NOTIFICATION_CHANNEL_ID, channelId) + putString(PrefsKey.NOTIFICATION_CHANNEL_NAME, channelName) + putString(PrefsKey.NOTIFICATION_CHANNEL_DESC, channelDesc) + putInt(PrefsKey.NOTIFICATION_CHANNEL_IMPORTANCE, channelImportance) + putInt(PrefsKey.NOTIFICATION_PRIORITY, priority) + putBoolean(PrefsKey.ENABLE_VIBRATION, enableVibration) + putBoolean(PrefsKey.PLAY_SOUND, playSound) + putBoolean(PrefsKey.SHOW_WHEN, showWhen) + putBoolean(PrefsKey.SHOW_BADGE, showBadge) + putBoolean(PrefsKey.ONLY_ALERT_ONCE, onlyAlertOnce) + putInt(PrefsKey.VISIBILITY, visibility) + commit() + } + } + + fun clearData(context: Context) { + val prefs = context.getSharedPreferences( + PrefsKey.NOTIFICATION_OPTIONS_PREFS, Context.MODE_PRIVATE) + + with(prefs.edit()) { + clear() + commit() + } + } + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/NotificationPermission.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/NotificationPermission.kt new file mode 100644 index 0000000..01e1419 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/models/NotificationPermission.kt @@ -0,0 +1,7 @@ +package com.pravera.flutter_foreground_task.models + +enum class NotificationPermission { + GRANTED, + DENIED, + PERMANENTLY_DENIED +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/ForegroundService.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/ForegroundService.kt new file mode 100644 index 0000000..f225ba2 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/ForegroundService.kt @@ -0,0 +1,601 @@ +package com.pravera.flutter_foreground_task.service + +import android.annotation.SuppressLint +import android.app.* +import android.content.* +import android.content.pm.PackageManager +import android.graphics.Color +import android.net.wifi.WifiManager +import android.os.* +import android.text.Spannable +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.core.app.NotificationCompat +import androidx.core.content.ContextCompat +import com.pravera.flutter_foreground_task.FlutterForegroundTaskLifecycleListener +import com.pravera.flutter_foreground_task.RequestCode +import com.pravera.flutter_foreground_task.models.* +import com.pravera.flutter_foreground_task.utils.ForegroundServiceUtils +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update + +/** + * A service class for implementing foreground service. + * + * @author Dev-hwang + * @version 1.0 + */ +class ForegroundService : Service() { + companion object { + private val TAG = ForegroundService::class.java.simpleName + + private const val ACTION_NOTIFICATION_PRESSED = "onNotificationPressed" + private const val ACTION_NOTIFICATION_DISMISSED = "onNotificationDismissed" + private const val ACTION_NOTIFICATION_BUTTON_PRESSED = "onNotificationButtonPressed" + private const val ACTION_RECEIVE_DATA = "onReceiveData" + private const val INTENT_DATA_NAME = "intentData" + + private val _isRunningServiceState = MutableStateFlow(false) + val isRunningServiceState = _isRunningServiceState.asStateFlow() + + private var task: ForegroundTask? = null + private var taskLifecycleListeners = ForegroundTaskLifecycleListeners() + + fun addTaskLifecycleListener(listener: FlutterForegroundTaskLifecycleListener) { + taskLifecycleListeners.addListener(listener) + } + + fun removeTaskLifecycleListener(listener: FlutterForegroundTaskLifecycleListener) { + taskLifecycleListeners.removeListener(listener) + } + + fun handleNotificationContentIntent(intent: Intent?) { + if (intent == null) return + + try { + // Check if the given intent is a LaunchIntent. + val isLaunchIntent = (intent.action == Intent.ACTION_MAIN) && + (intent.categories?.contains(Intent.CATEGORY_LAUNCHER) == true) + if (!isLaunchIntent) return + + val data = intent.getStringExtra(INTENT_DATA_NAME) + if (data == ACTION_NOTIFICATION_PRESSED) { + task?.invokeMethod(data, null) + } + } catch (e: Exception) { + Log.e(TAG, e.message, e) + } + } + + fun sendData(data: Any?) { + if (isRunningServiceState.value) { + task?.invokeMethod(ACTION_RECEIVE_DATA, data) + } + } + } + + private lateinit var foregroundServiceStatus: ForegroundServiceStatus + private lateinit var foregroundServiceTypes: ForegroundServiceTypes + private lateinit var foregroundTaskOptions: ForegroundTaskOptions + private lateinit var foregroundTaskData: ForegroundTaskData + private lateinit var notificationOptions: NotificationOptions + private lateinit var notificationContent: NotificationContent + private var prevForegroundTaskOptions: ForegroundTaskOptions? = null + private var prevForegroundTaskData: ForegroundTaskData? = null + private var prevNotificationOptions: NotificationOptions? = null + private var prevNotificationContent: NotificationContent? = null + + private var wakeLock: PowerManager.WakeLock? = null + private var wifiLock: WifiManager.WifiLock? = null + + private var isTimeout: Boolean = false + + // A broadcast receiver that handles intents that occur in the foreground service. + private var broadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (intent == null) return + + try { + // This intent has not sent from the current package. + val iPackageName = intent.`package` + val cPackageName = packageName + if (iPackageName != cPackageName) { + Log.d(TAG, "This intent has not sent from the current package. ($iPackageName != $cPackageName)") + return + } + + val action = intent.action ?: return + val data = intent.getStringExtra(INTENT_DATA_NAME) + task?.invokeMethod(action, data) + } catch (e: Exception) { + Log.e(TAG, e.message, e) + } + } + } + + override fun onCreate() { + super.onCreate() + registerBroadcastReceiver() + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + isTimeout = false + loadDataFromPreferences() + + var action = foregroundServiceStatus.action + val isSetStopWithTaskFlag = ForegroundServiceUtils.isSetStopWithTaskFlag(this) + + if (action == ForegroundServiceAction.API_STOP) { + stopForegroundService() + return START_NOT_STICKY + } + + try { + if (intent == null) { + ForegroundServiceStatus.setData(this, ForegroundServiceAction.RESTART) + foregroundServiceStatus = ForegroundServiceStatus.getData(this) + action = foregroundServiceStatus.action + } + + when (action) { + ForegroundServiceAction.API_START, + ForegroundServiceAction.API_RESTART -> { + startForegroundService() + createForegroundTask() + } + ForegroundServiceAction.API_UPDATE -> { + updateNotification() + val prevCallbackHandle = prevForegroundTaskData?.callbackHandle + val currCallbackHandle = foregroundTaskData.callbackHandle + if (prevCallbackHandle != currCallbackHandle) { + createForegroundTask() + } else { + val prevEventAction = prevForegroundTaskOptions?.eventAction + val currEventAction = foregroundTaskOptions.eventAction + if (prevEventAction != currEventAction) { + updateForegroundTask() + } + } + } + ForegroundServiceAction.REBOOT, + ForegroundServiceAction.RESTART -> { + startForegroundService() + createForegroundTask() + Log.d(TAG, "The service has been restarted by Android OS.") + } + } + } catch (e: Exception) { + Log.e(TAG, e.message, e) + stopForegroundService() + } + + return if (isSetStopWithTaskFlag) { + START_NOT_STICKY + } else { + START_STICKY + } + } + + override fun onBind(intent: Intent?): IBinder? { + return null + } + + override fun onDestroy() { + super.onDestroy() + val isTimeout = this.isTimeout + destroyForegroundTask(isTimeout) + stopForegroundService() + unregisterBroadcastReceiver() + + var isCorrectlyStopped = false + if (::foregroundServiceStatus.isInitialized) { + isCorrectlyStopped = foregroundServiceStatus.isCorrectlyStopped() + } + if (!isCorrectlyStopped && !ForegroundServiceUtils.isSetStopWithTaskFlag(this)) { + Log.e(TAG, "The service will be restarted after 5 seconds because it wasn't properly stopped.") + RestartReceiver.setRestartAlarm(this, 5000) + } + } + + override fun onTaskRemoved(rootIntent: Intent?) { + super.onTaskRemoved(rootIntent) + if (ForegroundServiceUtils.isSetStopWithTaskFlag(this)) { + stopSelf() + } else { + RestartReceiver.setRestartAlarm(this, 1000) + } + } + + override fun onTimeout(startId: Int) { + super.onTimeout(startId) + isTimeout = true + stopForegroundService() + Log.e(TAG, "The service(id: $startId) timed out and was terminated by the system.") + } + + @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) + override fun onTimeout(startId: Int, fgsType: Int) { + super.onTimeout(startId, fgsType) + isTimeout = true + stopForegroundService() + Log.e(TAG, "The service(id: $startId) timed out and was terminated by the system.") + } + + private fun loadDataFromPreferences() { + foregroundServiceStatus = ForegroundServiceStatus.getData(applicationContext) + foregroundServiceTypes = ForegroundServiceTypes.getData(applicationContext) + if (::foregroundTaskOptions.isInitialized) { prevForegroundTaskOptions = foregroundTaskOptions } + foregroundTaskOptions = ForegroundTaskOptions.getData(applicationContext) + if (::foregroundTaskData.isInitialized) { prevForegroundTaskData = foregroundTaskData } + foregroundTaskData = ForegroundTaskData.getData(applicationContext) + if (::notificationOptions.isInitialized) { prevNotificationOptions = notificationOptions } + notificationOptions = NotificationOptions.getData(applicationContext) + if (::notificationContent.isInitialized) { prevNotificationContent = notificationContent } + notificationContent = NotificationContent.getData(applicationContext) + } + + private fun registerBroadcastReceiver() { + val intentFilter = IntentFilter().apply { + addAction(ACTION_NOTIFICATION_BUTTON_PRESSED) + addAction(ACTION_NOTIFICATION_PRESSED) + addAction(ACTION_NOTIFICATION_DISMISSED) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED) + } else { + registerReceiver(broadcastReceiver, intentFilter) + } + } + + private fun unregisterBroadcastReceiver() { + unregisterReceiver(broadcastReceiver) + } + + @SuppressLint("WrongConstant", "SuspiciousIndentation") + private fun startForegroundService() { + RestartReceiver.cancelRestartAlarm(this) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createNotificationChannel() + } + + val serviceId = notificationOptions.serviceId + val notification = createNotification() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + startForeground(serviceId, notification, foregroundServiceTypes.value) + } else { + startForeground(serviceId, notification) + } + + releaseLockMode() + acquireLockMode() + + _isRunningServiceState.update { true } + } + + private fun stopForegroundService() { + RestartReceiver.cancelRestartAlarm(this) + + releaseLockMode() + stopForeground(true) + stopSelf() + + _isRunningServiceState.update { false } + } + + @RequiresApi(Build.VERSION_CODES.O) + private fun createNotificationChannel() { + val channelId = notificationOptions.channelId + val channelName = notificationOptions.channelName + val channelDesc = notificationOptions.channelDescription + val channelImportance = notificationOptions.channelImportance + + val nm = getSystemService(NotificationManager::class.java) + if (nm.getNotificationChannel(channelId) == null) { + val channel = NotificationChannel(channelId, channelName, channelImportance).apply { + if (channelDesc != null) { + description = channelDesc + } + enableVibration(notificationOptions.enableVibration) + if (!notificationOptions.playSound) { + setSound(null, null) + } + setShowBadge(notificationOptions.showBadge) + } + nm.createNotificationChannel(channel) + } + } + + private fun createNotification(): Notification { + // notification icon + val icon = notificationContent.icon + val iconResId = getIconResId(icon) + val iconBackgroundColor = icon?.backgroundColorRgb?.let(::getRgbColor) + + // notification intent + val contentIntent = getContentIntent() + val deleteIntent = getDeleteIntent() + + // notification actions + var needsRebuildButtons = false + val prevButtons = prevNotificationContent?.buttons + val currButtons = notificationContent.buttons + if (prevButtons != null) { + if (prevButtons.size != currButtons.size) { + needsRebuildButtons = true + } else { + for (i in currButtons.indices) { + if (prevButtons[i] != currButtons[i]) { + needsRebuildButtons = true + break + } + } + } + } else { + needsRebuildButtons = true + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val builder = Notification.Builder(this, notificationOptions.channelId) + builder.setOngoing(true) + builder.setShowWhen(notificationOptions.showWhen) + builder.setSmallIcon(iconResId) + builder.setContentIntent(contentIntent) + builder.setContentTitle(notificationContent.title) + builder.setContentText(notificationContent.text) + builder.style = Notification.BigTextStyle() + builder.setVisibility(notificationOptions.visibility) + builder.setOnlyAlertOnce(notificationOptions.onlyAlertOnce) + if (iconBackgroundColor != null) { + builder.setColor(iconBackgroundColor) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + builder.setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + builder.setDeleteIntent(deleteIntent) + } + + val actions = buildNotificationActions(currButtons, needsRebuildButtons) + for (action in actions) { + builder.addAction(action) + } + + return builder.build() + } else { + val builder = NotificationCompat.Builder(this, notificationOptions.channelId) + builder.setOngoing(true) + builder.setShowWhen(notificationOptions.showWhen) + builder.setSmallIcon(iconResId) + builder.setContentIntent(contentIntent) + builder.setContentTitle(notificationContent.title) + builder.setContentText(notificationContent.text) + builder.setStyle(NotificationCompat.BigTextStyle().bigText(notificationContent.text)) + builder.setVisibility(notificationOptions.visibility) + builder.setOnlyAlertOnce(notificationOptions.onlyAlertOnce) + if (iconBackgroundColor != null) { + builder.color = iconBackgroundColor + } + if (!notificationOptions.enableVibration) { + builder.setVibrate(longArrayOf(0L)) + } + if (!notificationOptions.playSound) { + builder.setSound(null) + } + builder.priority = notificationOptions.priority + + val actions = buildNotificationCompatActions(currButtons, needsRebuildButtons) + for (action in actions) { + builder.addAction(action) + } + + return builder.build() + } + } + + private fun updateNotification() { + val serviceId = notificationOptions.serviceId + val notification = createNotification() + val nm = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + getSystemService(NotificationManager::class.java) + } else { + // crash 23+ + ContextCompat.getSystemService(this, NotificationManager::class.java) + } + nm?.notify(serviceId, notification) + } + + @SuppressLint("WakelockTimeout") + private fun acquireLockMode() { + if (foregroundTaskOptions.allowWakeLock && (wakeLock == null || wakeLock?.isHeld == false)) { + wakeLock = + (applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager).run { + newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ForegroundService:WakeLock").apply { + setReferenceCounted(false) + acquire() + } + } + } + + if (foregroundTaskOptions.allowWifiLock && (wifiLock == null || wifiLock?.isHeld == false)) { + wifiLock = + (applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager).run { + createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "ForegroundService:WifiLock").apply { + setReferenceCounted(false) + acquire() + } + } + } + } + + private fun releaseLockMode() { + wakeLock?.let { + if (it.isHeld) { + it.release() + wakeLock = null + } + } + + wifiLock?.let { + if (it.isHeld) { + it.release() + wifiLock = null + } + } + } + + private fun createForegroundTask() { + destroyForegroundTask() + + task = ForegroundTask( + context = this, + serviceStatus = foregroundServiceStatus, + taskData = foregroundTaskData, + taskEventAction = foregroundTaskOptions.eventAction, + taskLifecycleListener = taskLifecycleListeners + ) + } + + private fun updateForegroundTask() { + task?.update(taskEventAction = foregroundTaskOptions.eventAction) + } + + private fun destroyForegroundTask(isTimeout: Boolean = false) { + task?.destroy(isTimeout) + task = null + } + + private fun getIconResId(icon: NotificationIcon?): Int { + try { + val packageManager = applicationContext.packageManager + val packageName = applicationContext.packageName + val appInfo = packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA) + + // application icon + if (icon == null) { + return appInfo.icon + } + + // custom icon + val metaData = appInfo.metaData + if (metaData != null) { + return metaData.getInt(icon.metaDataName) + } + + return 0 + } catch (e: Exception) { + Log.e(TAG, "getIconResId($icon)", e) + return 0 + } + } + + private fun getContentIntent(): PendingIntent { + val packageManager = applicationContext.packageManager + val packageName = applicationContext.packageName + val intent = packageManager.getLaunchIntentForPackage(packageName)?.apply { + putExtra(INTENT_DATA_NAME, ACTION_NOTIFICATION_PRESSED) + + // set initialRoute + val initialRoute = notificationContent.initialRoute + if (initialRoute != null) { + putExtra("route", initialRoute) + } + } + + var flags = PendingIntent.FLAG_UPDATE_CURRENT + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + flags = flags or PendingIntent.FLAG_IMMUTABLE + } + + return PendingIntent.getActivity(this, RequestCode.NOTIFICATION_PRESSED, intent, flags) + } + + private fun getDeleteIntent(): PendingIntent { + val intent = Intent(ACTION_NOTIFICATION_DISMISSED).apply { + setPackage(packageName) + } + + var flags = PendingIntent.FLAG_UPDATE_CURRENT + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + flags = flags or PendingIntent.FLAG_IMMUTABLE + } + + return PendingIntent.getBroadcast(this, RequestCode.NOTIFICATION_DISMISSED, intent, flags) + } + + private fun getRgbColor(rgb: String): Int? { + val rgbSet = rgb.split(",") + return if (rgbSet.size == 3) { + Color.rgb(rgbSet[0].toInt(), rgbSet[1].toInt(), rgbSet[2].toInt()) + } else { + null + } + } + + private fun getTextSpan(text: String, color: Int?): Spannable { + return if (color != null) { + SpannableString(text).apply { + setSpan(ForegroundColorSpan(color), 0, length, 0) + } + } else { + SpannableString(text) + } + } + + private fun buildNotificationActions( + buttons: List, + needsRebuild: Boolean = false + ): List { + val actions = mutableListOf() + for (i in buttons.indices) { + val intent = Intent(ACTION_NOTIFICATION_BUTTON_PRESSED).apply { + setPackage(packageName) + putExtra(INTENT_DATA_NAME, buttons[i].id) + } + var flags = PendingIntent.FLAG_IMMUTABLE + if (needsRebuild) { + flags = flags or PendingIntent.FLAG_CANCEL_CURRENT + } + val textColor = buttons[i].textColorRgb?.let(::getRgbColor) + val text = getTextSpan(buttons[i].text, textColor) + val pendingIntent = + PendingIntent.getBroadcast(this, i + 1, intent, flags) + val action = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Notification.Action.Builder(null, text, pendingIntent).build() + } else { + Notification.Action.Builder(0, text, pendingIntent).build() + } + actions.add(action) + } + + return actions + } + + private fun buildNotificationCompatActions( + buttons: List, + needsRebuild: Boolean = false + ): List { + val actions = mutableListOf() + for (i in buttons.indices) { + val intent = Intent(ACTION_NOTIFICATION_BUTTON_PRESSED).apply { + setPackage(packageName) + putExtra(INTENT_DATA_NAME, buttons[i].id) + } + var flags = PendingIntent.FLAG_IMMUTABLE + if (needsRebuild) { + flags = flags or PendingIntent.FLAG_CANCEL_CURRENT + } + val textColor = buttons[i].textColorRgb?.let(::getRgbColor) + val text = getTextSpan(buttons[i].text, textColor) + val pendingIntent = + PendingIntent.getBroadcast(this, i + 1, intent, flags) + val action = NotificationCompat.Action.Builder(0, text, pendingIntent).build() + actions.add(action) + } + + return actions + } +} \ No newline at end of file diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/ForegroundServiceManager.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/ForegroundServiceManager.kt new file mode 100644 index 0000000..18ad0df --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/ForegroundServiceManager.kt @@ -0,0 +1,91 @@ +package com.pravera.flutter_foreground_task.service + +import android.content.Context +import android.content.Intent +import androidx.core.content.ContextCompat +import com.pravera.flutter_foreground_task.errors.ServiceAlreadyStartedException +import com.pravera.flutter_foreground_task.errors.ServiceNotStartedException +import com.pravera.flutter_foreground_task.models.ForegroundServiceAction +import com.pravera.flutter_foreground_task.models.ForegroundServiceStatus +import com.pravera.flutter_foreground_task.models.ForegroundServiceTypes +import com.pravera.flutter_foreground_task.models.ForegroundTaskData +import com.pravera.flutter_foreground_task.models.ForegroundTaskOptions +import com.pravera.flutter_foreground_task.models.NotificationContent +import com.pravera.flutter_foreground_task.models.NotificationOptions + +/** + * A class that provides foreground service control and management functions. + * + * @author Dev-hwang + * @version 1.0 + */ +class ForegroundServiceManager { + /** Start the foreground service. */ + fun start(context: Context, arguments: Any?) { + if (isRunningService()) { + throw ServiceAlreadyStartedException() + } + + val nIntent = Intent(context, ForegroundService::class.java) + val argsMap = arguments as? Map<*, *> + ForegroundServiceStatus.setData(context, ForegroundServiceAction.API_START) + ForegroundServiceTypes.setData(context, argsMap) + NotificationOptions.setData(context, argsMap) + ForegroundTaskOptions.setData(context, argsMap) + ForegroundTaskData.setData(context, argsMap) + NotificationContent.setData(context, argsMap) + ContextCompat.startForegroundService(context, nIntent) + } + + /** Restart the foreground service. */ + fun restart(context: Context) { + if (!isRunningService()) { + throw ServiceNotStartedException() + } + + val nIntent = Intent(context, ForegroundService::class.java) + ForegroundServiceStatus.setData(context, ForegroundServiceAction.API_RESTART) + ContextCompat.startForegroundService(context, nIntent) + } + + /** Update the foreground service. */ + fun update(context: Context, arguments: Any?) { + if (!isRunningService()) { + throw ServiceNotStartedException() + } + + val nIntent = Intent(context, ForegroundService::class.java) + val argsMap = arguments as? Map<*, *> + ForegroundServiceStatus.setData(context, ForegroundServiceAction.API_UPDATE) + ForegroundTaskOptions.updateData(context, argsMap) + ForegroundTaskData.updateData(context, argsMap) + NotificationContent.updateData(context, argsMap) + ContextCompat.startForegroundService(context, nIntent) + } + + /** Stop the foreground service. */ + fun stop(context: Context) { + if (!isRunningService()) { + throw ServiceNotStartedException() + } + + val nIntent = Intent(context, ForegroundService::class.java) + ForegroundServiceStatus.setData(context, ForegroundServiceAction.API_STOP) + ForegroundServiceTypes.clearData(context) + NotificationOptions.clearData(context) + ForegroundTaskOptions.clearData(context) + ForegroundTaskData.clearData(context) + NotificationContent.clearData(context) + ContextCompat.startForegroundService(context, nIntent) + } + + /** Send data to TaskHandler. */ + fun sendData(data: Any?) { + if (data != null) { + ForegroundService.sendData(data) + } + } + + /** Returns whether the foreground service is running. */ + fun isRunningService(): Boolean = ForegroundService.isRunningServiceState.value +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/ForegroundTask.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/ForegroundTask.kt new file mode 100644 index 0000000..11a5032 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/ForegroundTask.kt @@ -0,0 +1,205 @@ +package com.pravera.flutter_foreground_task.service + +import android.content.Context +import android.util.Log +import com.pravera.flutter_foreground_task.FlutterForegroundTaskLifecycleListener +import com.pravera.flutter_foreground_task.FlutterForegroundTaskStarter +import com.pravera.flutter_foreground_task.models.ForegroundServiceAction +import com.pravera.flutter_foreground_task.models.ForegroundServiceStatus +import com.pravera.flutter_foreground_task.models.ForegroundTaskData +import com.pravera.flutter_foreground_task.models.ForegroundTaskEventAction +import com.pravera.flutter_foreground_task.models.ForegroundTaskEventType +import io.flutter.FlutterInjector +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.embedding.engine.dart.DartExecutor +import io.flutter.embedding.engine.loader.FlutterLoader +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.view.FlutterCallbackInformation +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class ForegroundTask( + context: Context, + private val serviceStatus: ForegroundServiceStatus, + private val taskData: ForegroundTaskData, + private var taskEventAction: ForegroundTaskEventAction, + private val taskLifecycleListener: FlutterForegroundTaskLifecycleListener, +) : MethodChannel.MethodCallHandler { + companion object { + private val TAG = ForegroundTask::class.java.simpleName + + private const val ACTION_TASK_START = "onStart" + private const val ACTION_TASK_REPEAT_EVENT = "onRepeatEvent" + private const val ACTION_TASK_DESTROY = "onDestroy" + } + + private val flutterEngine: FlutterEngine + private val flutterLoader: FlutterLoader + private val backgroundChannel: MethodChannel + private var repeatTask: Job? = null + private var isDestroyed: Boolean = false + + init { + // create flutter engine + flutterEngine = FlutterEngine(context) + flutterLoader = FlutterInjector.instance().flutterLoader() + if (!flutterLoader.initialized()) { + flutterLoader.startInitialization(context) + } + flutterLoader.ensureInitializationComplete(context, null) + taskLifecycleListener.onEngineCreate(flutterEngine) + + // create background channel + val messenger = flutterEngine.dartExecutor.binaryMessenger + backgroundChannel = MethodChannel(messenger, "flutter_foreground_task/background") + backgroundChannel.setMethodCallHandler(this) + + // execute callback + val callbackHandle = taskData.callbackHandle + if (callbackHandle != null) { + val bundlePath = flutterLoader.findAppBundlePath() + val callbackInfo = FlutterCallbackInformation.lookupCallbackInformation(callbackHandle) + val dartCallback = DartExecutor.DartCallback(context.assets, bundlePath, callbackInfo) + flutterEngine.dartExecutor.executeDartCallback(dartCallback) + } + } + + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "start" -> start() + else -> result.notImplemented() + } + } + + private fun start() { + runIfNotDestroyed { + runIfCallbackHandleExists { + val serviceAction = serviceStatus.action + val starter = if (serviceAction == ForegroundServiceAction.API_START || + serviceAction == ForegroundServiceAction.API_RESTART || + serviceAction == ForegroundServiceAction.API_UPDATE) { + FlutterForegroundTaskStarter.DEVELOPER + } else { + FlutterForegroundTaskStarter.SYSTEM + } + + backgroundChannel.invokeMethod(ACTION_TASK_START, starter.ordinal) { + runIfNotDestroyed { + startRepeatTask() + } + } + taskLifecycleListener.onTaskStart(starter) + } + } + } + + private fun invokeTaskRepeatEvent() { + backgroundChannel.invokeMethod(ACTION_TASK_REPEAT_EVENT, null) + taskLifecycleListener.onTaskRepeatEvent() + } + + private fun startRepeatTask() { + stopRepeatTask() + + val type = taskEventAction.type + val interval = taskEventAction.interval + + if (type == ForegroundTaskEventType.NOTHING) { + return + } + + if (type == ForegroundTaskEventType.ONCE) { + invokeTaskRepeatEvent() + return + } + + repeatTask = CoroutineScope(Dispatchers.Default).launch { + while (true) { + delay(interval) + withContext(Dispatchers.Main) { + try { + invokeTaskRepeatEvent() + } catch (e: Exception) { + Log.e(TAG, "repeatTask", e) + } + } + } + } + } + + private fun stopRepeatTask() { + repeatTask?.cancel() + repeatTask = null + } + + fun invokeMethod(method: String, data: Any?) { + runIfNotDestroyed { + backgroundChannel.invokeMethod(method, data) + } + } + + fun update(taskEventAction: ForegroundTaskEventAction) { + runIfNotDestroyed { + runIfCallbackHandleExists { + this.taskEventAction = taskEventAction + startRepeatTask() + } + } + } + + fun destroy(isTimeout: Boolean) { + runIfNotDestroyed { + stopRepeatTask() + + backgroundChannel.setMethodCallHandler(null) + if (taskData.callbackHandle == null) { + taskLifecycleListener.onEngineWillDestroy() + flutterEngine.destroy() + } else { + backgroundChannel.invokeMethod(ACTION_TASK_DESTROY, isTimeout) { + flutterEngine.destroy() + } + taskLifecycleListener.onTaskDestroy() + taskLifecycleListener.onEngineWillDestroy() + } + + isDestroyed = true + } + } + + private fun runIfCallbackHandleExists(call: () -> Unit) { + if (taskData.callbackHandle == null) { + return + } + call() + } + + private fun runIfNotDestroyed(call: () -> Unit) { + if (isDestroyed) { + return + } + call() + } + + private fun MethodChannel.invokeMethod(method: String, data: Any?, onComplete: () -> Unit = {}) { + val callback = object : MethodChannel.Result { + override fun success(result: Any?) { + onComplete() + } + + override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) { + onComplete() + } + + override fun notImplemented() { + onComplete() + } + } + invokeMethod(method, data, callback) + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/ForegroundTaskLifecycleListeners.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/ForegroundTaskLifecycleListeners.kt new file mode 100644 index 0000000..15bea15 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/ForegroundTaskLifecycleListeners.kt @@ -0,0 +1,49 @@ +package com.pravera.flutter_foreground_task.service + +import com.pravera.flutter_foreground_task.FlutterForegroundTaskLifecycleListener +import com.pravera.flutter_foreground_task.FlutterForegroundTaskStarter +import io.flutter.embedding.engine.FlutterEngine + +class ForegroundTaskLifecycleListeners : FlutterForegroundTaskLifecycleListener { + private var listeners = mutableListOf() + + fun addListener(listener: FlutterForegroundTaskLifecycleListener) { + if (!listeners.contains(listener)) { + listeners.add(listener) + } + } + + fun removeListener(listener: FlutterForegroundTaskLifecycleListener) { + listeners.remove(listener) + } + + override fun onEngineCreate(flutterEngine: FlutterEngine?) { + for (listener in listeners) { + listener.onEngineCreate(flutterEngine) + } + } + + override fun onTaskStart(starter: FlutterForegroundTaskStarter) { + for (listener in listeners) { + listener.onTaskStart(starter) + } + } + + override fun onTaskRepeatEvent() { + for (listener in listeners) { + listener.onTaskRepeatEvent() + } + } + + override fun onTaskDestroy() { + for (listener in listeners) { + listener.onTaskDestroy() + } + } + + override fun onEngineWillDestroy() { + for (listener in listeners) { + listener.onEngineWillDestroy() + } + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/NotificationPermissionCallback.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/NotificationPermissionCallback.kt new file mode 100644 index 0000000..e24792c --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/NotificationPermissionCallback.kt @@ -0,0 +1,8 @@ +package com.pravera.flutter_foreground_task.service + +import com.pravera.flutter_foreground_task.models.NotificationPermission + +interface NotificationPermissionCallback { + fun onResult(permissionStatus: NotificationPermission) + fun onError(exception: Exception) +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/NotificationPermissionManager.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/NotificationPermissionManager.kt new file mode 100644 index 0000000..e021d03 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/NotificationPermissionManager.kt @@ -0,0 +1,115 @@ +package com.pravera.flutter_foreground_task.service + +import android.Manifest +import android.app.Activity +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import com.pravera.flutter_foreground_task.PreferencesKey +import com.pravera.flutter_foreground_task.RequestCode +import com.pravera.flutter_foreground_task.errors.PermissionRequestCancelledException +import com.pravera.flutter_foreground_task.models.NotificationPermission +import io.flutter.plugin.common.PluginRegistry + +class NotificationPermissionManager : PluginRegistry.RequestPermissionsResultListener { + private var activity: Activity? = null + private var callback: NotificationPermissionCallback? = null + + fun checkPermission(activity: Activity): NotificationPermission { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + return NotificationPermission.GRANTED + } + + val permission = Manifest.permission.POST_NOTIFICATIONS + if (activity.isPermissionGranted(permission)) { + return NotificationPermission.GRANTED + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val prevPermissionStatus = activity.getPrevPermissionStatus(permission) + if (prevPermissionStatus != null + && prevPermissionStatus == NotificationPermission.PERMANENTLY_DENIED + && !activity.shouldShowRequestPermissionRationale(permission)) { + return NotificationPermission.PERMANENTLY_DENIED + } + } + return NotificationPermission.DENIED + } + } + + fun requestPermission(activity: Activity, callback: NotificationPermissionCallback) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + callback.onResult(NotificationPermission.GRANTED) + return + } + + this.activity = activity + this.callback = callback + + ActivityCompat.requestPermissions( + activity, + arrayOf(Manifest.permission.POST_NOTIFICATIONS), + RequestCode.REQUEST_NOTIFICATION_PERMISSION + ) + } + + private fun Context.isPermissionGranted(permission: String): Boolean { + return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED + } + + private fun Context.setPrevPermissionStatus(permission: String, status: NotificationPermission) { + val prefs = getSharedPreferences( + PreferencesKey.NOTIFICATION_PERMISSION_STATUS_PREFS, Context.MODE_PRIVATE) ?: return + with (prefs.edit()) { + putString(permission, status.toString()) + commit() + } + } + + private fun Context.getPrevPermissionStatus(permission: String): NotificationPermission? { + val prefs = getSharedPreferences( + PreferencesKey.NOTIFICATION_PERMISSION_STATUS_PREFS, Context.MODE_PRIVATE) ?: return null + val value = prefs.getString(permission, null) ?: return null + return NotificationPermission.valueOf(value) + } + + private fun disposeReference() { + this.activity = null + this.callback = null + } + + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray): Boolean { + if (grantResults.isEmpty()) { + callback?.onError(PermissionRequestCancelledException()) + disposeReference() + return false + } + + val permission: String + val permissionIndex: Int + var permissionStatus = NotificationPermission.DENIED + when (requestCode) { + RequestCode.REQUEST_NOTIFICATION_PERMISSION -> { + permission = Manifest.permission.POST_NOTIFICATIONS + permissionIndex = permissions.indexOf(permission) + if (permissionIndex >= 0 + && grantResults[permissionIndex] == PackageManager.PERMISSION_GRANTED) { + permissionStatus = NotificationPermission.GRANTED + } else { + if (activity?.shouldShowRequestPermissionRationale(permission) == false) { + permissionStatus = NotificationPermission.PERMANENTLY_DENIED + } + } + } + else -> return false + } + + activity?.setPrevPermissionStatus(permission, permissionStatus) + callback?.onResult(permissionStatus) + disposeReference() + return true + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/RebootReceiver.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/RebootReceiver.kt new file mode 100644 index 0000000..013ac9a --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/RebootReceiver.kt @@ -0,0 +1,75 @@ +package com.pravera.flutter_foreground_task.service + +import android.app.ForegroundServiceStartNotAllowedException +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Build +import android.util.Log +import androidx.core.content.ContextCompat +import com.pravera.flutter_foreground_task.models.ForegroundServiceAction +import com.pravera.flutter_foreground_task.models.ForegroundServiceStatus +import com.pravera.flutter_foreground_task.models.ForegroundTaskOptions +import com.pravera.flutter_foreground_task.utils.ForegroundServiceUtils + +/** + * The receiver that receives the BOOT_COMPLETED and MY_PACKAGE_REPLACED intent. + * + * @author Dev-hwang + * @version 1.0 + */ +class RebootReceiver : BroadcastReceiver() { + companion object { + private val TAG = RebootReceiver::class.java.simpleName + } + + override fun onReceive(context: Context?, intent: Intent?) { + if (context == null || intent == null) return + + // Ignore autoRunOnBoot option when android:stopWithTask is set to true. + if (ForegroundServiceUtils.isSetStopWithTaskFlag(context)) { + return + } + + // Ignore autoRunOnBoot option when service is stopped by developer. + val serviceStatus = ForegroundServiceStatus.getData(context) + if (serviceStatus.isCorrectlyStopped()) { + return + } + + val options = ForegroundTaskOptions.getData(context) + + // Check whether to start the service at boot intent. + if ((intent.action == Intent.ACTION_BOOT_COMPLETED || + intent.action == "android.intent.action.QUICKBOOT_POWERON") && options.autoRunOnBoot) { + return startForegroundService(context) + } + + // Check whether to start the service on my package replaced intent. + if (intent.action == Intent.ACTION_MY_PACKAGE_REPLACED && options.autoRunOnMyPackageReplaced) { + return startForegroundService(context) + } + } + + private fun startForegroundService(context: Context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + try { + val nIntent = Intent(context, ForegroundService::class.java) + ForegroundServiceStatus.setData(context, ForegroundServiceAction.REBOOT) + ContextCompat.startForegroundService(context, nIntent) + } catch (e: ForegroundServiceStartNotAllowedException) { + Log.e(TAG, "Foreground service start not allowed exception: ${e.message}") + } catch (e: Exception) { + Log.e(TAG, e.message, e) + } + } else { + try { + val nIntent = Intent(context, ForegroundService::class.java) + ForegroundServiceStatus.setData(context, ForegroundServiceAction.REBOOT) + ContextCompat.startForegroundService(context, nIntent) + } catch (e: Exception) { + Log.e(TAG, e.message, e) + } + } + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/RestartReceiver.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/RestartReceiver.kt new file mode 100644 index 0000000..dbf1249 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/RestartReceiver.kt @@ -0,0 +1,103 @@ +package com.pravera.flutter_foreground_task.service + +import android.app.ActivityManager +import android.app.AlarmManager +import android.app.ForegroundServiceStartNotAllowedException +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Build +import android.util.Log +import androidx.core.content.ContextCompat +import com.pravera.flutter_foreground_task.RequestCode +import com.pravera.flutter_foreground_task.models.ForegroundServiceAction +import com.pravera.flutter_foreground_task.models.ForegroundServiceStatus +import com.pravera.flutter_foreground_task.utils.PluginUtils + +/** + * The receiver that receives restart alarm event. + * + * @author Dev-hwang + * @version 1.0 + */ +class RestartReceiver : BroadcastReceiver() { + companion object { + private val TAG = RestartReceiver::class.java.simpleName + + fun setRestartAlarm(context: Context, millis: Int) { + val triggerTime = System.currentTimeMillis() + millis + + val intent = Intent(context, RestartReceiver::class.java) + var flags = PendingIntent.FLAG_UPDATE_CURRENT + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + flags = flags or PendingIntent.FLAG_MUTABLE + } + val operation = PendingIntent.getBroadcast( + context, RequestCode.SET_RESTART_SERVICE_ALARM, intent, flags) + + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && + PluginUtils.canScheduleExactAlarms(context)) { + val info = AlarmManager.AlarmClockInfo(triggerTime, operation) + alarmManager.setAlarmClock(info, operation) + } else { + alarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, operation) + } + } + + fun cancelRestartAlarm(context: Context) { + val intent = Intent(context, RestartReceiver::class.java) + var flags = PendingIntent.FLAG_CANCEL_CURRENT + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + flags = flags or PendingIntent.FLAG_MUTABLE + } + val operation = PendingIntent.getBroadcast( + context, RequestCode.SET_RESTART_SERVICE_ALARM, intent, flags) + + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + alarmManager.cancel(operation) + } + } + + override fun onReceive(context: Context?, intent: Intent?) { + if (context == null) return + + val serviceStatus = ForegroundServiceStatus.getData(context) + if (serviceStatus.isCorrectlyStopped()) { + return + } + + val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val isRunningService = manager.getRunningServices(Integer.MAX_VALUE) + .any { it.service.className == ForegroundService::class.java.name } + if (isRunningService) { + return + } + + val isIgnoringBatteryOptimizations = PluginUtils.isIgnoringBatteryOptimizations(context) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !isIgnoringBatteryOptimizations) { + Log.w(TAG, "Turn off battery optimization to restart service in the background.") + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + try { + val nIntent = Intent(context, ForegroundService::class.java) + ForegroundServiceStatus.setData(context, ForegroundServiceAction.RESTART) + ContextCompat.startForegroundService(context, nIntent) + } catch (e: ForegroundServiceStartNotAllowedException) { + Log.e(TAG, "Foreground service start not allowed exception: ${e.message}") + } catch (e: Exception) { + Log.e(TAG, e.message, e) + } + } else { + try { + val nIntent = Intent(context, ForegroundService::class.java) + ForegroundServiceStatus.setData(context, ForegroundServiceAction.RESTART) + ContextCompat.startForegroundService(context, nIntent) + } catch (e: Exception) { + Log.e(TAG, e.message, e) + } + } + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/ServiceProvider.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/ServiceProvider.kt new file mode 100644 index 0000000..6d64241 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/service/ServiceProvider.kt @@ -0,0 +1,7 @@ +package com.pravera.flutter_foreground_task.service + +/** ServiceProvider */ +interface ServiceProvider { + fun getNotificationPermissionManager(): NotificationPermissionManager + fun getForegroundServiceManager(): ForegroundServiceManager +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/utils/ErrorHandleUtils.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/utils/ErrorHandleUtils.kt new file mode 100644 index 0000000..6847ddd --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/utils/ErrorHandleUtils.kt @@ -0,0 +1,20 @@ +package com.pravera.flutter_foreground_task.utils + +import io.flutter.plugin.common.MethodChannel + +/** + * Utilities for error handling. + * + * @author Dev-hwang + * @version 1.0 + */ +class ErrorHandleUtils { + companion object { + /** Handles errors that occur in MethodChannel. */ + fun handleMethodCallError(result: MethodChannel.Result, exception: Exception) { + val name = exception.javaClass.simpleName + val message = exception.message + result.error(name, message, null) + } + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/utils/ForegroundServiceUtils.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/utils/ForegroundServiceUtils.kt new file mode 100644 index 0000000..fcaed8a --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/utils/ForegroundServiceUtils.kt @@ -0,0 +1,30 @@ +package com.pravera.flutter_foreground_task.utils + +import android.content.ComponentName +import android.content.Context +import android.content.pm.PackageManager +import android.content.pm.PackageManager.NameNotFoundException +import android.content.pm.ServiceInfo +import android.util.Log +import com.pravera.flutter_foreground_task.service.ForegroundService + +class ForegroundServiceUtils { + companion object { + private val TAG = ForegroundServiceUtils::class.java.simpleName + + fun isSetStopWithTaskFlag(context: Context): Boolean { + return try { + val pm = context.packageManager + val cName = ComponentName(context, ForegroundService::class.java) + val flags = pm.getServiceInfo(cName, PackageManager.GET_META_DATA).flags + (flags and ServiceInfo.FLAG_STOP_WITH_TASK) == 1 + } catch (e: NameNotFoundException) { + Log.e(TAG, "isSetStopWithTaskFlag >> The service component cannot be found on the system.") + true + } catch (e: Exception) { + Log.e(TAG, "isSetStopWithTaskFlag >> $e") + true + } + } + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/utils/PluginUtils.kt b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/utils/PluginUtils.kt new file mode 100644 index 0000000..b5f6d76 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/android/src/main/kotlin/com/pravera/flutter_foreground_task/utils/PluginUtils.kt @@ -0,0 +1,172 @@ +package com.pravera.flutter_foreground_task.utils + +import android.app.Activity +import android.app.ActivityManager +import android.app.ActivityManager.RunningAppProcessInfo +import android.app.ActivityOptions +import android.app.AlarmManager +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.PowerManager +import android.provider.Settings +import android.view.WindowManager +import com.pravera.flutter_foreground_task.errors.NotSupportedException + +/** + * Utilities supported in the plugin. + * + * @author Dev-hwang + * @version 1.0 + */ +class PluginUtils { + companion object { + /** Returns whether the app is in the foreground. */ + fun isAppOnForeground(context: Context): Boolean { + val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val processes: MutableList = am.runningAppProcesses + ?: return false + + val packageName = context.packageName + for (process in processes) { + if (process.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND + && process.processName.equals(packageName)) { + return true + } + } + + return false + } + + /** Minimize the app to the background. */ + fun minimizeApp(activity: Activity) { + activity.moveTaskToBack(true) + } + + /** Launch the app at [route] if it is not running otherwise open it. */ + fun launchApp(context: Context, route: String?) { + val pm = context.packageManager + val launchIntent = pm.getLaunchIntentForPackage(context.packageName) + if (launchIntent != null) { + launchIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + if (route != null) { + launchIntent.putExtra("route", route) + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + val options = ActivityOptions.makeBasic().apply { + pendingIntentBackgroundActivityStartMode = + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED + } + context.startActivity(launchIntent, options.toBundle()) + } else { + context.startActivity(launchIntent) + } + } + } + + /** Toggle on lockscreen visibility. */ + fun setOnLockScreenVisibility(activity: Activity, isVisible: Boolean) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + activity.setShowWhenLocked(isVisible) + } else { + if (isVisible) { + activity.window?.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) + } else { + activity.window?.clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) + } + } + } + + /** Wake up the screen of a device that is turned off. */ + fun wakeUpScreen(context: Context) { + val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager + val serviceFlag = PowerManager.SCREEN_BRIGHT_WAKE_LOCK + .or(PowerManager.ACQUIRE_CAUSES_WAKEUP) + .or(PowerManager.ON_AFTER_RELEASE) + + val newWakeLock = powerManager.newWakeLock(serviceFlag, "PluginUtils:WakeLock") + newWakeLock.acquire(1000) + newWakeLock.release() + } + + /** Returns whether the app has been excluded from battery optimization. */ + fun isIgnoringBatteryOptimizations(context: Context): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager + powerManager.isIgnoringBatteryOptimizations(context.packageName) + } else { + true + } + } + + /** Open the settings page where you can set ignore battery optimization. */ + fun openIgnoreBatteryOptimizationSettings(activity: Activity, requestCode: Int) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val intent = Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS) + activity.startActivityForResult(intent, requestCode) + } else { + throw NotSupportedException("only supports Android 6.0+") + } + } + + /** Request to ignore battery optimization. */ + fun requestIgnoreBatteryOptimization(activity: Activity, requestCode: Int) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val intent = Intent( + Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, + Uri.parse("package:" + activity.packageName) + ) + activity.startActivityForResult(intent, requestCode) + } else { + throw NotSupportedException("only supports Android 6.0+") + } + } + + /** Returns whether the "android.permission.SYSTEM_ALERT_WINDOW" permission was granted. */ + fun canDrawOverlays(context: Context): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Settings.canDrawOverlays(context) + } else { + true + } + } + + /** Open the settings page where you can allow/deny the "android.permission.SYSTEM_ALERT_WINDOW" permission. */ + fun openSystemAlertWindowSettings(activity: Activity, requestCode: Int) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val intent = Intent( + Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:" + activity.packageName) + ) + activity.startActivityForResult(intent, requestCode) + } else { + throw NotSupportedException("only supports Android 6.0+") + } + } + + /** Returns whether the "android.permission.SCHEDULE_EXACT_ALARM" permission is granted. */ + fun canScheduleExactAlarms(context: Context): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + alarmManager.canScheduleExactAlarms() + } else { + true + } + } + + /** Open the alarms & reminders settings page. */ + fun openAlarmsAndRemindersSettings(activity: Activity, requestCode: Int) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val intent = Intent( + Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM, + Uri.parse("package:" + activity.packageName) + ) + activity.startActivityForResult(intent, requestCode) + } else { + throw NotSupportedException("only supports Android 12.0+") + } + } + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/documentation/customize_notification_icon.md b/local_packages/flutter_foreground_task-9.1.0/documentation/customize_notification_icon.md new file mode 100644 index 0000000..6e512a8 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/documentation/customize_notification_icon.md @@ -0,0 +1,54 @@ +Go to [`page`][1] and generate the notification icon. +You can also use the [`image generator`][2] provided by Android Studio. + +Move the downloaded icon image file to the `android/app/src/res/drawable` folder. + +If successful, it will appear as shown in the image below: + +image + +Next, you need to add meta-data in the `AndroidManifest.xml` file so that the plugin can reference the icon resource. + +```xml + + + + + + + +``` + +The name of the meta-data can be customized, but it is recommended to set it to a unique name combined with your package name. + +Finally, you can create a NotificationIcon object to change the notification icon when starting or updating the service. + +```dart +void start() { + FlutterForegroundTask.startService( + notificationTitle: 'notificationTitle', + notificationText: 'notificationText', + notificationIcon: const NotificationIcon( + metaDataName: 'com.your_package.service.HEART_ICON', + backgroundColor: Colors.orange, + ), + ); +} + +void update() { + FlutterForegroundTask.updateService( + notificationIcon: const NotificationIcon( + metaDataName: 'com.your_package.service.HEART_ICON', + backgroundColor: Colors.purple, + ), + ); +} +``` + +[1]: https://romannurik.github.io/AndroidAssetStudio/icons-notification.html#source.type=clipart&source.clipart=ac_unit&source.space.trim=1&source.space.pad=0.15&name=ic_notification +[2]: https://developer.android.com/studio/write/create-app-icons#create-notification diff --git a/local_packages/flutter_foreground_task-9.1.0/documentation/migration_documentation.md b/local_packages/flutter_foreground_task-9.1.0/documentation/migration_documentation.md new file mode 100644 index 0000000..fc11b47 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/documentation/migration_documentation.md @@ -0,0 +1,251 @@ +## Migration + +### ver 9.0.0 + +- chore: Bump minimum supported SDK version to `Flutter 3.22/Dart 3.4`. + +``` +environment: + // sdk: ">=3.0.0 <4.0.0" + // flutter: ">=3.10.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" +``` + +- chore: Bump `kotlin_version(1.7.10 -> 1.9.10)`, `gradle(7.3.0 -> 8.6.0)` for Android 15. + +``` +[android/settings.gradle] +plugins { + // id "com.android.application" version "7.3.0" apply false + // id "org.jetbrains.kotlin.android" version "1.7.10" apply false + id "com.android.application" version "8.6.0" apply false + id "org.jetbrains.kotlin.android" version "1.9.10" apply false +} + +[android/gradle/wrapper/gradle-wrapper.properties] +// distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip + +[android/app/build.gradle] +android { + // compileSdk 34 + compileSdk 35 + + compileOptions { + // sourceCompatibility JavaVersion.VERSION_1_8 + // targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + kotlinOptions { + // jvmTarget = JavaVersion.VERSION_1_8 + jvmTarget = JavaVersion.VERSION_11 + } + + defaultConfig { + // targetSdkVersion 34 + targetSdkVersion 35 + } +} +``` + +- feat: Add `isTimeout` param to the onDestroy callback. + +```dart +// from +@override +Future onDestroy(DateTime timestamp) async {} + +// to +@override +Future onDestroy(DateTime timestamp, bool isTimeout) async {} +``` + +### ver 8.16.0 + +- Change `ServiceRequestResult` class to `sealed class` for improved code readability. + +```dart +void startService() { + final ServiceRequestResult result = + await FlutterForegroundTask.startService( + serviceId: 100, + notificationTitle: 'notificationTitle', + notificationText: 'notificationText', + callback: startLocationService, + ); + + // before: The distinction between success and failure of the request is unclear. + if (result.success) { + // The error should not be accessible when the request is successful. + final Object? error = result.error; + } else { + // Handle error + final Object? error = result.error; + } + + // after: The distinction between success and failure of the request is clear. + switch (result) { + case ServiceRequestSuccess(): + // The error cannot be accessed. + // final Object error = result.error; + print('success'); + case ServiceRequestFailure(): + // The error can only be accessed when the request fails, + // and no null check is required. + final Object error = result.error; + print('failure($error)'); + } +} +``` + +- Change method for customizing notification icon. [guide page](./customize_notification_icon.md) + +```dart +void startService() { + // before: There was an issue in the foreground service where only the white icon was displayed + // because the icon resource could not be referenced. + await FlutterForegroundTask.startService( + notificationTitle: 'notificationTitle', + notificationText: 'notificationText', + notificationIcon: const NotificationIconData( + resType: ResourceType.drawable, + resPrefix: ResourcePrefix.ic, + name: 'snow', + backgroundColor: Colors.orange, + ), + ); + + // after: Enabled static reference to the icon resource through meta-data. + await FlutterForegroundTask.startService( + notificationTitle: 'notificationTitle', + notificationText: 'notificationText', + notificationIcon: const NotificationIcon( + metaDataName: 'com.your_package.service.SNOW_ICON', + backgroundColor: Colors.orange, + ), + ); +} +``` + +### ver 8.10.0 + +- Change onStart, onDestroy callback return type from `void` to `Future`. + +```dart +// before +@override +void onStart(DateTime timestamp, TaskStarter starter) { } + +@override +void onDestroy(DateTime timestamp) { } + +// after +@override +Future onStart(DateTime timestamp, TaskStarter starter) async { } + +@override +Future onDestroy(DateTime timestamp) async { } +``` + +### ver 8.6.0 + +- Remove `interval`, `isOnceEvent` option in ForegroundTaskOptions model. +- Add `eventAction` option with ForegroundTaskEventAction constructor. + +```dart +// before +FlutterForegroundTask.init( + foregroundTaskOptions: ForegroundTaskOptions( + interval: 5000, + isOnceEvent: false, + ), +); + +// after +FlutterForegroundTask.init( + // ForegroundTaskEventAction.nothing() : Not use onRepeatEvent callback. + // ForegroundTaskEventAction.once() : Call onRepeatEvent only once. + // ForegroundTaskEventAction.repeat(interval) : Call onRepeatEvent at milliseconds interval. + foregroundTaskOptions: ForegroundTaskOptions( + eventAction: ForegroundTaskEventAction.repeat(5000), + ), +); +``` + +### ver 8.0.0 + +- The `sendPort` parameter was removed from the service callback(onStart, onRepeatEvent, onDestroy). + +```dart +// before +void onStart(DateTime timestamp, SendPort? sendPort) { + sendPort?.send(Object); +} + +// after +void onStart(DateTime timestamp) { + // Send data to main isolate. + FlutterForegroundTask.sendDataToMain(Object); +} +``` + +- `FlutterForegroundTask.receivePort` getter function was removed. + +```dart +// before +final ReceivePort? receivePort = FlutterForegroundTask.receivePort; +receivePort?.listen(_onReceiveTaskData) +receivePort?.close(); + +// after +void main() { + // Initialize port for communication between TaskHandler and UI. + FlutterForegroundTask.initCommunicationPort(); + runApp(const ExampleApp()); +} + +FlutterForegroundTask.addTaskDataCallback(_onReceiveTaskData); +FlutterForegroundTask.removeTaskDataCallback(_onReceiveTaskData); +``` + +- `sendData` renamed to `sendDataToTask`. + +```dart +// before +FlutterForegroundTask.sendData(Object); + +// after +FlutterForegroundTask.sendDataToTask(Object); +``` + +### ver 7.0.0 + +- Remove `iconData`, `buttons` from AndroidNotificationOptions. + +```dart +// before +FlutterForegroundTask.init( + androidNotificationOptions: AndroidNotificationOptions( + channelId: 'foreground_service', + channelName: 'Foreground Service Notification', + iconData: null, + buttons: [ + const NotificationButton(id: 'btn_hello', text: 'hello'), + ], + ), +); + +// after +FlutterForegroundTask.startService( + notificationTitle: 'Foreground Service is running', + notificationText: 'Tap to return to the app', + notificationIcon: null, + notificationButtons: [ + const NotificationButton(id: 'btn_hello', text: 'hello'), + ], + callback: startCallback, +) +``` diff --git a/local_packages/flutter_foreground_task-9.1.0/documentation/models_documentation.md b/local_packages/flutter_foreground_task-9.1.0/documentation/models_documentation.md new file mode 100644 index 0000000..d876fcc --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/documentation/models_documentation.md @@ -0,0 +1,140 @@ +## Models + +### :chicken: AndroidNotificationOptions + +Notification options for Android platform. + +| Property | Description | +|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------| +| `channelId` | Unique ID of the notification channel. It is set only once for the first time on Android 8.0+. | +| `channelName` | The name of the notification channel. It is set only once for the first time on Android 8.0+. | +| `channelDescription` | The description of the notification channel. It is set only once for the first time on Android 8.0+. | +| `channelImportance` | The importance of the notification channel. The default is `NotificationChannelImportance.LOW`. It is set only once for the first time on Android 8.0+. | +| `priority` | Priority of notifications for Android 7.1 and lower. The default is `NotificationPriority.LOW`. | +| `enableVibration` | Whether to enable vibration when creating notifications. The default is `false`. It is set only once for the first time on Android 8.0+. | +| `playSound` | Whether to play sound when creating notifications. The default is `false`. It is set only once for the first time on Android 8.0+. | +| `showWhen` | Whether to show the timestamp when the notification was created in the content view. The default is `false`. | +| `showBadge` | Whether to show the badge near the app icon when service is started. The default is `false`. It is set only once for the first time on Android 8.0+. | +| `onlyAlertOnce` | Whether to only alert once when the notification is created. The default is `false`. | +| `visibility` | Control the level of detail displayed in notifications on the lock screen. The default is `NotificationVisibility.VISIBILITY_PUBLIC`. | + +### :chicken: IOSNotificationOptions + +Notification options for iOS platform. + +| Property | Description | +|--------------------|----------------------------------------------------------------------------| +| `showNotification` | Whether to show notifications. The default is `true`. | +| `playSound` | Whether to play sound when creating notifications. The default is `false`. | + +### :chicken: ForegroundTaskOptions + +Data class with foreground task options. + +| Property | Description | +|------------------------------|----------------------------------------------------------------------------------------------------------------| +| `eventAction` | The action of onRepeatEvent in `TaskHandler`. | +| `autoRunOnBoot` | Whether to automatically run foreground task on boot. The default is `false`. | +| `autoRunOnMyPackageReplaced` | Whether to automatically run foreground task when the app is updated to a new version. The default is `false`. | +| `allowWakeLock` | Whether to keep the CPU turned on. The default is `true`. | +| `allowWifiLock` | Allows an application to keep the Wi-Fi radio awake. The default is `false`. | + +### :chicken: ForegroundTaskEventAction + +A class that defines the action of onRepeatEvent in `TaskHandler`. + +| Constructor | Description | +|--------------------|------------------------------------------------| +| `nothing()` | Not use onRepeatEvent callback. | +| `once()` | Call onRepeatEvent only once. | +| `repeat(interval)` | Call onRepeatEvent at milliseconds `interval`. | + +### :chicken: NotificationIcon + +A data class for dynamically changing the notification icon. + +| Property | Description | +|-------------------|------------------------------------------------------------------------------------------------| +| `metaDataName` | The name of the meta-data in the manifest that contains the drawable icon resource identifier. | +| `backgroundColor` | The background color for the notification icon. | + +### :chicken: NotificationButton + +The button to display in the notification. + +| Property | Description | +|-------------|--------------------------------------------| +| `id` | The button identifier. | +| `text` | The text to display on the button. | +| `textColor` | The button text color. (only work Android) | + +### :chicken: NotificationChannelImportance + +The importance of the notification channel. +See https://developer.android.com/training/notify-user/channels?hl=ko#importance + +| Value | Description | +|-----------|----------------------------------------------------------------------------------------------------------------------------------------------------------| +| `NONE` | A notification with no importance: does not show in the shade. | +| `MIN` | Min notification importance: only shows in the shade, below the fold. | +| `LOW` | Low notification importance: shows in the shade, and potentially in the status bar (see shouldHideSilentStatusBarIcons()), but is not audibly intrusive. | +| `DEFAULT` | Default notification importance: shows everywhere, makes noise, but does not visually intrude. | +| `HIGH` | Higher notification importance: shows everywhere, makes noise and peeks. May use full screen intents. | +| `MAX` | Max notification importance: same as HIGH, but generally not used. | + +### :chicken: NotificationPriority + +Priority of notifications for Android 7.1 and lower. + +| Value | Description | +|-----------|--------------------------------------------------------------------------| +| `MIN` | No sound and does not appear in the status bar. | +| `LOW` | No sound. | +| `DEFAULT` | Makes a sound. | +| `HIGH` | Makes a sound and appears as a heads-up notification. | +| `MAX` | Same as HIGH, but used when you want to notify notification immediately. | + +### :chicken: NotificationVisibility + +The level of detail displayed in notifications on the lock screen. + +| Value | Description | +|----------------------|----------------------------------------------------------------------------------------------------------------| +| `VISIBILITY_PUBLIC` | Show this notification in its entirety on all lockscreens. | +| `VISIBILITY_SECRET` | Do not reveal any part of this notification on a secure lockscreen. | +| `VISIBILITY_PRIVATE` | Show this notification on all lockscreens, but conceal sensitive or private information on secure lockscreens. | + +### :chicken: NotificationPermission + +enum class: Represents the result of a notification permission request. + +| value | Description | +|----------------------|------------------------------------------------------| +| `granted` | Notification permission has been granted. | +| `denied` | Notification permission has been denied. | +| `permanently_denied` | Notification permission has been permanently denied. | + +### :chicken: ServiceRequestResult + +sealed class: Represents the result of a service request. + +| child | Description | +|---------------------------------------|-------------------------------------| +| `ServiceRequestSuccess()` | The service request was successful. | +| `ServiceRequestFailure(Object error)` | The service request failed. | + +| error | Description | +|----------------------------------|----------------------------------------------------------------------------------------------------------------| +| `ServiceAlreadyStartedException` | The service has already started. | +| `ServiceNotInitializedException` | Not initialized. Please call this function after calling the init function. | +| `ServiceNotStartedException` | The service is not started. | +| `ServiceTimeoutException` | The service request timed out. (ref: https://developer.android.com/guide/components/services#StartingAService) | + +### :chicken: TaskStarter + +The starter that started the task. + +| Value | Description | +|-------------|---------------------------------------------| +| `developer` | The task has been started by the developer. | +| `system` | The task has been started by the system. | diff --git a/local_packages/flutter_foreground_task-9.1.0/documentation/utility_documentation.md b/local_packages/flutter_foreground_task-9.1.0/documentation/utility_documentation.md new file mode 100644 index 0000000..55b549f --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/documentation/utility_documentation.md @@ -0,0 +1,158 @@ +## Utility + +### :lollipop: minimizeApp + +Minimize the app to the background. + +> **Warning** +> It only works when the app is in the foreground. + +```dart +void function() => FlutterForegroundTask.minimizeApp(); +``` + +### :lollipop: launchApp (Android) + +Launch the app at `route` if it is not running otherwise open it. + +It is also possible to pass a route to this function but the route will only +be loaded if the app is not already running. + +This function requires the `android.permission.SYSTEM_ALERT_WINDOW` permission and +requires using the `openSystemAlertWindowSettings()` function to grant the permission. + +```dart +void requestPermission() async { + if (!await FlutterForegroundTask.canDrawOverlays) { + await FlutterForegroundTask.openSystemAlertWindowSettings(); + } +} + +void function() => FlutterForegroundTask.launchApp([route]); +``` + +### :lollipop: setOnLockScreenVisibility (Android) + +Toggles lockScreen visibility. + +> **Warning** +> It only works when the app is in the foreground. + +```dart +void function() => FlutterForegroundTask.setOnLockScreenVisibility(true); +``` + +### :lollipop: isAppOnForeground + +Returns whether the app is in the foreground. + +```dart +Future function() => FlutterForegroundTask.isAppOnForeground; +``` + +### :lollipop: wakeUpScreen (Android) + +Wake up the screen of a device that is turned off. + +```dart +void function() => FlutterForegroundTask.wakeUpScreen(); +``` + +### :lollipop: isIgnoringBatteryOptimizations (Android) + +Returns whether the app has been excluded from battery optimization. + +```dart +Future function() => FlutterForegroundTask.isIgnoringBatteryOptimizations; +``` + +### :lollipop: openIgnoreBatteryOptimizationSettings (Android) + +Open the settings page where you can set ignore battery optimization. + +> **Warning** +> It only works when the app is in the foreground. + +```dart +Future function() => FlutterForegroundTask.openIgnoreBatteryOptimizationSettings(); +``` + +### :lollipop: requestIgnoreBatteryOptimization (Android) + +Request to ignore battery optimization. + +This function requires the `android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` permission. + +> **Warning** +> It only works when the app is in the foreground. + +```dart +Future function() => FlutterForegroundTask.requestIgnoreBatteryOptimization(); +``` + +### :lollipop: canDrawOverlays (Android) + +Returns whether the `android.permission.SYSTEM_ALERT_WINDOW` permission is granted. + +```dart +Future function() => FlutterForegroundTask.canDrawOverlays; +``` + +### :lollipop: openSystemAlertWindowSettings (Android) + +Open the settings page where you can allow/deny the `android.permission.SYSTEM_ALERT_WINDOW` +permission. + +> **Warning** +> It only works when the app is in the foreground. + +```dart +Future function() => FlutterForegroundTask.openSystemAlertWindowSettings(); +``` + +### :lollipop: checkNotificationPermission + +Returns notification permission status. + +> **Warning** +> It only works when the app is in the foreground. + +```dart +Future function() => FlutterForegroundTask.checkNotificationPermission(); +``` + +### :lollipop: requestNotificationPermission + +Request notification permission. + +> **Warning** +> It only works when the app is in the foreground. + +```dart +Future function() => FlutterForegroundTask.requestNotificationPermission(); +``` + +### :lollipop: canScheduleExactAlarms (Android) + +Returns whether the `android.permission.SCHEDULE_EXACT_ALARM` permission is granted. + +```dart +Future function() => FlutterForegroundTask.canScheduleExactAlarms; +``` + +### :lollipop: openAlarmsAndRemindersSettings (Android) + +Open the alarms & reminders settings page. + +Use this utility only if you provide services that require long-term survival, +such as exact alarm service, healthcare service, or Bluetooth communication. + +This utility requires the `android.permission.SCHEDULE_EXACT_ALARM` permission. +Using this permission may make app distribution difficult due to Google policy. + +> **Warning** +> It only works when the app is in the foreground. + +```dart +Future function() => FlutterForegroundTask.openAlarmsAndRemindersSettings(); +``` diff --git a/local_packages/flutter_foreground_task-9.1.0/example/README.md b/local_packages/flutter_foreground_task-9.1.0/example/README.md new file mode 100644 index 0000000..dd5f895 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/README.md @@ -0,0 +1,16 @@ +# flutter_foreground_task_example + +Demonstrates how to use the flutter_foreground_task plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/local_packages/flutter_foreground_task-9.1.0/example/analysis_options.yaml b/local_packages/flutter_foreground_task-9.1.0/example/analysis_options.yaml new file mode 100644 index 0000000..1f65fcc --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/analysis_options.yaml @@ -0,0 +1,8 @@ +include: package:flutter_lints/flutter.yaml + +linter: + rules: + avoid_print: false + avoid_function_literals_in_foreach_calls: false + constant_identifier_names: false + prefer_void_to_null: false diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/build.gradle b/local_packages/flutter_foreground_task-9.1.0/example/android/app/build.gradle new file mode 100644 index 0000000..051a15b --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/android/app/build.gradle @@ -0,0 +1,73 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + namespace "com.pravera.flutter_foreground_task_example" + + compileSdk 36 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11 + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.pravera.flutter_foreground_task_example" + minSdkVersion 21 + targetSdkVersion 35 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + + debug { + shrinkResources true + minifyEnabled true + proguardFiles getDefaultProguardFile( + 'proguard-android-optimize.txt'), + 'proguard-rules.pro' + } + } +} + +flutter { + source '../..' +} + +dependencies {} diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/proguard-rules.pro b/local_packages/flutter_foreground_task-9.1.0/example/android/app/proguard-rules.pro new file mode 100644 index 0000000..8ef442b --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/android/app/proguard-rules.pro @@ -0,0 +1,7 @@ +-keep class io.flutter.app.** { *; } +-keep class io.flutter.plugin.** { *; } +-keep class io.flutter.util.** { *; } +-keep class io.flutter.view.** { *; } +-keep class io.flutter.** { *; } +-keep class io.flutter.plugins.** { *; } +-dontwarn io.flutter.embedding.** diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/debug/AndroidManifest.xml b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..0715582 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/AndroidManifest.xml b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..143e21c --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/kotlin/com/pravera/flutter_foreground_task_example/MainActivity.kt b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/kotlin/com/pravera/flutter_foreground_task_example/MainActivity.kt new file mode 100644 index 0000000..5497457 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/kotlin/com/pravera/flutter_foreground_task_example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.pravera.flutter_foreground_task_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-hdpi/ic_heart.png b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-hdpi/ic_heart.png new file mode 100644 index 0000000..61f9992 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-hdpi/ic_heart.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-hdpi/ic_home.png b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-hdpi/ic_home.png new file mode 100644 index 0000000..962de2a Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-hdpi/ic_home.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-mdpi/ic_heart.png b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-mdpi/ic_heart.png new file mode 100644 index 0000000..25baa09 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-mdpi/ic_heart.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-mdpi/ic_home.png b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-mdpi/ic_home.png new file mode 100644 index 0000000..f74a6c7 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-mdpi/ic_home.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-v21/launch_background.xml b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-xhdpi/ic_heart.png b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-xhdpi/ic_heart.png new file mode 100644 index 0000000..7560ef0 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-xhdpi/ic_heart.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-xhdpi/ic_home.png b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-xhdpi/ic_home.png new file mode 100644 index 0000000..d7a4ffa Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-xhdpi/ic_home.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-xxhdpi/ic_heart.png b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-xxhdpi/ic_heart.png new file mode 100644 index 0000000..da0c3aa Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-xxhdpi/ic_heart.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-xxhdpi/ic_home.png b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-xxhdpi/ic_home.png new file mode 100644 index 0000000..8c2ee5f Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-xxhdpi/ic_home.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-xxxhdpi/ic_heart.png b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-xxxhdpi/ic_heart.png new file mode 100644 index 0000000..af5cce9 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-xxxhdpi/ic_heart.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-xxxhdpi/ic_home.png b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-xxxhdpi/ic_home.png new file mode 100644 index 0000000..e2716a3 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable-xxxhdpi/ic_home.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable/launch_background.xml b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/values-night/styles.xml b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..449a9f9 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/values/styles.xml b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..d74aa35 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/profile/AndroidManifest.xml b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..1b51645 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/build.gradle b/local_packages/flutter_foreground_task-9.1.0/example/android/build.gradle new file mode 100644 index 0000000..bc157bd --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/android/build.gradle @@ -0,0 +1,18 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/gradle.properties b/local_packages/flutter_foreground_task-9.1.0/example/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/gradle/wrapper/gradle-wrapper.properties b/local_packages/flutter_foreground_task-9.1.0/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..7aeeb11 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip diff --git a/local_packages/flutter_foreground_task-9.1.0/example/android/settings.gradle b/local_packages/flutter_foreground_task-9.1.0/example/android/settings.gradle new file mode 100644 index 0000000..bf71fb9 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/android/settings.gradle @@ -0,0 +1,26 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.6.0" apply false + id "org.jetbrains.kotlin.android" version "1.9.10" apply false +} + +include ":app" diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Flutter/AppFrameworkInfo.plist b/local_packages/flutter_foreground_task-9.1.0/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Flutter/Debug.xcconfig b/local_packages/flutter_foreground_task-9.1.0/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Flutter/Release.xcconfig b/local_packages/flutter_foreground_task-9.1.0/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Podfile b/local_packages/flutter_foreground_task-9.1.0/example/ios/Podfile new file mode 100644 index 0000000..279576f --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcodeproj/project.pbxproj b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..6cc3c2b --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,557 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 37B6493A4744452DDC4DEBF7 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A32D55799B66D9DF641C7ED8 /* Pods_Runner.framework */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 2EDD805CC77857CB5704E03A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 8BA7286763F4F5A5D4D4791F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9DCDDA24B41ECFF3C6781683 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + A32D55799B66D9DF641C7ED8 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 37B6493A4744452DDC4DEBF7 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2105D355AEE26F31BDDE1572 /* Pods */ = { + isa = PBXGroup; + children = ( + 8BA7286763F4F5A5D4D4791F /* Pods-Runner.debug.xcconfig */, + 9DCDDA24B41ECFF3C6781683 /* Pods-Runner.release.xcconfig */, + 2EDD805CC77857CB5704E03A /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 2105D355AEE26F31BDDE1572 /* Pods */, + A07F075276D23FBA8C723152 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + A07F075276D23FBA8C723152 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A32D55799B66D9DF641C7ED8 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 8D396599160DECB90EDD47BE /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 6EA6E717D2A4A7547969F4C2 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 6EA6E717D2A4A7547969F4C2 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 8D396599160DECB90EDD47BE /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPat_icon_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 2BHAXN696Y; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.pravera.flutterForegroundTaskExam; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPat_icon_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 2BHAXN696Y; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.pravera.flutterForegroundTaskExam; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPat_icon_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 2BHAXN696Y; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.pravera.flutterForegroundTaskExam; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..6770915 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/AppDelegate.swift b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..e35f9ba --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,21 @@ +import UIKit +import Flutter + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + + SwiftFlutterForegroundTaskPlugin.setPluginRegistrantCallback { registry in + GeneratedPluginRegistrant.register(with: registry) + } + if #available(iOS 10.0, *) { + UNUserNotificationCenter.current().delegate = self + } + + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..28c6bf0 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..f091b6b Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cde121 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d0ef06e Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..dcdc230 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..c8f9ed8 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..75b2d16 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..c4df70d Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..6a84f41 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..d0e1f58 Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +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. \ No newline at end of file diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Base.lproj/Main.storyboard b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Info.plist b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Info.plist new file mode 100644 index 0000000..8bb4e7e --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Info.plist @@ -0,0 +1,57 @@ + + + + + BGTaskSchedulerPermittedIdentifiers + + com.pravera.flutter_foreground_task.refresh + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + flutter_foreground_task_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSupportsIndirectInputEvents + + UIBackgroundModes + + fetch + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Runner-Bridging-Header.h b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..10e9f02 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1,2 @@ +#import "GeneratedPluginRegistrant.h" +#import diff --git a/local_packages/flutter_foreground_task-9.1.0/example/lib/main.dart b/local_packages/flutter_foreground_task-9.1.0/example/lib/main.dart new file mode 100644 index 0000000..2d9ac57 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/lib/main.dart @@ -0,0 +1,304 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_foreground_task/flutter_foreground_task.dart'; + +void main() { + // Initialize port for communication between TaskHandler and UI. + FlutterForegroundTask.initCommunicationPort(); + runApp(const ExampleApp()); +} + +// The callback function should always be a top-level or static function. +@pragma('vm:entry-point') +void startCallback() { + FlutterForegroundTask.setTaskHandler(MyTaskHandler()); +} + +class MyTaskHandler extends TaskHandler { + static const String incrementCountCommand = 'incrementCount'; + + int _count = 0; + + void _incrementCount() { + _count++; + + // Update notification content. + FlutterForegroundTask.updateService( + notificationTitle: 'Hello MyTaskHandler :)', + notificationText: 'count: $_count', + ); + + // Send data to main isolate. + FlutterForegroundTask.sendDataToMain(_count); + } + + // Called when the task is started. + @override + Future onStart(DateTime timestamp, TaskStarter starter) async { + print('onStart(starter: ${starter.name})'); + _incrementCount(); + } + + // Called based on the eventAction set in ForegroundTaskOptions. + @override + void onRepeatEvent(DateTime timestamp) { + _incrementCount(); + } + + // Called when the task is destroyed. + @override + Future onDestroy(DateTime timestamp, bool isTimeout) async { + print('onDestroy(isTimeout: $isTimeout)'); + } + + // Called when data is sent using `FlutterForegroundTask.sendDataToTask`. + @override + void onReceiveData(Object data) { + print('onReceiveData: $data'); + if (data == incrementCountCommand) { + _incrementCount(); + } + } + + // Called when the notification button is pressed. + @override + void onNotificationButtonPressed(String id) { + print('onNotificationButtonPressed: $id'); + } + + // Called when the notification itself is pressed. + @override + void onNotificationPressed() { + print('onNotificationPressed'); + } + + // Called when the notification itself is dismissed. + @override + void onNotificationDismissed() { + print('onNotificationDismissed'); + } +} + +class ExampleApp extends StatelessWidget { + const ExampleApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + routes: { + '/': (context) => const ExamplePage(), + '/second': (context) => const SecondPage(), + }, + initialRoute: '/', + ); + } +} + +class ExamplePage extends StatefulWidget { + const ExamplePage({super.key}); + + @override + State createState() => _ExamplePageState(); +} + +class _ExamplePageState extends State { + final ValueNotifier _taskDataListenable = ValueNotifier(null); + + Future _requestPermissions() async { + // Android 13+, you need to allow notification permission to display foreground service notification. + // + // iOS: If you need notification, ask for permission. + final NotificationPermission notificationPermission = + await FlutterForegroundTask.checkNotificationPermission(); + if (notificationPermission != NotificationPermission.granted) { + await FlutterForegroundTask.requestNotificationPermission(); + } + + if (Platform.isAndroid) { + // Android 12+, there are restrictions on starting a foreground service. + // + // To restart the service on device reboot or unexpected problem, you need to allow below permission. + if (!await FlutterForegroundTask.isIgnoringBatteryOptimizations) { + // This function requires `android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` permission. + await FlutterForegroundTask.requestIgnoreBatteryOptimization(); + } + + // Use this utility only if you provide services that require long-term survival, + // such as exact alarm service, healthcare service, or Bluetooth communication. + // + // This utility requires the "android.permission.SCHEDULE_EXACT_ALARM" permission. + // Using this permission may make app distribution difficult due to Google policy. + if (!await FlutterForegroundTask.canScheduleExactAlarms) { + // When you call this function, will be gone to the settings page. + // So you need to explain to the user why set it. + await FlutterForegroundTask.openAlarmsAndRemindersSettings(); + } + } + } + + void _initService() { + FlutterForegroundTask.init( + androidNotificationOptions: AndroidNotificationOptions( + channelId: 'foreground_service', + channelName: 'Foreground Service Notification', + channelDescription: + 'This notification appears when the foreground service is running.', + onlyAlertOnce: true, + ), + iosNotificationOptions: const IOSNotificationOptions( + showNotification: false, + playSound: false, + ), + foregroundTaskOptions: ForegroundTaskOptions( + eventAction: ForegroundTaskEventAction.repeat(5000), + autoRunOnBoot: true, + autoRunOnMyPackageReplaced: true, + allowWakeLock: true, + allowWifiLock: true, + ), + ); + } + + Future _startService() async { + if (await FlutterForegroundTask.isRunningService) { + return FlutterForegroundTask.restartService(); + } else { + return FlutterForegroundTask.startService( + // You can manually specify the foregroundServiceType for the service + // to be started, as shown in the comment below. + // serviceTypes: [ + // ForegroundServiceTypes.dataSync, + // ForegroundServiceTypes.remoteMessaging, + // ], + serviceId: 256, + notificationTitle: 'Foreground Service is running', + notificationText: 'Tap to return to the app', + notificationIcon: null, + notificationButtons: [ + const NotificationButton(id: 'btn_hello', text: 'hello'), + ], + notificationInitialRoute: '/second', + callback: startCallback, + ); + } + } + + Future _stopService() { + return FlutterForegroundTask.stopService(); + } + + void _onReceiveTaskData(Object data) { + print('onReceiveTaskData: $data'); + _taskDataListenable.value = data; + } + + void _incrementCount() { + FlutterForegroundTask.sendDataToTask(MyTaskHandler.incrementCountCommand); + } + + @override + void initState() { + super.initState(); + // Add a callback to receive data sent from the TaskHandler. + FlutterForegroundTask.addTaskDataCallback(_onReceiveTaskData); + + WidgetsBinding.instance.addPostFrameCallback((_) { + // Request permissions and initialize the service. + _requestPermissions(); + _initService(); + }); + } + + @override + void dispose() { + // Remove a callback to receive data sent from the TaskHandler. + FlutterForegroundTask.removeTaskDataCallback(_onReceiveTaskData); + _taskDataListenable.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + // ** optional ** + // A widget that minimize the app without closing it when the user presses + // the soft back button. It only works when the service is running. + // + // This widget must be declared above the [Scaffold] widget. + return WithForegroundTask( + child: Scaffold( + appBar: AppBar( + title: const Text('Flutter Foreground Task'), + centerTitle: true, + ), + body: SafeArea( + child: Column( + children: [ + Expanded(child: _buildCommunicationDataText()), + _buildServiceControlButtons(), + ], + ), + ), + ), + ); + } + + Widget _buildCommunicationDataText() { + return ValueListenableBuilder( + valueListenable: _taskDataListenable, + builder: (context, data, _) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('You received data from TaskHandler:'), + Text('$data', style: Theme.of(context).textTheme.headlineMedium), + ], + ), + ); + }, + ); + } + + Widget _buildServiceControlButtons() { + buttonBuilder(String text, {VoidCallback? onPressed}) { + return ElevatedButton( + onPressed: onPressed, + child: Text(text), + ); + } + + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + buttonBuilder('start service', onPressed: _startService), + buttonBuilder('stop service', onPressed: _stopService), + buttonBuilder('increment count', onPressed: _incrementCount), + ], + ), + ); + } +} + +class SecondPage extends StatelessWidget { + const SecondPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Second Page'), + centerTitle: true, + ), + body: Center( + child: ElevatedButton( + onPressed: Navigator.of(context).pop, + child: const Text('pop this page'), + ), + ), + ); + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/example/pubspec.yaml b/local_packages/flutter_foreground_task-9.1.0/example/pubspec.yaml new file mode 100644 index 0000000..b0bbe59 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/example/pubspec.yaml @@ -0,0 +1,67 @@ +name: flutter_foreground_task_example +description: Demonstrates how to use the flutter_foreground_task plugin. + +# The following line prevents the package from being accidentally published to +# pub.dev using `pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +environment: + sdk: ^3.4.0 + flutter: ">=3.22.0" + +dependencies: + flutter: + sdk: flutter + + flutter_foreground_task: + # When depending on this package from a real application you should use: + # flutter_foreground_task: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + +dev_dependencies: + flutter_test: + sdk: flutter + + flutter_lints: ^5.0.0 + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/FlutterForegroundTaskLifecycleListener.swift b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/FlutterForegroundTaskLifecycleListener.swift new file mode 100644 index 0000000..c487900 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/FlutterForegroundTaskLifecycleListener.swift @@ -0,0 +1,35 @@ +// +// FlutterForegroundTaskLifecycleListener.swift +// flutter_foreground_task +// +// Created by Woo Jin Hwang on 7/15/24. +// + +import Foundation +import Flutter + +/** A listener that can listen to the task lifecycle events. */ +public protocol FlutterForegroundTaskLifecycleListener : AnyObject { + /** + * Each time a task starts, a new FlutterEngine is created. + * + * This is called before onTaskStart, Initialize the service you want to use in the task. (like PlatformChannel initialization) + */ + func onEngineCreate(flutterEngine: FlutterEngine?) + + /** Called when the task is started. */ + func onTaskStart(starter: FlutterForegroundTaskStarter) + + /** Called based on the eventAction set in ForegroundTaskOptions. */ + func onTaskRepeatEvent() + + /** Called when the task is destroyed. */ + func onTaskDestroy() + + /** + * If one task is finished or replaced by another, the FlutterEngine is destroyed. + * + * This is called after onTaskDestroy, where dispose the service that was initialized in onEngineCreate. + */ + func onEngineWillDestroy() +} diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/FlutterForegroundTaskPlugin.h b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/FlutterForegroundTaskPlugin.h new file mode 100644 index 0000000..db8610b --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/FlutterForegroundTaskPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface FlutterForegroundTaskPlugin : NSObject +@end diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/FlutterForegroundTaskPlugin.m b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/FlutterForegroundTaskPlugin.m new file mode 100644 index 0000000..e4e7a71 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/FlutterForegroundTaskPlugin.m @@ -0,0 +1,18 @@ +#import "FlutterForegroundTaskPlugin.h" +#if __has_include() +#import +#else +// Support project import fallback if the generated compatibility header +// is not copied when this plugin is created as a library. +// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 +#import "flutter_foreground_task-Swift.h" +#endif + +@implementation FlutterForegroundTaskPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + [SwiftFlutterForegroundTaskPlugin registerWithRegistrar:registrar]; +} ++ (void)setPluginRegistrantCallback:(FlutterPluginRegistrantCallback)callback { + [SwiftFlutterForegroundTaskPlugin setPluginRegistrantCallback:callback]; +} +@end diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/FlutterForegroundTaskStarter.swift b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/FlutterForegroundTaskStarter.swift new file mode 100644 index 0000000..cf69cf8 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/FlutterForegroundTaskStarter.swift @@ -0,0 +1,13 @@ +// +// FlutterForegroundTaskStarter.swift +// flutter_foreground_task +// +// Created by Woo Jin Hwang on 9/23/24. +// + +import Foundation + +public enum FlutterForegroundTaskStarter : Int { + case DEVELOPER = 0 + case SYSTEM = 1 +} diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/PreferencesKey.swift b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/PreferencesKey.swift new file mode 100644 index 0000000..da46a24 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/PreferencesKey.swift @@ -0,0 +1,26 @@ +// +// BackgroundServicePrefsKey.swift +// flutter_foreground_task +// +// Created by WOO JIN HWANG on 2021/08/11. +// + +// service status +let BACKGROUND_SERVICE_ACTION = "backgroundServiceAction" + +// notification options +let SHOW_NOTIFICATION = "showNotification" +let PLAY_SOUND = "playSound" + +// notification content +let NOTIFICATION_CONTENT_TITLE = "notificationContentTitle" +let NOTIFICATION_CONTENT_TEXT = "notificationContentText" +let NOTIFICATION_CONTENT_BUTTONS = "buttons" + +// task options +let TASK_EVENT_ACTION = "taskEventAction" // new +let INTERVAL = "interval" // deprecated +let IS_ONCE_EVENT = "isOnceEvent" // deprecated + +// task data +let CALLBACK_HANDLE = "callbackHandle" diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/SwiftFlutterForegroundTaskPlugin.swift b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/SwiftFlutterForegroundTaskPlugin.swift new file mode 100644 index 0000000..8c163e6 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/SwiftFlutterForegroundTaskPlugin.swift @@ -0,0 +1,191 @@ +import Flutter +import UIKit +import BackgroundTasks + +public class SwiftFlutterForegroundTaskPlugin: NSObject, FlutterPlugin { + // ====================== Plugin ====================== + static private(set) var registerPlugins: FlutterPluginRegistrantCallback? = nil + + private var notificationPermissionManager: NotificationPermissionManager? = nil + private var backgroundServiceManager: BackgroundServiceManager? = nil + + private var foregroundChannel: FlutterMethodChannel? = nil + + public static func register(with registrar: FlutterPluginRegistrar) { + let instance = SwiftFlutterForegroundTaskPlugin() + instance.initServices() + instance.initChannels(registrar.messenger()) + registrar.addApplicationDelegate(instance) + } + + public static func setPluginRegistrantCallback(_ callback: @escaping FlutterPluginRegistrantCallback) { + registerPlugins = callback + } + + public static func addTaskLifecycleListener(_ listener: FlutterForegroundTaskLifecycleListener) { + BackgroundService.sharedInstance.addTaskLifecycleListener(listener) + } + + public static func removeTaskLifecycleListener(_ listener: FlutterForegroundTaskLifecycleListener) { + BackgroundService.sharedInstance.removeTaskLifecycleListener(listener) + } + + private func initServices() { + notificationPermissionManager = NotificationPermissionManager() + backgroundServiceManager = BackgroundServiceManager() + } + + private func initChannels(_ messenger: FlutterBinaryMessenger) { + foregroundChannel = FlutterMethodChannel(name: "flutter_foreground_task/methods", binaryMessenger: messenger) + foregroundChannel?.setMethodCallHandler(onMethodCall) + } + + private func onMethodCall(call: FlutterMethodCall, result: @escaping FlutterResult) { + let args = call.arguments + do { + switch call.method { + case "checkNotificationPermission": + notificationPermissionManager!.checkPermission { permission in + result(permission.rawValue) + } + case "requestNotificationPermission": + notificationPermissionManager!.requestPermission { permission in + result(permission.rawValue) + } + case "startService": + try backgroundServiceManager!.start(arguments: args) + result(true) + case "restartService": + try backgroundServiceManager!.restart(arguments: args) + result(true) + case "updateService": + try backgroundServiceManager!.update(arguments: args) + result(true) + case "stopService": + try backgroundServiceManager!.stop() + result(true) + case "sendData": + backgroundServiceManager!.sendData(data: args) + case "isRunningService": + result(backgroundServiceManager!.isRunningService()) + case "minimizeApp": + UIControl().sendAction(#selector(URLSessionTask.suspend), to: UIApplication.shared, for: nil) + case "isAppOnForeground": + result(UIApplication.shared.applicationState == .active) + default: + result(FlutterMethodNotImplemented) + } + } catch { + let code = String(describing: error.self) + let message = error.localizedDescription + let flutterError = FlutterError(code: code, message: message, details: nil) + result(flutterError) + } + } + + // ================== App Lifecycle =================== + public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [AnyHashable : Any] = [:]) -> Bool { + UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum) + if #available(iOS 13.0, *) { + SwiftFlutterForegroundTaskPlugin.registerAppRefresh() + } + return true + } + + public func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) -> Bool { + completionHandler(.newData) + return true + } + + public func applicationDidEnterBackground(_ application: UIApplication) { + if #available(iOS 13.0, *) { + SwiftFlutterForegroundTaskPlugin.scheduleAppRefresh() + } + } + + public func applicationWillTerminate(_ application: UIApplication) { + if !BackgroundService.sharedInstance.isRunningService { + return + } + + BackgroundServiceStatus.setData(action: BackgroundServiceAction.APP_TERMINATE) + BackgroundService.sharedInstance.run() + + // Chance to handle onDestroy before app terminates + sleep(5) + } + + // ================= Service Delegate ================= + @available(iOS 10.0, *) + public func userNotificationCenter(_ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void) { + BackgroundService.sharedInstance.userNotificationCenter(center, response, completionHandler) + } + + @available(iOS 10.0, *) + public func userNotificationCenter(_ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + BackgroundService.sharedInstance.userNotificationCenter(center, notification, completionHandler) + } + + // ============== Background App Refresh ============== + public static var refreshIdentifier: String = "com.pravera.flutter_foreground_task.refresh" + + @available(iOS 13.0, *) + private static func registerAppRefresh() { + BGTaskScheduler.shared.register(forTaskWithIdentifier: refreshIdentifier, using: nil) { task in + handleAppRefresh(task: task as! BGAppRefreshTask) + } + } + + @available(iOS 13.0, *) + private static func scheduleAppRefresh() { + let request = BGAppRefreshTaskRequest(identifier: refreshIdentifier) + request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) + + do { + try BGTaskScheduler.shared.submit(request) + } catch { + print("Could not schedule app refresh: \(error)") + } + } + + @available(iOS 13.0, *) + private static func cancelAppRefresh() { + BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: refreshIdentifier) + } + + @available(iOS 13.0, *) + private static func handleAppRefresh(task: BGAppRefreshTask) { + let queue = OperationQueue() + let operation = AppRefreshOperation() + + task.expirationHandler = { + operation.cancel() + } + + operation.completionBlock = { + // Schedule a new refresh task + scheduleAppRefresh() + + task.setTaskCompleted(success: true) + } + + queue.addOperation(operation) + } +} + +class AppRefreshOperation: Operation { + override func main() { + let semaphore = DispatchSemaphore(value: 0) + + // avoid non-platform thread + DispatchQueue.main.asyncAfter(deadline: .now() + 25) { + semaphore.signal() + } + + semaphore.wait() + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/errors/ServiceError.swift b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/errors/ServiceError.swift new file mode 100644 index 0000000..c3f8bb3 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/errors/ServiceError.swift @@ -0,0 +1,30 @@ +// +// ServiceError.swift +// flutter_foreground_task +// +// Created by Woo Jin Hwang on 7/17/24. +// + +import Foundation + +enum ServiceError { + case ServiceArgumentNullException + case ServiceAlreadyStartedException + case ServiceNotStartedException + case ServiceNotSupportedException +} + +extension ServiceError : LocalizedError { + public var errorDescription: String? { + switch self { + case .ServiceArgumentNullException: + return NSLocalizedString("The required argument was not passed to the service.", comment: "ServiceArgumentNullException") + case .ServiceAlreadyStartedException: + return NSLocalizedString("The service has already started.", comment: "ServiceAlreadyStartedException") + case .ServiceNotStartedException: + return NSLocalizedString("The service is not started.", comment: "ServiceNotStartedException") + case .ServiceNotSupportedException: + return NSLocalizedString("The current iOS version does not support the service.", comment: "ServiceNotSupportedException") + } + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/BackgroundServiceAction.swift b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/BackgroundServiceAction.swift new file mode 100644 index 0000000..4c39f27 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/BackgroundServiceAction.swift @@ -0,0 +1,17 @@ +// +// BackgroundServiceAction.swift +// flutter_foreground_task +// +// Created by WOO JIN HWANG on 2021/08/11. +// + +import Foundation + +enum BackgroundServiceAction: String { + case API_START + case API_RESTART + case API_UPDATE + case API_STOP + + case APP_TERMINATE +} diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/BackgroundServiceStatus.swift b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/BackgroundServiceStatus.swift new file mode 100644 index 0000000..7bd9599 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/BackgroundServiceStatus.swift @@ -0,0 +1,25 @@ +// +// BackgroundServiceStatus.swift +// flutter_foreground_task +// +// Created by Woo Jin Hwang on 9/13/24. +// + +import Foundation + +struct BackgroundServiceStatus { + let action: BackgroundServiceAction + + static func getData() -> BackgroundServiceStatus { + let prefs = UserDefaults.standard + let actionValue = prefs.string(forKey: BACKGROUND_SERVICE_ACTION) ?? BackgroundServiceAction.API_STOP.rawValue + let action = BackgroundServiceAction(rawValue: actionValue)! + + return BackgroundServiceStatus(action: action) + } + + static func setData(action: BackgroundServiceAction) { + let prefs = UserDefaults.standard + prefs.set(action.rawValue, forKey: BACKGROUND_SERVICE_ACTION) + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/ForegroundTaskData.swift b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/ForegroundTaskData.swift new file mode 100644 index 0000000..1a35a26 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/ForegroundTaskData.swift @@ -0,0 +1,42 @@ +// +// ForegroundTaskData.swift +// flutter_foreground_task +// +// Created by Woo Jin Hwang on 9/24/24. +// + +import Foundation + +struct ForegroundTaskData { + let callbackHandle: Int64? + + static func getData() -> ForegroundTaskData { + let prefs = UserDefaults.standard + + let callbackHandle = prefs.object(forKey: CALLBACK_HANDLE) as? Int64 + + return ForegroundTaskData(callbackHandle: callbackHandle) + } + + static func setData(args: Dictionary) { + let prefs = UserDefaults.standard + + prefs.removeObject(forKey: CALLBACK_HANDLE) + if let callbackHandle = args[CALLBACK_HANDLE] as? Int64 { + prefs.set(callbackHandle, forKey: CALLBACK_HANDLE) + } + } + + static func updateData(args: Dictionary) { + let prefs = UserDefaults.standard + + if let callbackHandle = args[CALLBACK_HANDLE] as? Int64 { + prefs.set(callbackHandle, forKey: CALLBACK_HANDLE) + } + } + + static func clearData() { + let prefs = UserDefaults.standard + prefs.removeObject(forKey: CALLBACK_HANDLE) + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/ForegroundTaskEventAction.swift b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/ForegroundTaskEventAction.swift new file mode 100644 index 0000000..ac52a1b --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/ForegroundTaskEventAction.swift @@ -0,0 +1,39 @@ +// +// ForegroundTaskEventAction.swift +// flutter_foreground_task +// +// Created by Woo Jin Hwang on 8/28/24. +// + +import Foundation + +private let TASK_EVENT_TYPE_KEY = "taskEventType" +private let TASK_EVENT_INTERVAL_KEY = "taskEventInterval" + +struct ForegroundTaskEventAction: Equatable { + let type: ForegroundTaskEventType + let interval: Int + + static func fromJsonString(_ jsonString: String) -> ForegroundTaskEventAction { + var type: ForegroundTaskEventType = .NOTHING + var interval: Int = 5000 + + if let jsonData = jsonString.data(using: .utf8) { + if let jsonObj = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? Dictionary { + if let _type = jsonObj[TASK_EVENT_TYPE_KEY] as? Int { + type = ForegroundTaskEventType.fromValue(_type) + } + + if let _interval = jsonObj[TASK_EVENT_INTERVAL_KEY] as? Int { + interval = _interval + } + } + } + + return ForegroundTaskEventAction(type: type, interval: interval) + } + + static func == (lhs: Self, rhs: Self) -> Bool { + return lhs.type.rawValue == rhs.type.rawValue && lhs.interval == rhs.interval + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/ForegroundTaskEventType.swift b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/ForegroundTaskEventType.swift new file mode 100644 index 0000000..224eba1 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/ForegroundTaskEventType.swift @@ -0,0 +1,18 @@ +// +// ForegroundTaskEventType.swift +// flutter_foreground_task +// +// Created by Woo Jin Hwang on 8/28/24. +// + +import Foundation + +enum ForegroundTaskEventType: Int { + case NOTHING = 1 + case ONCE = 2 + case REPEAT = 3 + + static func fromValue(_ value: Int) -> ForegroundTaskEventType { + return ForegroundTaskEventType(rawValue: value) ?? .NOTHING + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/ForegroundTaskOptions.swift b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/ForegroundTaskOptions.swift new file mode 100644 index 0000000..c5cb1f9 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/ForegroundTaskOptions.swift @@ -0,0 +1,64 @@ +// +// ForegroundTaskOptions.swift +// flutter_foreground_task +// +// Created by Woo Jin Hwang on 9/24/24. +// + +import Foundation + +struct ForegroundTaskOptions { + let eventAction: ForegroundTaskEventAction + + static func getData() -> ForegroundTaskOptions { + let prefs = UserDefaults.standard + + let eventActionJsonString = prefs.string(forKey: TASK_EVENT_ACTION) + let eventAction: ForegroundTaskEventAction + if eventActionJsonString != nil { + eventAction = ForegroundTaskEventAction.fromJsonString(eventActionJsonString!) + } else { + // for deprecated api + let oldIsOnceEvent = prefs.bool(forKey: IS_ONCE_EVENT) + let oldInterval = prefs.integer(forKey: INTERVAL) + if oldIsOnceEvent { + eventAction = ForegroundTaskEventAction(type: .ONCE, interval: oldInterval) + } else { + eventAction = ForegroundTaskEventAction(type: .REPEAT, interval: oldInterval) + } + } + + return ForegroundTaskOptions(eventAction: eventAction) + } + + static func setData(args: Dictionary) { + let prefs = UserDefaults.standard + + if let eventActionJson = args[TASK_EVENT_ACTION] as? Dictionary { + if let eventActionJsonData = try? JSONSerialization.data(withJSONObject: eventActionJson, options: []) { + if let eventActionJsonString = String(data: eventActionJsonData, encoding: .utf8) { + prefs.set(eventActionJsonString, forKey: TASK_EVENT_ACTION) + } + } + } + } + + static func updateData(args: Dictionary) { + let prefs = UserDefaults.standard + + if let eventActionJson = args[TASK_EVENT_ACTION] as? Dictionary { + if let eventActionJsonData = try? JSONSerialization.data(withJSONObject: eventActionJson, options: []) { + if let eventActionJsonString = String(data: eventActionJsonData, encoding: .utf8) { + prefs.set(eventActionJsonString, forKey: TASK_EVENT_ACTION) + } + } + } + } + + static func clearData() { + let prefs = UserDefaults.standard + prefs.removeObject(forKey: TASK_EVENT_ACTION) // new + prefs.removeObject(forKey: INTERVAL) // deprecated + prefs.removeObject(forKey: IS_ONCE_EVENT) // deprecated + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/NotificationButton.swift b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/NotificationButton.swift new file mode 100644 index 0000000..5ea1870 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/NotificationButton.swift @@ -0,0 +1,33 @@ +// +// NotificationButton.swift +// flutter_foreground_task +// +// Created by Woo Jin Hwang on 8/5/24. +// + +import Foundation + +private let ID_KEY = "id" +private let TEXT_KEY = "text" + +struct NotificationButton { + let id: String + let text: String + + static func fromJSONObject(_ jsonObj: Any) -> NotificationButton { + var id: String = "unknown" + var text: String = "" + + if let _jsonObj = jsonObj as? Dictionary { + if let _id = _jsonObj[ID_KEY] as? String { + id = _id + } + + if let _text = _jsonObj[TEXT_KEY] as? String { + text = _text + } + } + + return NotificationButton(id: id, text: text) + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/NotificationContent.swift b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/NotificationContent.swift new file mode 100644 index 0000000..2d44f3c --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/NotificationContent.swift @@ -0,0 +1,79 @@ +// +// NotificationContent.swift +// flutter_foreground_task +// +// Created by Woo Jin Hwang on 8/2/24. +// + +import Foundation + +struct NotificationContent { + let title: String + let text: String + let buttons: Array + + static func getData() -> NotificationContent { + let prefs = UserDefaults.standard + + let title = prefs.string(forKey: NOTIFICATION_CONTENT_TITLE) ?? "" + let text = prefs.string(forKey: NOTIFICATION_CONTENT_TEXT) ?? "" + + var buttons: [NotificationButton] = [] + if let buttonsJsonString = prefs.string(forKey: NOTIFICATION_CONTENT_BUTTONS) { + if let buttonsJsonData = buttonsJsonString.data(using: .utf8) { + if let buttonsJsonArr = try? JSONSerialization.jsonObject(with: buttonsJsonData, options: []) as? [Any] { + for buttonJsonObj in buttonsJsonArr { + buttons.append(NotificationButton.fromJSONObject(buttonJsonObj)) + } + } + } + } + + return NotificationContent(title: title, text: text, buttons: buttons) + } + + static func setData(args: Dictionary) { + let prefs = UserDefaults.standard + + let title = args[NOTIFICATION_CONTENT_TITLE] as? String ?? "" + prefs.set(title, forKey: NOTIFICATION_CONTENT_TITLE) + + let text = args[NOTIFICATION_CONTENT_TEXT] as? String ?? "" + prefs.set(text, forKey: NOTIFICATION_CONTENT_TEXT) + + if let buttonsJson = args[NOTIFICATION_CONTENT_BUTTONS] as? [Any] { + if let buttonsJsonData = try? JSONSerialization.data(withJSONObject: buttonsJson, options: []) { + if let buttonsJsonString = String(data: buttonsJsonData, encoding: .utf8) { + prefs.set(buttonsJsonString, forKey: NOTIFICATION_CONTENT_BUTTONS) + } + } + } + } + + static func updateData(args: Dictionary) { + let prefs = UserDefaults.standard + + if let title = args[NOTIFICATION_CONTENT_TITLE] as? String { + prefs.set(title, forKey: NOTIFICATION_CONTENT_TITLE) + } + + if let text = args[NOTIFICATION_CONTENT_TEXT] as? String { + prefs.set(text, forKey: NOTIFICATION_CONTENT_TEXT) + } + + if let buttonsJson = args[NOTIFICATION_CONTENT_BUTTONS] as? [Any] { + if let buttonsJsonData = try? JSONSerialization.data(withJSONObject: buttonsJson, options: []) { + if let buttonsJsonString = String(data: buttonsJsonData, encoding: .utf8) { + prefs.set(buttonsJsonString, forKey: NOTIFICATION_CONTENT_BUTTONS) + } + } + } + } + + static func clearData() { + let prefs = UserDefaults.standard + prefs.removeObject(forKey: NOTIFICATION_CONTENT_TITLE) + prefs.removeObject(forKey: NOTIFICATION_CONTENT_TEXT) + prefs.removeObject(forKey: NOTIFICATION_CONTENT_BUTTONS) + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/NotificationOptions.swift b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/NotificationOptions.swift new file mode 100644 index 0000000..14fa854 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/NotificationOptions.swift @@ -0,0 +1,38 @@ +// +// NotificationOptions.swift +// flutter_foreground_task +// +// Created by Woo Jin Hwang on 8/2/24. +// + +import Foundation + +struct NotificationOptions { + let showNotification: Bool + let playSound: Bool + + static func getData() -> NotificationOptions { + let prefs = UserDefaults.standard + + let showNotification = prefs.bool(forKey: SHOW_NOTIFICATION) + let playSound = prefs.bool(forKey: PLAY_SOUND) + + return NotificationOptions(showNotification: showNotification, playSound: playSound) + } + + static func setData(args: Dictionary) { + let prefs = UserDefaults.standard + + let showNotification = args[SHOW_NOTIFICATION] as? Bool ?? false + let playSound = args[PLAY_SOUND] as? Bool ?? false + + prefs.set(showNotification, forKey: SHOW_NOTIFICATION) + prefs.set(playSound, forKey: PLAY_SOUND) + } + + static func clearData() { + let prefs = UserDefaults.standard + prefs.removeObject(forKey: SHOW_NOTIFICATION) + prefs.removeObject(forKey: PLAY_SOUND) + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/NotificationPermission.swift b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/NotificationPermission.swift new file mode 100644 index 0000000..157bd9d --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/models/NotificationPermission.swift @@ -0,0 +1,13 @@ +// +// NotificationPermission.swift +// flutter_foreground_task +// +// Created by Woo Jin Hwang on 8/6/24. +// + +import Foundation + +enum NotificationPermission : Int { + case GRANTED = 0 + case DENIED = 1 +} diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/service/BackgroundService.swift b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/service/BackgroundService.swift new file mode 100644 index 0000000..4029ad8 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/service/BackgroundService.swift @@ -0,0 +1,206 @@ +// +// BackgroundService.swift +// flutter_foreground_task +// +// Created by WOO JIN HWANG on 2021/08/11. +// + +import Flutter +import Foundation +import UserNotifications + +private let NOTIFICATION_ID = "flutter_foreground_task/notification" +private let NOTIFICATION_CATEGORY_ID = "flutter_foreground_task/notification_category" + +private let ACTION_RECEIVE_DATA = "onReceiveData" +private let ACTION_NOTIFICATION_BUTTON_PRESSED = "onNotificationButtonPressed" +private let ACTION_NOTIFICATION_PRESSED = "onNotificationPressed" +private let ACTION_NOTIFICATION_DISMISSED = "onNotificationDismissed" + +@available(iOS 10.0, *) +class BackgroundService: NSObject { + static let sharedInstance = BackgroundService() + + private(set) var isRunningService: Bool = false + + private var foregroundTask: ForegroundTask? = nil + private var taskLifecycleListeners = ForegroundTaskLifecycleListeners() + + func sendData(data: Any?) { + if isRunningService { + foregroundTask?.invokeMethod(ACTION_RECEIVE_DATA, arguments: data) + } + } + + func addTaskLifecycleListener(_ listener: FlutterForegroundTaskLifecycleListener) { + taskLifecycleListeners.addListener(listener) + } + + func removeTaskLifecycleListener(_ listener: FlutterForegroundTaskLifecycleListener) { + taskLifecycleListeners.removeListener(listener) + } + + private let notificationCenter: UNUserNotificationCenter + private let notificationPermissionManager: NotificationPermissionManager + private var canReceiveNotificationResponse: Bool = false + + private var backgroundServiceStatus: BackgroundServiceStatus + private var notificationOptions: NotificationOptions + private var notificationContent: NotificationContent + private var prevForegroundTaskOptions: ForegroundTaskOptions? + private var currForegroundTaskOptions: ForegroundTaskOptions + private var prevForegroundTaskData: ForegroundTaskData? + private var currForegroundTaskData: ForegroundTaskData + + override init() { + notificationCenter = UNUserNotificationCenter.current() + notificationPermissionManager = NotificationPermissionManager() + backgroundServiceStatus = BackgroundServiceStatus.getData() + notificationOptions = NotificationOptions.getData() + notificationContent = NotificationContent.getData() + currForegroundTaskOptions = ForegroundTaskOptions.getData() + currForegroundTaskData = ForegroundTaskData.getData() + super.init() + } + + func run() { + backgroundServiceStatus = BackgroundServiceStatus.getData() + notificationOptions = NotificationOptions.getData() + notificationContent = NotificationContent.getData() + prevForegroundTaskOptions = currForegroundTaskOptions + currForegroundTaskOptions = ForegroundTaskOptions.getData() + prevForegroundTaskData = currForegroundTaskData + currForegroundTaskData = ForegroundTaskData.getData() + + switch backgroundServiceStatus.action { + case .API_START, .API_RESTART: + requestNotification() + createForegroundTask() + isRunningService = true + break + case .API_UPDATE: + requestNotification() + let prevCallbackHandle = prevForegroundTaskData?.callbackHandle + let currCallbackHandle = currForegroundTaskData.callbackHandle + if prevCallbackHandle != currCallbackHandle { + createForegroundTask() + } else { + let prevEventAction = prevForegroundTaskOptions?.eventAction + let currEventAction = currForegroundTaskOptions.eventAction + if prevEventAction != currEventAction { + updateForegroundTask() + } + } + break + case .API_STOP, .APP_TERMINATE: + destroyForegroundTask() + removeAllNotification() + isRunningService = false + break + } + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, + _ response: UNNotificationResponse, + _ completionHandler: @escaping () -> Void) { + // If it is not a notification requested by this plugin, the processing below is ignored. + if response.notification.request.identifier != NOTIFICATION_ID { return } + + // Prevents duplicate processing due to the `registrar.addApplicationDelegate`. + if !canReceiveNotificationResponse { return } + canReceiveNotificationResponse = false + + let actionId = response.actionIdentifier + if notificationContent.buttons.contains(where: { $0.id == actionId }) { + foregroundTask?.invokeMethod(ACTION_NOTIFICATION_BUTTON_PRESSED, arguments: actionId) + } else if actionId == UNNotificationDefaultActionIdentifier { + foregroundTask?.invokeMethod(ACTION_NOTIFICATION_PRESSED, arguments: nil) + } else if actionId == UNNotificationDismissActionIdentifier { + foregroundTask?.invokeMethod(ACTION_NOTIFICATION_DISMISSED, arguments: nil) + } + + completionHandler() + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, + _ notification: UNNotification, + _ completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + // If it is not a notification requested by this plugin, the processing below is ignored. + if notification.request.identifier != NOTIFICATION_ID { return } + + if notificationOptions.playSound { + completionHandler([.alert, .sound]) + } else { + completionHandler([.alert]) + } + + // Prevents duplicate processing due to the `registrar.addApplicationDelegate`. + canReceiveNotificationResponse = true + } + + private func setNotificationActions() { + var actions: [UNNotificationAction] = [] + for button in notificationContent.buttons { + let action = UNNotificationAction(identifier: button.id, title: button.text) + actions.append(action) + } + + let category = UNNotificationCategory( + identifier: NOTIFICATION_CATEGORY_ID, + actions: actions, + intentIdentifiers: [], + options: .customDismissAction + ) + + notificationCenter.setNotificationCategories([category]) + } + + private func requestNotification() { + if !notificationOptions.showNotification { + return + } + + notificationPermissionManager.checkPermission { permission in + if permission == NotificationPermission.DENIED { + return + } + + let content = UNMutableNotificationContent() + content.title = self.notificationContent.title + content.body = self.notificationContent.text + content.categoryIdentifier = NOTIFICATION_CATEGORY_ID + if self.notificationOptions.playSound { + content.sound = .default + } + self.setNotificationActions() + + let request = UNNotificationRequest(identifier: NOTIFICATION_ID, content: content, trigger: nil) + self.notificationCenter.add(request, withCompletionHandler: nil) + } + } + + private func removeAllNotification() { + notificationCenter.removePendingNotificationRequests(withIdentifiers: [NOTIFICATION_ID]) + notificationCenter.removeDeliveredNotifications(withIdentifiers: [NOTIFICATION_ID]) + } + + private func createForegroundTask() { + destroyForegroundTask() + + foregroundTask = ForegroundTask( + serviceStatus: backgroundServiceStatus, + taskData: currForegroundTaskData, + taskEventAction: currForegroundTaskOptions.eventAction, + taskLifecycleListener: taskLifecycleListeners + ) + } + + private func updateForegroundTask() { + foregroundTask?.update(taskEventAction: currForegroundTaskOptions.eventAction) + } + + private func destroyForegroundTask() { + foregroundTask?.destroy() + foregroundTask = nil + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/service/BackgroundServiceManager.swift b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/service/BackgroundServiceManager.swift new file mode 100644 index 0000000..ae30acb --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/service/BackgroundServiceManager.swift @@ -0,0 +1,96 @@ +// +// BackgroundServiceManager.swift +// flutter_foreground_task +// +// Created by WOO JIN HWANG on 2021/08/10. +// + +import Flutter +import Foundation + +class BackgroundServiceManager: NSObject { + func start(arguments: Any?) throws { + if #available(iOS 10.0, *) { + if BackgroundService.sharedInstance.isRunningService { + throw ServiceError.ServiceAlreadyStartedException + } + + guard let args = arguments as? Dictionary else { + throw ServiceError.ServiceArgumentNullException + } + + BackgroundServiceStatus.setData(action: BackgroundServiceAction.API_START) + NotificationOptions.setData(args: args) + NotificationContent.setData(args: args) + ForegroundTaskOptions.setData(args: args) + ForegroundTaskData.setData(args: args) + BackgroundService.sharedInstance.run() + } else { + throw ServiceError.ServiceNotSupportedException + } + } + + func restart(arguments: Any?) throws { + if #available(iOS 10.0, *) { + if !BackgroundService.sharedInstance.isRunningService { + throw ServiceError.ServiceNotStartedException + } + + BackgroundServiceStatus.setData(action: BackgroundServiceAction.API_RESTART) + BackgroundService.sharedInstance.run() + } else { + throw ServiceError.ServiceNotSupportedException + } + } + + func update(arguments: Any?) throws { + if #available(iOS 10.0, *) { + if !BackgroundService.sharedInstance.isRunningService { + throw ServiceError.ServiceNotStartedException + } + + guard let args = arguments as? Dictionary else { + throw ServiceError.ServiceArgumentNullException + } + + BackgroundServiceStatus.setData(action: BackgroundServiceAction.API_UPDATE) + NotificationContent.updateData(args: args) + ForegroundTaskOptions.updateData(args: args) + ForegroundTaskData.updateData(args: args) + BackgroundService.sharedInstance.run() + } else { + throw ServiceError.ServiceNotSupportedException + } + } + + func stop() throws { + if #available(iOS 10.0, *) { + if !BackgroundService.sharedInstance.isRunningService { + throw ServiceError.ServiceNotStartedException + } + + BackgroundServiceStatus.setData(action: BackgroundServiceAction.API_STOP) + NotificationOptions.clearData() + NotificationContent.clearData() + ForegroundTaskOptions.clearData() + ForegroundTaskData.clearData() + BackgroundService.sharedInstance.run() + } else { + throw ServiceError.ServiceNotSupportedException + } + } + + func sendData(data: Any?) { + if data != nil { + BackgroundService.sharedInstance.sendData(data: data) + } + } + + func isRunningService() -> Bool { + if #available(iOS 10.0, *) { + return BackgroundService.sharedInstance.isRunningService + } else { + return false + } + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/service/ForegroundTask.swift b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/service/ForegroundTask.swift new file mode 100644 index 0000000..35ea418 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/service/ForegroundTask.swift @@ -0,0 +1,192 @@ +// +// ForegroundTask.swift +// flutter_foreground_task +// +// Created by Woo Jin Hwang on 9/24/24. +// + +import Foundation + +private let BG_ISOLATE_NAME = "flutter_foreground_task/backgroundIsolate" +private let BG_CHANNEL_NAME = "flutter_foreground_task/background" + +private let ACTION_TASK_START = "onStart" +private let ACTION_TASK_REPEAT_EVENT = "onRepeatEvent" +private let ACTION_TASK_DESTROY = "onDestroy" + +class ForegroundTask { + private let serviceStatus: BackgroundServiceStatus + private let taskData: ForegroundTaskData + private var taskEventAction: ForegroundTaskEventAction + private let taskLifecycleListener: FlutterForegroundTaskLifecycleListener + + private var flutterEngine: FlutterEngine? = nil + private var backgroundChannel: FlutterMethodChannel? = nil + private var repeatTask: Timer? = nil + private var isDestroyed: Bool = false + + init( + serviceStatus: BackgroundServiceStatus, + taskData: ForegroundTaskData, + taskEventAction: ForegroundTaskEventAction, + taskLifecycleListener: FlutterForegroundTaskLifecycleListener + ) { + self.serviceStatus = serviceStatus + self.taskData = taskData + self.taskEventAction = taskEventAction + self.taskLifecycleListener = taskLifecycleListener + initialize() + } + + private func initialize() { + guard let registerPlugins = SwiftFlutterForegroundTaskPlugin.registerPlugins else { + print("Please register the registerPlugins function using the SwiftFlutterForegroundTaskPlugin.setPluginRegistrantCallback.") + return + } + + guard let callbackHandle = taskData.callbackHandle else { + // no callback -> Unlike Android, the flutter engine does not start. + return + } + + // lookup callback + let callbackInfo = FlutterCallbackCache.lookupCallbackInformation(callbackHandle) + guard let entrypoint = callbackInfo?.callbackName else { + print("Entrypoint not found in callback information.") + return + } + guard let libraryURI = callbackInfo?.callbackLibraryPath else { + print("LibraryURI not found in callback information.") + return + } + + // create flutter engine & execute callback + let flutterEngine = FlutterEngine(name: BG_ISOLATE_NAME, project: nil, allowHeadlessExecution: true) + let isRunningEngine = flutterEngine.run(withEntrypoint: entrypoint, libraryURI: libraryURI) + + if isRunningEngine { + // register plugins + registerPlugins(flutterEngine) + taskLifecycleListener.onEngineCreate(flutterEngine: flutterEngine) + + // create background channel + let messenger = flutterEngine.binaryMessenger + let backgroundChannel = FlutterMethodChannel(name: BG_CHANNEL_NAME, binaryMessenger: messenger) + backgroundChannel.setMethodCallHandler(onMethodCall) + + self.flutterEngine = flutterEngine + self.backgroundChannel = backgroundChannel + } + } + + func onMethodCall(call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "start": + start() + default: + result(FlutterMethodNotImplemented) + } + } + + private func start() { + runIfNotDestroyed { + runIfCallbackHandleExists { + let serviceAction = serviceStatus.action + let starter: FlutterForegroundTaskStarter + if serviceAction == .API_START || serviceAction == .API_RESTART || serviceAction == .API_UPDATE { + starter = .DEVELOPER + } else { + starter = .SYSTEM + } + + backgroundChannel?.invokeMethod(ACTION_TASK_START, arguments: starter.rawValue) { _ in + self.runIfNotDestroyed { + self.startRepeatTask() + } + } + taskLifecycleListener.onTaskStart(starter: starter) + } + } + } + + private func invokeTaskRepeatEvent() { + backgroundChannel?.invokeMethod(ACTION_TASK_REPEAT_EVENT, arguments: nil) + taskLifecycleListener.onTaskRepeatEvent() + } + + private func startRepeatTask() { + stopRepeatTask() + + let type = taskEventAction.type + let interval = TimeInterval(Double(taskEventAction.interval) / 1000) + + if type == .NOTHING { + return + } + + if type == .ONCE { + invokeTaskRepeatEvent() + return + } + + repeatTask = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { _ in + self.invokeTaskRepeatEvent() + } + } + + private func stopRepeatTask() { + repeatTask?.invalidate() + repeatTask = nil + } + + func invokeMethod(_ method: String, arguments: Any?) { + runIfNotDestroyed { + backgroundChannel?.invokeMethod(method, arguments: arguments) + } + } + + func update(taskEventAction: ForegroundTaskEventAction) { + runIfNotDestroyed { + runIfCallbackHandleExists { + self.taskEventAction = taskEventAction + startRepeatTask() + } + } + } + + func destroy() { + runIfNotDestroyed { + stopRepeatTask() + + backgroundChannel?.setMethodCallHandler(nil) + if taskData.callbackHandle == nil { + taskLifecycleListener.onEngineWillDestroy() + flutterEngine?.destroyContext() + flutterEngine = nil + } else { + backgroundChannel?.invokeMethod(ACTION_TASK_DESTROY, arguments: nil) { _ in + self.flutterEngine?.destroyContext() + self.flutterEngine = nil + } + taskLifecycleListener.onTaskDestroy() + taskLifecycleListener.onEngineWillDestroy() + } + + isDestroyed = true + } + } + + private func runIfCallbackHandleExists(call: () -> Void) { + if taskData.callbackHandle == nil { + return + } + call() + } + + private func runIfNotDestroyed(call: () -> Void) { + if isDestroyed { + return + } + call() + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/service/ForegroundTaskLifecycleListeners.swift b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/service/ForegroundTaskLifecycleListeners.swift new file mode 100644 index 0000000..4a57853 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/service/ForegroundTaskLifecycleListeners.swift @@ -0,0 +1,54 @@ +// +// ForegroundTaskLifecycleListeners.swift +// flutter_foreground_task +// +// Created by Woo Jin Hwang on 9/24/24. +// + +import Foundation + +class ForegroundTaskLifecycleListeners : FlutterForegroundTaskLifecycleListener { + private var listeners: Array = [] + + func addListener(_ listener: FlutterForegroundTaskLifecycleListener) { + if listeners.contains(where: { $0 === listener }) == false { + listeners.append(listener) + } + } + + func removeListener(_ listener: FlutterForegroundTaskLifecycleListener) { + if let index = listeners.firstIndex(where: { $0 === listener }) { + listeners.remove(at: index) + } + } + + func onEngineCreate(flutterEngine: FlutterEngine?) { + for listener in listeners { + listener.onEngineCreate(flutterEngine: flutterEngine) + } + } + + func onTaskStart(starter: FlutterForegroundTaskStarter) { + for listener in listeners { + listener.onTaskStart(starter: starter) + } + } + + func onTaskRepeatEvent() { + for listener in listeners { + listener.onTaskRepeatEvent() + } + } + + func onTaskDestroy() { + for listener in listeners { + listener.onTaskDestroy() + } + } + + func onEngineWillDestroy() { + for listener in listeners { + listener.onEngineWillDestroy() + } + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/Classes/service/NotificationPermissionManager.swift b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/service/NotificationPermissionManager.swift new file mode 100644 index 0000000..461c9f7 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/Classes/service/NotificationPermissionManager.swift @@ -0,0 +1,38 @@ +// +// NotificationPermissionManager.swift +// flutter_foreground_task +// +// Created by Woo Jin Hwang on 8/6/24. +// + +import Foundation + +class NotificationPermissionManager { + func checkPermission(completion: @escaping (NotificationPermission) -> Void) { + UNUserNotificationCenter.current().getNotificationSettings { settings in + switch settings.authorizationStatus { + case .authorized: + completion(NotificationPermission.GRANTED) + case .denied, .ephemeral, .notDetermined, .provisional: + completion(NotificationPermission.DENIED) + @unknown default: + completion(NotificationPermission.DENIED) + } + } + } + + func requestPermission(completion: @escaping (NotificationPermission) -> Void) { + let options = UNAuthorizationOptions(arrayLiteral: .alert, .sound) + UNUserNotificationCenter.current().requestAuthorization(options: options) { granted, error in + if error != nil { + completion(NotificationPermission.DENIED) + } else { + if (granted) { + completion(NotificationPermission.GRANTED) + } else { + completion(NotificationPermission.DENIED) + } + } + } + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/ios/flutter_foreground_task.podspec b/local_packages/flutter_foreground_task-9.1.0/ios/flutter_foreground_task.podspec new file mode 100644 index 0000000..45788fd --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/ios/flutter_foreground_task.podspec @@ -0,0 +1,23 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint flutter_foreground_task.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'flutter_foreground_task' + s.version = '0.0.1' + s.summary = 'A new Flutter plugin.' + s.description = <<-DESC +A new Flutter plugin. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'Flutter' + s.platform = :ios, '8.0' + + # Flutter.framework does not contain a i386 slice. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } + s.swift_version = '5.0' +end diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/errors/service_already_started_exception.dart b/local_packages/flutter_foreground_task-9.1.0/lib/errors/service_already_started_exception.dart new file mode 100644 index 0000000..852b754 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/errors/service_already_started_exception.dart @@ -0,0 +1,9 @@ +class ServiceAlreadyStartedException implements Exception { + ServiceAlreadyStartedException( + [this.message = 'The service has already started.']); + + final String message; + + @override + String toString() => 'ServiceAlreadyStartedException: $message'; +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/errors/service_not_initialized_exception.dart b/local_packages/flutter_foreground_task-9.1.0/lib/errors/service_not_initialized_exception.dart new file mode 100644 index 0000000..5994c0d --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/errors/service_not_initialized_exception.dart @@ -0,0 +1,10 @@ +class ServiceNotInitializedException implements Exception { + ServiceNotInitializedException( + [this.message = + 'Not initialized. Please call this function after calling the init function.']); + + final String message; + + @override + String toString() => 'ServiceNotInitializedException: $message'; +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/errors/service_not_started_exception.dart b/local_packages/flutter_foreground_task-9.1.0/lib/errors/service_not_started_exception.dart new file mode 100644 index 0000000..7a32bf1 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/errors/service_not_started_exception.dart @@ -0,0 +1,8 @@ +class ServiceNotStartedException implements Exception { + ServiceNotStartedException([this.message = 'The service is not started.']); + + final String message; + + @override + String toString() => 'ServiceNotStartedException: $message'; +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/errors/service_timeout_exception.dart b/local_packages/flutter_foreground_task-9.1.0/lib/errors/service_timeout_exception.dart new file mode 100644 index 0000000..dea8a97 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/errors/service_timeout_exception.dart @@ -0,0 +1,10 @@ +class ServiceTimeoutException implements Exception { + ServiceTimeoutException( + [this.message = + 'The service request timed out. (ref: https://developer.android.com/guide/components/services#StartingAService)']); + + final String message; + + @override + String toString() => 'ServiceTimeoutException: $message'; +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/flutter_foreground_task.dart b/local_packages/flutter_foreground_task-9.1.0/lib/flutter_foreground_task.dart new file mode 100644 index 0000000..2fedb61 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/flutter_foreground_task.dart @@ -0,0 +1,424 @@ +import 'dart:async'; +import 'dart:isolate'; +import 'dart:ui'; + +import 'package:flutter/widgets.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'flutter_foreground_task_platform_interface.dart'; +import 'errors/service_already_started_exception.dart'; +import 'errors/service_not_initialized_exception.dart'; +import 'errors/service_not_started_exception.dart'; +import 'errors/service_timeout_exception.dart'; +import 'models/foreground_service_types.dart'; +import 'models/foreground_task_options.dart'; +import 'models/notification_button.dart'; +import 'models/notification_icon.dart'; +import 'models/notification_options.dart'; +import 'models/notification_permission.dart'; +import 'models/service_request_result.dart'; +import 'task_handler.dart'; +import 'utils/utility.dart'; + +export 'errors/service_already_started_exception.dart'; +export 'errors/service_not_initialized_exception.dart'; +export 'errors/service_not_started_exception.dart'; +export 'errors/service_timeout_exception.dart'; +export 'models/foreground_service_types.dart'; +export 'models/foreground_task_event_action.dart'; +export 'models/foreground_task_options.dart'; +export 'models/notification_button.dart'; +export 'models/notification_channel_importance.dart'; +export 'models/notification_icon.dart'; +export 'models/notification_options.dart'; +export 'models/notification_permission.dart'; +export 'models/notification_priority.dart'; +export 'models/notification_visibility.dart'; +export 'models/service_request_result.dart'; +export 'ui/with_foreground_task.dart'; +export 'task_handler.dart'; + +const String _kPortName = 'flutter_foreground_task/isolateComPort'; +const String _kPrefsKeyPrefix = 'com.pravera.flutter_foreground_task.prefs.'; + +typedef DataCallback = void Function(Object data); + +/// A class that implements foreground task and provides useful utilities. +class FlutterForegroundTask { + // ====================== Service ====================== + + @visibleForTesting + static AndroidNotificationOptions? androidNotificationOptions; + + @visibleForTesting + static IOSNotificationOptions? iosNotificationOptions; + + @visibleForTesting + static ForegroundTaskOptions? foregroundTaskOptions; + + @visibleForTesting + static bool isInitialized = false; + + @visibleForTesting + static bool skipServiceResponseCheck = false; + + // platform instance: MethodChannelFlutterForegroundTask + static FlutterForegroundTaskPlatform get _platform => + FlutterForegroundTaskPlatform.instance; + + /// Resets class's static values to allow for testing of service flow. + @visibleForTesting + static void resetStatic() { + androidNotificationOptions = null; + iosNotificationOptions = null; + foregroundTaskOptions = null; + isInitialized = false; + skipServiceResponseCheck = false; + + receivePort?.close(); + receivePort = null; + streamSubscription?.cancel(); + streamSubscription = null; + dataCallbacks.clear(); + } + + /// Initialize the [FlutterForegroundTask]. + static void init({ + required AndroidNotificationOptions androidNotificationOptions, + required IOSNotificationOptions iosNotificationOptions, + required ForegroundTaskOptions foregroundTaskOptions, + }) { + FlutterForegroundTask.androidNotificationOptions = + androidNotificationOptions; + FlutterForegroundTask.iosNotificationOptions = iosNotificationOptions; + FlutterForegroundTask.foregroundTaskOptions = foregroundTaskOptions; + FlutterForegroundTask.isInitialized = true; + } + + /// Start the foreground service. + static Future startService({ + int? serviceId, + List? serviceTypes, + required String notificationTitle, + required String notificationText, + NotificationIcon? notificationIcon, + List? notificationButtons, + String? notificationInitialRoute, + Function? callback, + }) async { + try { + if (!isInitialized) { + throw ServiceNotInitializedException(); + } + + if (await isRunningService) { + throw ServiceAlreadyStartedException(); + } + + await _platform.startService( + androidNotificationOptions: androidNotificationOptions!, + iosNotificationOptions: iosNotificationOptions!, + foregroundTaskOptions: foregroundTaskOptions!, + serviceId: serviceId, + serviceTypes: serviceTypes, + notificationTitle: notificationTitle, + notificationText: notificationText, + notificationIcon: notificationIcon, + notificationButtons: notificationButtons, + notificationInitialRoute: notificationInitialRoute, + callback: callback, + ); + + if (!skipServiceResponseCheck) { + await checkServiceStateChange(target: true); + } + + return const ServiceRequestSuccess(); + } catch (error) { + return ServiceRequestFailure(error: error); + } + } + + /// Restart the foreground service. + static Future restartService() async { + try { + if (!(await isRunningService)) { + throw ServiceNotStartedException(); + } + + await _platform.restartService(); + + return const ServiceRequestSuccess(); + } catch (error) { + return ServiceRequestFailure(error: error); + } + } + + /// Update the foreground service. + static Future updateService({ + ForegroundTaskOptions? foregroundTaskOptions, + String? notificationTitle, + String? notificationText, + NotificationIcon? notificationIcon, + List? notificationButtons, + String? notificationInitialRoute, + Function? callback, + }) async { + try { + if (!(await isRunningService)) { + throw ServiceNotStartedException(); + } + + await _platform.updateService( + foregroundTaskOptions: foregroundTaskOptions, + notificationText: notificationText, + notificationTitle: notificationTitle, + notificationIcon: notificationIcon, + notificationButtons: notificationButtons, + notificationInitialRoute: notificationInitialRoute, + callback: callback, + ); + + return const ServiceRequestSuccess(); + } catch (error) { + return ServiceRequestFailure(error: error); + } + } + + /// Stop the foreground service. + static Future stopService() async { + try { + if (!(await isRunningService)) { + throw ServiceNotStartedException(); + } + + await _platform.stopService(); + + if (!skipServiceResponseCheck) { + await checkServiceStateChange(target: false); + } + + return const ServiceRequestSuccess(); + } catch (error) { + return ServiceRequestFailure(error: error); + } + } + + @visibleForTesting + static Future checkServiceStateChange({required bool target}) async { + // official doc: Once the service has been created, the service must call its startForeground() method within 5 seconds. + // ref: https://developer.android.com/guide/components/services#StartingAService + final bool isCompleted = await Utility.instance.completedWithinDeadline( + deadline: const Duration(seconds: 5), + future: () async { + return target == await isRunningService; + }, + ); + + if (!isCompleted) { + throw ServiceTimeoutException(); + } + } + + /// Returns whether the foreground service is running. + static Future get isRunningService => _platform.isRunningService; + + /// Set up the task handler and start the foreground task. + /// + /// It must always be called from a top-level function, otherwise foreground task will not work. + static void setTaskHandler(TaskHandler handler) => + _platform.setTaskHandler(handler); + + // =================== Communication =================== + + @visibleForTesting + static ReceivePort? receivePort; + + @visibleForTesting + static StreamSubscription? streamSubscription; + + @visibleForTesting + static final List dataCallbacks = []; + + /// Initialize port for communication between TaskHandler and UI. + static void initCommunicationPort() { + final ReceivePort newReceivePort = ReceivePort(); + final SendPort newSendPort = newReceivePort.sendPort; + + IsolateNameServer.removePortNameMapping(_kPortName); + if (IsolateNameServer.registerPortWithName(newSendPort, _kPortName)) { + streamSubscription?.cancel(); + receivePort?.close(); + + receivePort = newReceivePort; + streamSubscription = receivePort?.listen((data) { + for (final DataCallback callback in dataCallbacks.toList()) { + callback.call(data); + } + }); + } + } + + /// Add a callback to receive data sent from the [TaskHandler]. + static void addTaskDataCallback(DataCallback callback) { + if (!dataCallbacks.contains(callback)) { + dataCallbacks.add(callback); + } + } + + /// Remove a callback to receive data sent from the [TaskHandler]. + static void removeTaskDataCallback(DataCallback callback) { + dataCallbacks.remove(callback); + } + + /// Send data to [TaskHandler]. + static void sendDataToTask(Object data) => _platform.sendDataToTask(data); + + /// Send date to main isolate. + static void sendDataToMain(Object data) { + final SendPort? sendPort = IsolateNameServer.lookupPortByName(_kPortName); + sendPort?.send(data); + } + + // ====================== Storage ====================== + + /// Get the stored data with [key]. + static Future getData({required String key}) async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.reload(); + + final Object? data = prefs.get(_kPrefsKeyPrefix + key); + + return (data is T) ? data : null; + } + + /// Get all stored data. + static Future> getAllData() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.reload(); + + final Map dataList = {}; + for (final String prefsKey in prefs.getKeys()) { + if (prefsKey.contains(_kPrefsKeyPrefix)) { + final Object? data = prefs.get(prefsKey); + if (data != null) { + final String originKey = prefsKey.replaceAll(_kPrefsKeyPrefix, ''); + dataList[originKey] = data; + } + } + } + + return dataList; + } + + /// Save data with [key]. + static Future saveData({ + required String key, + required Object value, + }) async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.reload(); + + final String prefsKey = _kPrefsKeyPrefix + key; + if (value is int) { + return prefs.setInt(prefsKey, value); + } else if (value is double) { + return prefs.setDouble(prefsKey, value); + } else if (value is String) { + return prefs.setString(prefsKey, value); + } else if (value is bool) { + return prefs.setBool(prefsKey, value); + } else { + return false; + } + } + + /// Remove data with [key]. + static Future removeData({required String key}) async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.reload(); + + return prefs.remove(_kPrefsKeyPrefix + key); + } + + /// Clears all stored data. + static Future clearAllData() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.reload(); + + for (final String prefsKey in prefs.getKeys()) { + if (prefsKey.contains(_kPrefsKeyPrefix)) { + await prefs.remove(prefsKey); + } + } + + return true; + } + + // ====================== Utility ====================== + + /// Minimize the app to the background. + static void minimizeApp() => _platform.minimizeApp(); + + /// Launch the app at [route] if it is not running otherwise open it. + /// + /// It is also possible to pass a route to this function but the route will only + /// be loaded if the app is not already running. + /// + /// This function requires the "android.permission.SYSTEM\_ALERT\_WINDOW" permission and + /// requires using the `openSystemAlertWindowSettings()` function to grant the permission. + static void launchApp([String? route]) => _platform.launchApp(route); + + /// Toggles lockScreen visibility. + static void setOnLockScreenVisibility(bool isVisible) => + _platform.setOnLockScreenVisibility(isVisible); + + /// Returns whether the app is in the foreground. + static Future get isAppOnForeground => _platform.isAppOnForeground; + + /// Wake up the screen of a device that is turned off. + static void wakeUpScreen() => _platform.wakeUpScreen(); + + /// Returns whether the app has been excluded from battery optimization. + static Future get isIgnoringBatteryOptimizations => + _platform.isIgnoringBatteryOptimizations; + + /// Open the settings page where you can set ignore battery optimization. + static Future openIgnoreBatteryOptimizationSettings() => + _platform.openIgnoreBatteryOptimizationSettings(); + + /// Request to ignore battery optimization. + /// + /// This function requires the "android.permission.REQUEST\_IGNORE\_BATTERY\_OPTIMIZATIONS" permission. + static Future requestIgnoreBatteryOptimization() => + _platform.requestIgnoreBatteryOptimization(); + + /// Returns whether the "android.permission.SYSTEM\_ALERT\_WINDOW" permission is granted. + static Future get canDrawOverlays => _platform.canDrawOverlays; + + /// Open the settings page where you can allow/deny the "android.permission.SYSTEM\_ALERT\_WINDOW" permission. + static Future openSystemAlertWindowSettings() => + _platform.openSystemAlertWindowSettings(); + + /// Returns notification permission status. + static Future checkNotificationPermission() => + _platform.checkNotificationPermission(); + + /// Request notification permission. + static Future requestNotificationPermission() => + _platform.requestNotificationPermission(); + + /// Returns whether the "android.permission.SCHEDULE\_EXACT\_ALARM" permission is granted. + static Future get canScheduleExactAlarms => + _platform.canScheduleExactAlarms; + + /// Open the alarms & reminders settings page. + /// + /// Use this utility only if you provide services that require long-term survival, + /// such as exact alarm service, healthcare service, or Bluetooth communication. + /// + /// This utility requires the "android.permission.SCHEDULE\_EXACT\_ALARM" permission. + /// Using this permission may make app distribution difficult due to Google policy. + static Future openAlarmsAndRemindersSettings() => + _platform.openAlarmsAndRemindersSettings(); +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/flutter_foreground_task_method_channel.dart b/local_packages/flutter_foreground_task-9.1.0/lib/flutter_foreground_task_method_channel.dart new file mode 100644 index 0000000..434888d --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/flutter_foreground_task_method_channel.dart @@ -0,0 +1,276 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:developer' as dev; +import 'dart:ui'; + +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:platform/platform.dart'; + +import 'flutter_foreground_task_platform_interface.dart'; +import 'models/foreground_service_types.dart'; +import 'models/foreground_task_options.dart'; +import 'models/notification_button.dart'; +import 'models/notification_icon.dart'; +import 'models/notification_options.dart'; +import 'models/notification_permission.dart'; +import 'models/service_options.dart'; +import 'task_handler.dart'; + +/// An implementation of [FlutterForegroundTaskPlatform] that uses method channels. +class MethodChannelFlutterForegroundTask extends FlutterForegroundTaskPlatform { + @visibleForTesting + final MethodChannel mMDChannel = + const MethodChannel('flutter_foreground_task/methods'); + + @visibleForTesting + final MethodChannel mBGChannel = + const MethodChannel('flutter_foreground_task/background'); + + @visibleForTesting + Platform platform = const LocalPlatform(); + + // ====================== Service ====================== + + @override + Future startService({ + required AndroidNotificationOptions androidNotificationOptions, + required IOSNotificationOptions iosNotificationOptions, + required ForegroundTaskOptions foregroundTaskOptions, + int? serviceId, + List? serviceTypes, + required String notificationTitle, + required String notificationText, + NotificationIcon? notificationIcon, + List? notificationButtons, + String? notificationInitialRoute, + Function? callback, + }) async { + final Map optionsJson = ServiceStartOptions( + serviceId: serviceId, + serviceTypes: serviceTypes, + androidNotificationOptions: androidNotificationOptions, + iosNotificationOptions: iosNotificationOptions, + foregroundTaskOptions: foregroundTaskOptions, + notificationContentTitle: notificationTitle, + notificationContentText: notificationText, + notificationIcon: notificationIcon, + notificationButtons: notificationButtons, + notificationInitialRoute: notificationInitialRoute, + callback: callback, + ).toJson(platform); + + await mMDChannel.invokeMethod('startService', optionsJson); + } + + @override + Future restartService() async { + await mMDChannel.invokeMethod('restartService'); + } + + @override + Future updateService({ + ForegroundTaskOptions? foregroundTaskOptions, + String? notificationTitle, + String? notificationText, + NotificationIcon? notificationIcon, + List? notificationButtons, + String? notificationInitialRoute, + Function? callback, + }) async { + final Map optionsJson = ServiceUpdateOptions( + foregroundTaskOptions: foregroundTaskOptions, + notificationContentTitle: notificationTitle, + notificationContentText: notificationText, + notificationIcon: notificationIcon, + notificationButtons: notificationButtons, + notificationInitialRoute: notificationInitialRoute, + callback: callback, + ).toJson(platform); + + await mMDChannel.invokeMethod('updateService', optionsJson); + } + + @override + Future stopService() async { + await mMDChannel.invokeMethod('stopService'); + } + + @override + Future get isRunningService async { + return await mMDChannel.invokeMethod('isRunningService'); + } + + @override + Future get attachedActivity async { + if (platform.isAndroid) { + return await mMDChannel.invokeMethod('attachedActivity'); + } + return true; + } + + @override + void setTaskHandler(TaskHandler handler) { + // Binding the framework to the flutter engine. + WidgetsFlutterBinding.ensureInitialized(); + DartPluginRegistrant.ensureInitialized(); + + // Set the method call handler for the background channel. + mBGChannel.setMethodCallHandler((call) async { + await onBackgroundChannel(call, handler); + }); + + mBGChannel.invokeMethod('start'); + } + + @visibleForTesting + Future onBackgroundChannel(MethodCall call, TaskHandler handler) async { + final DateTime timestamp = DateTime.timestamp(); + + switch (call.method) { + case 'onStart': + final TaskStarter starter = TaskStarter.fromIndex(call.arguments); + await handler.onStart(timestamp, starter); + break; + case 'onRepeatEvent': + handler.onRepeatEvent(timestamp); + break; + case 'onDestroy': + final bool isTimeout = call.arguments ?? false; + await handler.onDestroy(timestamp, isTimeout); + break; + case 'onReceiveData': + dynamic data = call.arguments; + if (data is List || data is Map || data is Set) { + try { + data = jsonDecode(jsonEncode(data)); + } catch (e, s) { + dev.log('onReceiveData error: $e\n$s'); + } + } + handler.onReceiveData(data); + break; + case 'onNotificationButtonPressed': + final String id = call.arguments.toString(); + handler.onNotificationButtonPressed(id); + break; + case 'onNotificationDismissed': + handler.onNotificationDismissed(); + break; + case 'onNotificationPressed': + handler.onNotificationPressed(); + break; + } + } + + // =================== Communication =================== + + @override + void sendDataToTask(Object data) { + mMDChannel.invokeMethod('sendData', data); + } + + // ====================== Utility ====================== + + @override + void minimizeApp() { + mMDChannel.invokeMethod('minimizeApp'); + } + + @override + void launchApp([String? route]) { + if (platform.isAndroid) { + mMDChannel.invokeMethod('launchApp', route); + } + } + + @override + void setOnLockScreenVisibility(bool isVisible) { + if (platform.isAndroid) { + mMDChannel.invokeMethod('setOnLockScreenVisibility', isVisible); + } + } + + @override + Future get isAppOnForeground async { + return await mMDChannel.invokeMethod('isAppOnForeground'); + } + + @override + void wakeUpScreen() { + if (platform.isAndroid) { + mMDChannel.invokeMethod('wakeUpScreen'); + } + } + + @override + Future get isIgnoringBatteryOptimizations async { + if (platform.isAndroid) { + return await mMDChannel.invokeMethod('isIgnoringBatteryOptimizations'); + } + return true; + } + + @override + Future openIgnoreBatteryOptimizationSettings() async { + if (platform.isAndroid) { + return await mMDChannel + .invokeMethod('openIgnoreBatteryOptimizationSettings'); + } + return true; + } + + @override + Future requestIgnoreBatteryOptimization() async { + if (platform.isAndroid) { + return await mMDChannel.invokeMethod('requestIgnoreBatteryOptimization'); + } + return true; + } + + @override + Future get canDrawOverlays async { + if (platform.isAndroid) { + return await mMDChannel.invokeMethod('canDrawOverlays'); + } + return true; + } + + @override + Future openSystemAlertWindowSettings() async { + if (platform.isAndroid) { + return await mMDChannel.invokeMethod('openSystemAlertWindowSettings'); + } + return true; + } + + @override + Future checkNotificationPermission() async { + final int result = + await mMDChannel.invokeMethod('checkNotificationPermission'); + return NotificationPermission.fromIndex(result); + } + + @override + Future requestNotificationPermission() async { + final int result = + await mMDChannel.invokeMethod('requestNotificationPermission'); + return NotificationPermission.fromIndex(result); + } + + @override + Future get canScheduleExactAlarms async { + if (platform.isAndroid) { + return await mMDChannel.invokeMethod('canScheduleExactAlarms'); + } + return true; + } + + @override + Future openAlarmsAndRemindersSettings() async { + if (platform.isAndroid) { + return await mMDChannel.invokeMethod('openAlarmsAndRemindersSettings'); + } + return true; + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/flutter_foreground_task_platform_interface.dart b/local_packages/flutter_foreground_task-9.1.0/lib/flutter_foreground_task_platform_interface.dart new file mode 100644 index 0000000..5d3974f --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/flutter_foreground_task_platform_interface.dart @@ -0,0 +1,156 @@ +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'flutter_foreground_task_method_channel.dart'; +import 'models/foreground_service_types.dart'; +import 'models/foreground_task_options.dart'; +import 'models/notification_button.dart'; +import 'models/notification_icon.dart'; +import 'models/notification_options.dart'; +import 'models/notification_permission.dart'; +import 'task_handler.dart'; + +abstract class FlutterForegroundTaskPlatform extends PlatformInterface { + /// Constructs a FlutterForegroundTaskPlatform. + FlutterForegroundTaskPlatform() : super(token: _token); + + static final Object _token = Object(); + + static FlutterForegroundTaskPlatform _instance = + MethodChannelFlutterForegroundTask(); + + /// The default instance of [FlutterForegroundTaskPlatform] to use. + /// + /// Defaults to [MethodChannelFlutterForegroundTask]. + static FlutterForegroundTaskPlatform get instance => _instance; + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [FlutterForegroundTaskPlatform] when + /// they register themselves. + static set instance(FlutterForegroundTaskPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + // ====================== Service ====================== + + Future startService({ + required AndroidNotificationOptions androidNotificationOptions, + required IOSNotificationOptions iosNotificationOptions, + required ForegroundTaskOptions foregroundTaskOptions, + int? serviceId, + List? serviceTypes, + required String notificationTitle, + required String notificationText, + NotificationIcon? notificationIcon, + List? notificationButtons, + String? notificationInitialRoute, + Function? callback, + }) { + throw UnimplementedError('startService() has not been implemented.'); + } + + Future restartService() { + throw UnimplementedError('restartService() has not been implemented.'); + } + + Future updateService({ + ForegroundTaskOptions? foregroundTaskOptions, + String? notificationTitle, + String? notificationText, + NotificationIcon? notificationIcon, + List? notificationButtons, + String? notificationInitialRoute, + Function? callback, + }) { + throw UnimplementedError('updateService() has not been implemented.'); + } + + Future stopService() { + throw UnimplementedError('stopService() has not been implemented.'); + } + + Future get isRunningService { + throw UnimplementedError('isRunningService has not been implemented.'); + } + + Future get attachedActivity { + throw UnimplementedError('attachedActivity has not been implemented.'); + } + + void setTaskHandler(TaskHandler handler) { + throw UnimplementedError('setTaskHandler() has not been implemented.'); + } + + // =================== Communication =================== + + void sendDataToTask(Object data) { + throw UnimplementedError('sendDataToTask() has not been implemented.'); + } + + // ====================== Utility ====================== + + void minimizeApp() { + throw UnimplementedError('minimizeApp() has not been implemented.'); + } + + void launchApp([String? route]) { + throw UnimplementedError('launchApp() has not been implemented.'); + } + + void setOnLockScreenVisibility(bool isVisible) { + throw UnimplementedError( + 'setOnLockScreenVisibility() has not been implemented.'); + } + + Future get isAppOnForeground { + throw UnimplementedError('isAppOnForeground has not been implemented.'); + } + + void wakeUpScreen() { + throw UnimplementedError('wakeUpScreen() has not been implemented.'); + } + + Future get isIgnoringBatteryOptimizations { + throw UnimplementedError( + 'isIgnoringBatteryOptimizations has not been implemented.'); + } + + Future openIgnoreBatteryOptimizationSettings() { + throw UnimplementedError( + 'openIgnoreBatteryOptimizationSettings() has not been implemented.'); + } + + Future requestIgnoreBatteryOptimization() { + throw UnimplementedError( + 'requestIgnoreBatteryOptimization() has not been implemented.'); + } + + Future get canDrawOverlays { + throw UnimplementedError('canDrawOverlays has not been implemented.'); + } + + Future openSystemAlertWindowSettings() { + throw UnimplementedError( + 'openSystemAlertWindowSettings() has not been implemented.'); + } + + Future checkNotificationPermission() { + throw UnimplementedError( + 'checkNotificationPermission() has not been implemented.'); + } + + Future requestNotificationPermission() { + throw UnimplementedError( + 'requestNotificationPermission() has not been implemented.'); + } + + Future get canScheduleExactAlarms { + throw UnimplementedError( + 'canScheduleExactAlarms has not been implemented.'); + } + + Future openAlarmsAndRemindersSettings() { + throw UnimplementedError( + 'openAlarmsAndRemindersSettings() has not been implemented.'); + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/models/foreground_service_types.dart b/local_packages/flutter_foreground_task-9.1.0/lib/models/foreground_service_types.dart new file mode 100644 index 0000000..97327db --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/models/foreground_service_types.dart @@ -0,0 +1,54 @@ +/// https://developer.android.com/about/versions/14/changes/fgs-types-required#system-exempted +class ForegroundServiceTypes { + /// Constructs an instance of [ForegroundServiceTypes]. + const ForegroundServiceTypes(this.rawValue); + + /// Continue to access the camera from the background, such as video chat apps that allow for multitasking. + static const camera = ForegroundServiceTypes(0); + + /// Interactions with external devices that require a Bluetooth, NFC, IR, USB, or network connection. + static const connectedDevice = ForegroundServiceTypes(1); + + /// Data transfer operations, such as the following: + /// + /// * Data upload or download + /// * Backup-and-restore operations + /// * Import or export operations + /// * Fetch data + /// * Local file processing + /// * Transfer data between a device and the cloud over a network + static const dataSync = ForegroundServiceTypes(2); + + /// Any long-running use cases to support apps in the fitness category such as exercise trackers. + static const health = ForegroundServiceTypes(3); + + /// Long-running use cases that require location access, such as navigation and location sharing. + static const location = ForegroundServiceTypes(4); + + /// Continue audio or video playback from the background. Support Digital Video Recording (DVR) functionality on Android TV. + static const mediaPlayback = ForegroundServiceTypes(5); + + /// Project content to non-primary display or external device using the MediaProjection APIs. This content doesn't have to be exclusively media content. + static const mediaProjection = ForegroundServiceTypes(6); + + /// Continue microphone capture from the background, such as voice recorders or communication apps. + static const microphone = ForegroundServiceTypes(7); + + /// Continue an ongoing call using the ConnectionService APIs. + static const phoneCall = ForegroundServiceTypes(8); + + /// Transfer text messages from one device to another. Assists with continuity of a user's messaging tasks when they switch devices. + static const remoteMessaging = ForegroundServiceTypes(9); + + /// Quickly finish critical work that cannot be interrupted or postponed. + static const shortService = ForegroundServiceTypes(10); + + /// Covers any valid foreground service use cases that aren't covered by the other foreground service types. + static const specialUse = ForegroundServiceTypes(11); + + /// Reserved for system applications and specific system integrations, to continue to use foreground services. + static const systemExempted = ForegroundServiceTypes(12); + + /// The raw value of [ForegroundServiceTypes]. + final int rawValue; +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/models/foreground_task_event_action.dart b/local_packages/flutter_foreground_task-9.1.0/lib/models/foreground_task_event_action.dart new file mode 100644 index 0000000..65c7cb0 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/models/foreground_task_event_action.dart @@ -0,0 +1,50 @@ +/// A class that defines the action of onRepeatEvent in [TaskHandler]. +class ForegroundTaskEventAction { + ForegroundTaskEventAction._private({ + required this.type, + this.interval, + }); + + /// Not use onRepeatEvent callback. + factory ForegroundTaskEventAction.nothing() => + ForegroundTaskEventAction._private(type: ForegroundTaskEventType.nothing); + + /// Call onRepeatEvent only once. + factory ForegroundTaskEventAction.once() => + ForegroundTaskEventAction._private(type: ForegroundTaskEventType.once); + + /// Call onRepeatEvent at milliseconds [interval]. + factory ForegroundTaskEventAction.repeat(int interval) => + ForegroundTaskEventAction._private( + type: ForegroundTaskEventType.repeat, interval: interval); + + /// The type for [ForegroundTaskEventAction]. + final ForegroundTaskEventType type; + + /// The interval(in milliseconds) at which onRepeatEvent is invoked. + final int? interval; + + /// Returns the data fields of [ForegroundTaskEventAction] in JSON format. + Map toJson() { + return { + 'taskEventType': type.value, + 'taskEventInterval': interval, + }; + } +} + +/// The type for [ForegroundTaskEventAction]. +enum ForegroundTaskEventType { + /// Not use onRepeatEvent callback. + nothing(1), + + /// Call onRepeatEvent only once. + once(2), + + /// Call onRepeatEvent at milliseconds interval. + repeat(3); + + const ForegroundTaskEventType(this.value); + + final int value; +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/models/foreground_task_options.dart b/local_packages/flutter_foreground_task-9.1.0/lib/models/foreground_task_options.dart new file mode 100644 index 0000000..4c62ccf --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/models/foreground_task_options.dart @@ -0,0 +1,62 @@ +import 'foreground_task_event_action.dart'; + +/// Data class with foreground task options. +class ForegroundTaskOptions { + /// Constructs an instance of [ForegroundTaskOptions]. + const ForegroundTaskOptions({ + required this.eventAction, + this.autoRunOnBoot = false, + this.autoRunOnMyPackageReplaced = false, + this.allowWakeLock = true, + this.allowWifiLock = false, + }); + + /// The action of onRepeatEvent in [TaskHandler]. + final ForegroundTaskEventAction eventAction; + + /// Whether to automatically run foreground task on boot. + /// The default is `false`. + final bool autoRunOnBoot; + + /// Whether to automatically run foreground task when the app is updated to a new version. + /// The default is `false`. + final bool autoRunOnMyPackageReplaced; + + /// Whether to keep the CPU turned on. + /// The default is `true`. + final bool allowWakeLock; + + /// Allows an application to keep the Wi-Fi radio awake. + /// The default is `false`. + /// + /// https://developer.android.com/reference/android/net/wifi/WifiManager.WifiLock.html + final bool allowWifiLock; + + /// Returns the data fields of [ForegroundTaskOptions] in JSON format. + Map toJson() { + return { + 'taskEventAction': eventAction.toJson(), + 'autoRunOnBoot': autoRunOnBoot, + 'autoRunOnMyPackageReplaced': autoRunOnMyPackageReplaced, + 'allowWakeLock': allowWakeLock, + 'allowWifiLock': allowWifiLock, + }; + } + + /// Creates a copy of the object replaced with new values. + ForegroundTaskOptions copyWith({ + ForegroundTaskEventAction? eventAction, + bool? autoRunOnBoot, + bool? autoRunOnMyPackageReplaced, + bool? allowWakeLock, + bool? allowWifiLock, + }) => + ForegroundTaskOptions( + eventAction: eventAction ?? this.eventAction, + autoRunOnBoot: autoRunOnBoot ?? this.autoRunOnBoot, + autoRunOnMyPackageReplaced: + autoRunOnMyPackageReplaced ?? this.autoRunOnMyPackageReplaced, + allowWakeLock: allowWakeLock ?? this.allowWakeLock, + allowWifiLock: allowWifiLock ?? this.allowWifiLock, + ); +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_button.dart b/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_button.dart new file mode 100644 index 0000000..b857ca2 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_button.dart @@ -0,0 +1,44 @@ +import 'dart:ui'; + +import 'package:flutter_foreground_task/utils/color_extension.dart'; + +/// The button to display in the notification. +class NotificationButton { + /// Constructs an instance of [NotificationButton]. + const NotificationButton({ + required this.id, + required this.text, + this.textColor, + }) : assert(id.length > 0), + assert(text.length > 0); + + /// The button identifier. + final String id; + + /// The text to display on the button. + final String text; + + /// The button text color. (only work Android) + final Color? textColor; + + /// Returns the data fields of [NotificationButton] in JSON format. + Map toJson() { + return { + 'id': id, + 'text': text, + 'textColorRgb': textColor?.toRgbString, + }; + } + + /// Creates a copy of the object replaced with new values. + NotificationButton copyWith({ + String? id, + String? text, + Color? textColor, + }) => + NotificationButton( + id: id ?? this.id, + text: text ?? this.text, + textColor: textColor ?? this.textColor, + ); +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_channel_importance.dart b/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_channel_importance.dart new file mode 100644 index 0000000..09d14ea --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_channel_importance.dart @@ -0,0 +1,33 @@ +/// The importance of the notification channel. +/// See https://developer.android.com/training/notify-user/channels?hl=ko#importance +class NotificationChannelImportance { + /// Constructs an instance of [NotificationChannelImportance]. + const NotificationChannelImportance(this.rawValue); + + /// A notification with no importance: does not show in the shade. + static const NotificationChannelImportance NONE = + NotificationChannelImportance(0); + + /// Min notification importance: only shows in the shade, below the fold. + static const NotificationChannelImportance MIN = + NotificationChannelImportance(1); + + /// Low notification importance: shows in the shade, and potentially in the status bar (see shouldHideSilentStatusBarIcons()), but is not audibly intrusive. + static const NotificationChannelImportance LOW = + NotificationChannelImportance(2); + + /// Default notification importance: shows everywhere, makes noise, but does not visually intrude. + static const NotificationChannelImportance DEFAULT = + NotificationChannelImportance(3); + + /// Higher notification importance: shows everywhere, makes noise and peeks. May use full screen intents. + static const NotificationChannelImportance HIGH = + NotificationChannelImportance(4); + + /// Max notification importance: same as HIGH, but generally not used. + static const NotificationChannelImportance MAX = + NotificationChannelImportance(5); + + /// The raw value of [NotificationChannelImportance]. + final int rawValue; +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_icon.dart b/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_icon.dart new file mode 100644 index 0000000..c688f4e --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_icon.dart @@ -0,0 +1,36 @@ +import 'dart:ui'; + +import 'package:flutter_foreground_task/utils/color_extension.dart'; + +/// A data class for dynamically changing the notification icon. +class NotificationIcon { + /// Constructs an instance of [NotificationIcon]. + const NotificationIcon({ + required this.metaDataName, + this.backgroundColor, + }) : assert(metaDataName.length > 0); + + /// The name of the meta-data in the manifest that contains the drawable icon resource identifier. + final String metaDataName; + + /// The background color for the notification icon. + final Color? backgroundColor; + + /// Returns the data fields of [NotificationIcon] in JSON format. + Map toJson() { + return { + 'metaDataName': metaDataName, + 'backgroundColorRgb': backgroundColor?.toRgbString, + }; + } + + /// Creates a copy of the object replaced with new values. + NotificationIcon copyWith({ + String? metaDataName, + Color? backgroundColor, + }) => + NotificationIcon( + metaDataName: metaDataName ?? this.metaDataName, + backgroundColor: backgroundColor ?? this.backgroundColor, + ); +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_options.dart b/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_options.dart new file mode 100644 index 0000000..5695162 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_options.dart @@ -0,0 +1,162 @@ +import 'notification_channel_importance.dart'; +import 'notification_priority.dart'; +import 'notification_visibility.dart'; + +/// Notification options for Android platform. +class AndroidNotificationOptions { + /// Constructs an instance of [AndroidNotificationOptions]. + AndroidNotificationOptions({ + @Deprecated('Use startService(serviceId) instead.') this.id, + required this.channelId, + required this.channelName, + this.channelDescription, + this.channelImportance = NotificationChannelImportance.LOW, + this.priority = NotificationPriority.LOW, + this.enableVibration = false, + this.playSound = false, + this.showWhen = false, + this.showBadge = false, + this.onlyAlertOnce = false, + this.visibility = NotificationVisibility.VISIBILITY_PUBLIC, + }) : assert(channelId.isNotEmpty), + assert(channelName.isNotEmpty); + + /// Unique ID of the notification. + final int? id; + + /// Unique ID of the notification channel. + /// + /// It is set only once for the first time on Android 8.0+. + final String channelId; + + /// The name of the notification channel. + /// + /// It is set only once for the first time on Android 8.0+. + final String channelName; + + /// The description of the notification channel. + /// + /// It is set only once for the first time on Android 8.0+. + final String? channelDescription; + + /// The importance of the notification channel. + /// The default is `NotificationChannelImportance.LOW`. + /// + /// It is set only once for the first time on Android 8.0+. + final NotificationChannelImportance channelImportance; + + /// Priority of notifications for Android 7.1 and lower. + /// The default is `NotificationPriority.LOW`. + final NotificationPriority priority; + + /// Whether to enable vibration when creating notifications. + /// The default is `false`. + /// + /// It is set only once for the first time on Android 8.0+. + final bool enableVibration; + + /// Whether to play sound when creating notifications. + /// The default is `false`. + /// + /// It is set only once for the first time on Android 8.0+. + final bool playSound; + + /// Whether to show the timestamp when the notification was created in the content view. + /// The default is `false`. + final bool showWhen; + + /// Whether to show the badge near the app icon when service is started. + /// The default is `false`. + /// + /// It is set only once for the first time on Android 8.0+. + final bool showBadge; + + /// Whether to only alert once when the notification is created. + /// The default is `false`. + final bool onlyAlertOnce; + + /// Control the level of detail displayed in notifications on the lock screen. + /// The default is `NotificationVisibility.VISIBILITY_PUBLIC`. + final NotificationVisibility visibility; + + /// Returns the data fields of [AndroidNotificationOptions] in JSON format. + Map toJson() { + return { + 'notificationId': id, + 'notificationChannelId': channelId, + 'notificationChannelName': channelName, + 'notificationChannelDescription': channelDescription, + 'notificationChannelImportance': channelImportance.rawValue, + 'notificationPriority': priority.rawValue, + 'enableVibration': enableVibration, + 'playSound': playSound, + 'showWhen': showWhen, + 'showBadge': showBadge, + 'onlyAlertOnce': onlyAlertOnce, + 'visibility': visibility.rawValue, + }; + } + + /// Creates a copy of the object replaced with new values. + AndroidNotificationOptions copyWith({ + String? channelId, + String? channelName, + String? channelDescription, + NotificationChannelImportance? channelImportance, + NotificationPriority? priority, + bool? enableVibration, + bool? playSound, + bool? showWhen, + bool? showBadge, + bool? onlyAlertOnce, + NotificationVisibility? visibility, + }) => + AndroidNotificationOptions( + channelId: channelId ?? this.channelId, + channelName: channelName ?? this.channelName, + channelDescription: channelDescription ?? this.channelDescription, + channelImportance: channelImportance ?? this.channelImportance, + priority: priority ?? this.priority, + enableVibration: enableVibration ?? this.enableVibration, + playSound: playSound ?? this.playSound, + showWhen: showWhen ?? this.showWhen, + showBadge: showBadge ?? this.showBadge, + onlyAlertOnce: onlyAlertOnce ?? this.onlyAlertOnce, + visibility: visibility ?? this.visibility, + ); +} + +/// Notification options for iOS platform. +class IOSNotificationOptions { + /// Constructs an instance of [IOSNotificationOptions]. + const IOSNotificationOptions({ + this.showNotification = true, + this.playSound = false, + }); + + /// Whether to show notifications. + /// The default is `true`. + final bool showNotification; + + /// Whether to play sound when creating notifications. + /// The default is `false`. + final bool playSound; + + /// Returns the data fields of [IOSNotificationOptions] in JSON format. + Map toJson() { + return { + 'showNotification': showNotification, + 'playSound': playSound, + }; + } + + /// Creates a copy of the object replaced with new values. + IOSNotificationOptions copyWith({ + bool? showNotification, + bool? playSound, + }) => + IOSNotificationOptions( + showNotification: showNotification ?? this.showNotification, + playSound: playSound ?? this.playSound, + ); +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_permission.dart b/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_permission.dart new file mode 100644 index 0000000..d6600c9 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_permission.dart @@ -0,0 +1,14 @@ +/// Represents the result of a notification permission request. +enum NotificationPermission { + /// Notification permission has been granted. + granted, + + /// Notification permission has been denied. + denied, + + /// Notification permission has been permanently denied. + permanently_denied; + + static NotificationPermission fromIndex(int? index) => NotificationPermission + .values[index ?? NotificationPermission.denied.index]; +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_priority.dart b/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_priority.dart new file mode 100644 index 0000000..5a463e4 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_priority.dart @@ -0,0 +1,23 @@ +/// Priority of notifications for Android 7.1 and lower. +class NotificationPriority { + /// Constructs an instance of [NotificationPriority]. + const NotificationPriority(this.rawValue); + + /// No sound and does not appear in the status bar. + static const NotificationPriority MIN = NotificationPriority(-2); + + /// No sound. + static const NotificationPriority LOW = NotificationPriority(-1); + + /// Makes a sound. + static const NotificationPriority DEFAULT = NotificationPriority(0); + + /// Makes a sound and appears as a heads-up notification. + static const NotificationPriority HIGH = NotificationPriority(1); + + /// Same as HIGH, but used when you want to notify notification immediately. + static const NotificationPriority MAX = NotificationPriority(2); + + /// The raw value of [NotificationPriority]. + final int rawValue; +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_visibility.dart b/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_visibility.dart new file mode 100644 index 0000000..c2f24eb --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/models/notification_visibility.dart @@ -0,0 +1,20 @@ +/// The level of detail displayed in notifications on the lock screen. +class NotificationVisibility { + /// Constructs an instance of [NotificationVisibility]. + const NotificationVisibility(this.rawValue); + + /// Show this notification in its entirety on all lockscreens. + static const NotificationVisibility VISIBILITY_PUBLIC = + NotificationVisibility(1); + + /// Do not reveal any part of this notification on a secure lockscreen. + static const NotificationVisibility VISIBILITY_SECRET = + NotificationVisibility(-1); + + /// Show this notification on all lockscreens, but conceal sensitive or private information on secure lockscreens. + static const NotificationVisibility VISIBILITY_PRIVATE = + NotificationVisibility(0); + + /// The raw value of [NotificationVisibility]. + final int rawValue; +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/models/service_options.dart b/local_packages/flutter_foreground_task-9.1.0/lib/models/service_options.dart new file mode 100644 index 0000000..4e4f391 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/models/service_options.dart @@ -0,0 +1,104 @@ +import 'dart:ui'; + +import 'package:platform/platform.dart'; + +import 'foreground_service_types.dart'; +import 'foreground_task_options.dart'; +import 'notification_button.dart'; +import 'notification_icon.dart'; +import 'notification_options.dart'; + +class ServiceStartOptions { + const ServiceStartOptions({ + this.serviceId, + this.serviceTypes, + required this.androidNotificationOptions, + required this.iosNotificationOptions, + required this.foregroundTaskOptions, + required this.notificationContentTitle, + required this.notificationContentText, + this.notificationIcon, + this.notificationButtons, + this.notificationInitialRoute, + this.callback, + }); + + final int? serviceId; + final List? serviceTypes; + final AndroidNotificationOptions androidNotificationOptions; + final IOSNotificationOptions iosNotificationOptions; + final ForegroundTaskOptions foregroundTaskOptions; + final String notificationContentTitle; + final String notificationContentText; + final NotificationIcon? notificationIcon; + final List? notificationButtons; + final String? notificationInitialRoute; + final Function? callback; + + Map toJson(Platform platform) { + final Map json = { + 'serviceId': serviceId, + 'serviceTypes': serviceTypes?.map((e) => e.rawValue).toList(), + ...foregroundTaskOptions.toJson(), + 'notificationContentTitle': notificationContentTitle, + 'notificationContentText': notificationContentText, + 'icon': notificationIcon?.toJson(), + 'buttons': notificationButtons?.map((e) => e.toJson()).toList(), + 'initialRoute': notificationInitialRoute, + }; + + if (platform.isAndroid) { + json.addAll(androidNotificationOptions.toJson()); + } else if (platform.isIOS) { + json.addAll(iosNotificationOptions.toJson()); + } + + if (callback != null) { + json['callbackHandle'] = + PluginUtilities.getCallbackHandle(callback!)?.toRawHandle(); + } + + return json; + } +} + +class ServiceUpdateOptions { + const ServiceUpdateOptions({ + required this.foregroundTaskOptions, + required this.notificationContentTitle, + required this.notificationContentText, + this.notificationIcon, + this.notificationButtons, + this.notificationInitialRoute, + this.callback, + }); + + final ForegroundTaskOptions? foregroundTaskOptions; + final String? notificationContentTitle; + final String? notificationContentText; + final NotificationIcon? notificationIcon; + final List? notificationButtons; + final String? notificationInitialRoute; + final Function? callback; + + Map toJson(Platform platform) { + final Map json = { + 'notificationContentTitle': notificationContentTitle, + 'notificationContentText': notificationContentText, + 'icon': notificationIcon?.toJson(), + 'buttons': notificationButtons?.map((e) => e.toJson()).toList(), + 'initialRoute': notificationInitialRoute, + }; + + if (foregroundTaskOptions != null) { + json.addAll(foregroundTaskOptions!.toJson()); + } + + if (callback != null) { + json['callbackHandle'] = + PluginUtilities.getCallbackHandle(callback!)?.toRawHandle(); + } + + return json; + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/models/service_request_result.dart b/local_packages/flutter_foreground_task-9.1.0/lib/models/service_request_result.dart new file mode 100644 index 0000000..679e40c --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/models/service_request_result.dart @@ -0,0 +1,17 @@ +/// Represents the result of a service request. +sealed class ServiceRequestResult { + const ServiceRequestResult(); +} + +/// The service request was successful. +final class ServiceRequestSuccess extends ServiceRequestResult { + const ServiceRequestSuccess(); +} + +/// The service request failed. +final class ServiceRequestFailure extends ServiceRequestResult { + const ServiceRequestFailure({required this.error}); + + /// The error that occurred when the service request failed. + final Object error; +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/task_handler.dart b/local_packages/flutter_foreground_task-9.1.0/lib/task_handler.dart new file mode 100644 index 0000000..f99015b --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/task_handler.dart @@ -0,0 +1,44 @@ +import 'flutter_foreground_task.dart'; + +/// A class that implements a task handler. +abstract class TaskHandler { + /// Called when the task is started. + Future onStart(DateTime timestamp, TaskStarter starter); + + /// Called based on the eventAction set in [ForegroundTaskOptions]. + /// + /// - .nothing() : Not use onRepeatEvent callback. + /// - .once() : Call onRepeatEvent only once. + /// - .repeat(interval) : Call onRepeatEvent at milliseconds interval. + void onRepeatEvent(DateTime timestamp); + + /// Called when the task is destroyed. + Future onDestroy(DateTime timestamp, bool isTimeout); + + /// Called when data is sent using [FlutterForegroundTask.sendDataToTask]. + void onReceiveData(Object data) {} + + /// Called when the notification button is pressed. + void onNotificationButtonPressed(String id) {} + + /// Called when the notification itself is pressed. + void onNotificationPressed() {} + + /// Called when the notification itself is dismissed. + /// + /// - AOS: only work Android 14+ + /// + /// - iOS: only work iOS 12+ + void onNotificationDismissed() {} +} + +/// The starter that started the task. +enum TaskStarter { + /// The task has been started by the developer. + developer, + + /// The task has been started by the system. + system; + + static TaskStarter fromIndex(int index) => TaskStarter.values[index]; +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/ui/with_foreground_task.dart b/local_packages/flutter_foreground_task-9.1.0/lib/ui/with_foreground_task.dart new file mode 100644 index 0000000..87a3ff8 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/ui/with_foreground_task.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_foreground_task/flutter_foreground_task.dart'; + +/// A widget that minimize the app without closing it when the user presses the soft back button. +/// It only works when the service is running. +/// +/// This widget must be declared above the [Scaffold] widget. +class WithForegroundTask extends StatefulWidget { + /// A child widget that contains the [Scaffold] widget. + final Widget child; + + const WithForegroundTask({super.key, required this.child}); + + @override + State createState() => _WithForegroundTaskState(); +} + +class _WithForegroundTaskState extends State { + Future _onWillPop() async { + final bool canPop = mounted ? Navigator.canPop(context) : false; + if (!canPop && await FlutterForegroundTask.isRunningService) { + FlutterForegroundTask.minimizeApp(); + return false; + } + + return true; + } + + @override + Widget build(BuildContext context) => + WillPopScope(onWillPop: _onWillPop, child: widget.child); +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/utils/color_extension.dart b/local_packages/flutter_foreground_task-9.1.0/lib/utils/color_extension.dart new file mode 100644 index 0000000..412a07c --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/utils/color_extension.dart @@ -0,0 +1,5 @@ +import 'dart:ui'; + +extension ColorExtension on Color { + String get toRgbString => '$red,$green,$blue'; +} diff --git a/local_packages/flutter_foreground_task-9.1.0/lib/utils/utility.dart b/local_packages/flutter_foreground_task-9.1.0/lib/utils/utility.dart new file mode 100644 index 0000000..1919a62 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/lib/utils/utility.dart @@ -0,0 +1,26 @@ +final class Utility { + Utility._(); + + static Utility instance = Utility._(); + + Future completedWithinDeadline({ + required Duration deadline, + required Future Function() future, + Duration tick = const Duration(milliseconds: 100), + }) async { + final Stopwatch stopwatch = Stopwatch()..start(); + bool completed = false; + await Future.doWhile(() async { + completed = await future(); + if (completed || + stopwatch.elapsedMilliseconds > deadline.inMilliseconds) { + return false; + } else { + await Future.delayed(tick); + return true; + } + }); + + return completed; + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/pubspec.yaml b/local_packages/flutter_foreground_task-9.1.0/pubspec.yaml new file mode 100644 index 0000000..54431ee --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/pubspec.yaml @@ -0,0 +1,29 @@ +name: flutter_foreground_task +description: This plugin is used to implement a foreground service on the Android platform. +version: 9.1.0 +homepage: https://github.com/Dev-hwang/flutter_foreground_task + +environment: + sdk: ^3.4.0 + flutter: ">=3.22.0" + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.1.8 + shared_preferences: ^2.5.3 + platform: ^3.1.6 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: + plugin: + platforms: + android: + package: com.pravera.flutter_foreground_task + pluginClass: FlutterForegroundTaskPlugin + ios: + pluginClass: FlutterForegroundTaskPlugin diff --git a/local_packages/flutter_foreground_task-9.1.0/test/dummy/service_dummy_data.dart b/local_packages/flutter_foreground_task-9.1.0/test/dummy/service_dummy_data.dart new file mode 100644 index 0000000..9e1e7ee --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/test/dummy/service_dummy_data.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_foreground_task/flutter_foreground_task.dart'; +import 'package:flutter_foreground_task/models/service_options.dart'; +import 'package:platform/platform.dart'; + +@pragma('vm:entry-point') +void testCallback() { + print('test'); +} + +class ServiceDummyData { + final AndroidNotificationOptions androidNotificationOptions = + AndroidNotificationOptions( + channelId: 'test_channel', + channelName: 'Test Channel', + channelDescription: 'Test Channel Description', + channelImportance: NotificationChannelImportance.LOW, + priority: NotificationPriority.LOW, + enableVibration: false, + playSound: false, + showWhen: false, + showBadge: false, + visibility: NotificationVisibility.VISIBILITY_PUBLIC, + ); + + final IOSNotificationOptions iosNotificationOptions = + const IOSNotificationOptions( + showNotification: false, + playSound: false, + ); + + final ForegroundTaskOptions foregroundTaskOptions = ForegroundTaskOptions( + eventAction: ForegroundTaskEventAction.repeat(1000), + autoRunOnBoot: true, + autoRunOnMyPackageReplaced: true, + allowWifiLock: true, + allowWakeLock: true, + ); + + final int serviceId = 200; + + final String notificationTitle = 'title'; + + final String notificationText = 'test'; + + final NotificationIcon notificationIcon = const NotificationIcon( + metaDataName: 'test', + backgroundColor: Colors.orange, + ); + + final List notificationButtons = [ + const NotificationButton( + id: 'id_test1', text: 'test1', textColor: Colors.purple), + const NotificationButton( + id: 'id_test2', text: 'test2', textColor: Colors.green), + ]; + + Map getStartServiceArgs(Platform platform) { + return ServiceStartOptions( + serviceId: serviceId, + androidNotificationOptions: androidNotificationOptions, + iosNotificationOptions: iosNotificationOptions, + foregroundTaskOptions: foregroundTaskOptions, + notificationContentTitle: notificationTitle, + notificationContentText: notificationText, + notificationIcon: notificationIcon, + notificationButtons: notificationButtons, + callback: testCallback, + ).toJson(platform); + } + + Map getUpdateServiceArgs(Platform platform) { + return ServiceUpdateOptions( + foregroundTaskOptions: foregroundTaskOptions, + notificationContentTitle: notificationTitle, + notificationContentText: notificationText, + notificationIcon: notificationIcon, + notificationButtons: notificationButtons, + callback: testCallback, + ).toJson(platform); + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/test/service_api_test.dart b/local_packages/flutter_foreground_task-9.1.0/test/service_api_test.dart new file mode 100644 index 0000000..13a0196 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/test/service_api_test.dart @@ -0,0 +1,633 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_foreground_task/flutter_foreground_task.dart'; +import 'package:flutter_foreground_task/flutter_foreground_task_method_channel.dart'; +import 'package:flutter_foreground_task/flutter_foreground_task_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:platform/platform.dart'; + +import 'dummy/service_dummy_data.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final ServiceDummyData dummyData = ServiceDummyData(); + + late MethodChannelFlutterForegroundTask platformChannel; + late ServiceApiMethodCallHandler methodCallHandler; + + setUp(() { + platformChannel = MethodChannelFlutterForegroundTask(); + FlutterForegroundTaskPlatform.instance = platformChannel; + FlutterForegroundTask.resetStatic(); + + methodCallHandler = + ServiceApiMethodCallHandler(() => platformChannel.platform); + + // method channel + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + platformChannel.mMDChannel, + methodCallHandler.onMethodCall, + ); + }); + + tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(platformChannel.mMDChannel, null); + }); + + group('Android', () { + final Platform platform = FakePlatform(operatingSystem: Platform.android); + + test('init', () { + platformChannel.platform = platform; + + expect(FlutterForegroundTask.isInitialized, false); + expect(FlutterForegroundTask.androidNotificationOptions, isNull); + expect(FlutterForegroundTask.iosNotificationOptions, isNull); + expect(FlutterForegroundTask.foregroundTaskOptions, isNull); + + _init(dummyData); + + expect(FlutterForegroundTask.isInitialized, true); + expect( + FlutterForegroundTask.androidNotificationOptions, + dummyData.androidNotificationOptions, + ); + expect( + FlutterForegroundTask.iosNotificationOptions, + dummyData.iosNotificationOptions, + ); + expect( + FlutterForegroundTask.foregroundTaskOptions, + dummyData.foregroundTaskOptions, + ); + }); + + test('startService', () async { + platformChannel.platform = platform; + FlutterForegroundTask.skipServiceResponseCheck = true; + + _init(dummyData); + + final ServiceRequestResult result = await _startService(dummyData); + expect(result, isA()); + expect( + methodCallHandler.log.last, + isMethodCall( + ServiceApiMethod.startService, + arguments: dummyData.getStartServiceArgs(platform), + ), + ); + }); + + test('startService (error: ServiceNotInitializedException)', () async { + platformChannel.platform = platform; + + final ServiceRequestResult result = await _startService(dummyData); + expect(result, isA()); + expect( + (result as ServiceRequestFailure).error, + isA(), + ); + }); + + test('startService (error: ServiceAlreadyStartedException)', () async { + platformChannel.platform = platform; + + _init(dummyData); + + final ServiceRequestResult result1 = await _startService(dummyData); + expect(result1, isA()); + + final ServiceRequestResult result2 = await _startService(dummyData); + expect(result2, isA()); + expect( + (result2 as ServiceRequestFailure).error, + isA(), + ); + }); + + test('startService (error: ServiceTimeoutException)', () async { + platformChannel.platform = platform; + + _init(dummyData); + + // set test + methodCallHandler.timeoutTest = true; + + final ServiceRequestResult result = await _startService(dummyData); + expect(result, isA()); + expect( + (result as ServiceRequestFailure).error, + isA(), + ); + }); + + test('restartService', () async { + platformChannel.platform = platform; + + _init(dummyData); + + final ServiceRequestResult result1 = await _startService(dummyData); + expect(result1, isA()); + + final ServiceRequestResult result2 = await _restartService(); + expect(result2, isA()); + expect( + methodCallHandler.log.last, + isMethodCall(ServiceApiMethod.restartService, arguments: null), + ); + }); + + test('restartService (error: ServiceNotStartedException)', () async { + platformChannel.platform = platform; + + final ServiceRequestResult result = await _restartService(); + expect(result, isA()); + expect( + (result as ServiceRequestFailure).error, + isA(), + ); + }); + + test('updateService', () async { + platformChannel.platform = platform; + + _init(dummyData); + + final ServiceRequestResult result1 = await _startService(dummyData); + expect(result1, isA()); + + final ServiceRequestResult result2 = await _updateService(dummyData); + expect(result2, isA()); + expect( + methodCallHandler.log.last, + isMethodCall( + ServiceApiMethod.updateService, + arguments: dummyData.getUpdateServiceArgs(platform), + ), + ); + }); + + test('updateService (error: ServiceNotStartedException)', () async { + platformChannel.platform = platform; + + final ServiceRequestResult result = await _updateService(dummyData); + expect(result, isA()); + expect( + (result as ServiceRequestFailure).error, + isA(), + ); + }); + + test('stopService', () async { + platformChannel.platform = platform; + FlutterForegroundTask.skipServiceResponseCheck = true; + + _init(dummyData); + + final ServiceRequestResult result1 = await _startService(dummyData); + expect(result1, isA()); + + final ServiceRequestResult result2 = await _stopService(); + expect(result2, isA()); + expect( + methodCallHandler.log.last, + isMethodCall(ServiceApiMethod.stopService, arguments: null), + ); + }); + + test('stopService (error: ServiceNotStartedException)', () async { + platformChannel.platform = platform; + + final ServiceRequestResult result = await _stopService(); + expect(result, isA()); + expect( + (result as ServiceRequestFailure).error, + isA(), + ); + }); + + test('stopService (error: ServiceTimeoutException)', () async { + platformChannel.platform = platform; + + _init(dummyData); + + final ServiceRequestResult result1 = await _startService(dummyData); + expect(result1, isA()); + + // set test + methodCallHandler.timeoutTest = true; + + final ServiceRequestResult result2 = await _stopService(); + expect(result2, isA()); + expect( + (result2 as ServiceRequestFailure).error, + isA(), + ); + }); + + test('isRunningService', () async { + platformChannel.platform = platform; + + _init(dummyData); + expect(await _isRunningService, false); + expect( + methodCallHandler.log.last, + isMethodCall(ServiceApiMethod.isRunningService, arguments: null), + ); + + await _startService(dummyData); + expect(await _isRunningService, true); + expect( + methodCallHandler.log.last, + isMethodCall(ServiceApiMethod.isRunningService, arguments: null), + ); + + await _restartService(); + expect(await _isRunningService, true); + expect( + methodCallHandler.log.last, + isMethodCall(ServiceApiMethod.isRunningService, arguments: null), + ); + + await _updateService(dummyData); + expect(await _isRunningService, true); + expect( + methodCallHandler.log.last, + isMethodCall(ServiceApiMethod.isRunningService, arguments: null), + ); + + await _stopService(); + expect(await _isRunningService, false); + expect( + methodCallHandler.log.last, + isMethodCall(ServiceApiMethod.isRunningService, arguments: null), + ); + }); + }); + + group('iOS', () { + final Platform platform = FakePlatform(operatingSystem: Platform.iOS); + + test('init', () { + platformChannel.platform = platform; + + expect(FlutterForegroundTask.isInitialized, false); + expect(FlutterForegroundTask.androidNotificationOptions, isNull); + expect(FlutterForegroundTask.iosNotificationOptions, isNull); + expect(FlutterForegroundTask.foregroundTaskOptions, isNull); + + _init(dummyData); + + expect(FlutterForegroundTask.isInitialized, true); + expect( + FlutterForegroundTask.androidNotificationOptions, + dummyData.androidNotificationOptions, + ); + expect( + FlutterForegroundTask.iosNotificationOptions, + dummyData.iosNotificationOptions, + ); + expect( + FlutterForegroundTask.foregroundTaskOptions, + dummyData.foregroundTaskOptions, + ); + }); + + test('startService', () async { + platformChannel.platform = platform; + FlutterForegroundTask.skipServiceResponseCheck = true; + + _init(dummyData); + + final ServiceRequestResult result = await _startService(dummyData); + expect(result, isA()); + expect( + methodCallHandler.log.last, + isMethodCall( + ServiceApiMethod.startService, + arguments: dummyData.getStartServiceArgs(platform), + ), + ); + }); + + test('startService (error: ServiceNotInitializedException)', () async { + platformChannel.platform = platform; + + final ServiceRequestResult result = await _startService(dummyData); + expect(result, isA()); + expect( + (result as ServiceRequestFailure).error, + isA(), + ); + }); + + test('startService (error: ServiceAlreadyStartedException)', () async { + platformChannel.platform = platform; + + _init(dummyData); + + final ServiceRequestResult result1 = await _startService(dummyData); + expect(result1, isA()); + + final ServiceRequestResult result2 = await _startService(dummyData); + expect(result2, isA()); + expect( + (result2 as ServiceRequestFailure).error, + isA(), + ); + }); + + test('startService (error: ServiceTimeoutException)', () async { + platformChannel.platform = platform; + + _init(dummyData); + + // set test + methodCallHandler.timeoutTest = true; + + final ServiceRequestResult result = await _startService(dummyData); + expect(result, isA()); + expect( + (result as ServiceRequestFailure).error, + isA(), + ); + }); + + test('restartService', () async { + platformChannel.platform = platform; + + _init(dummyData); + + final ServiceRequestResult result1 = await _startService(dummyData); + expect(result1, isA()); + + final ServiceRequestResult result2 = await _restartService(); + expect(result2, isA()); + expect( + methodCallHandler.log.last, + isMethodCall(ServiceApiMethod.restartService, arguments: null), + ); + }); + + test('restartService (error: ServiceNotStartedException)', () async { + platformChannel.platform = platform; + + final ServiceRequestResult result = await _restartService(); + expect(result, isA()); + expect( + (result as ServiceRequestFailure).error, + isA(), + ); + }); + + test('updateService', () async { + platformChannel.platform = platform; + + _init(dummyData); + + final ServiceRequestResult result1 = await _startService(dummyData); + expect(result1, isA()); + + final ServiceRequestResult result2 = await _updateService(dummyData); + expect(result2, isA()); + expect( + methodCallHandler.log.last, + isMethodCall( + ServiceApiMethod.updateService, + arguments: dummyData.getUpdateServiceArgs(platform), + ), + ); + }); + + test('updateService (error: ServiceNotStartedException)', () async { + platformChannel.platform = platform; + + final ServiceRequestResult result = await _updateService(dummyData); + expect(result, isA()); + expect( + (result as ServiceRequestFailure).error, + isA(), + ); + }); + + test('stopService', () async { + platformChannel.platform = platform; + FlutterForegroundTask.skipServiceResponseCheck = true; + + _init(dummyData); + + final ServiceRequestResult result1 = await _startService(dummyData); + expect(result1, isA()); + + final ServiceRequestResult result2 = await _stopService(); + expect(result2, isA()); + expect( + methodCallHandler.log.last, + isMethodCall(ServiceApiMethod.stopService, arguments: null), + ); + }); + + test('stopService (error: ServiceNotStartedException)', () async { + platformChannel.platform = platform; + + final ServiceRequestResult result = await _stopService(); + expect(result, isA()); + expect( + (result as ServiceRequestFailure).error, + isA(), + ); + }); + + test('stopService (error: ServiceTimeoutException)', () async { + platformChannel.platform = platform; + + _init(dummyData); + + final ServiceRequestResult result1 = await _startService(dummyData); + expect(result1, isA()); + + // set test + methodCallHandler.timeoutTest = true; + + final ServiceRequestResult result2 = await _stopService(); + expect(result2, isA()); + expect( + (result2 as ServiceRequestFailure).error, + isA(), + ); + }); + + test('isRunningService', () async { + platformChannel.platform = platform; + + _init(dummyData); + expect(await _isRunningService, false); + expect( + methodCallHandler.log.last, + isMethodCall(ServiceApiMethod.isRunningService, arguments: null), + ); + + await _startService(dummyData); + expect(await _isRunningService, true); + expect( + methodCallHandler.log.last, + isMethodCall(ServiceApiMethod.isRunningService, arguments: null), + ); + + await _restartService(); + expect(await _isRunningService, true); + expect( + methodCallHandler.log.last, + isMethodCall(ServiceApiMethod.isRunningService, arguments: null), + ); + + await _updateService(dummyData); + expect(await _isRunningService, true); + expect( + methodCallHandler.log.last, + isMethodCall(ServiceApiMethod.isRunningService, arguments: null), + ); + + await _stopService(); + expect(await _isRunningService, false); + expect( + methodCallHandler.log.last, + isMethodCall(ServiceApiMethod.isRunningService, arguments: null), + ); + }); + }); +} + +void _init(ServiceDummyData dummyData) { + FlutterForegroundTask.init( + androidNotificationOptions: dummyData.androidNotificationOptions, + iosNotificationOptions: dummyData.iosNotificationOptions, + foregroundTaskOptions: dummyData.foregroundTaskOptions, + ); +} + +Future _startService(ServiceDummyData dummyData) { + return FlutterForegroundTask.startService( + serviceId: dummyData.serviceId, + notificationTitle: dummyData.notificationTitle, + notificationText: dummyData.notificationText, + notificationIcon: dummyData.notificationIcon, + notificationButtons: dummyData.notificationButtons, + callback: testCallback, + ); +} + +Future _restartService() { + return FlutterForegroundTask.restartService(); +} + +Future _updateService(ServiceDummyData dummyData) { + return FlutterForegroundTask.updateService( + foregroundTaskOptions: dummyData.foregroundTaskOptions, + notificationTitle: dummyData.notificationTitle, + notificationText: dummyData.notificationText, + notificationIcon: dummyData.notificationIcon, + notificationButtons: dummyData.notificationButtons, + callback: testCallback, + ); +} + +Future _stopService() { + return FlutterForegroundTask.stopService(); +} + +Future get _isRunningService { + return FlutterForegroundTask.isRunningService; +} + +class ServiceApiMethod { + static const String startService = 'startService'; + static const String restartService = 'restartService'; + static const String updateService = 'updateService'; + static const String stopService = 'stopService'; + static const String isRunningService = 'isRunningService'; + static const String attachedActivity = 'attachedActivity'; + + static Set getImplementation(Platform platform) { + if (platform.isAndroid) { + return { + startService, + restartService, + updateService, + stopService, + isRunningService, + attachedActivity, + }; + } else if (platform.isIOS) { + return { + startService, + restartService, + updateService, + stopService, + isRunningService, + }; + } + + return {}; + } +} + +class ServiceApiMethodCallHandler { + ServiceApiMethodCallHandler(this._platformGetter); + + final ValueGetter _platformGetter; + + final List log = []; + + bool timeoutTest = false; + + bool _isRunningService = false; + + // unimplemented: throw UnimplementedError + void _checkImplementation(String method) { + final Platform platform = _platformGetter(); + if (!ServiceApiMethod.getImplementation(platform).contains(method)) { + throw UnimplementedError( + 'Unimplemented method on ${platform.operatingSystem}: $method'); + } + } + + Future? onMethodCall(MethodCall methodCall) async { + final String method = methodCall.method; + _checkImplementation(method); + + log.add(methodCall); + + if (method == ServiceApiMethod.startService) { + if (!timeoutTest) { + _isRunningService = true; + } + return Future.value(); + } else if (method == ServiceApiMethod.restartService) { + if (!timeoutTest) { + _isRunningService = true; + } + return Future.value(); + } else if (method == ServiceApiMethod.updateService) { + if (!timeoutTest) { + _isRunningService = true; + } + return Future.value(); + } else if (method == ServiceApiMethod.stopService) { + if (!timeoutTest) { + _isRunningService = false; + } + return Future.value(); + } else if (method == ServiceApiMethod.isRunningService) { + return _isRunningService; + } else if (method == ServiceApiMethod.attachedActivity) { + return true; + } + + throw UnimplementedError(); + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/test/storage_test.dart b/local_packages/flutter_foreground_task-9.1.0/test/storage_test.dart new file mode 100644 index 0000000..a4104ce --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/test/storage_test.dart @@ -0,0 +1,130 @@ +import 'package:flutter_foreground_task/flutter_foreground_task.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const TestData testString = TestData('testString', 'hello'); + const TestData testBool = TestData('testBool', false); + const TestData testInt = TestData('testInt', 1234); + const TestData testDouble = TestData('testDouble', 1.432); + + late TestDataStorage storage; + + setUp(() { + SharedPreferences.setMockInitialValues({}); + storage = TestDataStorage(); + }); + + test('saveData', () async { + expect(await storage.saveData(data: testString), true); + expect(await storage.saveData(data: testBool), true); + expect(await storage.saveData(data: testInt), true); + expect(await storage.saveData(data: testDouble), true); + }); + + test('getData', () async { + await storage.saveData(data: testString); + await storage.saveData(data: testBool); + await storage.saveData(data: testInt); + await storage.saveData(data: testDouble); + + final TestData? actualString = await storage.getData(key: testString.key); + expect(actualString, testString); + final TestData? actualBool = await storage.getData(key: testBool.key); + expect(actualBool, testBool); + final TestData? actualInt = await storage.getData(key: testInt.key); + expect(actualInt, testInt); + final TestData? actualDouble = await storage.getData(key: testDouble.key); + expect(actualDouble, testDouble); + }); + + test('getAllData', () async { + await storage.saveData(data: testString); + await storage.saveData(data: testBool); + await storage.saveData(data: testInt); + await storage.saveData(data: testDouble); + + final Map allData = await storage.getAllData(); + expect(allData, containsData(testString)); + expect(allData, containsData(testBool)); + expect(allData, containsData(testInt)); + expect(allData, containsData(testDouble)); + }); + + test('removeData', () async { + await storage.saveData(data: testString); + await storage.saveData(data: testInt); + + await storage.removeData(key: testInt.key); + final Map allData = await storage.getAllData(); + expect(allData.length, 1); + expect(allData, containsData(testString)); + expect(allData, isNot(containsData(testInt))); + }); + + test('clearAllData', () async { + await storage.saveData(data: testString); + await storage.saveData(data: testDouble); + + await storage.clearAllData(); + final Map allData = await storage.getAllData(); + expect(allData, isEmpty); + }); + + test('If the key already exists, the value will be replaced.', () async { + await storage.saveData(data: testString); + + // replace value + final TestData newTestString = TestData(testString.key, 'bye'); + await storage.saveData(data: newTestString); + + final TestData? replacedData = await storage.getData(key: testString.key); + expect(replacedData, newTestString); + }); +} + +Matcher containsData(TestData data) { + return containsPair(data.key, data.value); +} + +class TestData { + const TestData(this.key, this.value); + + final String key; + final T value; + + @override + bool operator ==(Object other) => + other is TestData && key == other.key && value == other.value; + + @override + int get hashCode => key.hashCode ^ value.hashCode; +} + +class TestDataStorage { + Future saveData({required TestData data}) { + return FlutterForegroundTask.saveData(key: data.key, value: data.value); + } + + Future?> getData({required String key}) async { + final T? value = await FlutterForegroundTask.getData(key: key); + if (value == null) { + return null; + } + return TestData(key, value); + } + + Future> getAllData() { + return FlutterForegroundTask.getAllData(); + } + + Future removeData({required String key}) { + return FlutterForegroundTask.removeData(key: key); + } + + Future clearAllData() { + return FlutterForegroundTask.clearAllData(); + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/test/task_handler_test.dart b/local_packages/flutter_foreground_task-9.1.0/test/task_handler_test.dart new file mode 100644 index 0000000..4b01131 --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/test/task_handler_test.dart @@ -0,0 +1,321 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_foreground_task/flutter_foreground_task.dart'; +import 'package:flutter_foreground_task/flutter_foreground_task_method_channel.dart'; +import 'package:flutter_foreground_task/flutter_foreground_task_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late MethodChannelFlutterForegroundTask platformChannel; + late TestTaskHandler taskHandler; + + setUp(() { + platformChannel = MethodChannelFlutterForegroundTask(); + FlutterForegroundTaskPlatform.instance = platformChannel; + FlutterForegroundTask.resetStatic(); + + taskHandler = TestTaskHandler(); + + // method channel + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + platformChannel.mMDChannel, + (MethodCall methodCall) async { + final String method = methodCall.method; + if (method == 'sendData') { + final dynamic data = methodCall.arguments; + platformChannel.mBGChannel + .invokeMethod(TaskEventMethod.onReceiveData, data); + } + return; + }, + ); + + // background channel + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + platformChannel.mBGChannel, + (MethodCall methodCall) { + return platformChannel.onBackgroundChannel(methodCall, taskHandler); + }, + ); + }); + + tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(platformChannel.mMDChannel, null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(platformChannel.mBGChannel, null); + }); + + group('TaskHandler', () { + test('onStart', () async { + const String method = TaskEventMethod.onStart; + const TaskStarter starter = TaskStarter.developer; + + await platformChannel.mBGChannel.invokeMethod(method, starter.index); + expect(taskHandler.log.last, isTaskEvent(method, starter.index)); + }); + + test('onRepeatEvent', () async { + const String method = TaskEventMethod.onRepeatEvent; + + await platformChannel.mBGChannel.invokeMethod(method); + expect(taskHandler.log.last, isTaskEvent(method)); + }); + + test('onDestroy', () async { + const String method = TaskEventMethod.onDestroy; + + await platformChannel.mBGChannel.invokeMethod(method); + expect(taskHandler.log.last, isTaskEvent(method, false)); + }); + + test('onReceiveData', () async { + const String method = TaskEventMethod.onReceiveData; + + const String stringData = 'hello'; + await platformChannel.mBGChannel.invokeMethod(method, stringData); + expect(taskHandler.log.last, isTaskEvent(method, stringData)); + + const int intData = 1234; + await platformChannel.mBGChannel.invokeMethod(method, intData); + expect(taskHandler.log.last, isTaskEvent(method, intData)); + + const double doubleData = 1.234; + await platformChannel.mBGChannel.invokeMethod(method, doubleData); + expect(taskHandler.log.last, isTaskEvent(method, doubleData)); + + const bool boolData = false; + await platformChannel.mBGChannel.invokeMethod(method, boolData); + expect(taskHandler.log.last, isTaskEvent(method, boolData)); + + const List listData = [1, 2, 3]; + await platformChannel.mBGChannel.invokeMethod(method, listData); + expect(taskHandler.log.last, isTaskEvent(method, listData)); + + const Map mapData = {'message': 'hello', 'data': 1}; + await platformChannel.mBGChannel.invokeMethod(method, mapData); + expect(taskHandler.log.last, isTaskEvent(method, mapData)); + }); + + test('onNotificationButtonPressed', () async { + const String method = TaskEventMethod.onNotificationButtonPressed; + + const String data = 'id_hello'; + await platformChannel.mBGChannel.invokeMethod(method, data); + expect(taskHandler.log.last, isTaskEvent(method, data)); + }); + + test('onNotificationPressed', () async { + const String method = TaskEventMethod.onNotificationPressed; + + await platformChannel.mBGChannel.invokeMethod(method); + expect(taskHandler.log.last, isTaskEvent(method)); + }); + + test('onNotificationDismissed', () async { + const String method = TaskEventMethod.onNotificationDismissed; + + await platformChannel.mBGChannel.invokeMethod(method); + expect(taskHandler.log.last, isTaskEvent(method)); + }); + }); + + group('CommunicationPort', () { + test('initCommunicationPort', () { + FlutterForegroundTask.initCommunicationPort(); + expect(FlutterForegroundTask.receivePort, isNotNull); + expect(FlutterForegroundTask.streamSubscription, isNotNull); + expect(FlutterForegroundTask.dataCallbacks, isEmpty); + }); + + test('addTaskDataCallback (case: other callback)', () { + FlutterForegroundTask.addTaskDataCallback((_) {}); + expect(FlutterForegroundTask.dataCallbacks.length, 1); + + FlutterForegroundTask.addTaskDataCallback((_) {}); + expect(FlutterForegroundTask.dataCallbacks.length, 2); + }); + + test('addTaskDataCallback (case: same callback)', () { + dataCallback(_) {} + + FlutterForegroundTask.addTaskDataCallback(dataCallback); + expect(FlutterForegroundTask.dataCallbacks.length, 1); + + FlutterForegroundTask.addTaskDataCallback(dataCallback); + expect(FlutterForegroundTask.dataCallbacks.length, 1); + }); + + test('removeTaskDataCallback', () { + dataCallback1(_) {} + dataCallback2(_) {} + + FlutterForegroundTask.addTaskDataCallback(dataCallback1); + FlutterForegroundTask.addTaskDataCallback(dataCallback2); + expect(FlutterForegroundTask.dataCallbacks.length, 2); + + FlutterForegroundTask.removeTaskDataCallback(dataCallback1); + expect(FlutterForegroundTask.dataCallbacks.length, 1); + + FlutterForegroundTask.removeTaskDataCallback(dataCallback2); + expect(FlutterForegroundTask.dataCallbacks.length, 0); + }); + + test('sendDataToTask', () { + const String method = TaskEventMethod.onReceiveData; + + const String stringData = 'hello'; + FlutterForegroundTask.sendDataToTask(stringData); + expect(taskHandler.log.last, isTaskEvent(method, stringData)); + + const int intData = 1234; + FlutterForegroundTask.sendDataToTask(intData); + expect(taskHandler.log.last, isTaskEvent(method, intData)); + + const double doubleData = 1.234; + FlutterForegroundTask.sendDataToTask(doubleData); + expect(taskHandler.log.last, isTaskEvent(method, doubleData)); + + const bool boolData = false; + FlutterForegroundTask.sendDataToTask(boolData); + expect(taskHandler.log.last, isTaskEvent(method, boolData)); + + const List listData = [1, 2, 3]; + FlutterForegroundTask.sendDataToTask(listData); + expect(taskHandler.log.last, isTaskEvent(method, listData)); + + const Map mapData = {'message': 'hello', 'data': 1}; + FlutterForegroundTask.sendDataToTask(mapData); + expect(taskHandler.log.last, isTaskEvent(method, mapData)); + }); + }); +} + +Matcher isTaskEvent(String method, [dynamic data]) { + return _IsTaskEvent(method, data); +} + +class _IsTaskEvent extends Matcher { + const _IsTaskEvent(this.method, this.data); + + final String method; + final dynamic data; + + @override + bool matches(dynamic item, Map matchState) { + if (item is! TaskEvent) { + return false; + } + if (item.method != method) { + return false; + } + return _deepEquals(item.data, data); + } + + bool _deepEquals(dynamic a, dynamic b) { + if (a == b) { + return true; + } + if (a is List) { + return b is List && _deepEqualsList(a, b); + } + if (a is Map) { + return b is Map && _deepEqualsMap(a, b); + } + return false; + } + + bool _deepEqualsList(List a, List b) { + if (a.length != b.length) { + return false; + } + for (int i = 0; i < a.length; i++) { + if (!_deepEquals(a[i], b[i])) { + return false; + } + } + return true; + } + + bool _deepEqualsMap(Map a, Map b) { + if (a.length != b.length) { + return false; + } + for (final dynamic key in a.keys) { + if (!b.containsKey(key) || !_deepEquals(a[key], b[key])) { + return false; + } + } + return true; + } + + @override + Description describe(Description description) { + return description + .add('has method: ') + .addDescriptionOf(method) + .add(' with data: ') + .addDescriptionOf(data); + } +} + +class TaskEvent { + const TaskEvent({required this.method, this.data}); + + final String method; + final dynamic data; +} + +class TaskEventMethod { + static const String onStart = 'onStart'; + static const String onRepeatEvent = 'onRepeatEvent'; + static const String onDestroy = 'onDestroy'; + static const String onReceiveData = 'onReceiveData'; + static const String onNotificationButtonPressed = + 'onNotificationButtonPressed'; + static const String onNotificationPressed = 'onNotificationPressed'; + static const String onNotificationDismissed = 'onNotificationDismissed'; +} + +class TestTaskHandler extends TaskHandler { + final List log = []; + + @override + Future onStart(DateTime timestamp, TaskStarter starter) async { + log.add(TaskEvent(method: TaskEventMethod.onStart, data: starter.index)); + } + + @override + void onRepeatEvent(DateTime timestamp) { + log.add(const TaskEvent(method: TaskEventMethod.onRepeatEvent)); + } + + @override + Future onDestroy(DateTime timestamp, bool isTimeout) async { + log.add(TaskEvent(method: TaskEventMethod.onDestroy, data: isTimeout)); + } + + @override + void onReceiveData(Object data) { + log.add(TaskEvent(method: TaskEventMethod.onReceiveData, data: data)); + } + + @override + void onNotificationButtonPressed(String id) { + log.add(TaskEvent( + method: TaskEventMethod.onNotificationButtonPressed, data: id)); + } + + @override + void onNotificationPressed() { + log.add(const TaskEvent(method: TaskEventMethod.onNotificationPressed)); + } + + @override + void onNotificationDismissed() { + log.add(const TaskEvent(method: TaskEventMethod.onNotificationDismissed)); + } +} diff --git a/local_packages/flutter_foreground_task-9.1.0/test/utility_test.dart b/local_packages/flutter_foreground_task-9.1.0/test/utility_test.dart new file mode 100644 index 0000000..6bc668d --- /dev/null +++ b/local_packages/flutter_foreground_task-9.1.0/test/utility_test.dart @@ -0,0 +1,495 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_foreground_task/flutter_foreground_task.dart'; +import 'package:flutter_foreground_task/flutter_foreground_task_method_channel.dart'; +import 'package:flutter_foreground_task/flutter_foreground_task_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:platform/platform.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late MethodChannelFlutterForegroundTask platformChannel; + late UtilityMethodCallHandler methodCallHandler; + + setUp(() { + platformChannel = MethodChannelFlutterForegroundTask(); + FlutterForegroundTaskPlatform.instance = platformChannel; + FlutterForegroundTask.resetStatic(); + + methodCallHandler = + UtilityMethodCallHandler(() => platformChannel.platform); + + // method channel + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + platformChannel.mMDChannel, + methodCallHandler.onMethodCall, + ); + }); + + tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(platformChannel.mMDChannel, null); + }); + + group('Android', () { + const String platform = Platform.android; + + test('minimizeApp', () { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + FlutterForegroundTask.minimizeApp(); + expect( + methodCallHandler.log.last, + isMethodCall(UtilityMethod.minimizeApp, arguments: null), + ); + }); + + test('launchApp', () { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + FlutterForegroundTask.launchApp(); + expect( + methodCallHandler.log.last, + isMethodCall(UtilityMethod.launchApp, arguments: null), + ); + + FlutterForegroundTask.launchApp('/root'); + expect( + methodCallHandler.log.last, + isMethodCall(UtilityMethod.launchApp, arguments: '/root'), + ); + }); + + test('setOnLockScreenVisibility', () { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + FlutterForegroundTask.setOnLockScreenVisibility(true); + expect( + methodCallHandler.log.last, + isMethodCall( + UtilityMethod.setOnLockScreenVisibility, + arguments: true, + ), + ); + + FlutterForegroundTask.setOnLockScreenVisibility(false); + expect( + methodCallHandler.log.last, + isMethodCall( + UtilityMethod.setOnLockScreenVisibility, + arguments: false, + ), + ); + }); + + test('isAppOnForeground', () async { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + final bool result = await FlutterForegroundTask.isAppOnForeground; + expect(result, false); + expect( + methodCallHandler.log.last, + isMethodCall(UtilityMethod.isAppOnForeground, arguments: null), + ); + }); + + test('wakeUpScreen', () { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + FlutterForegroundTask.wakeUpScreen(); + expect( + methodCallHandler.log.last, + isMethodCall(UtilityMethod.wakeUpScreen, arguments: null), + ); + }); + + test('isIgnoringBatteryOptimizations', () async { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + final bool result = + await FlutterForegroundTask.isIgnoringBatteryOptimizations; + expect(result, false); + expect( + methodCallHandler.log.last, + isMethodCall( + UtilityMethod.isIgnoringBatteryOptimizations, + arguments: null, + ), + ); + }); + + test('openIgnoreBatteryOptimizationSettings', () async { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + final bool result = + await FlutterForegroundTask.openIgnoreBatteryOptimizationSettings(); + expect(result, false); + expect( + methodCallHandler.log.last, + isMethodCall( + UtilityMethod.openIgnoreBatteryOptimizationSettings, + arguments: null, + ), + ); + }); + + test('requestIgnoreBatteryOptimization', () async { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + final bool result = + await FlutterForegroundTask.requestIgnoreBatteryOptimization(); + expect(result, false); + expect( + methodCallHandler.log.last, + isMethodCall( + UtilityMethod.requestIgnoreBatteryOptimization, + arguments: null, + ), + ); + }); + + test('canDrawOverlays', () async { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + final bool result = await FlutterForegroundTask.canDrawOverlays; + expect(result, false); + expect( + methodCallHandler.log.last, + isMethodCall(UtilityMethod.canDrawOverlays, arguments: null), + ); + }); + + test('openSystemAlertWindowSettings', () async { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + final bool result = + await FlutterForegroundTask.openSystemAlertWindowSettings(); + expect(result, false); + expect( + methodCallHandler.log.last, + isMethodCall( + UtilityMethod.openSystemAlertWindowSettings, + arguments: null, + ), + ); + }); + + test('checkNotificationPermission', () async { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + final NotificationPermission permission = + await FlutterForegroundTask.checkNotificationPermission(); + expect(permission, NotificationPermission.granted); + expect( + methodCallHandler.log.last, + isMethodCall( + UtilityMethod.checkNotificationPermission, + arguments: null, + ), + ); + }); + + test('requestNotificationPermission', () async { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + final NotificationPermission permission = + await FlutterForegroundTask.requestNotificationPermission(); + expect(permission, NotificationPermission.granted); + expect( + methodCallHandler.log.last, + isMethodCall( + UtilityMethod.requestNotificationPermission, + arguments: null, + ), + ); + }); + + test('canScheduleExactAlarms', () async { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + final bool result = await FlutterForegroundTask.canScheduleExactAlarms; + expect(result, false); + expect( + methodCallHandler.log.last, + isMethodCall(UtilityMethod.canScheduleExactAlarms, arguments: null), + ); + }); + + test('openAlarmsAndRemindersSettings', () async { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + final bool result = + await FlutterForegroundTask.openAlarmsAndRemindersSettings(); + expect(result, false); + expect( + methodCallHandler.log.last, + isMethodCall( + UtilityMethod.openAlarmsAndRemindersSettings, + arguments: null, + ), + ); + }); + }); + + group('iOS', () { + const String platform = Platform.iOS; + + test('minimizeApp', () { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + FlutterForegroundTask.minimizeApp(); + expect( + methodCallHandler.log.last, + isMethodCall(UtilityMethod.minimizeApp, arguments: null), + ); + }); + + test('launchApp', () { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + FlutterForegroundTask.launchApp(); + expect(methodCallHandler.log, isEmpty); + + FlutterForegroundTask.launchApp('/root'); + expect(methodCallHandler.log, isEmpty); + }); + + test('setOnLockScreenVisibility', () { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + FlutterForegroundTask.setOnLockScreenVisibility(true); + expect(methodCallHandler.log, isEmpty); + + FlutterForegroundTask.setOnLockScreenVisibility(false); + expect(methodCallHandler.log, isEmpty); + }); + + test('isAppOnForeground', () async { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + final bool result = await FlutterForegroundTask.isAppOnForeground; + expect(result, false); + expect( + methodCallHandler.log.last, + isMethodCall(UtilityMethod.isAppOnForeground, arguments: null), + ); + }); + + test('wakeUpScreen', () { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + FlutterForegroundTask.wakeUpScreen(); + expect(methodCallHandler.log, isEmpty); + }); + + test('isIgnoringBatteryOptimizations', () async { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + final bool result = + await FlutterForegroundTask.isIgnoringBatteryOptimizations; + expect(result, true); + expect(methodCallHandler.log, isEmpty); + }); + + test('openIgnoreBatteryOptimizationSettings', () async { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + final bool result = + await FlutterForegroundTask.openIgnoreBatteryOptimizationSettings(); + expect(result, true); + expect(methodCallHandler.log, isEmpty); + }); + + test('requestIgnoreBatteryOptimization', () async { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + final bool result = + await FlutterForegroundTask.requestIgnoreBatteryOptimization(); + expect(result, true); + expect(methodCallHandler.log, isEmpty); + }); + + test('canDrawOverlays', () async { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + final bool result = await FlutterForegroundTask.canDrawOverlays; + expect(result, true); + expect(methodCallHandler.log, isEmpty); + }); + + test('openSystemAlertWindowSettings', () async { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + final bool result = + await FlutterForegroundTask.openSystemAlertWindowSettings(); + expect(result, true); + expect(methodCallHandler.log, isEmpty); + }); + + test('checkNotificationPermission', () async { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + final NotificationPermission permission = + await FlutterForegroundTask.checkNotificationPermission(); + expect(permission, NotificationPermission.granted); + expect( + methodCallHandler.log.last, + isMethodCall( + UtilityMethod.checkNotificationPermission, + arguments: null, + ), + ); + }); + + test('requestNotificationPermission', () async { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + final NotificationPermission permission = + await FlutterForegroundTask.requestNotificationPermission(); + expect(permission, NotificationPermission.granted); + expect( + methodCallHandler.log.last, + isMethodCall( + UtilityMethod.requestNotificationPermission, + arguments: null, + ), + ); + }); + + test('canScheduleExactAlarms', () async { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + final bool result = await FlutterForegroundTask.canScheduleExactAlarms; + expect(result, true); + expect(methodCallHandler.log, isEmpty); + }); + + test('openAlarmsAndRemindersSettings', () async { + platformChannel.platform = FakePlatform(operatingSystem: platform); + + final bool result = + await FlutterForegroundTask.openAlarmsAndRemindersSettings(); + expect(result, true); + expect(methodCallHandler.log, isEmpty); + }); + }); +} + +class UtilityMethod { + static const String minimizeApp = 'minimizeApp'; + static const String launchApp = 'launchApp'; + static const String setOnLockScreenVisibility = 'setOnLockScreenVisibility'; + static const String isAppOnForeground = 'isAppOnForeground'; + static const String wakeUpScreen = 'wakeUpScreen'; + static const String isIgnoringBatteryOptimizations = + 'isIgnoringBatteryOptimizations'; + static const String openIgnoreBatteryOptimizationSettings = + 'openIgnoreBatteryOptimizationSettings'; + static const String requestIgnoreBatteryOptimization = + 'requestIgnoreBatteryOptimization'; + static const String canDrawOverlays = 'canDrawOverlays'; + static const String openSystemAlertWindowSettings = + 'openSystemAlertWindowSettings'; + static const String checkNotificationPermission = + 'checkNotificationPermission'; + static const String requestNotificationPermission = + 'requestNotificationPermission'; + static const String canScheduleExactAlarms = 'canScheduleExactAlarms'; + static const String openAlarmsAndRemindersSettings = + 'openAlarmsAndRemindersSettings'; + + static Set getImplementation(Platform platform) { + if (platform.isAndroid) { + return { + minimizeApp, + launchApp, + setOnLockScreenVisibility, + isAppOnForeground, + wakeUpScreen, + isIgnoringBatteryOptimizations, + openIgnoreBatteryOptimizationSettings, + requestIgnoreBatteryOptimization, + canDrawOverlays, + openSystemAlertWindowSettings, + checkNotificationPermission, + requestNotificationPermission, + canScheduleExactAlarms, + openAlarmsAndRemindersSettings, + }; + } else if (platform.isIOS) { + return { + minimizeApp, + isAppOnForeground, + checkNotificationPermission, + requestNotificationPermission, + }; + } + + return {}; + } +} + +class UtilityMethodCallHandler { + UtilityMethodCallHandler(this._platformGetter); + + final ValueGetter _platformGetter; + + final List log = []; + + // unimplemented: throw UnimplementedError + void _checkImplementation(String method) { + final Platform platform = _platformGetter(); + if (!UtilityMethod.getImplementation(platform).contains(method)) { + throw UnimplementedError( + 'Unimplemented method on ${platform.operatingSystem}: $method'); + } + } + + Future? onMethodCall(MethodCall methodCall) async { + final String method = methodCall.method; + _checkImplementation(method); + + log.add(methodCall); + + final dynamic arguments = methodCall.arguments; + if (method == UtilityMethod.minimizeApp) { + return Future.value(); + } else if (method == UtilityMethod.launchApp) { + return Future.value(); + } else if (method == UtilityMethod.setOnLockScreenVisibility) { + if (arguments == null) { + throw ArgumentError('The isVisible argument cannot be found.'); + } + if (arguments is! bool) { + throw ArgumentError('The isVisible argument is not of type boolean.'); + } + return Future.value(); + } else if (method == UtilityMethod.isAppOnForeground) { + return false; + } else if (method == UtilityMethod.wakeUpScreen) { + return Future.value(); + } else if (method == UtilityMethod.isIgnoringBatteryOptimizations) { + return false; + } else if (method == UtilityMethod.openIgnoreBatteryOptimizationSettings) { + return false; + } else if (method == UtilityMethod.requestIgnoreBatteryOptimization) { + return false; + } else if (method == UtilityMethod.canDrawOverlays) { + return false; + } else if (method == UtilityMethod.openSystemAlertWindowSettings) { + return false; + } else if (method == UtilityMethod.checkNotificationPermission) { + return NotificationPermission.granted.index; + } else if (method == UtilityMethod.requestNotificationPermission) { + return NotificationPermission.granted.index; + } else if (method == UtilityMethod.canScheduleExactAlarms) { + return false; + } else if (method == UtilityMethod.openAlarmsAndRemindersSettings) { + return false; + } + + throw UnimplementedError(); + } +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/CHANGELOG.md b/local_packages/flutter_ninepatch_image-1.0.2/CHANGELOG.md new file mode 100644 index 0000000..7c9d21e --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/CHANGELOG.md @@ -0,0 +1,23 @@ +## 1.0.2 + +- TODO: fix bug + +## 1.0.1 + +- TODO: add new topics + +## 1.0.0 + +- TODO: All platform support + +## 0.0.3 + +- TODO: add comment. + +## 0.0.2 + +- Fix Bug: supper scale. + +## 0.0.1 + +- TODO: Describe initial release. diff --git a/local_packages/flutter_ninepatch_image-1.0.2/LICENSE b/local_packages/flutter_ninepatch_image-1.0.2/LICENSE new file mode 100644 index 0000000..f81b7a1 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 hanzhongshuairc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/local_packages/flutter_ninepatch_image-1.0.2/README.md b/local_packages/flutter_ninepatch_image-1.0.2/README.md new file mode 100644 index 0000000..842c866 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/README.md @@ -0,0 +1,35 @@ +[![Pub Version](https://img.shields.io/pub/v/flutter_ninepatch_image?style=flat-square&logo=dart)](https://pub.dev/packages/flutter_ninepatch_image) +[![GitHub stars](https://img.shields.io/github/stars/hanzhongshuai/flutter_ninepatch_image?style=social)](https://github.com/hanzhongshuai/flutter_ninepatch_image) +![GitHub repo size](https://img.shields.io/github/repo-size/hanzhongshuai/flutter_ninepatch_image?style=flat-square) +[![Platforms](https://badgen.net/pub/flutter-platform/flutter_ninepatch_image)](https://pub.dev/packages/flutter_ninepatch_image) + +# ninepatch image for Flutter + +TODO: flutter .9 image + +## Usage + +```yaml +dependencies: + flutter_ninepatch_image: ^1.0.0 +``` + +```dart +final assetName = 'assets/test_ninepatch.9.png'; +NinePatchImage( + imageProvider: AssetImage(assetName), + sliceCachedKey: assetName, + scale: 3.0, + alignment: Alignment.center, + child: Text( + 'test', + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), +); +``` + +**Screenshot** : +![Screenshot](https://github.com/HanZhongShuai/flutter_ninepatch_image/blob/main/screenshot/screenshot1.png?raw=true) diff --git a/local_packages/flutter_ninepatch_image-1.0.2/analysis_options.yaml b/local_packages/flutter_ninepatch_image-1.0.2/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/README.md b/local_packages/flutter_ninepatch_image-1.0.2/example/README.md new file mode 100644 index 0000000..2b3fce4 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/README.md @@ -0,0 +1,16 @@ +# example + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/analysis_options.yaml b/local_packages/flutter_ninepatch_image-1.0.2/example/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/build.gradle.kts b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/build.gradle.kts new file mode 100644 index 0000000..21ecea9 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/debug/AndroidManifest.xml b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/AndroidManifest.xml b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..74a78b9 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 0000000..ac81bae --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/drawable-v21/launch_background.xml b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/drawable/launch_background.xml b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/values-night/styles.xml b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/values/styles.xml b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/profile/AndroidManifest.xml b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/android/build.gradle.kts b/local_packages/flutter_ninepatch_image-1.0.2/example/android/build.gradle.kts new file mode 100644 index 0000000..89176ef --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/android/gradle.properties b/local_packages/flutter_ninepatch_image-1.0.2/example/android/gradle.properties new file mode 100644 index 0000000..f018a61 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/android/gradle/wrapper/gradle-wrapper.properties b/local_packages/flutter_ninepatch_image-1.0.2/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..afa1e8e --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/android/settings.gradle.kts b/local_packages/flutter_ninepatch_image-1.0.2/example/android/settings.gradle.kts new file mode 100644 index 0000000..a439442 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.0" apply false + id("org.jetbrains.kotlin.android") version "1.8.22" apply false +} + +include(":app") diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/assets/test_ninepatch.9.png b/local_packages/flutter_ninepatch_image-1.0.2/example/assets/test_ninepatch.9.png new file mode 100644 index 0000000..9322467 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/assets/test_ninepatch.9.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Flutter/AppFrameworkInfo.plist b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Flutter/Debug.xcconfig b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Flutter/Release.xcconfig b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Podfile b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Podfile new file mode 100644 index 0000000..e549ee2 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Podfile.lock b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Podfile.lock new file mode 100644 index 0000000..aba8868 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Podfile.lock @@ -0,0 +1,30 @@ +PODS: + - Flutter (1.0.0) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - sqflite_darwin (0.0.4): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - Flutter (from `Flutter`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + sqflite_darwin: + :path: ".symlinks/plugins/sqflite_darwin/darwin" + +SPEC CHECKSUMS: + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d + +PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5 + +COCOAPODS: 1.16.2 diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcodeproj/project.pbxproj b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..b7ea15f --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,731 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 5CF1229589CB5787113AEC98 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE9FEB4F6FF50E528E92F50 /* Pods_Runner.framework */; }; + 5E2A75E7204828EC953C1285 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F578CEBD34D59E3198726DE8 /* Pods_RunnerTests.framework */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9DE9FEB4F6FF50E528E92F50 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A515F41A4DA4846220A30405 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + B05A4E911211DD20A6E783BE /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + B5DE20F8FE386FACAE5DB639 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + C89CD78AA94C027B266072FE /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + D09C8444938C535FE31C8452 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + D404015212DEF9F4B2C84E13 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + F578CEBD34D59E3198726DE8 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 17680CB27FCB4516BF6B3CE3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5E2A75E7204828EC953C1285 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5CF1229589CB5787113AEC98 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 42C9ECB1A1691042CBE7B5E8 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 9DE9FEB4F6FF50E528E92F50 /* Pods_Runner.framework */, + F578CEBD34D59E3198726DE8 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + A3041C8620A1AE80E0DFB220 /* Pods */, + 42C9ECB1A1691042CBE7B5E8 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + A3041C8620A1AE80E0DFB220 /* Pods */ = { + isa = PBXGroup; + children = ( + B05A4E911211DD20A6E783BE /* Pods-Runner.debug.xcconfig */, + D404015212DEF9F4B2C84E13 /* Pods-Runner.release.xcconfig */, + C89CD78AA94C027B266072FE /* Pods-Runner.profile.xcconfig */, + A515F41A4DA4846220A30405 /* Pods-RunnerTests.debug.xcconfig */, + B5DE20F8FE386FACAE5DB639 /* Pods-RunnerTests.release.xcconfig */, + D09C8444938C535FE31C8452 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 14BE445DD7D2E27BBA7603F9 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 17680CB27FCB4516BF6B3CE3 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9F2BC3F699000862C3324EAD /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 9D36CB9EEBBE2F98486CAB23 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 14BE445DD7D2E27BBA7603F9 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + 9D36CB9EEBBE2F98486CAB23 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9F2BC3F699000862C3324EAD /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 3A475SDHSX; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A515F41A4DA4846220A30405 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B5DE20F8FE386FACAE5DB639 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D09C8444938C535FE31C8452 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 3A475SDHSX; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 3A475SDHSX; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..15cada4 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/AppDelegate.swift b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..6266644 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +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. \ No newline at end of file diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Base.lproj/Main.storyboard b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Info.plist b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Info.plist new file mode 100644 index 0000000..5458fc4 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Runner-Bridging-Header.h b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/ios/RunnerTests/RunnerTests.swift b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/lib/main.dart b/local_packages/flutter_ninepatch_image-1.0.2/example/lib/main.dart new file mode 100644 index 0000000..061ecdd --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/lib/main.dart @@ -0,0 +1,163 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_ninepatch_image/flutter_ninepatch_image.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'ninepatch image', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + ), + home: const MyHomePage(title: 'ninepatch image demo'), + ); + } +} + +class MyHomePage extends StatelessWidget { + const MyHomePage({super.key, required this.title}); + + final String title; + + @override + Widget build(BuildContext context) { + final assetName = 'assets/test_ninepatch.9.png'; + final networkUrl = + 'https://github.com/HanZhongShuai/flutter_ninepatch_image/blob/main/images/test_ninepatch.9.png?raw=true'; + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Text(title), + ), + body: Center( + child: SingleChildScrollView( + child: IntrinsicHeight( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Spacer(), + + /// + Text('imageProvider: AssetImage'), + NinePatchImage( + imageProvider: AssetImage(assetName), + sliceCachedKey: assetName, + scale: 3.0, + alignment: Alignment.center, + child: Text( + 'test', + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + ), + SizedBox(height: 16), + + /// + Text('imageProvider: CachedNetworkImageProvider'), + NinePatchImage( + imageProvider: CachedNetworkImageProvider(networkUrl), + sliceCachedKey: networkUrl, + scale: 3.0, + alignment: Alignment.center, + child: Text( + 'test', + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + defaultBuilder: (context, child) { + return Container( + padding: EdgeInsetsDirectional.symmetric( + horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: Colors.black45, + borderRadius: BorderRadius.circular(8), + ), + child: child, + ); + }, + ), + SizedBox(height: 16), + + /// + Text('NinePatchImage.asset 1, scale: 3.0'), + NinePatchImage.asset( + name: assetName, + scale: 3.0, + alignment: Alignment.center, + child: Text( + 'test', + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + ), + SizedBox(height: 16), + + /// + Text('NinePatchImage.asset 2, scale: 3.0'), + NinePatchImage.asset( + name: assetName, + scale: 3.0, + alignment: Alignment.center, + child: Text( + 'test\ntest\ntest\ntest', + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + ), + SizedBox(height: 16), + + /// + Text('NinePatchImage.asset 3, scale: 3.0'), + NinePatchImage.asset( + name: assetName, + scale: 3.0, + alignment: Alignment.center, + child: Text( + 'test test test test test test test test test test test test test test test test test test test test', + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + ), + SizedBox(height: 16), + + /// + Text('NinePatchImage.asset 4, scale: 1.0'), + NinePatchImage.asset( + name: assetName, + alignment: Alignment.center, + child: Text( + 'test test test test test test test test test test test test test test test test test test test test', + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + ), + SizedBox(height: 16), + Spacer(), + ], + ), + ), + ), + ), + ); + } +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/linux/CMakeLists.txt b/local_packages/flutter_ninepatch_image-1.0.2/example/linux/CMakeLists.txt new file mode 100644 index 0000000..7a9a314 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "example") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/linux/flutter/CMakeLists.txt b/local_packages/flutter_ninepatch_image-1.0.2/example/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/linux/flutter/generated_plugin_registrant.cc b/local_packages/flutter_ninepatch_image-1.0.2/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..e71a16d --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/linux/flutter/generated_plugin_registrant.h b/local_packages/flutter_ninepatch_image-1.0.2/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/linux/flutter/generated_plugins.cmake b/local_packages/flutter_ninepatch_image-1.0.2/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..2e1de87 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/linux/runner/CMakeLists.txt b/local_packages/flutter_ninepatch_image-1.0.2/example/linux/runner/CMakeLists.txt new file mode 100644 index 0000000..e97dabc --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/linux/runner/main.cc b/local_packages/flutter_ninepatch_image-1.0.2/example/linux/runner/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/linux/runner/my_application.cc b/local_packages/flutter_ninepatch_image-1.0.2/example/linux/runner/my_application.cc new file mode 100644 index 0000000..6c81082 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/linux/runner/my_application.cc @@ -0,0 +1,130 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "example"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/linux/runner/my_application.h b/local_packages/flutter_ninepatch_image-1.0.2/example/linux/runner/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/linux/runner/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Flutter/Flutter-Debug.xcconfig b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Flutter/Flutter-Release.xcconfig b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Flutter/GeneratedPluginRegistrant.swift b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..252c004 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import path_provider_foundation +import sqflite_darwin + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Podfile b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Podfile new file mode 100644 index 0000000..29c8eb3 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner.xcodeproj/project.pbxproj b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..daa7bf1 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,705 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..ac78810 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/AppDelegate.swift b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..b3c1761 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Base.lproj/MainMenu.xib b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Configs/AppInfo.xcconfig b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..dda9752 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.example + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Configs/Debug.xcconfig b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Configs/Release.xcconfig b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Configs/Warnings.xcconfig b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/DebugProfile.entitlements b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Info.plist b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/MainFlutterWindow.swift b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Release.entitlements b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/macos/RunnerTests/RunnerTests.swift b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/pubspec.yaml b/local_packages/flutter_ninepatch_image-1.0.2/example/pubspec.yaml new file mode 100644 index 0000000..9e8151b --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/pubspec.yaml @@ -0,0 +1,91 @@ +name: example +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: '>=3.2.0 <4.0.0' + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + flutter_ninepatch_image: + path: ../ + cached_network_image: ^3.4.1 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^5.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + assets: + - assets/ + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/test/widget_test.dart b/local_packages/flutter_ninepatch_image-1.0.2/example/test/widget_test.dart new file mode 100644 index 0000000..092d222 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:example/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/web/favicon.png b/local_packages/flutter_ninepatch_image-1.0.2/example/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/web/favicon.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/web/icons/Icon-192.png b/local_packages/flutter_ninepatch_image-1.0.2/example/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/web/icons/Icon-192.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/web/icons/Icon-512.png b/local_packages/flutter_ninepatch_image-1.0.2/example/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/web/icons/Icon-512.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/web/icons/Icon-maskable-192.png b/local_packages/flutter_ninepatch_image-1.0.2/example/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/web/icons/Icon-maskable-192.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/web/icons/Icon-maskable-512.png b/local_packages/flutter_ninepatch_image-1.0.2/example/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/web/icons/Icon-maskable-512.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/web/index.html b/local_packages/flutter_ninepatch_image-1.0.2/example/web/index.html new file mode 100644 index 0000000..29b5808 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + example + + + + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/web/manifest.json b/local_packages/flutter_ninepatch_image-1.0.2/example/web/manifest.json new file mode 100644 index 0000000..096edf8 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/windows/CMakeLists.txt b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/CMakeLists.txt new file mode 100644 index 0000000..d960948 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(example LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/windows/flutter/CMakeLists.txt b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/windows/flutter/generated_plugin_registrant.cc b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..8b6d468 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/windows/flutter/generated_plugin_registrant.h b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/windows/flutter/generated_plugins.cmake b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..b93c4c3 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/CMakeLists.txt b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/Runner.rc b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/Runner.rc new file mode 100644 index 0000000..289fc7e --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "example.exe" "\0" + VALUE "ProductName", "example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/flutter_window.cpp b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/flutter_window.h b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/main.cpp b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/main.cpp new file mode 100644 index 0000000..a61bf80 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/resource.h b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/resources/app_icon.ico b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/resources/app_icon.ico differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/runner.exe.manifest b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/utils.cpp b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/utils.cpp new file mode 100644 index 0000000..3a0b465 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/utils.h b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/win32_window.cpp b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/win32_window.h b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/example/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/images/test_ninepatch.9.png b/local_packages/flutter_ninepatch_image-1.0.2/images/test_ninepatch.9.png new file mode 100644 index 0000000..9322467 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/images/test_ninepatch.9.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/lib/flutter_ninepatch_image.dart b/local_packages/flutter_ninepatch_image-1.0.2/lib/flutter_ninepatch_image.dart new file mode 100644 index 0000000..5d6efc3 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/lib/flutter_ninepatch_image.dart @@ -0,0 +1,5 @@ +library flutter_ninepatch_image; + +export 'src/ninepatch_image.dart'; +export 'src/ninepatch_cache.dart'; +export 'src/ninepatch_info.dart'; diff --git a/local_packages/flutter_ninepatch_image-1.0.2/lib/src/ninepatch_cache.dart b/local_packages/flutter_ninepatch_image-1.0.2/lib/src/ninepatch_cache.dart new file mode 100644 index 0000000..b4896e8 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/lib/src/ninepatch_cache.dart @@ -0,0 +1,42 @@ +import 'ninepatch_info.dart'; + +/// Ninepatch size Cache +class NinepatchCache { + static final NinepatchCache _instance = NinepatchCache._private(); + static NinepatchCache get instance => _instance; + factory NinepatchCache() => _instance; + NinepatchCache._private(); + + /// cache map + final Map _cache = {}; + + /// get [NinepatchInfo] + NinepatchInfo? get({required String? key}) { + if (key != null && key.isNotEmpty) { + return _cache[key]; + } else { + return null; + } + } + + /// add [NinepatchInfo] + void add({String? key, required NinepatchInfo value}) { + if (key != null && key.isNotEmpty) { + _cache[key] = value; + } + } + + /// remove [NinepatchInfo] + NinepatchInfo? remove({required String? key}) { + if (key != null && key.isNotEmpty) { + return _cache.remove(key); + } else { + return null; + } + } + + /// clear all [NinepatchInfo] + void clear({required String? key}) { + _cache.clear(); + } +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/lib/src/ninepatch_image.dart b/local_packages/flutter_ninepatch_image-1.0.2/lib/src/ninepatch_image.dart new file mode 100644 index 0000000..686466a --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/lib/src/ninepatch_image.dart @@ -0,0 +1,276 @@ +import 'package:flutter/material.dart'; +import 'package:image_pixels/image_pixels.dart'; + +import 'ninepatch_cache.dart'; +import 'ninepatch_info.dart'; + +/// Default Widget Builder +typedef NinePatchDefaultWidgetBuilder = Widget Function( + BuildContext context, Widget child); + +/// Nine Patch Image Widget +class NinePatchImage extends StatelessWidget { + static const int _kPatchAlphaThreshold = 245; + static const int _kPatchRgbThreshold = 20; + + /// hide black lines, default true + final bool hideLines; + + /// [Container] alignment + final AlignmentGeometry? alignment; + + /// [Container] scale + final double scale; + + /// [Container] color + final Color? color; + + /// [Container] borderRadius + final BorderRadius? borderRadius; + + /// image provider + final ImageProvider imageProvider; + + /// child Widget + final Widget child; + + /// default builder widget, contain child, + /// for loading image and calculated image size show widget + final NinePatchDefaultWidgetBuilder? defaultBuilder; + + /// unique cache key + final String sliceCachedKey; + + /// defaule initialize method + const NinePatchImage({ + super.key, + required this.imageProvider, + required this.child, + required this.sliceCachedKey, + this.defaultBuilder, + this.alignment, + this.scale = 1.0, + this.color, + this.borderRadius, + this.hideLines = true, + }); + + /// asset initialize method + NinePatchImage.asset({ + super.key, + required String name, + required this.child, + this.defaultBuilder, + this.alignment, + this.scale = 1.0, + this.color, + this.borderRadius, + this.hideLines = true, + }) : imageProvider = AssetImage(name), + sliceCachedKey = name; + + /// network initialize method + NinePatchImage.network({ + super.key, + required String url, + required this.child, + this.defaultBuilder, + this.alignment, + this.scale = 1.0, + this.color, + this.borderRadius, + this.hideLines = true, + }) : imageProvider = NetworkImage(url), + sliceCachedKey = url; + + @override + Widget build(BuildContext context) { + final config = NinepatchCache.instance.get(key: sliceCachedKey); + if (config != null) { + return _getContent(context, config); + } else { + return ImagePixels( + imageProvider: imageProvider, + builder: (context, img) { + var width = img.width; + var height = img.height; + if (!img.hasImage || width == null || height == null) { + return defaultBuilder?.call(context, child) ?? child; + } + if (width < 1 || height < 1) { + return defaultBuilder?.call(context, child) ?? child; + } + + var left1 = -1; + var left2 = -1; + + var top1 = -1; + var top2 = -1; + + var right1 = -1; + var right2 = -1; + + var bottom1 = -1; + var bottom2 = -1; + + for (var i = 0; i < height; i++) { + var color = img.pixelColorAt!(0, i); + if (_isPatchMarkerColor(color)) { + if (left1 == -1) { + left1 = i; + } + if (left1 != -1) { + left2 = i; + } + } + var rightColor = img.pixelColorAt!(width - 1, i); + if (_isPatchMarkerColor(rightColor)) { + if (right1 == -1) { + right1 = i; + } + if (right1 != -1) { + right2 = i; + } + } + } + + for (var i = 0; i < img.width!; i++) { + var color = img.pixelColorAt!(i, 0); + if (_isPatchMarkerColor(color)) { + if (top1 == -1) { + top1 = i; + } + if (top1 != -1) { + top2 = i; + } + } + var bottomColor = img.pixelColorAt!(i, height - 1); + if (_isPatchMarkerColor(bottomColor)) { + if (bottom1 == -1) { + bottom1 = i; + } + if (bottom1 != -1) { + bottom2 = i; + } + } + } + + if (left1 == -1) left1 = 0; + if (left2 == -1) left2 = height; + + if (top1 == -1) top1 = 0; + if (top2 == -1) top2 = width; + + if (right1 == -1) right1 = 0; + if (right2 == -1) right2 = height; + + if (bottom1 == -1) bottom1 = 0; + if (bottom2 == -1) bottom2 = width; + + double leftOffset = 1; + double topOffset = 1; + if (left1 == left2) { + left2 = left1 + 1; + leftOffset = leftOffset + 1; + } + if (top1 == top2) { + top2 = top1 + 1; + topOffset = topOffset + 1; + } + if (right1 == right2) right2 = right1 + 1; + if (bottom1 == bottom2) bottom2 = bottom1 + 1; + + final padding = EdgeInsets.fromLTRB( + bottom1.toDouble(), + right1.toDouble(), + width - bottom2.toDouble(), + height - right2.toDouble()); + final constraints = BoxConstraints( + minWidth: (width - (top2 - top1)).toDouble() + topOffset, + minHeight: (height - (left2 - left1)).toDouble() + leftOffset, + ); + final centerSlice = Rect.fromLTRB(top1.toDouble(), left1.toDouble(), + top2.toDouble(), left2.toDouble()); + final info = NinepatchInfo( + width: width, + height: height, + padding: padding.isNonNegative ? padding : EdgeInsets.zero, + constraints: constraints, + centerSlice: centerSlice, + ); + NinepatchCache.instance.add( + key: sliceCachedKey, + value: info, + ); + return _getContent(context, info); + }, + ); + } + } + + Widget _getContent(BuildContext context, NinepatchInfo? info) { + if (info != null) { + return ClipPath( + clipper: _BlackLineClipper(hideLines: hideLines, scale: scale), + child: IntrinsicWidth( + child: Container( + padding: info.padding / scale, + constraints: info.constraints / scale, + decoration: BoxDecoration( + color: color, + borderRadius: borderRadius, + image: DecorationImage( + image: imageProvider, + // fit: BoxFit.fill, + centerSlice: Rect.fromLTRB( + info.centerSlice.left / scale, + info.centerSlice.top / scale, + info.centerSlice.right / scale, + info.centerSlice.bottom / scale), + scale: scale, + ), + ), + alignment: alignment, + child: child, + ), + ), + ); + } else { + return defaultBuilder?.call(context, child) ?? child; + } + } + + bool _isPatchMarkerColor(Color color) { + if (color.alpha < _kPatchAlphaThreshold) { + return false; + } + return color.red <= _kPatchRgbThreshold && + color.green <= _kPatchRgbThreshold && + color.blue <= _kPatchRgbThreshold; + } +} + +class _BlackLineClipper extends CustomClipper { + final bool hideLines; + final double scale; + + _BlackLineClipper({ + required this.hideLines, + required this.scale, + }); + + @override + Path getClip(Size size) { + final path = Path(); + double x = hideLines ? 2 / scale : 0; + path.moveTo(x, x); + path.lineTo(x, size.height - x); + path.lineTo(size.width - x, size.height - x); + path.lineTo(size.width - x, x); + path.close(); + return path; + } + + @override + bool shouldReclip(_BlackLineClipper oldClipper) => true; +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/lib/src/ninepatch_info.dart b/local_packages/flutter_ninepatch_image-1.0.2/lib/src/ninepatch_info.dart new file mode 100644 index 0000000..3cda5b5 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/lib/src/ninepatch_info.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +/// Ninepatch Ninepatch Info +class NinepatchInfo { + /// image width + final int width; + + /// image height + final int height; + + /// [Container] padding + final EdgeInsets padding; + + /// [Container] constraints + final BoxConstraints constraints; + + /// [DecorationImage] centerSlice + final Rect centerSlice; + + /// initialize method + const NinepatchInfo({ + required this.width, + required this.height, + required this.padding, + required this.constraints, + required this.centerSlice, + }); +} diff --git a/local_packages/flutter_ninepatch_image-1.0.2/pubspec.yaml b/local_packages/flutter_ninepatch_image-1.0.2/pubspec.yaml new file mode 100644 index 0000000..75ef673 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/pubspec.yaml @@ -0,0 +1,61 @@ +name: flutter_ninepatch_image +description: "ninepatch image for Flutter. support image scale, can super 2x and 3x images." +version: 1.0.2 +homepage: https://github.com/HanZhongShuai/flutter_ninepatch_image +topics: + - ninepatch + - nine + - ninepatch-image + - nine-slicing + - slicing + +environment: + sdk: ">=3.2.0 <4.0.0" + flutter: ">=3.16.0" + +dependencies: + flutter: + sdk: flutter + image_pixels: ^4.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/to/asset-from-package + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/to/font-from-package diff --git a/local_packages/flutter_ninepatch_image-1.0.2/screenshot/screenshot1.png b/local_packages/flutter_ninepatch_image-1.0.2/screenshot/screenshot1.png new file mode 100644 index 0000000..528f851 Binary files /dev/null and b/local_packages/flutter_ninepatch_image-1.0.2/screenshot/screenshot1.png differ diff --git a/local_packages/flutter_ninepatch_image-1.0.2/test/flutter_ninepatch_image_test.dart b/local_packages/flutter_ninepatch_image-1.0.2/test/flutter_ninepatch_image_test.dart new file mode 100644 index 0000000..f959ca1 --- /dev/null +++ b/local_packages/flutter_ninepatch_image-1.0.2/test/flutter_ninepatch_image_test.dart @@ -0,0 +1,5 @@ +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('test', () {}); +} diff --git a/local_packages/on_audio_query-2.9.0/CHANGELOG.md b/local_packages/on_audio_query-2.9.0/CHANGELOG.md new file mode 100644 index 0000000..dfe7d6a --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/CHANGELOG.md @@ -0,0 +1,1155 @@ +## [[2.9.0](https://github.com/LucJosin/on_audio_query/releases/tag/2.9.0)] + +### Features + +- **Added** support to Dart 3. + +## [[2.8.1](https://github.com/LucJosin/on_audio_query/releases/tag/2.8.1)] + +### Fixes + +- **Fixed** broken pubspec links. - [#115](https://github.com/LucJosin/on_audio_query/issues/115) + +#### iOS + +- **Fixed** wrong name of podspec in iOS. - [#116](https://github.com/LucJosin/on_audio_query/issues/116) + +### Changes + +- **Updated** dart-analyzer to support cache +- **Updated** README + +## [[2.8.0](https://github.com/LucJosin/on_audio_query/releases/tag/2.8.0)] + +### Features + +- **Added** `showDetailedLog`. + +### Changes + +- **Moved** `android` and `ios` into separated folders. +- **Replaced** `/details` with `/src`. + +### ⚠ Important Changes + +#### Android + +- **Updated** kotlin version from `1.4.32` to `1.6.10`. - [#110](https://github.com/LucJosin/on_audio_query/issues/110) +- **Updated** kotlin coroutines version from `1.5.2-native-mt` to `1.6.4`. + +## [[2.7.0](https://github.com/LucJosin/on_audio_query/releases/tag/2.7.0)] - [03.29.2023] + +### Features + +- **Added** `[LogType]`. +- **Added** `[LogConfig]`. +- **Added** `[PermissionController]` **(Native)** +- **Added** `[PluginProvider]` **(Native)** +- **Added** `[setLogConfig]` method. +- **Added** `[checkAndRequest]` method. +- **Added** `[controller]` to `[QueryArtworkWidget]`. +- **Added** `[retryRequest]` param to `[checkAndRequest]` and `[permissionsRequest]`. + +### Fixes + +#### Android + +- **Fixed** crash after request permission. - [#68](https://github.com/LucJosin/on_audio_query/issues/68) +- **Fixed** quality always being sent as `200` using `[queryArtwork]`. + +### Changes + +- **Updated** example. +- **Renamed** natives files/folders. +- **Reduced** the default `artwork` resolution (from 100 to 50). +- **Updated** `[QueryArtworkWidget]` params. +- **Updated** quality assert on `[QueryArtworkWidget]`. + +### ⚠ Important Changes + +- **Updated** application permission check. + - If application doesn't have permission to access the library, will throw a PlatformException. +- **Updated** `quality` param from `[QueryArtworkWidget]`. + - This param cannot be defined as null anymore and, by default, will be set to `50`. +- **Updated** minimum supported **Dart** version. + - Increase minimum version from `2.12` to `2.17`. + +## [2.6.2] - [03.03.2023] + +### Fixes + +#### Android + +- **Fixed** incompatibility with `Android 13`. - [#91](https://github.com/LucJosin/on_audio_query/issues/91) - Thanks [@ruchit-7span](https://github.com/ruchit-7span) + +## [2.6.1] - [05.17.2022] + +### Fixes + +#### Android + +- **Fixed** incompatibility with `Flutter 3`. - [#78](https://github.com/LucJosin/on_audio_query/issues/78) + +## [2.6.0] - [02.01.2022] + +### Features + +- **Added** `[scanMedia]` method that will scan the given path and update the `[Android]` MediaStore. + +### Fixes + +- **Fixed** media showing when calling `[querySongs]` even after deleting with `[dart:io]`. - [#67](https://github.com/LucJosin/on_audio_query/issues/67) + +### Changes + +- **Updated** some required packages. + +### Documentation + +- Updated `README` documentation. +- Updated `DEPRECATED` documentation. +- Updated `PLATFORMS` documentation. +- Updated some `broken` links. + +## [2.5.3+1] - [01.20.2022] + +### Changes + +- **Updated** all Github links. + +## [2.5.3] - [11.10.2021] + +### Fixes + +#### IOS + +- **Fixed** song/artist/album from `Apple Music` returning when 'querying' - [#61](https://github.com/LucJosin/on_audio_query/issues/61) +- **Fixed** wrong `artistId` returning from `[AlbumModel]` - [#60](https://github.com/LucJosin/on_audio_query/issues/60) + +### Documentation + +- Updated `README` documentation. + +## [2.5.2] - [10.25.2021] + +### Fixes + +#### Android + +- **Fixed** wrong value returning from: - [#56](https://github.com/LucJosin/on_audio_query/issues/56) + - `[is_music]`. + - `[is_alarm]`. + - `[is_notification]`. + - `[is_ringtone]`. + - `[is_podcast]`. + - `[is_audiobook]`. + +### Documentation + +- Updated `README` documentation. + +## [2.5.1] - [10.19.2021] + +### Fixes + +#### Dart + +- **Fixed** wrong value returning from `[artistId]` when using `[AlbumModel]`. - [#54](https://github.com/LucJosin/on_audio_query/issues/54) + +#### Android + +- **Fixed** missing songs from `[queryAudiosFrom]` when using `GENRE`. - [#46](https://github.com/LucJosin/on_audio_query/issues/46) + +### Documentation + +- Updated `README` documentation. + +### ⚠ Important Changes + +#### Dart + +- Now `[artistId]` from `[AlbumModel]` return a `[int]`. + +## [2.5.0] - [10.15.2021] + +### Release + +- `[2.5.0]` release. + +### Features + +#### Dart + +- **Added** `errorBuilder` and `frameBuilder` to `[QueryArtworkWidget]`. + +### Fixes + +#### Web + +- **Fixed** empty result when using `[querySongs]`. +- **Fixed** error when decoding some images. + +See all development [changes](https://github.com/LucJosin/on_audio_query/blob/main/on_audio_query/CHANGELOG.md): + +- [2.5.0-alpha.0](#250-alpha0---10152021) + +## [2.5.0-alpha.0] - [10.15.2021] + +### Features + +#### All platforms + +- **Added** `artwork` to genres. - [#41](https://github.com/LucJosin/on_audio_query/issues/41) +- **Added** `sortType`, `orderType` and `ignoreCase` to `[queryAudiosFrom]`. + +#### Android + +- Re-**Added** `path` parameter to `[querySongs]`. - [#48](https://github.com/LucJosin/on_audio_query/issues/48) + +#### Web + +- **Added** `path` parameter to `[querySongs]`. + +### Fixes + +#### Android + +- **Fixed** empty `Uint8List` when using `[queryArtwork]` on Android 7. - [#47](https://github.com/LucJosin/on_audio_query/issues/47) +- **Fixed** null `albumId` when using Android 9 or below. - [#53](https://github.com/LucJosin/on_audio_query/issues/53) + +### Documentation + +- Updated `README` documentation. New `[queryAudiosFrom]` section. +- Updated `DEPRECATED` documentation. + +### Changes + +- Downgraded `Kotlin` and `Gradle` version. - [#51](https://github.com/LucJosin/on_audio_query/issues/51) + +### ⚠ Important Changes + +#### @Deprecated + +- `[albumId]` from `[AlbumModel]`. + - Use `[id]` instead. + +## [2.4.2] - [10.01.2021] + +### Fixes + +#### IOS + +- **Fixed** no artwork returning from `[queryArtwork]` when using `ArtworkType.ALBUM`. - [#45](https://github.com/LucJosin/on_audio_query/issues/45) + +### Documentation + +- Updated `README` documentation. + +## [2.4.1] - [09.29.2021] + +### Fixes + +#### Dart + +- **Fixed** wrong type of `numOfSongs` from `[SongModel]`. - [#39](https://github.com/LucJosin/on_audio_query/issues/39) + +#### IOS + +- **Fixed** wrong filter configuration when using `[queryWithFilters]`. +- **Fixed** crash when using any `'query'` method with a null `sortType`. - [#43](https://github.com/LucJosin/on_audio_query/issues/43) +- **Fixed** error with wrong `[MPMediaQuery]` filter. - [#38](https://github.com/LucJosin/on_audio_query/issues/38) + +### Documentation + +- Updated `README` documentation. + +## [2.4.0] - [09.28.2021] + +### Features + +#### Android + +- **Added** a better 'search' method to `[queryWithFilters]`, now the query uses 'contains' when 'querying'. - [#35](https://github.com/LucJosin/on_audio_query/issues/35) + +### Fixes + +#### IOS + +- **Fixed** error with wrong `[MPMediaQuery]` type and wrong value from `[jpegData]`. - [#37](https://github.com/LucJosin/on_audio_query/issues/37) + +#### Documentation + +- Updated broken `README` links. - [#36](https://github.com/LucJosin/on_audio_query/issues/36) + +### Documentation + +- Updated `README` documentation. + +## [2.3.1] - [09.27.2021] + +### Features + +#### Android/Web + +- **Added** `[ignoreCase]` to: + - `[querySongs]`. + - `[queryAlbums]`. + - `[queryArtists]`. + - `[queryPlaylists]`. + - `[queryGenres]`. + +### Fixes + +#### Android + +- **Fixed** `error` when trying to build using `Android`. - [#32](https://github.com/LucJosin/on_audio_query/issues/32) & [#33](https://github.com/LucJosin/on_audio_query/issues/33) +- **Fixed** `error` related to android song projection. - [#31](https://github.com/LucJosin/on_audio_query/issues/31) +- **Fixed** `'bug'` when using `SongSortType.TITLE`. This is now a `'feature'` and can be controlled using `[ignoreCase]`. - [#29](https://github.com/LucJosin/on_audio_query/issues/29) + +### Documentation + +- Updated `README` documentation. + +### Changes + +#### Android + +- Updated `[Kotlin]` and `[Dependencies]` versions. +- Moved from `[JCenter]` to `[MavenCentral]`. + +## [2.3.0] - [09.25.2021] + +### Features + +#### Android/IOS/Web + +- **Added** `[numOfSongs]` to `[PlaylistModel]` and `[GenreModel]`. +- **Added** `Playlist` and `Artist` to `ArtworkType`. + +#### Android/IOS + +- **Added** `quality` to `queryArtwork`. + +#### Android + +- **Added** `[isAudioBook]`, `[Genre]` and `[GenreId]` to `[SongModel]`. +- Re-**Added** to `[SongModel]`: + - `[isAlarm]`. + - `[isMusic]`. + - `[isNotification]`. + - `[isPodcast]`. + - `[isRingtone]`. + +### Fixes + +#### Android + +- **Fixed** wrong value returning from `[id]` when using `[ArtistModel]`. +- **Fixed** wrong value returning from `[id]` when using `[GenreModel]`. +- **Fixed** no value returning from `[queryAudiosFrom]` when using `ARTIST_ID`. + +### Documentation + +- Updated `README` documentation. +- Updated `OnAudioQuery` and `OnAudioQueryExample` documentation to support new `[Flutter 2.5]`. + +### Changes + +- **[Changed]** wrong name `DATA_ADDED` to `DATE_ADDED` for both `[SongSortType]` and `[PlaylistSortType]`. - [#27](https://github.com/LucJosin/on_audio_query/issues/27) + +### ⚠ Important Changes + +#### Dart + +- The parameter `args` from `[queryWithFilters]` is no longer required. + +#### @Deprecated + +- `[DEFAULT]` from `[SongSortType]`. +- `[DEFAULT]` from `[PlaylistSortType]`. +- `[DEFAULT]` from `[ArtistSortType]`. +- `[DEFAULT]` from `[AlbumSortType]`. +- `[DEFAULT]` from `[GenreSortType]`. +- `[ARTIST_KEY]` from `[ArtistSortType]`. +- `[ARTIST_NAME]` from `[ArtistSortType]`. +- `[ALBUM_NAME]` from `[AlbumSortType]`. +- `[GENRE_NAME]` from `[GenreSortType]`. +- `[DATA_ADDED]` from `[SongSortType]`. +- `[DATA_ADDED]` from `[PlaylistSortType]`. + + + + +## [2.2.0] - [08.25.2021] + +### Features + +#### IOS + +- Added a `filter` to avoid cloud audios/songs. + +### Fixes + +#### IOS + +- **Fixed** wrong value returning from `[permissionsStatus]`. - [#24](https://github.com/LucJosin/on_audio_query/issues/24) + +### Documentation + +- Updated `README` documentation. + +## [2.1.2] - [08.24.2021] + +### Fixes + +#### Android + +- **Fixed** duplicate `media` from `[queryWithFilters]`. +- **Fixed** `crash` when calling `[queryWithFilters]`. - [#23](https://github.com/LucJosin/on_audio_query/issues/23) +- **Fixed** `null` artwork returning from `[queryArtwork]` on Android 11/R. - [#21](https://github.com/LucJosin/on_audio_query/issues/21) + +### Documentation + +- Updated `README` documentation. +- Updated `pubspec` documentation. + +## [2.1.1] - [08.23.2021] + +### Fixes + +#### Android + +- **Fixed** error when using `[removeFromPlaylist]`. - [#22](https://github.com/LucJosin/on_audio_query/issues/22) + +### Documentation + +- Updated `README` documentation. +- Updated `[OnAudioQueryExample]` to support `[Web]` platform. + +## [2.1.0] - [08.23.2021] + +### Features + +#### on_audio_query + +- The plugin now supports `[Web]`. +- The plugin now utilize `[Platform interface]` package. + +#### Web + +- Added: + - `[querySongs]`. + - `[queryAlbums]`. + - `[queryArtists]`. + - `[queryGenres]`. + - `[queryAudiosFrom]`. + - `[queryWithFilters]`. + - `[queryArtwork]`. + - `[queryDeviceInfo]`. + +### Documentation + +- Updated `on_audio_query` documentation. +- Updated `README` documentation. +- Updated `PLATFORMS` documentation. +- Added documentation to `Web` platform. + +## [2.0.0] - [08.17.2021] + +### Release + +- `[2.0.0]` release. + +See all development [changes](https://github.com/LucJosin/on_audio_query/blob/main/CHANGELOG.md): + +- [2.0.0-beta.3](#200-beta3---08172021---github-only) +- [2.0.0-beta.2](#200-beta2---08152021) +- [2.0.0-beta.1](#200-beta1---08142021) +- [2.0.0-beta.0](#200-beta0---08132021) +- [2.0.0-alpha.1](#200-alpha1---08082021---github-only) +- [2.0.0-alpha.0](#200-alpha0---08052021---github-only) +- [2.0.0-dev.1](#200-dev1---08052021---internal) +- [2.0.0-dev.0](#200-dev0---08022021---internal) + +## [2.0.0-beta.3] - [08.17.2021] - [GitHub Only] + +### Features + +#### Android + +- Now **ALL** methods will only `"query"` if has permission to `READ`. + +### Fixes + +#### Android + +- **Fixed** no value returning when using `[permissionsRequest]`. + +### Documentation + +- Updated `README` documentation. +- Added more documentation to `Android` platform. + +## [2.0.0-beta.2] - [08.15.2021] + +### Features + +#### IOS + +- Now **ALL** methods will only `"query"` if has permission to `Library`. +- Added `[addToPlaylist]`. + +#### Dart + +- Added `[author]` and `[desc]` arguments to `[createPlaylist]`. **(IOS only)** + +### Fixes + +#### IOS + +- **Fixed** crash when using `[queryArtwork]`. +- **Fixed** wrong `[id]` value returning from `[PlaylistModel]`. + +### Documentation + +- Updated `README` documentation. + +## [2.0.0-beta.1] - [08.14.2021] + +### Features + +#### IOS + +- Added `[queryArtwork]`. + +### Fixes + +#### Android + +- **Fixed** `error` when building to `[Android]`. + +#### IOS + +- **Fixed** wrong `[duration]`, `[dateAdded]` and `[bookmark]` values returning from `[SongModel]`. + +### Documentation + +- Updated `on_audio_query` documentation. +- Updated `README` documentation. +- Updated `DEPRECATED` documentation. +- Added documentation to `IOS` platform. + +### ⚠ Important Changes + +#### @Deprecated + +- `[artwork]` from `[QueryArtworkWidget]`. +- `[deviceSDK]` from `[QueryArtworkWidget]`. +- `[requestPermission]` from `[QueryArtworkWidget]`. + +## [2.0.0-beta.0] - [08.13.2021] + +### Features + +#### on_audio_query + +- Added a [`DART ANALYZER`](https://github.com/axel-op/dart-package-analyzer/) to `PULL_REQUEST` and `PUSH`. + +### Documentation + +- Updated `on_audio_query` documentation. +- Updated `README` documentation. +- Updated `DEPRECATED` documentation. +- Created [`PLATFORMS`](https://github.com/LucJosin/on_audio_query/blob/2.0.0-dev/PLATFORMS.md) file. + +### ⚠ Important Changes + +#### Dart + +- Now **ALL** methods has `Named Optional` arguments. +- Changed `[queryArtworks]` to `[queryArtwork]`. + +#### @Deprecated + +- `[requestPermission]` argument from **ALL** methods. +- `[queryAudios]`. +- `[artwork]` from `[SongModel]`. +- `[path]` from `[querySongs]`. + +## [2.0.0-alpha.1] - [08.08.2021] - [GitHub Only] + +### Features + +#### Dart + +- Added `[artwork]` to `[PlaylistModel]` as `[Uint8List]` +- Added `[numOfTracks]` to `[PlaylistModel]` +- Added `[playlistAuthor]` and `[playlistDesc]` parameter to `[createPlaylist]` (IOS only) +- Added `[OnModelFormatter]` extension. + - Added `[toSongModel]`. + - Added `[toAlbumModel]`. + - Added `[toPlaylistModel]`. + - Added `[toArtistModel]`. + - Added `[toGenreModel]`. + +#### IOS + +- Added `[queryWithFilters]` method. +- Added `[createPlaylist]` method. +- Added `[queryPlaylists]` method. +- Added `[queryAudiosFrom]` method. + +### ⚠ Important Changes + +#### Dart + +- Now `[dateAdded]` from `[PlaylistModel]` return a `[int]`. +- Now `[dateModified]` from `[PlaylistModel]` return a `[int]`. + +#### @Deprecated + +- `[queryAudiosOnly]` +- `[AudiosOnlyType]` +- `[queryAudiosBy]` +- `[AudiosByType]` + +### Dev Changes + +#### Dart + +- ~~Added checker to all `[int]` from `[PlaylistModel]`.~~ + - Temporary + +## [2.0.0-alpha.0] - [08.05.2021] - [GitHub Only] + +### Release + +- `[2.0.0-alpha.0]` release. + +## [2.0.0-dev.1] - [08.05.2021] - [Internal] + +### Features + +#### IOS + +- Added `[queryArtists]` and `[queryGenres]`. + +### ⚠ Important Changes + +#### @Deprecated + +- Removed `[artwork]` from `[ArtistModel]`. +- Removed `[artwork]` from `[GenreModel]`. + +### Dev Changes + +#### Dart + +- ~~Added a checker to all int items from `[ArtistModel]`.~~ + - Temporary +- ~~Added a checker to all int items from `[GenreModel]`.~~ + - Temporary + +## [2.0.0-dev.0] - [08.02.2021] - [Internal] + +### Features + +#### on_audio_query + +- The plugin now supports `[IOS]`. **(Not 100%)** + +#### IOS + +- Added `[querySongs]` and `[queryAlbums]`. + +#### Dart + +- Added `[model]` to `[DeviceModel]`. + +### Changes + +#### Dart + +- Now `[sdk]` are `[version]`. +- Now `[deviceType]` are `[type]`. + +### ⚠ Important Changes + +#### Dart + +- Now `[artwork]` from `[SongModel]` return a `[Uint8list]`. +- ~~Now all `[int]` from `[SongModel]` can be `[null]`.~~ +- Now `[artwork]` from `[AlbumModel]` return a `[Uint8list]`. + +#### @**Deprecated** + +- `[numOfSongsArtists]` from `[AlbumModel]`. +- `[maxyear]` from `[AlbumModel]`. +- `[minyear]` from `[AlbumModel]`. +- `[release]` from `[DeviceModel]`. +- `[code]` from `[DeviceModel]`. +- `[year]` from `[SongModel]`. +- `[is_alarm]` from `[SongModel]`. +- `[is_music]` from `[SongModel]`. +- `[is_notification]` from `[SongModel]`. +- `[is_ringtone]` from `[SongModel]`. +- `[is_podcast]` from `[SongModel]`. +- `[file_parent]` from `[SongModel]`. +- `[firstYear]` from `[AlbumModel]`. +- `[lastYear]` from `[AlbumModel]`. + +### Dev Changes + +#### Dart + +- Now `[queryDeviceInfo]` will return Map instead of List. +- ~~Added a checker to all int items from `[SongModel]`.~~ + - Temporary. +- ~~Added a checker to all int items from `[AlbumModel]`.~~ + - Temporary. + +## [1.2.0] - [07.30.2021] + +### Features + +- Added `[path]` parameter to `[querySongs]` and `[queryAudio]`. +- Added `[getMap]` to: + - `[SongModel]`. + - `[AlbumModel]`. + - `[ArtistModel]`. + - `[GenreModel]`. + - `[PlaylistModel]`. + - `[DeviceModel]`. + +### Documentation + +- Updated `README` documentation. + +## [1.1.3+1] - [07.19.2021] + +### Fixes + +#### Android + +- **Fixed** `[Kotlin]` issue when installing the plugin. + +### Documentation + +- Updated `README` documentation. + +### Changes + +#### Android + +- Downgraded some `[Kotlin]` dependencies. + +## [1.1.3] - [07.18.2021] + +### Fixes + +#### Android + +- **Fixed** `[cursor]` problem when using `[AudiosFromType.GENRE_NAME]` or `[AudiosFromType.GENRE_ID]` on `[queryAudiosFrom]`. - [#16](https://github.com/LucJosin/on_audio_query/issues/16) and [#12](https://github.com/LucJosin/on_audio_query/issues/12) + +### Documentation + +- Updated `README` documentation. + +### Changes + +#### Android + +- Updated some `[Kotlin]` dependencies. + +## [1.1.2] - [07.07.2021] + +### Fixes + +#### Android + +- ~~**Fixed** `[cursor]` problem when using `[AudiosFromType.GENRE_NAME]` or `[AudiosFromType.GENRE_ID]` on `[queryAudiosFrom]`.~~ + +### Documentation + +- Updated `README` documentation. + +## [1.1.1] - [06.23.2021] + +### Features + +#### Dart/Android + +- Added `[uri]` to `[SongModel]`. - [Added #10](https://github.com/LucJosin/on_audio_query/issues/10) + +### Fixes + +#### Android + +- **Fixed** `java.lang.Integer cannot be cast to java.lang.Long` from `[queryArtworks]`. - [#11](https://github.com/LucJosin/on_audio_query/issues/11) + +### Documentation + +- Updated `README` documentation. +- Created `DEPRECATED` file/history. + +### Changes + +#### Dart + +- Changed from `[deviceInfo]` to `[deviceSDK]` on `[QueryArtworkWidget]`. + +### ⚠ Important Changes + +#### Dart + +- Deprecated `[deviceInfo]` from `[QueryArtworkWidget]`. + +## [1.1.0] - [06.03.2021] + +### Features + +#### Dart/Android + +- Added `[queryDeviceInfo]`. +- Added `[dateModified]` to `[SongModel]`. +- Added `[querySongsBy]` and `[SongsByType]`. + +### Fixes + +#### Android + +- **Fixed** incompatibility with `[permission_handler]`. - [#3](https://github.com/LucJosin/on_audio_query/issues/3) - Thanks [@mvanbeusekom](https://github.com/mvanbeusekom) + +#### Dart + +- **Fixed** wrong name. From `[dataAdded]` to `[dateAdded]`. + +### Documentation + +- Updated `README` documentation. +- Updated `[OnAudioQueryExample]` to add new `[queryDeviceInfo]` and `[QueryArtworkWidget]` methods. + +### Changes + +#### Android + +- Updated some `[Kotlin]` dependencies. +- Changed some `[Kotlin]` methods. + +### ⚠ Important Changes + +#### Dart + +- Now `[getDeviceSDK]`, `[getDeviceRelease]` and `[getDeviceCode]` are part of `[queryDeviceInfo]`. +- Now `[QueryArtworkWidget]` support Android above and below 29/Q/10. +- Now `[size]`, `[albumId]`, `[artistId]`, `[dataAdded]`, `[dataModified]`, `[duration]`, `[track]` and `[year]` from `[SongModel]` will return `[int]`. + +## [1.0.8] - [05.19.2021] + +### Features + +#### Dart + +- Added `[artworkClipBehavior]`, `[keepOldArtwork]`, `[repeat]` and `[scale]` to `[QueryArtworkWidget]`. +- Added comments to `[QueryArtworkWidget]`. + +### Fixes + +#### Android + +- **Fixed** Now `[queryArtworks]` will return null. - [#6](https://github.com/LucJosin/on_audio_query/issues/6) + +### Documentation + +- Updated `README` documentation. + +### ⚠ Important Changes + +#### Dart + +- Now `[queryArtworks]` return `[Uint8List?]`. + +## [1.0.7] - [05.18.2021] + +### Features + +#### Dart/Android + +- Added `[queryFromFolder]`. +- Added `[queryAllPath]`. +- Added `[_display_name_wo_ext]` (`[displayName]` without extension) to `[SongModel]`. - [Added #5](https://github.com/LucJosin/on_audio_query/issues/5) +- Added `[file_extension]` (Only file extension) to `[SongModel]`. +- Added `[file_parent]` (All the path before file) to `[SongModel]`. +- Added `[Genre]` to `[queryAudiosFrom]`. +- Added `[ALBUM_ID]`, `[ARTIST_ID]` and `[PLAYLIST_ID]` to `[AudiosFromType]`. - [Added #2](https://github.com/LucJosin/on_audio_query/issues/2) + +### Documentation + +- Updated `README` documentation. + +### Changes + +#### Dart/Android + +- Now `[queryAudiosFrom]` supports `[name]` and `[id]`. +- Now `[albumId]` from `[AlbumModel]` return a `[int]`. + +#### Android + +- Now all `[Kotlin]` checks will throw a `[Exception]` if value don't exist. +- Updated some `[Kotlin]` dependencies. + +### ⚠ Important Changes + +#### Dart/Android + +- Changed `[ALBUM]`, `[ARTIST]` and `[PLAYLIST]` to `[ALBUM_NAME]`, `[ARTIST_NAME]` and `[PLAYLIST_NAME]` in `[AudiosFromType]`. + +## [1.0.6] - [04.08.2021] + +### Fixes + +#### Android + +- **Fixed** `[queryArtwork]` returning null album image in Android 11. - [#1](https://github.com/LucJosin/on_audio_query/issues/1) + +### Documentation + +- Updated `README` documentation. + +### Changes + +#### Android + +- Removed unnecessary code from `[WithFiltersType]`. +- Updated some `[Kotlin]` dependencies. + +## [1.0.5] - [03.31.2021] + +### Features + +#### Dart/Android + +- Added `[queryAudiosOnly]`. +- Added `[queryWithFilters]`. +- Added `[AudiosOnlyType]` and `[WithFiltersType]`. +- Added `[SongsArgs]`, `[AlbumsArgs]`, `[PlaylistsArgs]`, `[ArtistsArgs]`, `[GenresArgs]`. +- Added `[EXTERNAL]` and `[INTERNAL]` parameters for some query methods. + +### Documentation + +- Updated `README` documentation. + +### Changes + +#### Dart/Android + +- Now `[querySongs]`, `[queryAlbums]`, `[queryArtists]`, `[queryPlaylists]` and `[queryGenres]` have `[UriType]` as parameter. + +#### Android + +- Updated some `[Kotlin]` dependencies. + +## [1.0.3] - [03.28.2021] + +### ⚠ Important Changes + +#### Dart + +- Migrate to null safety. + +## [1.0.2] - [03.27.2021] + +### Fixes + +#### Dart + +- **Fixed** flutter example. + +#### Android + +- **Fixed** `[audiosFromPlaylist]` [**Now this method is part of queryAudiosFrom**] +- **Fixed** `"count(*)"` error from `[addToPlaylist]`. [**Permission bug on Android 10 still happening**] + +### Documentation + +- Updated `README` documentation. + +### Changes + +#### Dart + +- Now `[Id]` in models return `[int]` instead `[String]`. + +### ⚠ Important Changes + +#### Dart/Android + +- Removed `[ALBUM_KEY]`, `[ARTIST_KEY]` from all query audio methods. + +#### Android + +- Moved `[audiosFromPlaylist]` to `[queryAudiosFrom]`. + +## [1.0.0] - [03.24.2021] + +### Release + +- `[on_audio_query]` release. + +## [0.5.0] - [03.23.2021] + +### Features + +#### Dart/Android + +- Changed some methods structure. +- Added `[moveItemTo]` method to Playlist. +- Added `[Size]` and `[Format]` parameters to `[queryArtwork]`. +- Added `[getDeviceSDK]`, `[getDeviceRelease]` and `[getDeviceCode]`. +- Added `[retryRequest]` parameter to `[permissionsRequest]`. + +#### Dart + +- Added `[QueryArtworkWidget]`. + +### Fixes + +- Added parameter `[AudioId]` to `[addToPlaylist]` and `[removeFromPlaylist]`. + +### Documentation + +- Updated `README` documentation. +- Added more comments to `[Kotlin]` and `[Dart]` code. + +### Changes + +- Now Playlist methods parameters request `[id]` instead Name. +- Now `[renamePlaylist]` add more information -> `[Date_Modified]`. +- Now when `[requestPermission]` parameter is set to true or `[permissionsRequest]` method is called, both `[READ]` and `[WRITE]` is requested. + +## [0.4.0] - [03.18.2021] + +### Features + +#### Dart/Android + +- Changed some methods structure. +- Added `[renamePlaylist]`. +- Added separate option for sortType order `[ASC_OR_SMALLER]` and `[DESC_OR_GREATER]`. +- Added `[permissionsStatus]` and `[permissionsRequest]`. + +### Documentation + +- Updated `README` documentation. +- Added some comments to `[Kotlin]` and `[Dart]` code. + +### Changes + +- Now `[createPlaylist]`, `[removePlaylist]`, `[addToPlaylist]` and `[removeFromPlaylist]` return bool. + +## [0.3.0] - [03.16.2021] + +### Features + +#### Dart/Android + +- Added `[createPlaylist]`, `[removePlaylist]`, `[addToPlaylist]` and `[removeFromPlaylist]`. + +#### Dart + +- Updated the `[Example]` application. + +### Documentation + +- Updated `README` documentation. + +## [0.2.5] - [03.11.2021] + +### Features + +#### Dart/Android + +- Added `[queryArtworks]` and `[queryAudiosFrom]`. + +### Fixes + +- Added a better performance for query images. + +### Documentation + +- Updated `README` documentation. + +## [0.2.0] - [03.10.2021] + +### Features + +#### Dart/Android + +- Added `[queryArtists]`, `[queryPlaylists]` and `[queryGenres]`. +- Added `[ArtistSortType]`, `[PlaylistsSortType]` and `[GenreSortType]`. + +#### Android + +- Now all methods use `Kotlin Coroutines` for query in background, adding a better performance. + +### Documentation + +- Updated `README` documentation. +- Updated `pubspec.yaml`. +- Created `README` translation section. +- Created `README` translation for `pt-BR` [Portuguese]. + +## [0.1.5] - [03.08.2021] + +### Features + +#### Dart/Android + +- Added `[querySongs]`, `[queryAudio]` and `[queryAlbums]`. +- Added `[AudioSortType]` and `[AlbumSortType]`. + +#### Android + +- Added `[Optional]` and `[Built-in]` Write and Read Storage Permission. + +### Documentation + +- Created a `README` documentation. + +## [0.0.1] - [02.16.2021] + +### Features + +#### Dart/Android + +- Created the base for the plugin. + + + + diff --git a/local_packages/on_audio_query-2.9.0/DEPRECATED.md b/local_packages/on_audio_query-2.9.0/DEPRECATED.md new file mode 100644 index 0000000..855d0c0 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/DEPRECATED.md @@ -0,0 +1,86 @@ +## [2.5.0] - [10.15.2021] -> [2.6.0] - [02.01.2022] +### Deprecated +- `[albumId]` from `[AlbumModel]`. + - Use `[id]` instead. + +## [2.3.0] - [09.25.2021] -> [2.5.0] - [10.15.2021] +### Deprecated +- `[DEFAULT]` from `[SongSortType]`. + - Use `[TITLE]` instead. +- `[DEFAULT]` from `[PlaylistSortType]`. + - Use `[PLAYLIST]` instead. +- `[DEFAULT]` from `[ArtistSortType]`. + - Use `[ARTIST]` instead. +- `[DEFAULT]` from `[AlbumSortType]`. + - Use `[ALBUM]` instead. +- `[DEFAULT]` from `[GenreSortType]`. + - Use `[GENRE]` instead. +- `[ARTIST_KEY]` from `[ArtistSortType]`. +- `[ARTIST_NAME]` from `[ArtistSortType]`. + - Use `[ARTIST]` instead. +- `[ALBUM_NAME]` from `[AlbumSortType]`. + - Use `[ALBUM]` instead. +- `[GENRE_NAME]` from `[GenreSortType]`. + - Use `[GENRE]` instead. +- `[DATA_ADDED]` from `[SongSortType]`. + - Use `[DATE_ADDED]` instead. +- `[DATA_ADDED]` from `[PlaylistSortType]`. + - Use `[DATE_ADDED]` instead. + +## [2.0.0] - [06.23.2021] -> [2.3.0] - [09.25.2021] +### Deprecated +- `[albumName]` from `[AlbumModel]`. + - Use `[album]` instead. +- `[artistName]` from `[ArtistModel]`. + - Use `[artist]` instead. +- `[genreName]` from `[GenreModel]`. + - Use `[genre]` instead. +- `[playlistName]` from `[PlaylistModel]`. + - Use `[playlist]` instead. +- `[ALBUM_NAME]` FROM `[AudiosFromType]`. + - Use `[ALBUM]` instead. +- `[ARTIST_NAME]` FROM `[AudiosFromType]`. + - Use `[ARTIST]` instead. +- `[GENRE_NAME]` FROM `[AudiosFromType]`. + - Use `[GENRE]` instead. +- `[ALBUM_NAME]` FROM `[AlbumsArgs]`. + - Use `[ALBUM]` instead. +- `[PLAYLIST_NAME]` FROM `[PlaylistsArgs]`. + - Use `[PLAYLIST]` instead. +- `[PLAYLIST_NAME]` FROM `[PlaylistSortType]`. + - Use `[PLAYLIST]` instead. +- `[ARTIST_NAME]` FROM `[ArtistsArgs]`. + - Use `[ARTIST]` instead. +- `[GENRE_NAME]` FROM `[GenresArgs]`. + - Use `[GENRE]` instead. +- `[numOfSongsArtists]` from `[AlbumModel]`. +- `[firstYear]` from `[AlbumModel]`. + - `[FIRST_YEAR]` from `[AlbumSortType]`. +- `[lastYear]` from `[AlbumModel]`. + - `[LAST_YEAR]` from `[AlbumSortType]`. +- `[release]` from `[DeviceModel]`. +- `[code]` from `[DeviceModel]`. +- `[year]` from `[SongModel]`. +- ~~`[is_alarm]` from `[SongModel]`~~. +- ~~`[is_music]` from `[SongModel]`~~. +- ~~`[is_notification]` from `[SongModel]`~~. +- ~~`[is_ringtone]` from `[SongModel]`~~. +- ~~`[is_podcast]` from `[SongModel]`~~. +- `[file_parent]` from `[SongModel]`. +- `[path]` from `[querySongs]`. +- `[artwork]` from `[SongModel]`. + - Use `[queryArtwork]` instead. +- `[artwork]` from `[ArtistModel]`. +- `[artwork]` from `[GenreModel]`. +- `[artwork]` from `[QueryArtworkWidget]`. +- `[deviceSDK]` from `[QueryArtworkWidget]`. +- `[requestPermission]` from `[QueryArtworkWidget]`. +- `[queryAudiosOnly]`. +- `[querySongsBy]`. +- `[AudiosOnlyType]`. +- `[SongsByType]`. +- `[queryAudios]`. + +## [1.1.1] - [06.23.2021] -> [1.2.0] - [07.30.2021] +### Deprecated +- `[deviceInfo]` from `[QueryArtworkWidget]`. \ No newline at end of file diff --git a/local_packages/on_audio_query-2.9.0/LICENSE b/local_packages/on_audio_query-2.9.0/LICENSE new file mode 100644 index 0000000..e55366d --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2021, Lucas Josino +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of Lucas Josino nor the names of its contributors may be +used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/local_packages/on_audio_query-2.9.0/PLATFORMS.md b/local_packages/on_audio_query-2.9.0/PLATFORMS.md new file mode 100644 index 0000000..c8ae753 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/PLATFORMS.md @@ -0,0 +1,272 @@ +# on_audio_query - Platforms support + +Here you'll see a extra information about every method/type etc.. + +## Topics: + +* [Methods](#methods) +* [Sort Types](#sorttypes) + * [SongSortType](#songsorttype) + * [AlbumSortType](#albumsorttype) + * [ArtistSortType](#artistsorttype) + * [PlaylistSortType](#playlistsorttype) + * [GenreSortType](#genresorttype) +* [Order Types](#ordertypes) +* [Uri Types](#uritypes) +* [Artwork Types](#artworktype) +* [Artwork Format Types](#artworkformat) +* [Audios From Types](#audiosfromtype) +* [With Filter Types](#withfilterstype) + * [AudiosArgs](#audiosargs) + * [AlbumsArgs](#albumsargs) + * [PlaylistsArgs](#playlistsargs) + * [ArtistsArgs](#artistsargs) + * [GenresArgs](#genressargs) +* [Models](#models) + * [SongModel](#songmodel) + * [AlbumModel](#albummodel) + * [PlaylistModel](#playlistmodel) + * [ArtistModel](#artistmodel) + * [GenreModel](#genremodel) +* [DeviceModel](#devicemodel) + +✔️ -> Supported
+❌ -> Not Supported
+ +## Methods + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `querySongs` | `✔️` | `✔️` | `✔️` |
+| `queryAlbums` | `✔️` | `✔️` | `✔️` |
+| `queryArtists` | `✔️` | `✔️` | `✔️` |
+| `queryPlaylists` | `✔️` | `✔️` | `❌` |
+| `queryGenres` | `✔️` | `✔️` | `✔️` |
+| `queryAudiosFrom` | `✔️` | `✔️` | `✔️` |
+| `queryWithFilters` | `✔️` | `✔️` | `✔️` |
+| `queryArtwork` | `✔️` | `✔️` | `✔️` |
+| `createPlaylist` | `✔️` | `✔️` | `❌` |
+| `removePlaylist` | `✔️` | `❌` | `❌` |
+| `addToPlaylist` | `✔️` | `✔️` | `❌` |
+| `removeFromPlaylist` | `✔️` | `❌` | `❌` |
+| `renamePlaylist` | `✔️` | `❌` | `❌` |
+| `moveItemTo` | `✔️` | `❌` | `❌` |
+| `permissionsRequest` | `✔️` | `✔️` | `❌` |
+| `permissionsStatus` | `✔️` | `✔️` | `❌` |
+| `queryDeviceInfo` | `✔️` | `✔️` | `✔️` |
+| `scanMedia` | `✔️` | `❌` | `❌` |
+ +## SortTypes + +### SongSortType + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `DEFAULT` | `✔️` | `✔️` | `✔️` |
+| `ARTIST` | `✔️` | `✔️` | `✔️` |
+| `ALBUM` | `✔️` | `✔️` | `✔️` |
+| `DURATION` | `✔️` | `✔️` | `✔️` |
+| `DATA_ADDED` | `✔️` | `✔️` | `❌` |
+| `SIZE` | `✔️` | `✔️` | `✔️` |
+| `DISPLAY_NAME` | `✔️` | `✔️` | `✔️` |
+ +### AlbumSortType + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `DEFAULT` | `✔️` | `✔️` | `✔️` |
+| `ARTIST` | `✔️` | `✔️` | `✔️` |
+| `ALBUM` | `✔️` | `✔️` | `✔️` |
+| `NUM_OF_SONGS` | `✔️` | `✔️` | `✔️` |
+ +### ArtistSortType + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `DEFAULT` | `✔️` | `❌` | `✔️` |
+| `ARTIST_NAME` | `✔️` | `✔️` | `✔️` |
+| `ARTIST_KEY` | `✔️` | `❌` | `❌` |
+| `NUM_OF_TRACKS` | `✔️` | `✔️` | `✔️` |
+| `NUM_OF_ALBUMS` | `✔️` | `✔️` | `✔️` |
+ +### PlaylistSortType + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `DEFAULT` | `✔️` | `❌` | `❌` |
+| `DATA_ADDED` | `✔️` | `❌` | `❌` |
+| `PLAYLIST_NAME` | `✔️` | `❌` | `❌` |
+ +### GenreSortType + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `DEFAULT` | `✔️` | `✔️` | `✔️` |
+ +## OrderTypes + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `ASC` | `✔️` | `✔️` | `✔️` |
+| `DESC` | `✔️` | `✔️` | `✔️` |
+ +## UriTypes + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `EXTERNAL` | `✔️` | `❌` | `❌` |
+| `INTERNAL` | `✔️` | `✔️` | `✔️` |
+ +## ArtworkTypes + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `AUDIO` | `✔️` | `✔️` | `✔️` |
+| `ALBUM` | `✔️` | `✔️` | `✔️` |
+ +## ArtworkFormat + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `JPEG` | `✔️` | `✔️` | `❌` |
+| `PNG` | `✔️` | `✔️` | `❌` |
+ +## AudiosFromType + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `ALBUM_NAME` | `✔️` | `✔️` | `✔️` |
+| `ALBUM_ID` | `✔️` | `✔️` | `❌` |
+| `ARTIST_NAME` | `✔️` | `✔️` | `✔️` |
+| `ARTIST_ID` | `✔️` | `✔️` | `❌` |
+| `GENRE_NAME` | `✔️` | `✔️` | `✔️` |
+| `GENRE_ID` | `✔️` | `✔️` | `❌` |
+| `PLAYLIST` | `✔️` | `✔️` | `❌` |
+ +## WithFiltersType + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `AUDIOS` | `✔️` | `✔️` | `✔️` |
+| `ALBUMS` | `✔️` | `✔️` | `✔️` |
+| `PLAYLISTS` | `✔️` | `✔️` | `❌` |
+| `ARTISTS` | `✔️` | `✔️` | `✔️` |
+| `GENRES` | `✔️` | `✔️` | `✔️` |
+ +### AudiosArgs + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `TITLE` | `✔️` | `✔️` | `✔️` |
+| `DISPLAY_NAME` | `✔️` | `❌` | `✔️` |
+| `ALBUM` | `✔️` | `✔️` | `✔️` |
+| `ARTIST` | `✔️` | `✔️` | `✔️` |
+ +### AlbumsArgs + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `ALBUM_NAME` | `✔️` | `✔️` | `✔️` |
+| `ARTIST` | `✔️` | `✔️` | `✔️` |
+ +### PlaylistsArgs + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `PLAYLIST_NAME` | `✔️` | `✔️` | `❌` |
+ +### ArtistsArgs + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `ARTIST_NAME` | `✔️` | `✔️` | `✔️` |
+ +### GenresArgs + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `GENRE_NAME` | `✔️` | `✔️` | `✔️` |
+ +## Models + +### SongModel + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `id` | `✔️` | `✔️` | `✔️` |
+| `data` | `✔️` | `✔️` | `✔️` |
+| `uri` | `✔️` | `❌` | `❌` |
+| `displayName` | `✔️` | `✔️` | `✔️` |
+| `displayNameWOExt` | `✔️` | `✔️` | `✔️` |
+| `size` | `✔️` | `✔️` | `✔️` |
+| `album` | `✔️` | `✔️` | `✔️` |
+| `albumId` | `✔️` | `✔️` | `✔️` |
+| `artist` | `✔️` | `✔️` | `✔️` |
+| `artistId` | `✔️` | `✔️` | `✔️` |
+| `genre` | `✔️` | `✔️` | `✔️` |
+| `genreId` | `✔️` | `✔️` | `✔️` |
+| `bookmark` | `✔️` | `✔️` | `❌` |
+| `composer` | `✔️` | `✔️` | `❌` |
+| `dateAdded` | `✔️` | `✔️` | `❌` |
+| `dateModified` | `✔️` | `❌` | `✔️` |
+| `duration` | `✔️` | `✔️` | `❌` |
+| `title` | `✔️` | `✔️` | `✔️` |
+| `track` | `✔️` | `✔️` | `✔️` |
+| `fileExtension` | `✔️` | `✔️` | `✔️` |
+| `is_alarm` | `✔️` | `❌` | `❌` |
+| `is_audiobook` | `✔️` | `❌` | `❌` |
+| `is_music` | `✔️` | `❌` | `❌` |
+| `is_notification` | `✔️` | `❌` | `❌` |
+| `is_podcast` | `✔️` | `❌` | `❌` |
+| `is_ringtone` | `✔️` | `❌` | `❌` |
+ +### AlbumModel + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `id` | `✔️` | `✔️` | `✔️` |
+| `album` | `✔️` | `✔️` | `✔️` |
+| `albumId` | `✔️` | `✔️` | `✔️` |
+| `artist` | `✔️` | `✔️` | `✔️` |
+| `artistId` | `✔️` | `✔️` | `✔️` |
+| `numOfSongs` | `✔️` | `✔️` | `✔️` |
+ +### PlaylistModel + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `id` | `✔️` | `✔️` | `❌` |
+| `playlist` | `✔️` | `✔️` | `❌` |
+| `data` | `✔️` | `❌` | `❌` |
+| `dateAdded` | `✔️` | `✔️` | `❌` |
+| `dateModified` | `✔️` | `✔️` | `❌` |
+| `numOfSongs` | `✔️` | `✔️` | `❌` |
+| `artwork` | `❌` | `✔️` | `❌` |
+ +### ArtistModel + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `id` | `✔️` | `✔️` | `✔️` |
+| `artist` | `✔️` | `✔️` | `✔️` |
+| `numberOfAlbums` | `✔️` | `✔️` | `✔️` |
+| `numberOfTracks` | `✔️` | `✔️` | `✔️` |
+ +### GenreModel + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `id` | `✔️` | `✔️` | `✔️` |
+| `genre` | `✔️` | `✔️` | `✔️` |
+| `numOfSongs` | `✔️` | `✔️` | `✔️` |
+ +### DeviceModel + +| Methods | Android | IOS | Web | +|--------------|-----------------|-----------------|-----------------| +| `version` | `✔️` | `✔️` | `✔️` |
+| `type` | `✔️` | `✔️` | `✔️` |
+| `model` | `✔️` | `✔️` | `❌` |
+ + + diff --git a/local_packages/on_audio_query-2.9.0/README.md b/local_packages/on_audio_query-2.9.0/README.md new file mode 100644 index 0000000..34045ab --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/README.md @@ -0,0 +1,194 @@ +
+ +# on_audio_query +[![Pub.dev](https://img.shields.io/pub/v/on_audio_query?color=9cf&label=Pub.dev&style=flat-square)](https://pub.dev/packages/on_audio_query) +[![Platforms](https://img.shields.io/badge/Platforms-Android%20%7C%20IOS%20%7C%20Web-9cf?&style=flat-square)]() +[![Languages](https://img.shields.io/badge/Languages-Dart%20%7C%20Kotlin%20%7C%20Swift-9cf?&style=flat-square)]() + +[Flutter](https://flutter.dev/) Plugin used to query audios/songs 🎶 infos [title, artist, album, etc..] from device storage.
+ +**Any problem? [Issues](https://github.com/LucJosin/on_audio_query/issues)**
+**Any suggestion? [Pull request](https://github.com/LucJosin/on_audio_query/pulls)** + +
+ +### Topics: + +* [Installation](#installation) +* [Platforms](#platforms) +* [Overview](#overview) +* [Examples](#examples) +* [Gif Examples](#gif-examples) +* [License](#license) + +## Platforms: + + +| Methods | Android | IOS | Web | +|-------|:----------:|:----------:|:----------:| +| `querySongs` | `✔️` | `✔️` | `✔️` |
+| `queryAlbums` | `✔️` | `✔️` | `✔️` |
+| `queryArtists` | `✔️` | `✔️` | `✔️` |
+| `queryPlaylists` | `✔️` | `✔️` | `❌` |
+| `queryGenres` | `✔️` | `✔️` | `✔️` |
+| `queryAudiosFrom` | `✔️` | `✔️` | `✔️` |
+| `queryWithFilters` | `✔️` | `✔️` | `✔️` |
+| `queryArtwork` | `✔️` | `✔️` | `✔️` |
+| `createPlaylist` | `✔️` | `✔️` | `❌` |
+| `removePlaylist` | `✔️` | `❌` | `❌` |
+| `addToPlaylist` | `✔️` | `✔️` | `❌` |
+| `removeFromPlaylist` | `✔️` | `❌` | `❌` |
+| `renamePlaylist` | `✔️` | `❌` | `❌` |
+| `moveItemTo` | `✔️` | `❌` | `❌` |
+| `checkAndRequest` | `✔️` | `✔️` | `❌` |
+| `permissionsRequest` | `✔️` | `✔️` | `❌` |
+| `permissionsStatus` | `✔️` | `✔️` | `❌` |
+| `queryDeviceInfo` | `✔️` | `✔️` | `✔️` |
+| `scanMedia` | `✔️` | `❌` | `❌` |
+ +✔️ -> Supported
+❌ -> Not Supported
+ +**[See all platforms methods support](https://github.com/LucJosin/on_audio_query/blob/main/on_audio_query/PLATFORMS.md)** + +## Installation: + +Add the following code to your `pubspec.yaml`: +```yaml +dependencies: + on_audio_query: ^2.9.0 +``` + +### Request Permission: + +#### Android: +To use this plugin add the following code to your [AndroidManifest.xml](https://github.com/LucJosin/on_audio_query/blob/main/on_audio_query/example/android/app/src/main/AndroidManifest.xml) +```xml + + + + + + + + + + + + +``` + +#### IOS: +To use this plugin add the following code to your [Info.plist](https://github.com/LucJosin/on_audio_query/blob/main/on_audio_query/example/ios/Runner/Info.plist) +``` + + + NSAppleMusicUsageDescription + $(PROJECT_NAME) requires access to media library + + +``` + +## Some Features: + +* Optional and Built-in storage `READ` and `WRITE` permission request +* Get all audios/songs. +* Get all albums and album-specific audios. +* Get all artists and artist-specific audios. +* Get all playlists and playlists-specific audios. +* Get all genres and genres-specific audios. +* Get all query methods with specific `keys` [Search]. +* Create/Delete/Rename playlists. +* Add/Remove/Move specific audios to playlists. +* Specific sort types for all query methods. + +## Overview: + +All types of methods on this plugin: + +### Artwork Widget + +```dart + Widget someOtherName() async { + return QueryArtworkWidget( + id: , + type: ArtworkType.AUDIO, + ); + } +``` + +**See more: [QueryArtworkWidget](https://pub.dev/documentation/on_audio_query/latest/on_audio_query/QueryArtworkWidget-class.html)** + +## Examples: + +#### OnAudioQuery + +```dart +final OnAudioQuery _audioQuery = OnAudioQuery(); +``` + +#### Query methods: + +- queryAudios(); +- queryAlbums(); +- queryArtists(); +- queryPlaylists(); +- queryGenres(). + +```dart + someName() async { + // Query Audios + List audios = await _audioQuery.queryAudios(); + + // Query Albums + List albums = await _audioQuery.queryAlbums(); + } +``` + +#### scanMedia + +You'll use this method when updating a media from storage. This method will update the media 'state' and +Android `MediaStore` will be able to know this 'state'. + +```dart + someName() async { + OnAudioQuery _audioQuery = OnAudioQuery(); + File file = File('path'); + try { + if (file.existsSync()) { + file.deleteSync(); + _audioQuery.scanMedia(file.path); // Scan the media 'path' + } + } catch (e) { + debugPrint('$e'); + } + } +``` + +#### queryArtwork + +```dart + someName() async { + // DEFAULT: ArtworkFormat.JPEG, 200 and false + Uint8List something = await _audioQuery.queryArtwork( + , + ArtworkType.AUDIO, + ..., + ); + } +``` + +Or you can use a basic and custom Widget. +**See example [QueryArtworkWidget](#artwork-widget)** + +## Gif Examples: +| | | | | +|:---:|:---:|:---:|:---:| +| | | | | +| Songs | Albums | Playlists | Artists | + +## LICENSE: + +* [LICENSE](https://github.com/LucJosin/on_audio_query/blob/main/on_audio_query/LICENSE) + +> * [Back to top](#on_audio_query) diff --git a/local_packages/on_audio_query-2.9.0/analysis_options.yaml b/local_packages/on_audio_query-2.9.0/analysis_options.yaml new file mode 100644 index 0000000..839cc64 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options \ No newline at end of file diff --git a/local_packages/on_audio_query-2.9.0/example/README.md b/local_packages/on_audio_query-2.9.0/example/README.md new file mode 100644 index 0000000..008afc8 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/README.md @@ -0,0 +1,16 @@ +# on_audio_query_example + +Demonstrates how to use the on_audio_query plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/local_packages/on_audio_query-2.9.0/example/analysis_options.yaml b/local_packages/on_audio_query-2.9.0/example/analysis_options.yaml new file mode 100644 index 0000000..839cc64 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options \ No newline at end of file diff --git a/local_packages/on_audio_query-2.9.0/example/android/app/build.gradle b/local_packages/on_audio_query-2.9.0/example/android/app/build.gradle new file mode 100644 index 0000000..e163856 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/android/app/build.gradle @@ -0,0 +1,63 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 36 + namespace "com.lucasjosino.on_audio_query_example" + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.lucasjosino.on_audio_query_example" + minSdkVersion 16 + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/local_packages/on_audio_query-2.9.0/example/android/app/src/debug/AndroidManifest.xml b/local_packages/on_audio_query-2.9.0/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..fdc56fa --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/local_packages/on_audio_query-2.9.0/example/android/app/src/main/AndroidManifest.xml b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..183e75c --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/on_audio_query-2.9.0/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java new file mode 100644 index 0000000..e86636e --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -0,0 +1,29 @@ +package io.flutter.plugins; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import io.flutter.Log; + +import io.flutter.embedding.engine.FlutterEngine; + +/** + * Generated file. Do not edit. + * This file is generated by the Flutter tool based on the + * plugins that support the Android platform. + */ +@Keep +public final class GeneratedPluginRegistrant { + private static final String TAG = "GeneratedPluginRegistrant"; + public static void registerWith(@NonNull FlutterEngine flutterEngine) { + try { + flutterEngine.getPlugins().add(new dev.flutter.plugins.integration_test.IntegrationTestPlugin()); + } catch (Exception e) { + Log.e(TAG, "Error registering plugin integration_test, dev.flutter.plugins.integration_test.IntegrationTestPlugin", e); + } + try { + flutterEngine.getPlugins().add(new com.lucasjosino.on_audio_query.OnAudioQueryPlugin()); + } catch (Exception e) { + Log.e(TAG, "Error registering plugin on_audio_query_android, com.lucasjosino.on_audio_query.OnAudioQueryPlugin", e); + } + } +} diff --git a/local_packages/on_audio_query-2.9.0/example/android/app/src/main/kotlin/com/lucasjosino/example/MainActivity.kt b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/kotlin/com/lucasjosino/example/MainActivity.kt new file mode 100644 index 0000000..13d792d --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/kotlin/com/lucasjosino/example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.lucasjosino.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/local_packages/on_audio_query-2.9.0/example/android/app/src/main/kotlin/com/lucasjosino/on_audio_query_example/MainActivity.kt b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/kotlin/com/lucasjosino/on_audio_query_example/MainActivity.kt new file mode 100644 index 0000000..3eafeb1 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/kotlin/com/lucasjosino/on_audio_query_example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.lucasjosino.on_audio_query_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/drawable-v21/launch_background.xml b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/drawable/launch_background.xml b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/values-night/styles.xml b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..449a9f9 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/values/styles.xml b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..d74aa35 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/local_packages/on_audio_query-2.9.0/example/android/app/src/profile/AndroidManifest.xml b/local_packages/on_audio_query-2.9.0/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..fdc56fa --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/local_packages/on_audio_query-2.9.0/example/android/build.gradle b/local_packages/on_audio_query-2.9.0/example/android/build.gradle new file mode 100644 index 0000000..0059eb6 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '2.1.0' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.0.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/local_packages/on_audio_query-2.9.0/example/android/gradle.properties b/local_packages/on_audio_query-2.9.0/example/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/local_packages/on_audio_query-2.9.0/example/android/gradle/wrapper/gradle-wrapper.properties b/local_packages/on_audio_query-2.9.0/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..9168fdc --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Oct 15 16:26:20 BRT 2021 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/local_packages/on_audio_query-2.9.0/example/android/local.properties b/local_packages/on_audio_query-2.9.0/example/android/local.properties new file mode 100644 index 0000000..28e8fd6 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/android/local.properties @@ -0,0 +1,2 @@ +sdk.dir=D:\\AndroidSDK +flutter.sdk=D:\\FlutterSDK\\flutter_windows_3.29.3-stable\\flutter \ No newline at end of file diff --git a/local_packages/on_audio_query-2.9.0/example/android/settings.gradle b/local_packages/on_audio_query-2.9.0/example/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/local_packages/on_audio_query-2.9.0/example/android/settings_aar.gradle b/local_packages/on_audio_query-2.9.0/example/android/settings_aar.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/android/settings_aar.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Flutter/AppFrameworkInfo.plist b/local_packages/on_audio_query-2.9.0/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Flutter/Debug.xcconfig b/local_packages/on_audio_query-2.9.0/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Flutter/Generated.xcconfig b/local_packages/on_audio_query-2.9.0/example/ios/Flutter/Generated.xcconfig new file mode 100644 index 0000000..0e6fc18 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Flutter/Generated.xcconfig @@ -0,0 +1,14 @@ +// This is a generated file; do not edit or check into version control. +FLUTTER_ROOT=D:\FlutterSDK\flutter_windows_3.29.3-stable\flutter +FLUTTER_APPLICATION_PATH=D:\AndroidProject\Likei\local_packages\on_audio_query-2.9.0\example +COCOAPODS_PARALLEL_CODE_SIGN=true +FLUTTER_TARGET=lib\main.dart +FLUTTER_BUILD_DIR=build +FLUTTER_BUILD_NAME=1.0.0 +FLUTTER_BUILD_NUMBER=1 +EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386 +EXCLUDED_ARCHS[sdk=iphoneos*]=armv7 +DART_OBFUSCATION=false +TRACK_WIDGET_CREATION=true +TREE_SHAKE_ICONS=false +PACKAGE_CONFIG=.dart_tool/package_config.json diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Flutter/Release.xcconfig b/local_packages/on_audio_query-2.9.0/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Flutter/flutter_export_environment.sh b/local_packages/on_audio_query-2.9.0/example/ios/Flutter/flutter_export_environment.sh new file mode 100644 index 0000000..29ef937 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Flutter/flutter_export_environment.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# This is a generated file; do not edit or check into version control. +export "FLUTTER_ROOT=D:\FlutterSDK\flutter_windows_3.29.3-stable\flutter" +export "FLUTTER_APPLICATION_PATH=D:\AndroidProject\Likei\local_packages\on_audio_query-2.9.0\example" +export "COCOAPODS_PARALLEL_CODE_SIGN=true" +export "FLUTTER_TARGET=lib\main.dart" +export "FLUTTER_BUILD_DIR=build" +export "FLUTTER_BUILD_NAME=1.0.0" +export "FLUTTER_BUILD_NUMBER=1" +export "DART_OBFUSCATION=false" +export "TRACK_WIDGET_CREATION=true" +export "TREE_SHAKE_ICONS=false" +export "PACKAGE_CONFIG=.dart_tool/package_config.json" diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcodeproj/project.pbxproj b/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..c5d9fb5 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,556 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 0454AA9BC1C6A74D29F0DC05 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 89EDBAA686FD6A0F3044D3BE /* Pods_Runner.framework */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0E792F2A41600ECBFBB0ACDA /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3B7A1BC6F17ED8BDB08A32DE /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 76E158E1888B20842AFB70C7 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 89EDBAA686FD6A0F3044D3BE /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0454AA9BC1C6A74D29F0DC05 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 47B71ED6578BEB388DFE7ED6 /* Pods */ = { + isa = PBXGroup; + children = ( + 0E792F2A41600ECBFBB0ACDA /* Pods-Runner.debug.xcconfig */, + 3B7A1BC6F17ED8BDB08A32DE /* Pods-Runner.release.xcconfig */, + 76E158E1888B20842AFB70C7 /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 47B71ED6578BEB388DFE7ED6 /* Pods */, + F3BDBD7B8197D374D9E11050 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + F3BDBD7B8197D374D9E11050 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 89EDBAA686FD6A0F3044D3BE /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9801508057E29DB85D6FE897 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 25DEC95C3A6807E8090BAA98 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 25DEC95C3A6807E8090BAA98 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + 9801508057E29DB85D6FE897 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 29Q2WS8ZTA; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.lucasjosino.onAudioQueryExampleIOS; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 29Q2WS8ZTA; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.lucasjosino.onAudioQueryExampleIOS; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 29Q2WS8ZTA; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.lucasjosino.onAudioQueryExampleIOS; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..3db53b6 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/AppDelegate.swift b/local_packages/on_audio_query-2.9.0/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..28c6bf0 Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..f091b6b Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cde121 Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d0ef06e Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..dcdc230 Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..c8f9ed8 Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..75b2d16 Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..c4df70d Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..6a84f41 Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..d0e1f58 Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +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. \ No newline at end of file diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Base.lproj/Main.storyboard b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/GeneratedPluginRegistrant.m b/local_packages/on_audio_query-2.9.0/example/ios/Runner/GeneratedPluginRegistrant.m new file mode 100644 index 0000000..984d48c --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Runner/GeneratedPluginRegistrant.m @@ -0,0 +1,28 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#import "GeneratedPluginRegistrant.h" + +#if __has_include() +#import +#else +@import integration_test; +#endif + +#if __has_include() +#import +#else +@import on_audio_query_ios; +#endif + +@implementation GeneratedPluginRegistrant + ++ (void)registerWithRegistry:(NSObject*)registry { + [IntegrationTestPlugin registerWithRegistrar:[registry registrarForPlugin:@"IntegrationTestPlugin"]]; + [OnAudioQueryPlugin registerWithRegistrar:[registry registrarForPlugin:@"OnAudioQueryPlugin"]]; +} + +@end diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Info.plist b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Info.plist new file mode 100644 index 0000000..20945da --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + on_audio_query_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + NSAppleMusicUsageDescription + Please let this application access your music library + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/local_packages/on_audio_query-2.9.0/example/ios/Runner/Runner-Bridging-Header.h b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/local_packages/on_audio_query-2.9.0/example/lib/main.dart b/local_packages/on_audio_query-2.9.0/example/lib/main.dart new file mode 100644 index 0000000..22247ae --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/lib/main.dart @@ -0,0 +1,143 @@ +/* +============= +Author: Lucas Josino +Github: https://github.com/LucJosin +Website: https://www.lucasjosino.com/ +============= +Plugin/Id: on_audio_query#0 +Homepage: https://github.com/LucJosin/on_audio_query +Pub: https://pub.dev/packages/on_audio_query +License: https://github.com/LucJosin/on_audio_query/blob/main/on_audio_query/LICENSE +Copyright: © 2021, Lucas Josino. All rights reserved. +============= +*/ + +import 'package:flutter/material.dart'; +import 'package:on_audio_query/on_audio_query.dart'; + +void main() { + runApp( + const MaterialApp( + home: Songs(), + ), + ); +} + +class Songs extends StatefulWidget { + const Songs({Key? key}) : super(key: key); + + @override + _SongsState createState() => _SongsState(); +} + +class _SongsState extends State { + // Main method. + final OnAudioQuery _audioQuery = OnAudioQuery(); + + // Indicate if application has permission to the library. + bool _hasPermission = false; + + @override + void initState() { + super.initState(); + // (Optinal) Set logging level. By default will be set to 'WARN'. + // + // Log will appear on: + // * XCode: Debug Console + // * VsCode: Debug Console + // * Android Studio: Debug and Logcat Console + LogConfig logConfig = LogConfig(logType: LogType.DEBUG); + _audioQuery.setLogConfig(logConfig); + + // Check and request for permission. + checkAndRequestPermissions(); + } + + checkAndRequestPermissions({bool retry = false}) async { + // The param 'retryRequest' is false, by default. + _hasPermission = await _audioQuery.checkAndRequest( + retryRequest: retry, + ); + + // Only call update the UI if application has all required permissions. + _hasPermission ? setState(() {}) : null; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("OnAudioQueryExample"), + elevation: 2, + ), + body: Center( + child: !_hasPermission + ? noAccessToLibraryWidget() + : FutureBuilder>( + // Default values: + future: _audioQuery.querySongs( + sortType: null, + orderType: OrderType.ASC_OR_SMALLER, + uriType: UriType.EXTERNAL, + ignoreCase: true, + ), + builder: (context, item) { + // Display error, if any. + if (item.hasError) { + return Text(item.error.toString()); + } + + // Waiting content. + if (item.data == null) { + return const CircularProgressIndicator(); + } + + // 'Library' is empty. + if (item.data!.isEmpty) return const Text("Nothing found!"); + + // You can use [item.data!] direct or you can create a: + // List songs = item.data!; + return ListView.builder( + itemCount: item.data!.length, + itemBuilder: (context, index) { + return ListTile( + title: Text(item.data![index].title), + subtitle: Text(item.data![index].artist ?? "No Artist"), + trailing: const Icon(Icons.arrow_forward_rounded), + // This Widget will query/load image. + // You can use/create your own widget/method using [queryArtwork]. + leading: QueryArtworkWidget( + controller: _audioQuery, + id: item.data![index].id, + type: ArtworkType.AUDIO, + ), + ); + }, + ); + }, + ), + ), + ); + } + + Widget noAccessToLibraryWidget() { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.redAccent.withOpacity(0.5), + ), + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text("Application doesn't have access to the library"), + const SizedBox(height: 10), + ElevatedButton( + onPressed: () => checkAndRequestPermissions(retry: true), + child: const Text("Allow"), + ), + ], + ), + ); + } +} diff --git a/local_packages/on_audio_query-2.9.0/example/pubspec.lock b/local_packages/on_audio_query-2.9.0/example/pubspec.lock new file mode 100644 index 0000000..c6545dc --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/pubspec.lock @@ -0,0 +1,328 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.12.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.19.1" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.8" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.2" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.0.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: b543301ad291598523947dc534aaddc5aaad597b709d2426d3a0e0d44c5cb493 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.4" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + id3: + dependency: transitive + description: + name: id3 + sha256: "24176a6e08db6297c8450079e94569cd8387f913c817e5e3d862be7dc191e0b8" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.2" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + url: "https://pub.flutter-io.cn" + source: hosted + version: "10.0.8" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.16.0" + on_audio_query: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "2.9.0" + on_audio_query_android: + dependency: transitive + description: + name: on_audio_query_android + sha256: c0b946011ee7aa8ec76b21837f0cb0007120ac291202ed6c6fb4c498ba2ff6fe + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" + on_audio_query_ios: + dependency: transitive + description: + name: on_audio_query_ios + sha256: "9b3efa39a656fa3720980e3c6a1f55b7257d0032a45ffeb3f70eaa2c7f10f929" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" + on_audio_query_platform_interface: + dependency: transitive + description: + name: on_audio_query_platform_interface + sha256: c23e019a31bd0774828476e428fd33b0dd1d82c9d4791dba80429358fc65dcd3 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.7.0" + on_audio_query_web: + dependency: transitive + description: + name: on_audio_query_web + sha256: "990efa52d879e6caffa97f24b34acd9caa1ce2c4c4cb873fe5a899a9b1af02c7" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.6.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.9.1" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.8" + process: + dependency: transitive + description: + name: process + sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.10.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.1" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.4" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + url: "https://pub.flutter-io.cn" + source: hosted + version: "14.3.1" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.4" +sdks: + dart: ">=3.7.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/local_packages/on_audio_query-2.9.0/example/pubspec.yaml b/local_packages/on_audio_query-2.9.0/example/pubspec.yaml new file mode 100644 index 0000000..9576001 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/pubspec.yaml @@ -0,0 +1,73 @@ +name: on_audio_query_example +description: Demonstrates how to use the on_audio_query plugin. + +# The following line prevents the package from being accidentally published to +# pub.dev using `pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +environment: + sdk: '>=2.12.0 <3.0.0' + +dependencies: + flutter: + sdk: flutter + + on_audio_query: + # When depending on this package from a real application you should use: + # on_audio_query: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.1 + +dev_dependencies: + flutter_lints: ^1.0.4 + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - assets/ + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/local_packages/on_audio_query-2.9.0/example/web/favicon.png b/local_packages/on_audio_query-2.9.0/example/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/web/favicon.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/web/icons/Icon-192.png b/local_packages/on_audio_query-2.9.0/example/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/web/icons/Icon-192.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/web/icons/Icon-512.png b/local_packages/on_audio_query-2.9.0/example/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/local_packages/on_audio_query-2.9.0/example/web/icons/Icon-512.png differ diff --git a/local_packages/on_audio_query-2.9.0/example/web/index.html b/local_packages/on_audio_query-2.9.0/example/web/index.html new file mode 100644 index 0000000..0081e18 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/web/index.html @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + example + + + + + + + diff --git a/local_packages/on_audio_query-2.9.0/example/web/manifest.json b/local_packages/on_audio_query-2.9.0/example/web/manifest.json new file mode 100644 index 0000000..8c01291 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/example/web/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/local_packages/on_audio_query-2.9.0/lib/on_audio_query.dart b/local_packages/on_audio_query-2.9.0/lib/on_audio_query.dart new file mode 100644 index 0000000..e3b2f04 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/lib/on_audio_query.dart @@ -0,0 +1,32 @@ +/* +============= +Author: Lucas Josino +Github: https://github.com/LucJosin +Website: https://www.lucasjosino.com/ +============= +Plugin/Id: on_audio_query#0 +Homepage: https://github.com/LucJosin/on_audio_query +Pub: https://pub.dev/packages/on_audio_query +License: https://github.com/LucJosin/on_audio_query/blob/main/on_audio_query/LICENSE +Copyright: © 2021, Lucas Josino. All rights reserved. +============= +*/ + +library on_audio_query; + +// Dart +import 'dart:async'; + +// Flutter +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +// Platform Interface +import 'package:on_audio_query_platform_interface/on_audio_query_platform_interface.dart'; +export 'package:on_audio_query_platform_interface/on_audio_query_platform_interface.dart'; + +// Controllers +part 'src/on_audio_query.dart'; + +// Widgets +part 'widget/query_artwork_widget.dart'; diff --git a/local_packages/on_audio_query-2.9.0/lib/src/on_audio_query.dart b/local_packages/on_audio_query-2.9.0/lib/src/on_audio_query.dart new file mode 100644 index 0000000..4f47688 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/lib/src/on_audio_query.dart @@ -0,0 +1,667 @@ +/* +============= +Author: Lucas Josino +Github: https://github.com/LucJosin +Website: https://www.lucasjosino.com/ +============= +Plugin/Id: on_audio_query#0 +Homepage: https://github.com/LucJosin/on_audio_query +Pub: https://pub.dev/packages/on_audio_query +License: https://github.com/LucJosin/on_audio_query/blob/main/on_audio_query/LICENSE +Copyright: © 2021, Lucas Josino. All rights reserved. +============= +*/ + +part of on_audio_query; + +/// Interface and Main method for use on_audio_query +class OnAudioQuery { + /// The platform interface that drives this plugin + static OnAudioQueryPlatform get platform => OnAudioQueryPlatform.instance; + + dynamic _getArgs( + WithFiltersType withType, + ) { + switch (withType) { + case WithFiltersType.AUDIOS: + return AudiosArgs.TITLE; + case WithFiltersType.ALBUMS: + return AlbumsArgs.ALBUM; + case WithFiltersType.PLAYLISTS: + return PlaylistsArgs.PLAYLIST; + case WithFiltersType.ARTISTS: + return ArtistsArgs.ARTIST; + case WithFiltersType.GENRES: + return GenresArgs.GENRE; + } + } + + /// Simplified version of [permissionsStatus] and [permissionsRequest]. + /// + /// Will check and request, if necessary, all required permissions. + /// + /// **OBS: Will always return true on web platform.** + Future checkAndRequest({bool retryRequest = false}) async { + if (kIsWeb) return true; + + bool hasPermission = await platform.permissionsStatus(); + if (!hasPermission) { + hasPermission = await platform.permissionsRequest( + retryRequest: retryRequest, + ); + } + + return hasPermission; + } + + /// Used to set the logging behavior. + /// + /// Parameters: + /// + /// * [logType] is used to define the logging level. [LogType]. + /// * [detailedLog] is used to define if detailed log will be shown + /// (Disable by default to avoid spam). + /// + /// Important: + /// + /// * If [logType] is null, will be set to [WARN]. + /// * If [detailedLog] is null, will be set to [false]. + Future setLogConfig(LogConfig? logConfig) async { + return platform.setLogConfig(logConfig); + } + + /// Used to return Songs Info based in [SongModel]. + /// + /// Parameters: + /// + /// * [orderType] is used to define if order will be Ascending or Descending. + /// * [sortType] is used to define list sort. + /// * [uriType] is used to define if songs will be catch in [EXTERNAL] or [INTERNAL] storage. + /// * [ignoreCase] is used to define if sort will ignore the lowercase or not. + /// * [path] is used to define where the songs will be 'queried'. + /// + /// Important: + /// + /// * If [orderType] is null, will be set to [ASC_OR_SMALLER]. + /// * If [sortType] is null, will be set to [DEFAULT]. + /// * If [uriType] is null, will be set to [EXTERNAL]. + /// * If [ignoreCase] is null, will be set to [true]. + /// * If [path] is null, will be set to the default platform [path]. + /// + /// Platforms: + /// + /// | Android | IOS | Web | + /// |--------------|-----------------|-----------------| + /// | `✔️` | `✔️` | `✔️` |
+ /// + /// See more about [platforms support](https://github.com/LucJosin/on_audio_query/blob/main/PLATFORMS.md) + Future> querySongs({ + SongSortType? sortType, + OrderType? orderType, + UriType? uriType, + bool? ignoreCase, + String? path, + }) async { + return platform.querySongs( + sortType: sortType, + orderType: orderType, + uriType: uriType, + ignoreCase: ignoreCase, + path: path, + ); + } + + /// Used to return Albums Info based in [AlbumModel]. + /// + /// Parameters: + /// + /// * [orderType] is used to define if order will be Ascending or Descending. + /// * [sortType] is used to define list sort. + /// * [uriType] is used to define if songs will be catch in [EXTERNAL] or [INTERNAL] storage. + /// * [ignoreCase] is used to define if sort will ignore the lowercase or not. + /// + /// Important: + /// + /// * If [orderType] is null, will be set to [ASC_OR_SMALLER]. + /// * If [sortType] is null, will be set to [AlbumName]. + /// * If [uriType] is null, will be set to [EXTERNAL]. + /// * If [ignoreCase] is null, will be set to [true]. + /// + /// Platforms: + /// + /// | Android | IOS | Web | + /// |--------------|-----------------|-----------------| + /// | `✔️` | `✔️` | `✔️` |
+ /// + /// See more about [platforms support](https://github.com/LucJosin/on_audio_query/blob/main/PLATFORMS.md) + Future> queryAlbums({ + AlbumSortType? sortType, + OrderType? orderType, + UriType? uriType, + bool? ignoreCase, + }) async { + return platform.queryAlbums( + sortType: sortType, + orderType: orderType, + uriType: uriType, + ignoreCase: ignoreCase, + ); + } + + /// Used to return Artists Info based in [ArtistModel]. + /// + /// Parameters: + /// + /// * [orderType] is used to define if order will be Ascending or Descending. + /// * [sortType] is used to define list sort. + /// * [uriType] is used to define if songs will be catch in [EXTERNAL] or [INTERNAL] storage. + /// * [ignoreCase] is used to define if sort will ignore the lowercase or not. + /// + /// Important: + /// + /// * If [orderType] is null, will be set to [ASC_OR_SMALLER]. + /// * If [sortType] is null, will be set to [ArtistName]. + /// * If [uriType] is null, will be set to [EXTERNAL]. + /// * If [ignoreCase] is null, will be set to [true]. + /// + /// Platforms: + /// + /// | Android | IOS | Web | + /// |--------------|-----------------|-----------------| + /// | `✔️` | `✔️` | `✔️` |
+ /// + /// See more about [platforms support](https://github.com/LucJosin/on_audio_query/blob/main/PLATFORMS.md) + Future> queryArtists({ + ArtistSortType? sortType, + OrderType? orderType, + UriType? uriType, + bool? ignoreCase, + }) async { + return platform.queryArtists( + sortType: sortType, + orderType: orderType, + uriType: uriType, + ignoreCase: ignoreCase, + ); + } + + /// Used to return Playlists Info based in [PlaylistModel]. + /// + /// Parameters: + /// + /// * [orderType] is used to define if order will be Ascending or Descending. + /// * [sortType] is used to define list sort. + /// * [uriType] is used to define if songs will be catch in [EXTERNAL] or [INTERNAL] storage. + /// * [ignoreCase] is used to define if sort will ignore the lowercase or not. + /// + /// Important: + /// + /// * If [orderType] is null, will be set to [ASC_OR_SMALLER]. + /// * If [sortType] is null, will be set to [PlaylistName]. + /// * If [uriType] is null, will be set to [EXTERNAL]. + /// * If [ignoreCase] is null, will be set to [true]. + /// + /// Platforms: + /// + /// | Android | IOS | Web | + /// |--------------|-----------------|-----------------| + /// | `✔️` | `✔️` | `❌` |
+ /// + /// See more about [platforms support](https://github.com/LucJosin/on_audio_query/blob/main/PLATFORMS.md) + Future> queryPlaylists({ + PlaylistSortType? sortType, + OrderType? orderType, + UriType? uriType, + bool? ignoreCase, + }) async { + return platform.queryPlaylists( + sortType: sortType, + orderType: orderType, + uriType: uriType, + ignoreCase: ignoreCase, + ); + } + + /// Used to return Genres Info based in [GenreModel]. + /// + /// Parameters: + /// + /// * [orderType] is used to define if order will be Ascending or Descending. + /// * [sortType] is used to define list sort. + /// * [uriType] is used to define if songs will be catch in [EXTERNAL] or [INTERNAL] storage. + /// * [ignoreCase] is used to define if sort will ignore the lowercase or not. + /// + /// Important: + /// + /// * If [orderType] is null, will be set to [ASC_OR_SMALLER]. + /// * If [sortType] is null, will be set to [GenreName]. + /// * If [uriType] is null, will be set to [EXTERNAL]. + /// * If [ignoreCase] is null, will be set to [true]. + /// + /// Platforms: + /// + /// | Android | IOS | Web | + /// |--------------|-----------------|-----------------| + /// | `✔️` | `✔️` | `✔️` |
+ /// + /// See more about [platforms support](https://github.com/LucJosin/on_audio_query/blob/main/PLATFORMS.md) + Future> queryGenres({ + GenreSortType? sortType, + OrderType? orderType, + UriType? uriType, + bool? ignoreCase, + }) async { + return platform.queryGenres( + sortType: sortType, + orderType: orderType, + uriType: uriType, + ignoreCase: ignoreCase, + ); + } + + /// Used to return Songs/Audios Info from a specific queryType based in [SongModel]. + /// + /// Parameters: + /// + /// * [type] is used to define where audio will be query. + /// * [where] is used to query audios from specific method. + /// * [orderType] is used to define if order will be Ascending or Descending. + /// * [sortType] is used to define list sort. + /// * [ignoreCase] is used to define if sort will ignore the lowercase or not. + /// + /// Platforms: + /// + /// | Android | IOS | Web | + /// |--------------|-----------------|-----------------| + /// | `✔️` | `✔️` | `✔️` |
+ /// + /// See more about [platforms support](https://github.com/LucJosin/on_audio_query/blob/main/PLATFORMS.md) + Future> queryAudiosFrom( + AudiosFromType type, + Object where, { + SongSortType? sortType, + OrderType? orderType, + bool? ignoreCase, + }) async { + return platform.queryAudiosFrom( + type, + where, + sortType: sortType, + orderType: orderType, + ignoreCase: ignoreCase, + ); + } + + /// Used to return Songs Info based in Something. Works like a "Search". + /// + /// Parameters: + /// + /// * [withType] The type of search based in [WithFiltersType]. + /// * [args] is used to define what you're looking for. + /// * [argsVal] The "key". + /// + /// Before you use: + /// + /// * [queryWithFilters] implements all types based in [WithFiltersType], this method return always a [dynamic] List. + /// * After call this method you will need to specify the [Model]. See [Example1]. + /// + /// Example1: + /// + /// ```dart + /// //Using [FutureBuilder] + /// //I changed [>] to [-] + /// builder: (context, AsyncSnapshot-List-dynamic-- item) { + /// List-SongModel- = item.data!.map((e) => SongModel(e)).toList(); //Ex1 + /// List-ArtistModel- = item.data!.map((e) => ArtistModel(e)).toList(); //Ex2 + /// ...} + /// ``` + /// + /// Important: + /// + /// * If [args] is null, will be set to [Title] or [Name]. + /// * If Android >= Q/10 [artwork] will return null, in this case, it's necessary use [queryArtwork]. + /// + /// Platforms: + /// + /// | Android | IOS | Web | + /// |--------------|-----------------|-----------------| + /// | `✔️` | `✔️` | `✔️` |
+ /// + /// See more about [platforms support](https://github.com/LucJosin/on_audio_query/blob/main/PLATFORMS.md) + Future> queryWithFilters( + String argsVal, + WithFiltersType withType, { + dynamic args, + }) async { + return platform.queryWithFilters( + argsVal, + withType, + args ?? _getArgs(withType), + ); + } + + /// Used to return Songs Artwork. + /// + /// Parameters: + /// + /// * [type] is used to define if artwork is from audios or albums. + /// * [format] is used to define type [PNG] or [JPEG]. + /// * [size] is used to define image quality. + /// + /// Usage and Performance: + /// + /// * Using [PNG] will return a better image quality but a slow performance. + /// * Using [Size] greater than 200 probably won't make difference in quality but will cause a slow performance. + /// + /// Important: + /// + /// * This method is only necessary for API >= 29 [Android Q/10]. + /// * If [queryArtwork] is called in Android below Q/10, will return null. + /// * If [format] is null, will be set to [JPEG] for better performance. + /// * If [size] is null, will be set to [200] for better performance + /// * We need this method separated from [querySongs/queryAudios] because + /// return [Uint8List] and using inside query causes a slow performance. + /// + /// Platforms: + /// + /// | Android | IOS | Web | + /// |--------------|-----------------|-----------------| + /// | `✔️` | `✔️` | `✔️` |
+ /// + /// See more about [platforms support](https://github.com/LucJosin/on_audio_query/blob/main/PLATFORMS.md) + Future queryArtwork( + int id, + ArtworkType type, { + ArtworkFormat? format, + int? size, + int? quality, + }) async { + return platform.queryArtwork( + id, + type, + format: format, + size: size, + quality: quality, + ); + } + + /// Used to return Songs Info from a specific [Folder] based in [SongModel]. + /// + /// Parameters: + /// + /// * [path] is used to define where the plugin will search for audio. + /// * [orderType] is used to define if order will be Ascending or Descending. + /// * [sortType] is used to define list sort. + /// * [uriType] is used to define if songs will be catch in [EXTERNAL] or [INTERNAL] storage. + /// + /// Important: + /// + /// * If [orderType] is null, will be set to [ASC_OR_SMALLER]. + /// * If [sortType] is null, will be set to [title]. + /// * If [uriType] is null, will be set to [EXTERNAL]. + /// * If Android >= Q/10 [artwork] will return null, in this case, it's necessary use [queryArtwork]. + /// + /// Platforms: + /// + /// | Android | IOS | Web | + /// |--------------|-----------------|-----------------| + /// | `✔️` | `❌` | `❌` |
+ /// + /// See more about [platforms support](https://github.com/LucJosin/on_audio_query/blob/main/PLATFORMS.md) + Future> queryFromFolder( + String path, { + SongSortType? sortType, + OrderType? orderType, + UriType? uriType, + }) async { + return platform.queryFromFolder( + path, + sortType: sortType, + orderType: orderType, + uriType: uriType, + ); + } + + /// Used to return Songs path. + /// + /// Important: + /// + /// * Duplicate path will be ignored. + /// + /// Platforms: + /// + /// | Android | IOS | Web | + /// |--------------|-----------------|-----------------| + /// | `✔️` | `❌` | `❌` |
+ /// + /// See more about [platforms support](https://github.com/LucJosin/on_audio_query/blob/main/PLATFORMS.md) + Future> queryAllPath() async { + return platform.queryAllPath(); + } + + //Playlist methods + + /// Used to create a Playlist + /// + /// Parameters: + /// + /// * [name] the playlist name. + /// * [author] the playlist author. (IOS only) + /// * [desc] the playlist description. (IOS only) + /// + /// Important: + /// + /// * This method create a playlist using [External Storage], all apps will be able to see this playlist + /// + /// Platforms: + /// + /// | Android | IOS | Web | + /// |--------------|-----------------|-----------------| + /// | `✔️` | `✔️` | `❌` |
+ /// + /// See more about [platforms support](https://github.com/LucJosin/on_audio_query/blob/main/PLATFORMS.md) + Future createPlaylist( + String name, { + String? author, + String? desc, + }) async { + return platform.createPlaylist( + name, + author: author, + desc: desc, + ); + } + + /// Used to remove/delete a Playlist + /// + /// Parameters: + /// + /// * [playlistId] is used to check if Playlist exist. + /// + /// Platforms: + /// + /// | Android | IOS | Web | + /// |--------------|-----------------|-----------------| + /// | `✔️` | `❌` | `❌` |
+ /// + /// See more about [platforms support](https://github.com/LucJosin/on_audio_query/blob/main/PLATFORMS.md) + Future removePlaylist(int playlistId) async { + return platform.removePlaylist(playlistId); + } + + /// Used to add a specific song/audio to a specific Playlist + /// + /// Parameters: + /// + /// * [playlistId] is used to check if Playlist exist. + /// * [audioId] is used to add specific audio to Playlist. + /// + /// Platforms: + /// + /// | Android | IOS | Web | + /// |--------------|-----------------|-----------------| + /// | `✔️` | `✔️` | `❌` |
+ /// + /// See more about [platforms support](https://github.com/LucJosin/on_audio_query/blob/main/PLATFORMS.md) + Future addToPlaylist(int playlistId, int audioId) async { + return platform.addToPlaylist(playlistId, audioId); + } + + /// Used to remove a specific song/audio from a specific Playlist + /// + /// Parameters: + /// + /// * [playlistId] is used to check if Playlist exist. + /// * [audioId] is used to remove specific audio from Playlist. + /// + /// Platforms: + /// + /// | Android | IOS | Web | + /// |--------------|-----------------|-----------------| + /// | `✔️` | `❌` | `❌` |
+ /// + /// See more about [platforms support](https://github.com/LucJosin/on_audio_query/blob/main/PLATFORMS.md) + Future removeFromPlaylist(int playlistId, int audioId) async { + return platform.removeFromPlaylist(playlistId, audioId); + } + + /// Used to change song/audio position from a specific Playlist + /// + /// Parameters: + /// + /// * [playlistId] is used to check if Playlist exist. + /// * [from] is the old position from a audio/song. + /// * [to] is the new position from a audio/song. + /// + /// Platforms: + /// + /// | Android | IOS | Web | + /// |--------------|-----------------|-----------------| + /// | `✔️` | `❌` | `❌` |
+ /// + /// See more about [platforms support](https://github.com/LucJosin/on_audio_query/blob/main/PLATFORMS.md) + Future moveItemTo(int playlistId, int from, int to) async { + return platform.moveItemTo(playlistId, from, to); + } + + /// Used to rename a specific Playlist + /// + /// Parameters: + /// + /// * [playlistId] is used to check if Playlist exist. + /// * [newName] is used to add a new name to a Playlist. + /// + /// Platforms: + /// + /// | Android | IOS | Web | + /// |--------------|-----------------|-----------------| + /// | `✔️` | `❌` | `❌` |
+ /// + /// See more about [platforms support](https://github.com/LucJosin/on_audio_query/blob/main/PLATFORMS.md) + Future renamePlaylist(int playlistId, String newName) async { + return renamePlaylist(playlistId, newName); + } + + // Permissions methods + + /// Used to check Android permissions status + /// + /// Important: + /// + /// * This method will always return a bool. + /// * If return true `[READ]` and `[WRITE]` permissions is Granted, + /// else `[READ]` and `[WRITE]` is Denied. + /// + /// Platforms: + /// + /// | Android | IOS | Web | + /// |--------------|-----------------|-----------------| + /// | `✔️` | `✔️` | `❌` |
+ /// + /// See more about [platforms support](https://github.com/LucJosin/on_audio_query/blob/main/PLATFORMS.md) + Future permissionsStatus() async { + return platform.permissionsStatus(); + } + + /// Used to request Android permissions. + /// + /// Important: + /// + /// * This method will always return a bool. + /// * If return true `[READ]` and `[WRITE]` permissions is Granted, + /// else `[READ]` and `[WRITE]` is Denied. + /// + /// Platforms: + /// + /// | Android | IOS | Web | + /// |--------------|-----------------|-----------------| + /// | `✔️` | `✔️` | `❌` |
+ /// + /// See more about [platforms support](https://github.com/LucJosin/on_audio_query/blob/main/PLATFORMS.md) + Future permissionsRequest({bool retryRequest = false}) async { + return platform.permissionsRequest(retryRequest: retryRequest); + } + + // Device Information + + /// Used to return Device Info + /// + /// Will return: + /// + /// * Device SDK. + /// * Device Release. + /// * Device Code. + /// * Device Type. + /// + /// Platforms: + /// + /// | Android | IOS | Web | + /// |--------------|-----------------|-----------------| + /// | `✔️` | `✔️` | `✔️` |
+ /// + /// See more about [platforms support](https://github.com/LucJosin/on_audio_query/blob/main/PLATFORMS.md) + Future queryDeviceInfo() async { + return platform.queryDeviceInfo(); + } + + // Others + + /// Used to scan the given [path] + /// + /// Will return: + /// + /// * A boolean indicating if the path was scanned or not. + /// + /// Usage: + /// + /// * When using the [Android] platform. After deleting a media using the [dart:io], + /// call this method to update the media. If the media was successfully and the path + /// not scanned. Will keep showing on [querySongs]. + /// + /// Example: + /// + /// ```dart + /// OnAudioQuery _audioQuery = OnAudioQuery(); + /// File file = File('path'); + /// + /// try { + /// if (file.existsSync()) { + /// file.deleteSync(); + /// _audioQuery.scanMedia(file.path); // Scan the media 'path' + /// } + /// } catch (e) { + /// debugPrint('$e'); + /// } + /// ``` + /// + /// Platforms: + /// + /// | Android | IOS | Web | + /// |--------------|-----------------|-----------------| + /// | `✔️` | `❌` | `❌` |
+ /// + /// See more about [platforms support](https://github.com/LucJosin/on_audio_query/blob/main/on_audio_query/PLATFORMS.md) + Future scanMedia(String path) async { + return await platform.scanMedia(path); + } +} diff --git a/local_packages/on_audio_query-2.9.0/lib/widget/query_artwork_widget.dart b/local_packages/on_audio_query-2.9.0/lib/widget/query_artwork_widget.dart new file mode 100644 index 0000000..d199d11 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/lib/widget/query_artwork_widget.dart @@ -0,0 +1,334 @@ +/* +============= +Author: Lucas Josino +Github: https://github.com/LucJosin +Website: https://www.lucasjosino.com/ +============= +Plugin/Id: on_audio_query#0 +Homepage: https://github.com/LucJosin/on_audio_query +Pub: https://pub.dev/packages/on_audio_query +License: https://github.com/LucJosin/on_audio_query/blob/main/on_audio_query/LICENSE +Copyright: © 2021, Lucas Josino. All rights reserved. +============= +*/ + +part of on_audio_query; + +/// Widget that will help to "query" artwork for song/album. +/// +/// A simple example on how you can use the [queryArtwork]. +/// +/// Important: +/// +/// * If [controller] is null, will be create a new instance. +/// * Log set with [setLogConfig] will only work if [controller] is not null. +/// +/// See more: [QueryArtworkWidget](https://pub.dev/documentation/on_audio_query/latest/on_audio_query/QueryArtworkWidget-class.html) +class QueryArtworkWidget extends StatelessWidget { + /// Used to find and get image. + /// + /// All Audio/Song has a unique [id]. + final int id; + + /// Used to call the platform specific method. + /// + /// Important: + /// + /// * If [controller] is null, will be create a new instance. + /// * Log set with [setLogConfig] will only work if [controller] is not null. + final OnAudioQuery? controller; + + /// Used to define artwork [type]. + /// + /// Opts: [AUDIO] and [ALBUM]. + final ArtworkType type; + + /// Used to define artwork [format]. + /// + /// Opts: [JPEG] and [PNG]. + /// + /// Important: + /// + /// * If [format] is not defined, will be set to [JPEG]. + final ArtworkFormat format; + + /// Used to define artwork [size]. + /// + /// Important: + /// + /// * If [size] is not defined, will be set to [200]. + /// * This value have a directly influence to image quality. + final int size; + + /// Used to define artwork [quality]. + /// + /// Important: + /// + /// * If [quality] is not defined, will be set to [100]. + final int quality; + + /// Used to define the artwork [border radius]. + /// + /// Important: + /// + /// * If [artworkBorder] is null, will be set to [50]. + final BorderRadius? artworkBorder; + + /// Used to define the artwork [quality]. + /// + /// Important: + /// + /// * If [artworkQuality] is not defined, will be set to [low]. + /// * This value doesn't have a directly influence to image quality. + final FilterQuality artworkQuality; + + /// Used to define artwork [width]. + /// + /// Important: + /// + /// * If [artworkWidth] is not defined, will be set to [50]. + final double artworkWidth; + + /// Used to define artwork [height]. + /// + /// Important: + /// + /// * If [artworkHeight] is not defined, will be set to [50]. + final double artworkHeight; + + /// Used to define artwork [fit]. + /// + /// Important: + /// + /// * If [artworkFit] is not defined, will be set to [cover]. + final BoxFit artworkFit; + + /// Used to define artwork [clip]. + /// + /// Important: + /// + /// * If [artworkClipBehavior] is not defined, will be set to [antiAlias]. + final Clip artworkClipBehavior; + + /// Used to define artwork [scale]. + /// + /// Important: + /// + /// * If [artworkScale] is not defined, will be set to [1.0]. + final double artworkScale; + + /// Used to define if artwork should [repeat]. + /// + /// Important: + /// + /// * If [artworkRepeat] is not defined, will be set to [false]. + final ImageRepeat artworkRepeat; + + /// Used to define artwork [color]. + /// + /// Important: + /// + /// * [artworkColor] default value is [null]. + final Color? artworkColor; + + /// Used to define artwork [blend]. + /// + /// Important: + /// + /// * [artworkBlendMode] default value is [null]. + final BlendMode? artworkBlendMode; + + /// Used to define if artwork should [keep] old art even when [Flutter State] change. + /// + /// ## Flutter Docs: + /// + /// ### Why is the default value of [gaplessPlayback] false? + /// + /// Having the default value of [gaplessPlayback] be false helps prevent + /// situations where stale or misleading information might be presented. + /// Consider the following case: + /// + /// We have constructed a 'Person' widget that displays an avatar [Image] of + /// the currently loaded person along with their name. We could request for a + /// new person to be loaded into the widget at any time. Suppose we have a + /// person currently loaded and the widget loads a new person. What happens + /// if the [Image] fails to load? + /// + /// * Option A ([gaplessPlayback] = false): The new person's name is coupled + /// with a blank image. + /// + /// * Option B ([gaplessPlayback] = true): The widget displays the avatar of + /// the previous person and the name of the newly loaded person. + /// + /// Important: + /// + /// * If [keepOldArtwork] is not defined, will be set to [false]. + final bool keepOldArtwork; + + /// Used to define a Widget when audio/song don't return any artwork. + /// + /// Important: + /// + /// * If [nullArtworkWidget] is null, will be set to [image_not_supported] icon. + final Widget? nullArtworkWidget; + + /// A builder function that is called if an error occurs during image loading. + /// + /// + /// If this builder is not provided, any exceptions will be reported to + /// [FlutterError.onError]. If it is provided, the caller should either handle + /// the exception by providing a replacement widget, or rethrow the exception. + /// + /// Important: + /// + /// * If [errorBuilder] is null, will set the [nullArtworkWidget] and if is null + /// will be used a icon(image_not_supported). + /// + /// The following sample uses [errorBuilder] to show a '😢' in place of the + /// image that fails to load, and prints the error to the console. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return DecoratedBox( + /// decoration: BoxDecoration( + /// color: Colors.white, + /// border: Border.all(), + /// borderRadius: BorderRadius.circular(20), + /// ), + /// child: Image.network( + /// 'https://example.does.not.exist/image.jpg', + /// errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) { + /// // Appropriate logging or analytics, e.g. + /// // myAnalytics.recordError( + /// // 'An error occurred loading "https://example.does.not.exist/image.jpg"', + /// // exception, + /// // stackTrace, + /// // ); + /// return const Text('😢'); + /// }, + /// ), + /// ); + /// } + /// ``` + /// + /// `From flutter documentation` + /// + final Widget Function(BuildContext, Object, StackTrace?)? errorBuilder; + + /// A builder function responsible for creating the widget that represents + /// this image. + /// + /// The following sample demonstrates how to use this builder to implement an + /// image that fades in once it's been loaded. + /// + /// This sample contains a limited subset of the functionality that the + /// [FadeInImage] widget provides out of the box. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return DecoratedBox( + /// decoration: BoxDecoration( + /// color: Colors.white, + /// border: Border.all(), + /// borderRadius: BorderRadius.circular(20), + /// ), + /// child: Image.network( + /// 'https://flutter.github.io/assets-for-api-docs/assets/widgets/puffin.jpg', + /// frameBuilder: (BuildContext context, Widget child, int? frame, bool wasSynchronouslyLoaded) { + /// if (wasSynchronouslyLoaded) { + /// return child; + /// } + /// return AnimatedOpacity( + /// child: child, + /// opacity: frame == null ? 0 : 1, + /// duration: const Duration(seconds: 1), + /// curve: Curves.easeOut, + /// ); + /// }, + /// ), + /// ); + /// } + /// ``` + /// + /// `From flutter documentation` + /// + final Widget Function(BuildContext, Widget, int?, bool)? frameBuilder; + + /// Widget that will help to "query" artwork for song/album. + /// + /// A simple example on how you can use the [queryArtwork]. + /// + /// See more: [QueryArtworkWidget](https://pub.dev/documentation/on_audio_query/latest/on_audio_query/QueryArtworkWidget-class.html) + const QueryArtworkWidget({ + Key? key, + required this.id, + required this.type, + this.quality = 50, + this.controller, + this.format = ArtworkFormat.JPEG, + this.size = 200, + this.artworkQuality = FilterQuality.low, + this.artworkBorder, + this.artworkWidth = 50, + this.artworkHeight = 50, + this.artworkFit = BoxFit.cover, + this.artworkClipBehavior = Clip.antiAlias, + this.artworkScale = 1.0, + this.artworkRepeat = ImageRepeat.noRepeat, + this.artworkColor, + this.artworkBlendMode, + this.keepOldArtwork = false, + this.nullArtworkWidget, + this.errorBuilder, + this.frameBuilder, + }) : assert(quality <= 100), + super(key: key); + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: (controller ?? OnAudioQuery()).queryArtwork( + id, + type, + format: format, + size: size, + quality: quality, + ), + builder: (context, item) { + if (item.data != null && item.data!.isNotEmpty) { + return ClipRRect( + borderRadius: artworkBorder ?? BorderRadius.circular(50), + clipBehavior: artworkClipBehavior, + child: Image.memory( + item.data!, + gaplessPlayback: keepOldArtwork, + repeat: artworkRepeat, + scale: artworkScale, + width: artworkWidth, + height: artworkHeight, + fit: artworkFit, + color: artworkColor, + colorBlendMode: artworkBlendMode, + filterQuality: artworkQuality, + frameBuilder: frameBuilder, + errorBuilder: errorBuilder ?? + (context, exception, stackTrace) { + return nullArtworkWidget ?? + const Icon( + Icons.image_not_supported, + size: 50, + ); + }, + ), + ); + } + return nullArtworkWidget ?? + const Icon( + Icons.image_not_supported, + size: 50, + ); + }, + ); + } +} diff --git a/local_packages/on_audio_query-2.9.0/pubspec.lock b/local_packages/on_audio_query-2.9.0/pubspec.lock new file mode 100644 index 0000000..a0cb754 --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/pubspec.lock @@ -0,0 +1,258 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.12.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.19.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.2" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: b543301ad291598523947dc534aaddc5aaad597b709d2426d3a0e0d44c5cb493 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.4" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + id3: + dependency: transitive + description: + name: id3 + sha256: "24176a6e08db6297c8450079e94569cd8387f913c817e5e3d862be7dc191e0b8" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + url: "https://pub.flutter-io.cn" + source: hosted + version: "10.0.8" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.16.0" + on_audio_query_android: + dependency: "direct main" + description: + name: on_audio_query_android + sha256: c0b946011ee7aa8ec76b21837f0cb0007120ac291202ed6c6fb4c498ba2ff6fe + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" + on_audio_query_ios: + dependency: "direct main" + description: + name: on_audio_query_ios + sha256: "9b3efa39a656fa3720980e3c6a1f55b7257d0032a45ffeb3f70eaa2c7f10f929" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" + on_audio_query_platform_interface: + dependency: "direct main" + description: + name: on_audio_query_platform_interface + sha256: c23e019a31bd0774828476e428fd33b0dd1d82c9d4791dba80429358fc65dcd3 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.7.0" + on_audio_query_web: + dependency: "direct main" + description: + name: on_audio_query_web + sha256: "990efa52d879e6caffa97f24b34acd9caa1ce2c4c4cb873fe5a899a9b1af02c7" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.6.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.9.1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.8" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.10.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.4" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + url: "https://pub.flutter-io.cn" + source: hosted + version: "14.3.1" +sdks: + dart: ">=3.7.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/local_packages/on_audio_query-2.9.0/pubspec.yaml b/local_packages/on_audio_query-2.9.0/pubspec.yaml new file mode 100644 index 0000000..9c3feac --- /dev/null +++ b/local_packages/on_audio_query-2.9.0/pubspec.yaml @@ -0,0 +1,52 @@ +# ======== +# author: Lucas Josino +# github: https://github.com/LucJosin +# website: https://www.lucasjosino.com/ +# ======== +name: on_audio_query +description: Flutter Plugin used to query audios/songs infos [title, artist, album, etc..] from device storage. +version: 2.9.0 +homepage: https://github.com/LucJosin/on_audio_query/tree/main/packages/on_audio_query +issue_tracker: https://github.com/LucJosin/on_audio_query/issues +# pub.dev: https://pub.dev/packages/on_audio_query +topics: + - audio + - song + - audioquery + - on-audio-query + - storage + - mediastore + - mpmediaquery + +environment: + sdk: ">=2.17.0 <4.0.0" + flutter: ">=1.20.0" + +dependencies: + # on_audio_query + on_audio_query_platform_interface: ^1.7.0 + on_audio_query_web: ^1.6.0 + on_audio_query_ios: ^1.1.0 + on_audio_query_android: + path: ../on_audio_query_android-1.1.0 + + # Flutter + flutter: + sdk: flutter + +dev_dependencies: + # Flutter + flutter_lints: ^1.0.4 + flutter_test: + sdk: flutter + +# The following section is specific to Flutter. +flutter: + # Material Design + uses-material-design: true + plugin: + platforms: + android: + default_package: on_audio_query_android + ios: + default_package: on_audio_query_ios diff --git a/local_packages/on_audio_query_android-1.1.0/CHANGELOG.md b/local_packages/on_audio_query_android-1.1.0/CHANGELOG.md new file mode 100644 index 0000000..106912b --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/CHANGELOG.md @@ -0,0 +1,15 @@ +## 1.1.0 + +- See more [on_audio_query - CHANGELOG](https://github.com/LucJosin/on_audio_query/blob/main/on_audio_query/CHANGELOG.md). + +## 1.0.1 + +- See more [on_audio_query - CHANGELOG](https://github.com/LucJosin/on_audio_query/blob/main/on_audio_query/CHANGELOG.md). + +## 1.0.0 + +- See more [on_audio_query - CHANGELOG](https://github.com/LucJosin/on_audio_query/blob/main/on_audio_query/CHANGELOG.md). + +## 1.0.0-prerelease.0 - [06.20.2022] + +- See more [on_audio_query - CHANGELOG](https://github.com/LucJosin/on_audio_query/blob/main/on_audio_query/CHANGELOG.md). diff --git a/local_packages/on_audio_query_android-1.1.0/LICENSE b/local_packages/on_audio_query_android-1.1.0/LICENSE new file mode 100644 index 0000000..29b7826 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2022, Lucas Josino +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/local_packages/on_audio_query_android-1.1.0/README.md b/local_packages/on_audio_query_android-1.1.0/README.md new file mode 100644 index 0000000..4dd5e72 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/README.md @@ -0,0 +1,11 @@ +# on_audio_query_android + +The Android implementation of [`on_audio_query`][1]. + +## Usage + +This package is [endorsed][2], which means you can simply use `on_audio_query` +normally. This package will be automatically included in your app when you do. + +[1]: https://pub.dev/packages/on_audio_query +[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin diff --git a/local_packages/on_audio_query_android-1.1.0/android/build.gradle b/local_packages/on_audio_query_android-1.1.0/android/build.gradle new file mode 100644 index 0000000..d61c1a3 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/build.gradle @@ -0,0 +1,51 @@ +group 'com.lucasjosino.on_audio_query' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '2.1.0' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.3' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 36 + namespace "com.lucasjosino.on_audio_query" + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + defaultConfig { + minSdkVersion 16 + } +} + +dependencies { + def lifecycle_version = "2.3.1" + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4' + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" +} diff --git a/local_packages/on_audio_query_android-1.1.0/android/gradle.properties b/local_packages/on_audio_query_android-1.1.0/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/local_packages/on_audio_query_android-1.1.0/android/gradle/wrapper/gradle-wrapper.properties b/local_packages/on_audio_query_android-1.1.0/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..afabedb --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Sep 27 10:16:16 BRT 2021 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/local_packages/on_audio_query_android-1.1.0/android/settings.gradle b/local_packages/on_audio_query_android-1.1.0/android/settings.gradle new file mode 100644 index 0000000..1cff923 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'on_audio_query' diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/AndroidManifest.xml b/local_packages/on_audio_query_android-1.1.0/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..bab7186 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/OnAudioQueryPlugin.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/OnAudioQueryPlugin.kt new file mode 100644 index 0000000..c8eed40 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/OnAudioQueryPlugin.kt @@ -0,0 +1,188 @@ +/* +============= +Author: Lucas Josino +Github: https://github.com/LucJosin +Website: https://www.lucasjosino.com/ +============= +Plugin/Id: on_audio_query#0 +Homepage: https://github.com/LucJosin/on_audio_query +Pub: https://pub.dev/packages/on_audio_query +License: https://github.com/LucJosin/on_audio_query/blob/main/on_audio_query/LICENSE +Copyright: © 2021, Lucas Josino. All rights reserved. +============= +*/ + +package com.lucasjosino.on_audio_query + +import android.media.MediaScannerConnection +import android.os.Build +import com.lucasjosino.on_audio_query.consts.Method +import com.lucasjosino.on_audio_query.controllers.MethodController +import com.lucasjosino.on_audio_query.controllers.PermissionController +import io.flutter.Log +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result + +class OnAudioQueryPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { + + init { + // Set default logging level + Log.setLogLevel(Log.WARN) + } + + companion object { + // Get the current class name. + private const val TAG: String = "OnAudioQueryPlugin" + + // Method channel name. + private const val CHANNEL_NAME = "com.lucasjosino.on_audio_query" + } + + private var permissionController = PermissionController() + private var methodController = MethodController() + + private var binding: ActivityPluginBinding? = null + + private lateinit var channel: MethodChannel + + // Dart <-> Kotlin communication + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + Log.i(TAG, "Attached to engine") + + // Setup the method channel communication. + channel = MethodChannel(flutterPluginBinding.binaryMessenger, CHANNEL_NAME) + channel.setMethodCallHandler(this) + } + + // Methods will always follow the same route: + // Receive method -> check permission -> controller -> do what's needed -> return to dart + override fun onMethodCall(call: MethodCall, result: Result) { + Log.d(TAG, "Started method call (${call.method})") + + // Init the plugin provider with current 'call' and 'result'. + PluginProvider.setCurrentMethod(call, result) + + // If user deny permission request a pop up will immediately show up + // If [retryRequest] is null, the message will only show when call method again + val retryRequest = call.argument("retryRequest") ?: false + permissionController.retryRequest = retryRequest + + Log.i(TAG, "Method call: ${call.method}") + when (call.method) { + // Permissions + Method.PERMISSION_STATUS -> { + val hasPermission = permissionController.permissionStatus() + result.success(hasPermission) + } + Method.PERMISSION_REQUEST -> { + permissionController.requestPermission() + } + + // Device information + Method.QUERY_DEVICE_INFO -> { + result.success( + hashMapOf( + "device_model" to Build.MODEL, + "device_sys_version" to Build.VERSION.SDK_INT, + "device_sys_type" to "Android" + ) + ) + } + + // This method will scan the given path to update the 'state'. + // When deleting a file using 'dart:io', call this method to update the file 'state'. + Method.SCAN -> { + val sPath: String? = call.argument("path") + val context = PluginProvider.context() + + // Check if the given file is null or empty. + if (sPath == null || sPath.isEmpty()) { + Log.w(TAG, "Method 'scan' was called with null or empty 'path'") + result.success(false) + } + + // Scan and return + MediaScannerConnection.scanFile(context, arrayOf(sPath), null) { _, _ -> + Log.d(TAG, "Scanned file: $sPath") + result.success(true) + } + } + + // Logging + Method.SET_LOG_CONFIG -> { + // Log level + Log.setLogLevel(call.argument("level")!!) + + // Define if 'warn' level will show more detailed logging. + PluginProvider.showDetailedLog = call.argument("showDetailedLog")!! + + result.success(true) + } + + // All others methods + else -> { + Log.d(TAG, "Checking permissions...") + + val hasPermission = permissionController.permissionStatus() + Log.d(TAG, "Application has permissions: $hasPermission") + + if (!hasPermission) { + Log.w(TAG, "The application doesn't have access to the library") + result.error( + "MissingPermissions", + "Application doesn't have access to the library", + "Call the [permissionsRequest] method or install a external plugin to handle the app permission." + ) + } + + methodController.find() + } + } + + Log.d(TAG, "Ended method call (${call.method})\n ") + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + Log.i(TAG, "Detached from engine") + channel.setMethodCallHandler(null) + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + Log.i(TAG, "Attached to activity") + + // Init plugin provider with 'activity' and 'context'. + PluginProvider.set(binding.activity) + + // Add to controller the permission to listen to the request result. + this.binding = binding + binding.addRequestPermissionsResultListener(permissionController) + } + + override fun onDetachedFromActivityForConfigChanges() { + Log.i(TAG, "Detached from engine (config changes)") + onDetachedFromActivity() + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + Log.i(TAG, "Reattached to activity (config changes)") + onAttachedToActivity(binding) + } + + // Detach all parameters. + override fun onDetachedFromActivity() { + Log.i(TAG, "Detached from activity") + + // Remove the permission listener + if (binding != null) { + binding!!.removeRequestPermissionsResultListener(permissionController) + } + + this.binding = null + Log.i(TAG, "Removed all declared methods") + } +} diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/PluginProvider.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/PluginProvider.kt new file mode 100644 index 0000000..3d5e8ad --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/PluginProvider.kt @@ -0,0 +1,97 @@ +package com.lucasjosino.on_audio_query + +import android.app.Activity +import android.content.Context +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import java.lang.ref.WeakReference + +/** + * A singleton used to define all variables/methods that will be used on all plugin. + * + * The singleton will provider the ability to 'request' required variables/methods on any moment. + * + * All variables/methods should be defined after plugin initialization (activity/context) and + * dart request (call/result). + */ +object PluginProvider { + private const val ERROR_MESSAGE = + "Tried to get one of the methods but the 'PluginProvider' has not initialized" + + /** + * Define if 'warn' level will show more detailed logging. + * + * Will be used when a query produce some error. + */ + var showDetailedLog: Boolean = false + + private lateinit var context: WeakReference + + private lateinit var activity: WeakReference + + private lateinit var call: WeakReference + + private lateinit var result: WeakReference + + /** + * Used to define the current [Activity] and [Context]. + * + * Should be defined once. + */ + fun set(activity: Activity) { + this.context = WeakReference(activity.applicationContext) + this.activity = WeakReference(activity) + } + + /** + * Used to define the current dart request. + * + * Should be defined/redefined on every [MethodChannel.MethodCallHandler.onMethodCall] request. + */ + fun setCurrentMethod(call: MethodCall, result: MethodChannel.Result) { + this.call = WeakReference(call) + this.result = WeakReference(result) + } + + /** + * The current plugin 'context'. Defined once. + * + * @throws UninitializedPluginProviderException + * @return [Context] + */ + fun context(): Context { + return this.context.get() ?: throw UninitializedPluginProviderException(ERROR_MESSAGE) + } + + /** + * The current plugin 'activity'. Defined once. + * + * @throws UninitializedPluginProviderException + * @return [Activity] + */ + fun activity(): Activity { + return this.activity.get() ?: throw UninitializedPluginProviderException(ERROR_MESSAGE) + } + + /** + * The current plugin 'call'. Will be replace with newest dart request. + * + * @throws UninitializedPluginProviderException + * @return [MethodCall] + */ + fun call(): MethodCall { + return this.call.get() ?: throw UninitializedPluginProviderException(ERROR_MESSAGE) + } + + /** + * The current plugin 'result'. Will be replace with newest dart request. + * + * @throws UninitializedPluginProviderException + * @return [MethodChannel.Result] + */ + fun result(): MethodChannel.Result { + return this.result.get() ?: throw UninitializedPluginProviderException(ERROR_MESSAGE) + } + + class UninitializedPluginProviderException(msg: String) : Exception(msg) +} \ No newline at end of file diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/consts/Method.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/consts/Method.kt new file mode 100644 index 0000000..108cdb3 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/consts/Method.kt @@ -0,0 +1,29 @@ +package com.lucasjosino.on_audio_query.consts + +object Method { + // General methods + const val PERMISSION_STATUS = "permissionsStatus" + const val PERMISSION_REQUEST = "permissionsRequest" + const val QUERY_DEVICE_INFO = "queryDeviceInfo" + const val SCAN = "scan" + const val SET_LOG_CONFIG = "setLogConfig" + + // Query methods + const val QUERY_AUDIOS = "querySongs" + const val QUERY_ALBUMS = "queryAlbums" + const val QUERY_ARTISTS = "queryArtists" + const val QUERY_GENRES = "queryGenres" + const val QUERY_PLAYLISTS = "queryPlaylists" + const val QUERY_ARTWORK = "queryArtwork" + const val QUERY_AUDIOS_FROM = "queryAudiosFrom" + const val QUERY_WITH_FILTERS = "queryWithFilters" + const val QUERY_ALL_PATHS = "queryAllPath" + + // Playlist methods + const val CREATE_PLAYLIST = "createPlaylist" + const val REMOVE_PLAYLIST = "removePlaylist" + const val ADD_TO_PLAYLIST = "addToPlaylist" + const val REMOVE_FROM_PLAYLIST = "removeFromPlaylist" + const val RENAME_PLAYLIST = "renamePlaylist" + const val MOVE_ITEM_TO = "moveItemTo" +} \ No newline at end of file diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/controllers/MethodController.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/controllers/MethodController.kt new file mode 100644 index 0000000..608d862 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/controllers/MethodController.kt @@ -0,0 +1,32 @@ +package com.lucasjosino.on_audio_query.controllers + +import com.lucasjosino.on_audio_query.PluginProvider +import com.lucasjosino.on_audio_query.consts.Method +import com.lucasjosino.on_audio_query.queries.* + +class MethodController() { + + // + fun find() { + when (PluginProvider.call().method) { + //Query methods + Method.QUERY_AUDIOS -> AudioQuery().querySongs() + Method.QUERY_ALBUMS -> AlbumQuery().queryAlbums() + Method.QUERY_ARTISTS -> ArtistQuery().queryArtists() + Method.QUERY_PLAYLISTS -> PlaylistQuery().queryPlaylists() + Method.QUERY_GENRES -> GenreQuery().queryGenres() + Method.QUERY_ARTWORK -> ArtworkQuery().queryArtwork() + Method.QUERY_AUDIOS_FROM -> AudioFromQuery().querySongsFrom() + Method.QUERY_WITH_FILTERS -> WithFiltersQuery().queryWithFilters() + Method.QUERY_ALL_PATHS -> AllPathQuery().queryAllPath() + //Playlists methods + Method.CREATE_PLAYLIST -> PlaylistController().createPlaylist() + Method.REMOVE_PLAYLIST -> PlaylistController().removePlaylist() + Method.ADD_TO_PLAYLIST -> PlaylistController().addToPlaylist() + Method.REMOVE_FROM_PLAYLIST -> PlaylistController().removeFromPlaylist() + Method.RENAME_PLAYLIST -> PlaylistController().renamePlaylist() + Method.MOVE_ITEM_TO -> PlaylistController().moveItemTo() + else -> PluginProvider.result().notImplemented() + } + } +} \ No newline at end of file diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/controllers/PermissionController.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/controllers/PermissionController.kt new file mode 100644 index 0000000..e1a19fb --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/controllers/PermissionController.kt @@ -0,0 +1,112 @@ +package com.lucasjosino.on_audio_query.controllers + +import android.Manifest +import android.content.pm.PackageManager +import android.os.Build +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import com.lucasjosino.on_audio_query.PluginProvider +import com.lucasjosino.on_audio_query.interfaces.PermissionManagerInterface +import io.flutter.Log +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.PluginRegistry + +class PermissionController : PermissionManagerInterface, + PluginRegistry.RequestPermissionsResultListener { + + companion object { + private const val TAG: String = "PermissionController" + + private const val REQUEST_CODE: Int = 88560 + } + + var retryRequest: Boolean = false + + private var pendingResult: MethodChannel.Result? = null + + private var permissions: Array = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + arrayOf( + Manifest.permission.READ_MEDIA_AUDIO, + Manifest.permission.READ_MEDIA_IMAGES + ) + } else { + arrayOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) + } + + override fun permissionStatus(): Boolean = permissions.all { + // After "leaving" this class, context will be null so, we need this context argument to + // call the [checkSelfPermission]. + return ContextCompat.checkSelfPermission( + PluginProvider.context(), + it + ) == PackageManager.PERMISSION_GRANTED + } + + override fun requestPermission() { + Log.d(TAG, "Requesting permissions.") + Log.d(TAG, "SDK: ${Build.VERSION.SDK_INT}, Should retry request: $retryRequest") + pendingResult = try { + PluginProvider.result() + } catch (e: PluginProvider.UninitializedPluginProviderException) { + Log.w(TAG, "Keeping existing pending result because plugin result is unavailable.", e) + pendingResult + } + val activity = PluginProvider.activity() + ActivityCompat.requestPermissions(activity, permissions, REQUEST_CODE) + } + + // Second requestPermission, this one with the option "Never Ask Again". + override fun retryRequestPermission() { + val activity = PluginProvider.activity() + if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permissions[0]) + || ActivityCompat.shouldShowRequestPermissionRationale(activity, permissions[1]) + ) { + Log.d(TAG, "Retrying permission request") + retryRequest = false + requestPermission() + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ): Boolean { + // When the incoming request code doesn't match the request codes defined by the on_audio_query + // plugin return [false] to indicate the [on_audio_query] plugin is not handling the request + // result and Android should continue executing other registered handlers. + if (REQUEST_CODE != requestCode) return false + + // Check permission + val isPermissionGranted = (grantResults.isNotEmpty() + && grantResults[0] == PackageManager.PERMISSION_GRANTED) + + Log.d(TAG, "Permission accepted: $isPermissionGranted") + + // After all checks, we can handle the permission request. + val result = pendingResult + when { + result == null -> Log.w( + TAG, + "Permission result received but no pending callback is available." + ) + isPermissionGranted -> { + result.success(true) + pendingResult = null + } + retryRequest -> retryRequestPermission() + else -> { + result.success(false) + pendingResult = null + } + } + + // Return [true] here to indicate that the [on_audio_query] plugin handled the permission request + // result and Android should not continue executing other registered handlers. + return true + } +} diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/controllers/PlaylistController.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/controllers/PlaylistController.kt new file mode 100644 index 0000000..1bf9958 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/controllers/PlaylistController.kt @@ -0,0 +1,215 @@ +package com.lucasjosino.on_audio_query.controllers + +import android.content.ContentResolver +import android.content.ContentUris +import android.content.ContentValues +import android.os.Build +import android.provider.MediaStore +import android.util.Log +import com.lucasjosino.on_audio_query.PluginProvider + +/** OnPlaylistsController */ +class PlaylistController { + + //Main parameters + private val uri = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI + private val contentValues = ContentValues() + private val channelError = "on_audio_error" + private lateinit var resolver: ContentResolver + + //Query projection + private val columns = arrayOf( + "count(*)" + ) + + private val context = PluginProvider.context() + private val result = PluginProvider.result() + private val call = PluginProvider.call() + + // + fun createPlaylist() { + this.resolver = context.contentResolver + val playlistName = call.argument("playlistName")!! + + //For create we don't check if name already exist + contentValues.put(MediaStore.Audio.Playlists.NAME, playlistName) + contentValues.put(MediaStore.Audio.Playlists.DATE_ADDED, System.currentTimeMillis()) + resolver.insert(uri, contentValues) + result.success(true) + } + + // + fun removePlaylist() { + this.resolver = context.contentResolver + val playlistId = call.argument("playlistId")!! + + //Check if Playlist exists based in Id + if (!checkPlaylistId(playlistId)) result.success(false) + else { + val delUri = ContentUris.withAppendedId(uri, playlistId.toLong()) + resolver.delete(delUri, null, null) + result.success(true) + } + } + + //TODO Add option to use a list + //TODO Fix error on Android 10 + fun addToPlaylist() { + this.resolver = context.contentResolver + val playlistId = call.argument("playlistId")!! + val audioId = call.argument("audioId")!! + + + //Check if Playlist exists based in Id + if (!checkPlaylistId(playlistId)) result.success(false) + else { + val uri = + MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId.toLong()) + //If Android is Q/10 or above "count(*)" don't count, so, we use other method. + val columnsBasedOnVersion = if (Build.VERSION.SDK_INT < 29) columns else null + val cursor = resolver.query(uri, columnsBasedOnVersion, null, null, null) + var count = -1 + while (cursor != null && cursor.moveToNext()) { + count += if (Build.VERSION.SDK_INT < 29) cursor.count else cursor.getInt(0) + } + cursor?.close() + // + try { + contentValues.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, count + 1) + contentValues.put(MediaStore.Audio.Playlists.Members.AUDIO_ID, audioId.toLong()) + resolver.insert(uri, contentValues) + result.success(true) + } catch (e: Exception) { + Log.i(channelError, e.toString()) + } + } + } + + //TODO Add option to use a list + fun removeFromPlaylist() { + this.resolver = context.contentResolver + val playlistId = call.argument("playlistId")!! + val audioId = call.argument("audioId")!! + + //Check if Playlist exists based on Id + if (!checkPlaylistId(playlistId)) result.success(false) + else { + try { + val uri = MediaStore.Audio.Playlists.Members.getContentUri( + "external", + playlistId.toLong() + ) + val where = MediaStore.Audio.Playlists.Members._ID + "=?" + resolver.delete(uri, where, arrayOf(audioId.toString())) + result.success(true) + } catch (e: Exception) { + Log.i("on_audio_error: ", e.toString()) + result.success(false) + } + } + } + + //TODO("Need tests") + fun moveItemTo() { + this.resolver = context.contentResolver + val playlistId = call.argument("playlistId")!! + val from = call.argument("from")!! + val to = call.argument("to")!! + + //Check if Playlist exists based in Id + if (!checkPlaylistId(playlistId)) result.success(false) + else { + MediaStore.Audio.Playlists.Members.moveItem(resolver, playlistId.toLong(), from, to) + result.success(true) + } + } + + // + fun renamePlaylist() { + this.resolver = context.contentResolver + val playlistId = call.argument("playlistId")!! + val newPlaylistName = call.argument("newPlName")!! + + //Check if Playlist exists based in Id + if (!checkPlaylistId(playlistId)) result.success(false) + else { + contentValues.put(MediaStore.Audio.Playlists.NAME, newPlaylistName) + contentValues.put(MediaStore.Audio.Playlists.DATE_MODIFIED, System.currentTimeMillis()) + resolver.update(uri, contentValues, "_id=${playlistId.toLong()}", null) + result.success(true) + } + } + + //Return true if playlist already exist, false if don't exist + private fun checkPlaylistId(plId: Int): Boolean { + val cursor = resolver.query( + uri, + arrayOf(MediaStore.Audio.Playlists.NAME, MediaStore.Audio.Playlists._ID), + null, + null, + null + ) + while (cursor != null && cursor.moveToNext()) { + val playListId = cursor.getInt(1) //Id + if (playListId == plId) return true + } + cursor?.close() + return false + } +} + +//Extras: + +//I/PlaylistCursor[All]: [ +// title_key +// instance_id +// playlist_id +// duration +// is_ringtone +// album_artist +// orientation +// artist +// height +// is_drm +// bucket_display_name +// is_audiobook +// owner_package_name +// volume_name +// title_resource_uri +// date_modified +// date_expires +// composer +// _display_name +// datetaken +// mime_type +// is_notification +// _id +// year +// _data +// _hash +// _size +// album +// is_alarm +// title +// track +// width +// is_music +// album_key +// is_trashed +// group_id +// document_id +// artist_id +// artist_key +// is_pending +// date_added +// audio_id +// is_podcast +// album_id +// primary_directory +// secondary_directory +// original_document_id +// bucket_id +// play_order +// bookmark +// relative_path +// ] \ No newline at end of file diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/interfaces/PermissionManagerInterface.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/interfaces/PermissionManagerInterface.kt new file mode 100644 index 0000000..b0bdab9 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/interfaces/PermissionManagerInterface.kt @@ -0,0 +1,7 @@ +package com.lucasjosino.on_audio_query.interfaces + +interface PermissionManagerInterface { + fun permissionStatus() : Boolean + fun requestPermission() + fun retryRequestPermission() +} \ No newline at end of file diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/AlbumQuery.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/AlbumQuery.kt new file mode 100644 index 0000000..dea0245 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/AlbumQuery.kt @@ -0,0 +1,92 @@ +package com.lucasjosino.on_audio_query.queries + +import android.content.ContentResolver +import android.net.Uri +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.lucasjosino.on_audio_query.PluginProvider +import com.lucasjosino.on_audio_query.queries.helper.QueryHelper +import com.lucasjosino.on_audio_query.types.checkAlbumsUriType +import com.lucasjosino.on_audio_query.types.sorttypes.checkAlbumSortType +import io.flutter.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** OnAlbumsQuery */ +class AlbumQuery : ViewModel() { + + companion object { + private const val TAG = "OnAlbumsQuery" + } + + // Main parameters. + private val helper = QueryHelper() + + private lateinit var uri: Uri + private lateinit var sortType: String + private lateinit var resolver: ContentResolver + + /** + * Method to "query" all albums. + */ + fun queryAlbums() { + val call = PluginProvider.call() + val result = PluginProvider.result() + val context = PluginProvider.context() + this.resolver = context.contentResolver + + // Sort: Type and Order. + sortType = checkAlbumSortType( + call.argument("sortType"), + call.argument("orderType")!!, + call.argument("ignoreCase")!! + ) + + // Check uri: + // * 0 -> External + // * 1 -> Internal + uri = checkAlbumsUriType(call.argument("uri")!!) + + Log.d(TAG, "Query config: ") + Log.d(TAG, "\tsortType: $sortType") + Log.d(TAG, "\turi: $uri") + + // Query everything in background for a better performance. + viewModelScope.launch { + val queryResult = loadAlbums() + result.success(queryResult) + } + } + + // Loading in Background + private suspend fun loadAlbums(): ArrayList> = + withContext(Dispatchers.IO) { + // Setup the cursor with 'uri', 'projection'(null == all items) and 'sortType'. + val cursor = resolver.query(uri, null, null, null, sortType) + + val albumList: ArrayList> = ArrayList() + + Log.d(TAG, "Cursor count: ${cursor?.count}") + + // For each item(album) inside this "cursor", take one and "format" + // into a 'Map'. + while (cursor != null && cursor.moveToNext()) { + val tempData: MutableMap = HashMap() + + for (albumMedia in cursor.columnNames) { + tempData[albumMedia] = helper.loadAlbumItem(albumMedia, cursor) + } + + // Android 10 and above 'album_art' will return null. Use 'queryArtwork' instead. + val art = tempData["album_art"].toString() + if (art.isEmpty()) tempData.remove("album_art") + + albumList.add(tempData) + } + + // Close cursor to avoid memory leaks. + cursor?.close() + return@withContext albumList + } +} diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/AllPathQuery.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/AllPathQuery.kt new file mode 100644 index 0000000..fefe456 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/AllPathQuery.kt @@ -0,0 +1,59 @@ +package com.lucasjosino.on_audio_query.queries + +import android.annotation.SuppressLint +import android.content.ContentResolver +import android.net.Uri +import android.provider.MediaStore +import com.lucasjosino.on_audio_query.PluginProvider +import io.flutter.Log +import java.io.File + +/** OnAllPathQuery */ +class AllPathQuery { + + companion object { + private const val TAG = "OnAllPathQuery" + + private val URI: Uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + } + + private lateinit var resolver: ContentResolver + + /** + * Method to "query" all paths. + */ + fun queryAllPath() { + val result = PluginProvider.result() + val context = PluginProvider.context() + this.resolver = context.contentResolver + + val resultAllPath = loadAllPath() + result.success(resultAllPath) + } + + // Ignore the '_data' deprecation because this plugin support older versions. + @SuppressLint("Range") + private fun loadAllPath(): ArrayList { + val cursor = resolver.query(URI, null, null, null, null) + + val songPathList: ArrayList = ArrayList() + + Log.d(TAG, "Cursor count: ${cursor?.count}") + + // For each item(path) inside this "cursor", take one and add to the list. + while (cursor != null && cursor.moveToNext()) { + val content = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA)) + + val path = File(content).parent + + // Check if path is null or if already exist inside list. + if (path != null && !songPathList.contains(path)) { + songPathList.add(path) + } + } + + // Close cursor to avoid memory leaks. + cursor?.close() + return songPathList + } +} \ No newline at end of file diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/ArtistQuery.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/ArtistQuery.kt new file mode 100644 index 0000000..d4e0abb --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/ArtistQuery.kt @@ -0,0 +1,91 @@ +package com.lucasjosino.on_audio_query.queries + +import android.content.ContentResolver +import android.net.Uri +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.lucasjosino.on_audio_query.PluginProvider +import com.lucasjosino.on_audio_query.controllers.PermissionController +import com.lucasjosino.on_audio_query.queries.helper.QueryHelper +import com.lucasjosino.on_audio_query.types.checkArtistsUriType +import com.lucasjosino.on_audio_query.types.sorttypes.checkArtistSortType +import com.lucasjosino.on_audio_query.utils.artistProjection +import io.flutter.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** OnArtistsQuery */ +class ArtistQuery : ViewModel() { + + companion object { + private const val TAG = "OnArtistsQuery" + } + + //Main parameters + private val helper = QueryHelper() + + // None of this methods can be null. + private lateinit var uri: Uri + private lateinit var resolver: ContentResolver + private lateinit var sortType: String + + /** + * Method to "query" all artists. + */ + fun queryArtists() { + val call = PluginProvider.call() + val result = PluginProvider.result() + val context = PluginProvider.context() + this.resolver = context.contentResolver + + // Sort: Type and Order + sortType = checkArtistSortType( + call.argument("sortType"), + call.argument("orderType")!!, + call.argument("ignoreCase")!! + ) + + // Check uri: + // * 0 -> External. + // * 1 -> Internal. + uri = checkArtistsUriType(call.argument("uri")!!) + + Log.d(TAG, "Query config: ") + Log.d(TAG, "\tsortType: $sortType") + Log.d(TAG, "\turi: $uri") + + // Query everything in background for a better performance. + viewModelScope.launch { + val queryResult = loadArtists() + result.success(queryResult) + } + } + + // Loading in Background + private suspend fun loadArtists(): ArrayList> = + withContext(Dispatchers.IO) { + // Setup the cursor with 'uri', 'projection' and 'sortType'. + val cursor = resolver.query(uri, artistProjection, null, null, sortType) + + val artistList: ArrayList> = ArrayList() + + Log.d(TAG, "Cursor count: ${cursor?.count}") + + // For each item(artist) inside this "cursor", take one and "format" + // into a 'Map'. + while (cursor != null && cursor.moveToNext()) { + val tempData: MutableMap = HashMap() + + for (artistMedia in cursor.columnNames) { + tempData[artistMedia] = helper.loadArtistItem(artistMedia, cursor) + } + + artistList.add(tempData) + } + + // Close cursor to avoid memory leaks. + cursor?.close() + return@withContext artistList + } +} diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/ArtworkQuery.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/ArtworkQuery.kt new file mode 100644 index 0000000..1d9025a --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/ArtworkQuery.kt @@ -0,0 +1,187 @@ +package com.lucasjosino.on_audio_query.queries + +import android.content.ContentResolver +import android.content.ContentUris +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.media.MediaMetadataRetriever +import android.net.Uri +import android.os.Build +import android.util.Size +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.lucasjosino.on_audio_query.PluginProvider +import com.lucasjosino.on_audio_query.queries.helper.QueryHelper +import com.lucasjosino.on_audio_query.types.checkArtworkFormat +import com.lucasjosino.on_audio_query.types.checkArtworkType +import io.flutter.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.ByteArrayOutputStream +import java.io.FileInputStream + +class ArtworkQuery : ViewModel() { + + companion object { + private const val TAG = "OnArtworksQuery" + } + + private val helper = QueryHelper() + private var type: Int = -1 + private var id: Number = 0 + private var quality: Int = 100 + private var size: Int = 200 + private var showDetailedLog: Boolean = false + + private lateinit var uri: Uri + private lateinit var resolver: ContentResolver + private lateinit var format: Bitmap.CompressFormat + + /** + * Method to "query" artwork. + */ + fun queryArtwork() { + val call = PluginProvider.call() + val result = PluginProvider.result() + val context = PluginProvider.context() + this.resolver = context.contentResolver + this.showDetailedLog = PluginProvider.showDetailedLog + + id = call.argument("id")!! + + // If the 'size' is null, will be '200'. + size = call.argument("size")!! + + // The 'quality' value cannot be greater than 100 so, we check and if is, set to '50'. + quality = call.argument("quality")!! + if (quality > 100) quality = 50 + + // Check format: + // * 0 -> JPEG + // * 1 -> PNG + format = checkArtworkFormat(call.argument("format")!!) + + // Check uri: + // * 0 -> Song. + // * 1 -> Album. + // * 2 -> Playlist. + // * 3 -> Artist. + // * 4 -> Genre. + uri = checkArtworkType(call.argument("type")!!) + + // This query is 'universal' will work for multiple types (audio, album, artist, etc...). + type = call.argument("type")!! + + Log.d(TAG, "Query config: ") + Log.d(TAG, "\tid: $id") + Log.d(TAG, "\tquality: $quality") + Log.d(TAG, "\tformat: $format") + Log.d(TAG, "\turi: $uri") + Log.d(TAG, "\ttype: $type") + + // Query everything in background for a better performance. + viewModelScope.launch { + var resultArtList = loadArt() + + // Sometimes android will extract a 'wrong' or 'empty' artwork. Just set as null. + if (resultArtList != null && resultArtList.isEmpty()) { + Log.i(TAG, "Artwork for '$id' is empty. Returning null") + resultArtList = null + } + + result.success(resultArtList) + } + } + + //Loading in Background + private suspend fun loadArt(): ByteArray? = withContext(Dispatchers.IO) { + var artData: ByteArray? = null + + // If 'Android' >= 29/Q: + // * Limited access to files/folders. Use 'loadThumbnail'. + // If 'Android' < 29/Q: + // * Use the 'embeddedPicture' from 'MediaMetadataRetriever' to get the image. + if (Build.VERSION.SDK_INT >= 29) { + try { + // If 'type' is 2, 3 or 4, Get the first item from playlist or artist. + // Use the first artist song to 'simulate' the artwork. + // + // Type: + // * 2 -> Playlist. + // * 3 -> Artist. + // * 4 -> Genre. + // + // OBS: The 'id' is defined as 'Number'. Convert to 'Long' + val query = if (type == 2 || type == 3 || type == 4) { + val item = helper.loadFirstItem(type, id, resolver) ?: return@withContext null + ContentUris.withAppendedId(uri, item.toLong()) + } else { + ContentUris.withAppendedId(uri, id.toLong()) + } + + val bitmap = resolver.loadThumbnail(query, Size(size, size), null) + artData = convertOrResize(bitmap = bitmap)!! + } catch (e: Exception) { + // This may produce a lot of logging on console so, will required a explicit request + // to show the errors. + if (showDetailedLog) Log.w(TAG, "($id) Message: $e") + } + } else { + // If 'uri == Audio': + // * Load the first 'item' from cursor using the 'id' as filter. + // else: + // * Load the first 'item' from 'album' using the 'id' as filter. + // + // If 'item' return null, no song/album has found, return null. + val item = helper.loadFirstItem(type, id, resolver) ?: return@withContext null + + try { + val file = FileInputStream(item) + val metadata = MediaMetadataRetriever() + + metadata.setDataSource(file.fd) + val image = metadata.embeddedPicture + + // Convert image. If null, return + artData = convertOrResize(byteArray = image) ?: return@withContext null + + // 'close' can only be called using 'Android' >= 29/Q. + if (Build.VERSION.SDK_INT >= 29) metadata.close() + } catch (e: Exception) { + // This may produce a lot of logging on console so, will required a explicit request + // to show the errors. + if (showDetailedLog) Log.w(TAG, "($id) Message: $e") + } + } + + return@withContext artData + } + + // + private fun convertOrResize(bitmap: Bitmap? = null, byteArray: ByteArray? = null): ByteArray? { + val convertedBytes: ByteArray? + val byteArrayBase = ByteArrayOutputStream() + + try { + // If 'bitmap' isn't null: + // * The image(bitmap) is from first method. (Android >= 29/Q). + // else: + // * The image(bytearray) is from second method. (Android < 29/Q). + if (bitmap != null) { + bitmap.compress(format, quality, byteArrayBase) + } else { + val convertedBitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray!!.size) + convertedBitmap.compress(format, quality, byteArrayBase) + } + } catch (e: Exception) { + // This may produce a lot of logging on console so, will required a explicit request + // to show the errors. + if (showDetailedLog) Log.w(TAG, "($id) Message: $e") + } + + convertedBytes = byteArrayBase.toByteArray() + byteArrayBase.close() + return convertedBytes + } +} \ No newline at end of file diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/AudioFromQuery.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/AudioFromQuery.kt new file mode 100644 index 0000000..00c0933 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/AudioFromQuery.kt @@ -0,0 +1,214 @@ +package com.lucasjosino.on_audio_query.queries + +import android.content.ContentResolver +import android.net.Uri +import android.os.Build +import android.provider.MediaStore +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.lucasjosino.on_audio_query.PluginProvider +import com.lucasjosino.on_audio_query.controllers.PermissionController +import com.lucasjosino.on_audio_query.queries.helper.QueryHelper +import com.lucasjosino.on_audio_query.types.checkAudiosFromType +import com.lucasjosino.on_audio_query.types.sorttypes.checkSongSortType +import com.lucasjosino.on_audio_query.utils.songProjection +import io.flutter.Log +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** OnAudiosFromQuery */ +class AudioFromQuery : ViewModel() { + + companion object { + private const val TAG = "OnAudiosFromQuery" + + private val URI: Uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + } + + //Main parameters + private val helper = QueryHelper() + private var pId = 0 + private var pUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + + // None of this methods can be null. + private lateinit var where: String + private lateinit var whereVal: String + private lateinit var sortType: String + private lateinit var resolver: ContentResolver + + /** + * Method to "query" all songs from a specific item. + */ + fun querySongsFrom() { + val call = PluginProvider.call() + val result = PluginProvider.result() + val context = PluginProvider.context() + this.resolver = context.contentResolver + + // The type of 'item': + // * 0 -> Album + // * 1 -> Album Id + // * 2 -> Artist + // * 3 -> Artist Id + // * 4 -> Genre + // * 5 -> Genre Id + // * 6 -> Playlist + val type = call.argument("type")!! + + // Sort: Type and Order. + sortType = checkSongSortType( + call.argument("sortType"), + call.argument("orderType")!!, + call.argument("ignoreCase")!! + ) + + Log.d(TAG, "Query config: ") + Log.d(TAG, "\tsortType: $sortType") + Log.d(TAG, "\ttype: $type") + Log.d(TAG, "\turi: $URI") + + // TODO: Add a better way to handle this query + // This will fix (for now) the problem between Android < 30 && Android > 30 + // The method used to query genres on Android >= 30 don't work properly on Android < 30 so, + // we need separate. + // + // If helper == 6 (Playlist) send to 'querySongsFromPlaylistOrGenre' in any version. + // If helper == 4 (Genre) || helper == 5 (GenreId) and Android < 30 send to + // 'querySongsFromPlaylistOrGenre' else, follow the rest of the "normal" code. + // + // Why? Android 10 and below doesn't have "genre" category and we need use a "workaround". + // 'MediaStore'(https://developer.android.com/reference/android/provider/MediaStore.Audio.AudioColumns#GENRE) + if (type == 6 || ((type == 4 || type == 5) && Build.VERSION.SDK_INT < 30)) { + // Works on Android 10. + querySongsFromPlaylistOrGenre(result, call, type) + } else { + // Works on Android 11. + // 'whereVal' -> Album/Artist/Genre + // 'where' -> uri + whereVal = call.argument("where")!!.toString() + where = checkAudiosFromType(type) + + // Query everything in background for a better performance. + viewModelScope.launch { + val resultSongList = loadSongsFrom() + result.success(resultSongList) + } + } + } + + //Loading in Background + private suspend fun loadSongsFrom(): ArrayList> = + withContext(Dispatchers.IO) { + // Setup the cursor with 'uri', 'projection', 'selection'(where) and 'values'(whereVal). + val cursor = resolver.query(URI, songProjection(), where, arrayOf(whereVal), sortType) + + val songsFromList: ArrayList> = ArrayList() + + Log.d(TAG, "Cursor count: ${cursor?.count}") + + // For each item(song) inside this "cursor", take one and "format" + // into a 'Map'. + while (cursor != null && cursor.moveToNext()) { + val tempData: MutableMap = HashMap() + for (audioMedia in cursor.columnNames) { + tempData[audioMedia] = helper.loadSongItem(audioMedia, cursor) + } + + //Get a extra information from audio, e.g: extension, uri, etc.. + val tempExtraData = helper.loadSongExtraInfo(URI, tempData) + tempData.putAll(tempExtraData) + + songsFromList.add(tempData) + } + + // Close cursor to avoid memory leaks. + cursor?.close() + return@withContext songsFromList + } + + // TODO: Remove unnecessary code. + private fun querySongsFromPlaylistOrGenre( + result: MethodChannel.Result, + call: MethodCall, + type: Int + ) { + val info = call.argument("where")!! + + // Check if playlist exists using the id. + val checkedName = if (type == 4 || type == 5) { + checkName(genreName = info.toString()) + } else { + checkName(plName = info.toString()) + } + + if (!checkedName) pId = info.toString().toInt() + + pUri = if (type == 4 || type == 5) { + MediaStore.Audio.Genres.Members.getContentUri("external", pId.toLong()) + } else { + MediaStore.Audio.Playlists.Members.getContentUri("external", pId.toLong()) + } + + // Query everything in background for a better performance. + viewModelScope.launch { + val resultSongsFrom = loadSongsFromPlaylistOrGenre() + result.success(resultSongsFrom) + } + } + + private suspend fun loadSongsFromPlaylistOrGenre(): ArrayList> = + withContext(Dispatchers.IO) { + val songsFrom: ArrayList> = ArrayList() + + val cursor = resolver.query(pUri, songProjection(), null, null, sortType) + + Log.d(TAG, "Cursor count: ${cursor?.count}") + + while (cursor != null && cursor.moveToNext()) { + val tempData: MutableMap = HashMap() + + for (media in cursor.columnNames) { + tempData[media] = helper.loadSongItem(media, cursor) + } + + //Get a extra information from audio, e.g: extension, uri, etc.. + val tempExtraData = helper.loadSongExtraInfo(URI, tempData) + tempData.putAll(tempExtraData) + + songsFrom.add(tempData) + } + + cursor?.close() + return@withContext songsFrom + } + + // Return true if playlist or genre exists and false, if don't. + private fun checkName(plName: String? = null, genreName: String? = null): Boolean { + val uri: Uri + val projection: Array + + if (plName != null) { + uri = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI + projection = arrayOf(MediaStore.Audio.Playlists.NAME, MediaStore.Audio.Playlists._ID) + } else { + uri = MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI + projection = arrayOf(MediaStore.Audio.Genres.NAME, MediaStore.Audio.Genres._ID) + } + + val cursor = resolver.query(uri, projection, null, null, null) + while (cursor != null && cursor.moveToNext()) { + val name = cursor.getString(0) + + if (name != null && name == plName || name == genreName) { + pId = cursor.getInt(1) + return true + } + } + + cursor?.close() + return false + } +} diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/AudioQuery.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/AudioQuery.kt new file mode 100644 index 0000000..1ad07d5 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/AudioQuery.kt @@ -0,0 +1,101 @@ +package com.lucasjosino.on_audio_query.queries + +import android.content.ContentResolver +import android.net.Uri +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.lucasjosino.on_audio_query.PluginProvider +import com.lucasjosino.on_audio_query.queries.helper.QueryHelper +import com.lucasjosino.on_audio_query.types.checkAudiosUriType +import com.lucasjosino.on_audio_query.types.sorttypes.checkSongSortType +import com.lucasjosino.on_audio_query.utils.songProjection +import io.flutter.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** OnAudiosQuery */ +class AudioQuery : ViewModel() { + + companion object { + private const val TAG = "OnAudiosQuery" + } + + // Main parameters + private val helper = QueryHelper() + private var selection: String? = null + + private lateinit var uri: Uri + private lateinit var sortType: String + private lateinit var resolver: ContentResolver + + /** + * Method to "query" all songs. + */ + fun querySongs() { + val call = PluginProvider.call() + val result = PluginProvider.result() + val context = PluginProvider.context() + this.resolver = context.contentResolver + + // Sort: Type and Order. + sortType = checkSongSortType( + call.argument("sortType"), + call.argument("orderType")!!, + call.argument("ignoreCase")!! + ) + + // Check uri: + // * 0 -> External. + // * 1 -> Internal. + uri = checkAudiosUriType(call.argument("uri")!!) + + // Here we provide a custom 'path'. + if (call.argument("path") != null) { + val projection = songProjection() + selection = projection[0] + " like " + "'%" + call.argument("path") + "/%'" + } + + Log.d(TAG, "Query config: ") + Log.d(TAG, "\tsortType: $sortType") + Log.d(TAG, "\tselection: $selection") + Log.d(TAG, "\turi: $uri") + + // Query everything in background for a better performance. + viewModelScope.launch { + val queryResult = loadSongs() + result.success(queryResult) + } + } + + //Loading in Background + private suspend fun loadSongs(): ArrayList> = + withContext(Dispatchers.IO) { + // Setup the cursor with 'uri', 'projection' and 'sortType'. + val cursor = resolver.query(uri, songProjection(), selection, null, sortType) + + val songList: ArrayList> = ArrayList() + + Log.d(TAG, "Cursor count: ${cursor?.count}") + + // For each item(song) inside this "cursor", take one and "format" + // into a 'Map'. + while (cursor != null && cursor.moveToNext()) { + val tempData: MutableMap = HashMap() + + for (audioMedia in cursor.columnNames) { + tempData[audioMedia] = helper.loadSongItem(audioMedia, cursor) + } + + //Get a extra information from audio, e.g: extension, uri, etc.. + val tempExtraData = helper.loadSongExtraInfo(uri, tempData) + tempData.putAll(tempExtraData) + + songList.add(tempData) + } + + // Close cursor to avoid memory leaks. + cursor?.close() + return@withContext songList + } +} diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/GenreQuery.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/GenreQuery.kt new file mode 100644 index 0000000..536e991 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/GenreQuery.kt @@ -0,0 +1,95 @@ +package com.lucasjosino.on_audio_query.queries + +import android.content.ContentResolver +import android.net.Uri +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.lucasjosino.on_audio_query.PluginProvider +import com.lucasjosino.on_audio_query.queries.helper.QueryHelper +import com.lucasjosino.on_audio_query.types.checkGenresUriType +import com.lucasjosino.on_audio_query.types.sorttypes.checkGenreSortType +import com.lucasjosino.on_audio_query.utils.genreProjection +import io.flutter.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** OnGenresQuery */ +class GenreQuery : ViewModel() { + + companion object { + private const val TAG = "OnGenresQuery" + } + + // Main parameters. + private val helper = QueryHelper() + + private lateinit var uri: Uri + private lateinit var sortType: String + private lateinit var resolver: ContentResolver + + /** + * Method to "query" all genres. + */ + fun queryGenres() { + val call = PluginProvider.call() + val result = PluginProvider.result() + val context = PluginProvider.context() + this.resolver = context.contentResolver + + // Sort: Type and Order. + sortType = checkGenreSortType( + call.argument("sortType"), + call.argument("orderType")!!, + call.argument("ignoreCase")!! + ) + + // Check uri: + // * 0 -> External + // * 1 -> Internal + uri = checkGenresUriType(call.argument("uri")!!) + + Log.d(TAG, "Query config: ") + Log.d(TAG, "\tsortType: $sortType") + Log.d(TAG, "\turi: $uri") + + // Query everything in background for a better performance. + viewModelScope.launch { + val queryResult = loadGenres() + result.success(queryResult) + } + } + + // Loading in Background + private suspend fun loadGenres(): ArrayList> = + withContext(Dispatchers.IO) { + // Setup the cursor with 'uri', 'projection' and 'sortType'. + val cursor = resolver.query(uri, genreProjection, null, null, sortType) + + val genreList: ArrayList> = ArrayList() + + Log.d(TAG, "Cursor count: ${cursor?.count}") + + // For each item(genre) inside this "cursor", take one and "format" + // into a 'Map'. + while (cursor != null && cursor.moveToNext()) { + val genreData: MutableMap = HashMap() + + for (genreMedia in cursor.columnNames) { + genreData[genreMedia] = helper.loadGenreItem(genreMedia, cursor) + } + + // Count and add the number of songs for every genre. + val mediaCount = helper.getMediaCount(0, genreData["_id"].toString(), resolver) + genreData["num_of_songs"] = mediaCount + + if (genreData["name"] != null && genreData["_id"] != 0) { + genreList.add(genreData) + } + } + + // Close cursor to avoid memory leaks. + cursor?.close() + return@withContext genreList + } +} diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/PlaylistQuery.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/PlaylistQuery.kt new file mode 100644 index 0000000..e7501f6 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/PlaylistQuery.kt @@ -0,0 +1,93 @@ +package com.lucasjosino.on_audio_query.queries + +import android.content.ContentResolver +import android.net.Uri +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.lucasjosino.on_audio_query.PluginProvider +import com.lucasjosino.on_audio_query.controllers.PermissionController +import com.lucasjosino.on_audio_query.queries.helper.QueryHelper +import com.lucasjosino.on_audio_query.types.checkPlaylistsUriType +import com.lucasjosino.on_audio_query.types.sorttypes.checkGenreSortType +import com.lucasjosino.on_audio_query.utils.playlistProjection +import io.flutter.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** OnPlaylistQuery */ +class PlaylistQuery : ViewModel() { + + companion object { + private const val TAG = "OnPlaylistQuery" + } + + //Main parameters + private val helper = QueryHelper() + + private lateinit var uri: Uri + private lateinit var resolver: ContentResolver + private lateinit var sortType: String + + /** + * Method to "query" all playlists. + */ + fun queryPlaylists() { + val call = PluginProvider.call() + val result = PluginProvider.result() + val context = PluginProvider.context() + this.resolver = context.contentResolver + + // Sort: Type and Order. + sortType = checkGenreSortType( + call.argument("sortType"), + call.argument("orderType")!!, + call.argument("ignoreCase")!! + ) + // Check uri: + // * 0 -> External. + // * 1 -> Internal. + uri = checkPlaylistsUriType(call.argument("uri")!!) + + Log.d(TAG, "Query config: ") + Log.d(TAG, "\tsortType: $sortType") + Log.d(TAG, "\turi: $uri") + + // Query everything in background for a better performance. + viewModelScope.launch { + val queryResult = loadPlaylists() + result.success(queryResult) + } + } + + //Loading in Background + private suspend fun loadPlaylists(): ArrayList> = + withContext(Dispatchers.IO) { + // Setup the cursor with 'uri' and 'projection'. + val cursor = resolver.query(uri, playlistProjection, null, null, null) + + val playlistList: ArrayList> = ArrayList() + + Log.d(TAG, "Cursor count: ${cursor?.count}") + + // For each item(playlist) inside this "cursor", take one and "format" + // into a 'Map'. + while (cursor != null && cursor.moveToNext()) { + val playlistData: MutableMap = HashMap() + + for (playlistMedia in cursor.columnNames) { + playlistData[playlistMedia] = helper.loadPlaylistItem(playlistMedia, cursor) + } + + // Count and add the number of songs for every playlist. + val mediaCount = helper.getMediaCount(1, playlistData["_id"].toString(), resolver) + playlistData["num_of_songs"] = mediaCount + + playlistList.add(playlistData) + } + + // Close cursor to avoid memory leaks. + cursor?.close() + return@withContext playlistList + } +} diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/WithFiltersQuery.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/WithFiltersQuery.kt new file mode 100644 index 0000000..ed80ce3 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/WithFiltersQuery.kt @@ -0,0 +1,117 @@ +package com.lucasjosino.on_audio_query.queries + +import android.content.ContentResolver +import android.net.Uri +import android.provider.MediaStore +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.lucasjosino.on_audio_query.PluginProvider +import com.lucasjosino.on_audio_query.queries.helper.QueryHelper +import com.lucasjosino.on_audio_query.types.* +import io.flutter.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class WithFiltersQuery : ViewModel() { + + companion object { + private const val TAG = "OnWithFiltersQuery" + + private val URI = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + } + + //Main parameters + private val helper = QueryHelper() + private var projection: Array? = arrayOf() + + private lateinit var resolver: ContentResolver + private lateinit var withType: Uri + private lateinit var argsVal: String + private lateinit var argsKey: String + + // + fun queryWithFilters() { + val call = PluginProvider.call() + val result = PluginProvider.result() + val context = PluginProvider.context() + this.resolver = context.contentResolver + + // Choose the type. + // * 0 -> Audios + // * 1 -> Albums + // * 2 -> Playlists + // * 3 -> Artists + // * 4 -> Genres + withType = checkWithFiltersType(call.argument("withType")!!) + + // The 'args' are converted to 'String' before send to 'MethodChannel'. + argsVal = "%" + call.argument("argsVal")!! + "%" + + // A dynamic 'projection' to every type of "query". + projection = checkProjection(withType) + + // Choose the 'arg'. + argsKey = when (withType) { + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI -> checkSongsArgs(call.argument("args")!!) + MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI -> checkAlbumsArgs(call.argument("args")!!) + MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI -> checkPlaylistsArgs( + call.argument( + "args" + )!! + ) + MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI -> checkArtistsArgs(call.argument("args")!!) + MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI -> checkGenresArgs(call.argument("args")!!) + else -> throw Exception("[argsKey] returned null. Report this issue on [on_audio_query] GitHub.") + } + + Log.d(TAG, "Query config: ") + Log.d(TAG, "\twithType: $withType") + Log.d(TAG, "\targsVal: $argsVal") + Log.d(TAG, "\targsKey: $argsKey") + + // Query everything in background for a better performance. + viewModelScope.launch { + val queryResult = loadWithFilters() + result.success(queryResult) + } + } + + //Loading in Background + private suspend fun loadWithFilters(): ArrayList> = + withContext(Dispatchers.IO) { + // Setup the cursor with 'uri', 'projection', 'argsKey' and 'argsVal'. + val cursor = resolver.query(withType, projection, argsKey, arrayOf(argsVal), null) + + val withFiltersList: ArrayList> = ArrayList() + + Log.d(TAG, "Cursor count: ${cursor?.count}") + + // For each item inside this "cursor", take one and "format" + // into a 'Map'. + while (cursor != null && cursor.moveToNext()) { + val tempData: MutableMap = HashMap() + + for (media in cursor.columnNames) { + tempData[media] = helper.chooseWithFilterType( + withType, + media, + cursor + ) + } + + // If 'withType' is a song media, add the extra information. + if (withType == MediaStore.Audio.Media.EXTERNAL_CONTENT_URI) { + //Get a extra information from audio, e.g: extension, uri, etc.. + val tempExtraData = helper.loadSongExtraInfo(URI, tempData) + tempData.putAll(tempExtraData) + } + + withFiltersList.add(tempData) + } + + // Close cursor to avoid memory leaks. + cursor?.close() + return@withContext withFiltersList + } +} \ No newline at end of file diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/helper/QueryHelper.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/helper/QueryHelper.kt new file mode 100644 index 0000000..083c7df --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/queries/helper/QueryHelper.kt @@ -0,0 +1,237 @@ +package com.lucasjosino.on_audio_query.queries.helper + +import android.content.ContentResolver +import android.content.ContentUris +import android.database.Cursor +import android.net.Uri +import android.os.Build +import android.provider.MediaStore +import android.util.Log +import java.io.File + +class QueryHelper { + //This method will load some extra information about audio/song + fun loadSongExtraInfo( + uri: Uri, + songData: MutableMap + ): MutableMap { + val file = File(songData["_data"].toString()) + + //Getting displayName without [Extension]. + songData["_display_name_wo_ext"] = file.nameWithoutExtension + //Adding only the extension + songData["file_extension"] = file.extension + + //A different type of "data" + val tempUri = ContentUris.withAppendedId(uri, songData["_id"].toString().toLong()) + songData["_uri"] = tempUri.toString() + + return songData + } + + //This method will separate [String] from [Int] + fun loadSongItem(itemProperty: String, cursor: Cursor): Any? { + return when (itemProperty) { + // Int + "_id", + "album_id", + "artist_id" -> { + // The [id] from Android >= 30/R is a [Long] instead of [Int]. + if (Build.VERSION.SDK_INT >= 30) { + cursor.getLong(cursor.getColumnIndex(itemProperty)) + } else { + cursor.getInt(cursor.getColumnIndex(itemProperty)) + } + } + "_size", + "bookmark", + "date_added", + "date_modified", + "duration", + "track" -> cursor.getInt(cursor.getColumnIndex(itemProperty)) + // Boolean + "is_alarm", + "is_audiobook", + "is_music", + "is_notification", + "is_podcast", + "is_ringtone" -> { + val value = cursor.getString(cursor.getColumnIndex(itemProperty)) + if (value == "0") return false + return true + } + // String + else -> cursor.getString(cursor.getColumnIndex(itemProperty)) + } + } + + //This method will separate [String] from [Int] + fun loadAlbumItem(itemProperty: String, cursor: Cursor): Any? { + return when (itemProperty) { + "_id", + "artist_id" -> { + // The [album] id from Android >= 30/R is a [Long] instead of [Int]. + if (Build.VERSION.SDK_INT >= 30) { + cursor.getLong(cursor.getColumnIndex(itemProperty)) + } else { + cursor.getInt(cursor.getColumnIndex(itemProperty)) + } + } + "numsongs" -> cursor.getInt(cursor.getColumnIndex(itemProperty)) + else -> cursor.getString(cursor.getColumnIndex(itemProperty)) + } + } + + //This method will separate [String] from [Int] + fun loadPlaylistItem(itemProperty: String, cursor: Cursor): Any? { + return when (itemProperty) { + "_id", + "date_added", + "date_modified" -> cursor.getLong(cursor.getColumnIndex(itemProperty)) + else -> cursor.getString(cursor.getColumnIndex(itemProperty)) + } + } + + //This method will separate [String] from [Int] + fun loadArtistItem(itemProperty: String, cursor: Cursor): Any? { + return when (itemProperty) { + "_id" -> { + // The [artist] id from Android >= 30/R is a [Long] instead of [Int]. + if (Build.VERSION.SDK_INT >= 30) { + cursor.getLong(cursor.getColumnIndex(itemProperty)) + } else { + cursor.getInt(cursor.getColumnIndex(itemProperty)) + } + } + "number_of_albums", + "number_of_tracks" -> cursor.getInt(cursor.getColumnIndex(itemProperty)) + else -> cursor.getString(cursor.getColumnIndex(itemProperty)) + } + } + + //This method will separate [String] from [Int] + fun loadGenreItem(itemProperty: String, cursor: Cursor): Any? { + return when (itemProperty) { + "_id" -> { + // The [genre] id from Android >= 30/R is a [Long] instead of [Int]. + if (Build.VERSION.SDK_INT >= 30) { + cursor.getLong(cursor.getColumnIndex(itemProperty)) + } else { + cursor.getInt(cursor.getColumnIndex(itemProperty)) + } + } + else -> cursor.getString(cursor.getColumnIndex(itemProperty)) + } + } + + fun getMediaCount(type: Int, arg: String, resolver: ContentResolver): Int { + val uri: Uri = if (type == 0) { + MediaStore.Audio.Genres.Members.getContentUri("external", arg.toLong()) + } else { + MediaStore.Audio.Playlists.Members.getContentUri("external", arg.toLong()) + } + val cursor = resolver.query(uri, null, null, null, null) + val count = cursor?.count ?: -1 + cursor?.close() + return count + } + + // Ignore the [Data] deprecation because this plugin support older versions. + @Suppress("DEPRECATION") + fun loadFirstItem(type: Int, id: Number, resolver: ContentResolver): String? { + + // We use almost the same method to 'query' the first item from Song/Album/Artist and we + // need to use a different uri when 'querying' from playlist. + // If [type] is something different, return null. + val selection: String? = when (type) { + 0 -> MediaStore.Audio.Media._ID + "=?" + 1 -> MediaStore.Audio.Media.ALBUM_ID + "=?" + 2 -> null + 3 -> MediaStore.Audio.Media.ARTIST_ID + "=?" + 4 -> null + else -> return null + } + + var dataOrId: String? = null + var cursor: Cursor? = null + try { + // Type 2 or 4 we use a different uri. + // + // Type 2 == Playlist + // Type 4 == Genre + // + // And the others we use the normal uri. + when (true) { + (type == 2 && selection == null) -> { + cursor = resolver.query( + MediaStore.Audio.Playlists.Members.getContentUri("external", id.toLong()), + arrayOf( + MediaStore.Audio.Playlists.Members.DATA, + MediaStore.Audio.Playlists.Members.AUDIO_ID + ), + null, + null, + null + ) + } + (type == 4 && selection == null) -> { + cursor = resolver.query( + MediaStore.Audio.Genres.Members.getContentUri("external", id.toLong()), + arrayOf( + MediaStore.Audio.Genres.Members.DATA, + MediaStore.Audio.Genres.Members.AUDIO_ID + ), + null, + null, + null + ) + } + else -> { + cursor = resolver.query( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + arrayOf(MediaStore.Audio.Media.DATA, MediaStore.Audio.Media._ID), + selection, + arrayOf(id.toString()), + null + ) + } + } + } catch (e: Exception) { +// Log.i("on_audio_error", e.toString()) + } + + // + if (cursor != null) { + cursor.moveToFirst() + // Try / Catch to avoid problems. Everytime someone request the first song from a playlist and + // this playlist is empty will crash the app, so we just 'print' the error. + try { + dataOrId = + if (Build.VERSION.SDK_INT >= 29 && (type == 2 || type == 3 || type == 4)) { + cursor.getString(1) + } else { + cursor.getString(0) + } + } catch (e: Exception) { + Log.i("on_audio_error", e.toString()) + } + } + cursor?.close() + + return dataOrId + } + + fun chooseWithFilterType(uri: Uri, itemProperty: String, cursor: Cursor): Any? { + return when (uri) { + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI -> loadSongItem(itemProperty, cursor) + MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI -> loadAlbumItem(itemProperty, cursor) + MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI -> loadPlaylistItem( + itemProperty, + cursor + ) + MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI -> loadArtistItem(itemProperty, cursor) + MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI -> loadGenreItem(itemProperty, cursor) + else -> null + } + } +} \ No newline at end of file diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/ArtworkType.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/ArtworkType.kt new file mode 100644 index 0000000..024ba08 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/ArtworkType.kt @@ -0,0 +1,21 @@ +package com.lucasjosino.on_audio_query.types + +import android.graphics.Bitmap +import android.net.Uri +import android.provider.MediaStore + +fun checkArtworkType(sortType: Int): Uri { + return when (sortType) { + 0, 2, 3, 4 -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + 1 -> MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI + else -> throw Exception("[checkArtworkType] value don't exist!") + } +} + +fun checkArtworkFormat(format: Int) : Bitmap.CompressFormat { + return when (format) { + 0 -> Bitmap.CompressFormat.JPEG + 1 -> Bitmap.CompressFormat.PNG + else -> throw Exception("[checkArtworkFormat] value don't exist!") + } +} \ No newline at end of file diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/AudiosFromType.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/AudiosFromType.kt new file mode 100644 index 0000000..0c79b85 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/AudiosFromType.kt @@ -0,0 +1,18 @@ +package com.lucasjosino.on_audio_query.types + +import android.annotation.SuppressLint +import android.provider.MediaStore + +@SuppressLint("InlinedApi") +fun checkAudiosFromType(sortType: Int): String { + return when (sortType) { + 0 -> MediaStore.Audio.Media.ALBUM + " like ?" + 1 -> MediaStore.Audio.Media.ALBUM_ID + " like ?" + 2 -> MediaStore.Audio.Media.ARTIST + " like ?" + 3 -> MediaStore.Audio.Media.ARTIST_ID + " like ?" + // Use the [TRIM] to remove all empty space from genre name. + 4 -> "TRIM(\"" + MediaStore.Audio.Media.GENRE + "\")" + " like ?" + 5 -> MediaStore.Audio.Media.GENRE_ID + " like ?" + else -> throw Exception("[checkAudiosFromType] value don't exist!") + } +} \ No newline at end of file diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/UriType.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/UriType.kt new file mode 100644 index 0000000..d3f7573 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/UriType.kt @@ -0,0 +1,44 @@ +package com.lucasjosino.on_audio_query.types + +import android.net.Uri +import android.provider.MediaStore + +fun checkAudiosUriType(uriType: Int): Uri { + return when (uriType) { + 0 -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + 1 -> MediaStore.Audio.Media.INTERNAL_CONTENT_URI + else -> throw Exception("[checkAudiosUriType] value don't exist!") + } +} + +fun checkAlbumsUriType(uriType: Int): Uri { + return when (uriType) { + 0 -> MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI + 1 -> MediaStore.Audio.Albums.INTERNAL_CONTENT_URI + else -> throw Exception("[checkAlbumsUriType] value don't exist!") + } +} + +fun checkPlaylistsUriType(uriType: Int): Uri { + return when (uriType) { + 0 -> MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI + 1 -> MediaStore.Audio.Playlists.INTERNAL_CONTENT_URI + else -> throw Exception("[checkPlaylistsUriType] value don't exist!") + } +} + +fun checkArtistsUriType(uriType: Int): Uri { + return when (uriType) { + 0 -> MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI + 1 -> MediaStore.Audio.Artists.INTERNAL_CONTENT_URI + else -> throw Exception("[checkArtistsUriType] value don't exist!") + } +} + +fun checkGenresUriType(uriType: Int): Uri { + return when (uriType) { + 0 -> MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI + 1 -> MediaStore.Audio.Genres.INTERNAL_CONTENT_URI + else -> throw Exception("[checkGenresUriType] value don't exist!") + } +} \ No newline at end of file diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/WithFiltersType.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/WithFiltersType.kt new file mode 100644 index 0000000..5e98113 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/WithFiltersType.kt @@ -0,0 +1,73 @@ +package com.lucasjosino.on_audio_query.types + +import android.annotation.SuppressLint +import android.net.Uri +import android.provider.MediaStore +import com.lucasjosino.on_audio_query.utils.artistProjection +import com.lucasjosino.on_audio_query.utils.genreProjection +import com.lucasjosino.on_audio_query.utils.playlistProjection +import com.lucasjosino.on_audio_query.utils.songProjection + +fun checkWithFiltersType(sortType: Int): Uri { + return when (sortType) { + 0 -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + 1 -> MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI + 2 -> MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI + 3 -> MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI + 4 -> MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI + else -> throw Exception("[checkWithFiltersType] value don't exist!") + } +} + +fun checkProjection(withType: Uri): Array? { + return when (withType) { + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI -> songProjection() + // [Album] projection is null because we need all items. + MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI -> null + MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI -> playlistProjection + MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI -> artistProjection + MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI -> genreProjection + else -> songProjection() + } +} + +@SuppressLint("InlinedApi") +fun checkSongsArgs(args: Int): String { + return when (args) { + 0 -> MediaStore.Audio.Media.TITLE + " like ?" + 1 -> MediaStore.Audio.Media.DISPLAY_NAME + " like ?" + 2 -> MediaStore.Audio.Media.ALBUM + " like ?" + 3 -> MediaStore.Audio.Media.ARTIST + " like ?" + else -> throw Exception("[checkSongsArgs] value don't exist!") + } +} + +fun checkAlbumsArgs(args: Int): String { + return when (args) { + 0 -> MediaStore.Audio.Albums.ALBUM + " like ?" + 1 -> MediaStore.Audio.Albums.ARTIST + " like ?" + else -> throw Exception("[checkAlbumsArgs] value don't exist!") + } +} + +fun checkPlaylistsArgs(args: Int): String { + return when (args) { + 0 -> MediaStore.Audio.Playlists.NAME + " like ?" + else -> throw Exception("[checkPlaylistsArgs] value don't exist!") + } +} + +fun checkArtistsArgs(args: Int): String { + return when (args) { + 0 -> MediaStore.Audio.Artists.ARTIST + " like ?" + else -> throw Exception("[checkArtistsArgs] value don't exist!") + } +} + +fun checkGenresArgs(args: Int): String { + return when (args) { + 0 -> MediaStore.Audio.Genres.NAME + " like ?" + else -> throw Exception("[checkGenresArgs] value don't exist!") + } +} + diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/sorttypes/AlbumSortType.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/sorttypes/AlbumSortType.kt new file mode 100644 index 0000000..8cf5f7b --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/sorttypes/AlbumSortType.kt @@ -0,0 +1,20 @@ +package com.lucasjosino.on_audio_query.types.sorttypes + +import android.provider.MediaStore + +fun checkAlbumSortType(sortType: Int?, order: Int, ignoreCase: Boolean): String { + //[ASC] = Ascending Order + //[DESC] = Descending Order + //TODO: **Review this code later** + val orderAndCase: String = if (ignoreCase) { + if (order == 0) " COLLATE NOCASE ASC" else " COLLATE NOCASE DESC" + } else { + if (order == 0) " ASC" else " DESC" + } + return when (sortType) { + 0 -> MediaStore.Audio.Albums.ALBUM + orderAndCase + 1 -> MediaStore.Audio.Albums.ARTIST + orderAndCase + 2 -> MediaStore.Audio.Albums.NUMBER_OF_SONGS + orderAndCase + else -> MediaStore.Audio.Albums.DEFAULT_SORT_ORDER + orderAndCase + } +} \ No newline at end of file diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/sorttypes/ArtistSortType.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/sorttypes/ArtistSortType.kt new file mode 100644 index 0000000..321eaf8 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/sorttypes/ArtistSortType.kt @@ -0,0 +1,20 @@ +package com.lucasjosino.on_audio_query.types.sorttypes + +import android.provider.MediaStore + +fun checkArtistSortType(sortType: Int?, order: Int, ignoreCase: Boolean): String { + //[ASC] = Ascending Order + //[DESC] = Descending Order + //TODO: **Review this code later** + val orderAndCase: String = if (ignoreCase) { + if (order == 0) " COLLATE NOCASE ASC" else " COLLATE NOCASE DESC" + } else { + if (order == 0) " ASC" else " DESC" + } + return when (sortType) { + 0 -> MediaStore.Audio.Artists.ARTIST + orderAndCase + 1 -> MediaStore.Audio.Artists.NUMBER_OF_TRACKS + orderAndCase + 2 -> MediaStore.Audio.Artists.NUMBER_OF_ALBUMS + orderAndCase + else -> MediaStore.Audio.Artists.DEFAULT_SORT_ORDER + orderAndCase + } +} \ No newline at end of file diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/sorttypes/GenreSortType.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/sorttypes/GenreSortType.kt new file mode 100644 index 0000000..9b6141e --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/sorttypes/GenreSortType.kt @@ -0,0 +1,18 @@ +package com.lucasjosino.on_audio_query.types.sorttypes + +import android.provider.MediaStore + +fun checkGenreSortType(sortType: Int?, order: Int, ignoreCase: Boolean): String { + //[ASC] = Ascending Order + //[DESC] = Descending Order + //TODO: **Review this code later** + val orderAndCase: String = if (ignoreCase) { + if (order == 0) " COLLATE NOCASE ASC" else " COLLATE NOCASE DESC" + } else { + if (order == 0) " ASC" else " DESC" + } + return when (sortType) { + 0 -> MediaStore.Audio.Genres.NAME + orderAndCase + else -> MediaStore.Audio.Genres.DEFAULT_SORT_ORDER + orderAndCase + } +} \ No newline at end of file diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/sorttypes/PlaylistSortType.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/sorttypes/PlaylistSortType.kt new file mode 100644 index 0000000..9be3a4a --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/sorttypes/PlaylistSortType.kt @@ -0,0 +1,19 @@ +package com.lucasjosino.on_audio_query.types.sorttypes + +import android.provider.MediaStore + +fun checkPlaylistSortType(sortType: Int?, order: Int, ignoreCase: Boolean): String { + //[ASC] = Ascending Order + //[DESC] = Descending Order + //TODO: **Review this code later** + val orderAndCase: String = if (ignoreCase) { + if (order == 0) " COLLATE NOCASE ASC" else " COLLATE NOCASE DESC" + } else { + if (order == 0) " ASC" else " DESC" + } + return when (sortType) { + 0 -> MediaStore.Audio.Playlists.NAME + orderAndCase + 1 -> MediaStore.Audio.Playlists.DATE_ADDED + orderAndCase + else -> MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER + orderAndCase + } +} \ No newline at end of file diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/sorttypes/SongSortType.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/sorttypes/SongSortType.kt new file mode 100644 index 0000000..aca1151 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/types/sorttypes/SongSortType.kt @@ -0,0 +1,26 @@ +package com.lucasjosino.on_audio_query.types.sorttypes + +import android.annotation.SuppressLint +import android.provider.MediaStore + +@SuppressLint("InlinedApi") +fun checkSongSortType(sortType: Int?, order: Int, ignoreCase: Boolean): String { + //[ASC] = Ascending Order + //[DESC] = Descending Order + //TODO: **Review this code later** + val orderAndCase: String = if (ignoreCase) { + if (order == 0) " COLLATE NOCASE ASC" else " COLLATE NOCASE DESC" + } else { + if (order == 0) " ASC" else " DESC" + } + return when (sortType) { + 0 -> MediaStore.Audio.Media.TITLE + orderAndCase + 1 -> MediaStore.Audio.Media.ARTIST + orderAndCase + 2 -> MediaStore.Audio.Media.ALBUM + orderAndCase + 3 -> MediaStore.Audio.Media.DURATION + orderAndCase + 4 -> MediaStore.Audio.Media.DATE_ADDED + orderAndCase + 5 -> MediaStore.Audio.Media.SIZE + orderAndCase + 6 -> MediaStore.Audio.Media.DISPLAY_NAME + orderAndCase + else -> MediaStore.Audio.Media.DEFAULT_SORT_ORDER + orderAndCase + } +} \ No newline at end of file diff --git a/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/utils/CursorProjection.kt b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/utils/CursorProjection.kt new file mode 100644 index 0000000..db0d195 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/android/src/main/kotlin/com/lucasjosino/on_audio_query/utils/CursorProjection.kt @@ -0,0 +1,73 @@ +package com.lucasjosino.on_audio_query.utils + +import android.annotation.SuppressLint +import android.os.Build +import android.provider.MediaStore + +// Query songs projection +@SuppressLint("InlinedApi") +// Ignore the [Data] deprecation because this plugin support older versions. +@Suppress("DEPRECATION") +fun songProjection(): Array { + val tmpProjection = arrayListOf( + MediaStore.Audio.Media.DATA, // TODO: Deprecated + MediaStore.Audio.Media.DISPLAY_NAME, + MediaStore.Audio.Media._ID, + MediaStore.Audio.Media.SIZE, + MediaStore.Audio.Media.ALBUM, + MediaStore.Audio.Media.ALBUM_ARTIST, + MediaStore.Audio.Media.ALBUM_ID, + MediaStore.Audio.Media.ARTIST, + MediaStore.Audio.Media.ARTIST_ID, + MediaStore.Audio.Media.BOOKMARK, + MediaStore.Audio.Media.COMPOSER, + MediaStore.Audio.Media.DATE_ADDED, + MediaStore.Audio.Media.DATE_MODIFIED, + MediaStore.Audio.Media.DURATION, + MediaStore.Audio.Media.TITLE, + MediaStore.Audio.Media.TRACK, + MediaStore.Audio.Media.YEAR, + MediaStore.Audio.Media.IS_ALARM, + MediaStore.Audio.Media.IS_MUSIC, + MediaStore.Audio.Media.IS_NOTIFICATION, + MediaStore.Audio.Media.IS_PODCAST, + MediaStore.Audio.Media.IS_RINGTONE, + ) + + if (Build.VERSION.SDK_INT >= 29) { + tmpProjection.add(MediaStore.Audio.Media.IS_AUDIOBOOK) // Only Api >= 29 + } + + if (Build.VERSION.SDK_INT >= 30) { + tmpProjection.add(MediaStore.Audio.Media.GENRE) // Only Api >= 30 + tmpProjection.add(MediaStore.Audio.Media.GENRE_ID) // Only Api >= 30 + } + + return tmpProjection.toTypedArray() +} + + +// Query playlists projection +// Ignore the [Data] deprecation because this plugin support older versions. +@Suppress("DEPRECATION") +val playlistProjection = arrayOf( + MediaStore.Audio.Playlists.DATA, + MediaStore.Audio.Playlists._ID, + MediaStore.Audio.Playlists.DATE_ADDED, + MediaStore.Audio.Playlists.DATE_MODIFIED, + MediaStore.Audio.Playlists.NAME +) + +//Query artists projection +val artistProjection = arrayOf( + MediaStore.Audio.Artists._ID, + MediaStore.Audio.Artists.ARTIST, + MediaStore.Audio.Artists.NUMBER_OF_ALBUMS, + MediaStore.Audio.Artists.NUMBER_OF_TRACKS +) + +//Query genres projection +val genreProjection = arrayOf( + MediaStore.Audio.Genres._ID, + MediaStore.Audio.Genres.NAME +) \ No newline at end of file diff --git a/local_packages/on_audio_query_android-1.1.0/pubspec.yaml b/local_packages/on_audio_query_android-1.1.0/pubspec.yaml new file mode 100644 index 0000000..1b6d899 --- /dev/null +++ b/local_packages/on_audio_query_android-1.1.0/pubspec.yaml @@ -0,0 +1,37 @@ +# ======== +# author: Lucas Josino +# github: https://github.com/LucJosin +# website: https://www.lucasjosino.com/ +# ======== +name: on_audio_query_android +description: Android implementation of the on_audio_query plugin. +version: 1.1.0 +homepage: https://github.com/LucJosin/on_audio_query/tree/main/packages/on_audio_query_android +# pub.dev: https://pub.dev/packages/on_audio_query +# pub.dev (Android): https://pub.dev/packages/on_audio_query_android + +environment: + sdk: ">=2.17.0 <4.0.0" + flutter: ">=2.5.0" + +dependencies: + # on_audio_query + on_audio_query_platform_interface: ^1.7.0 + + # Flutter + flutter: + sdk: flutter + +dev_dependencies: + # Flutter + flutter_lints: ^2.0.0 + flutter_test: + sdk: flutter + +flutter: + plugin: + implements: on_audio_query + platforms: + android: + package: com.lucasjosino.on_audio_query + pluginClass: OnAudioQueryPlugin diff --git a/local_packages/tancent_vap-1.0.0+1/CHANGELOG.md b/local_packages/tancent_vap-1.0.0+1/CHANGELOG.md new file mode 100644 index 0000000..b524437 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/CHANGELOG.md @@ -0,0 +1,5 @@ +## 1.0.0+1 + +* VAPView with Android & iOS Support +* VAPView dynamic content support +* Can play with multiple widget diff --git a/local_packages/tancent_vap-1.0.0+1/LICENSE b/local_packages/tancent_vap-1.0.0+1/LICENSE new file mode 100644 index 0000000..a364f9c --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Mochamad Nizwar Syafuan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/README.md b/local_packages/tancent_vap-1.0.0+1/README.md new file mode 100644 index 0000000..29813bd --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/README.md @@ -0,0 +1,156 @@ +# Tencent VAP - Flutter + +A Flutter plugin that integrates Tencent's Video Animation Player (VAP) technology for high-performance video animations with dynamic content replacement. Supports multiple simultaneous animations through individual VAPView widgets. + +## Features + +- **High-Performance Playback**: Hardware-accelerated video animation rendering +- **Flexible Controls**: Loop options, scale types, audio control, lifecycle management +- **Dynamic Content**: Text and image injection (Base64, files, assets, URLs) +- **Event System**: Complete lifecycle and error handling callbacks +- **Multiple Instances**: Run concurrent animations with independent widgets + +### Platform Setup + +**Android** (`android/app/build.gradle`): +```gradle +android { + compileSdkVersion 34 + defaultConfig { + minSdkVersion 21 + } +} +``` + +**iOS** (`ios/Podfile`): +```ruby +platform :ios, '11.0' +``` + +## Quick Start + +```dart +// Basic VAP animation +VapView( + scaleType: ScaleType.fitCenter, + repeat: -1, + onViewCreated: (controller) { + controller.setAnimListener( + onVideoStart: () => print('Started'), + onVideoComplete: () => print('Completed'), + onFailed: (code, type, msg) => print('Error: $code'), + ); + controller.playAsset('animations/sample.mp4'); + }, +) + +// With dynamic content +VapView( + vapTagContents: { + 'username': TextContent('John Doe'), + 'avatar': ImageURLContent('https://example.com/avatar.jpg'), + 'badge': ImageAssetContent('assets/images/badge.png'), + }, + onViewCreated: (controller) => controller.playAsset('animations/profile.mp4'), +) +``` +## API Reference + +### VapView Properties +- `scaleType`: How video scales (`fitCenter`, `centerCrop`, `fitXY`) +- `repeat`: Loop count (0=once, -1=infinite, 1=2times, 2=3times and so on) +- `mute`: Audio enabled/disabled +- `vapTagContents`: Initial dynamic content +- `onViewCreated`: Controller callback + +### VapController Methods +```dart +// Playback +await controller.playFile('/path/to/file.mp4'); +await controller.playAsset('assets/anim.mp4'); +await controller.stop(); +await controller.setLoop(-1); + +// Content Management +await controller.setVapTagContent('tag', TextContent('text')); +await controller.setVapTagContents({'tag1': content1, 'tag2': content2}); +VAPContent? content = await controller.getVapTagContent('tag'); +await controller.clearVapTagContents(); + +// Events +controller.setAnimListener( + onVideoStart: () {}, + onVideoComplete: () {}, + onFailed: (code, type, msg) {}, +); +``` + +### VAPContent Types +- `TextContent('text')` - Dynamic text +- `ImageBase64Content('base64...')` - Base64 images +- `ImageFileContent('/path/file.jpg')` - Local files +- `ImageAssetContent('assets/img.png')` - Flutter assets +- `ImageURLContent('https://url.jpg')` - Remote images + +## Error Handling + +Common error codes: +- `-1`: File not found +- `-2`: Playback error +- `-1001`: Invalid video data +- `-1002`: Decoder malfunction +- `-1006`: File too large + +```dart +controller.setAnimListener( + onFailed: (code, type, message) { + switch (code) { + case -1: /* Handle file not found */; break; + case -1001: /* Handle corrupted data */; break; + default: print('Error $code: $message'); + } + }, +); +``` + +## Best Practices + +- **Memory Management**: Always dispose controllers in `dispose()` +- **Batch Updates**: Use `setVapTagContents()` for multiple content updates +- **Error Handling**: Wrap playback calls in try-catch blocks +- **Performance**: Use appropriate scale types and avoid unnecessary infinite loops + +## Troubleshooting + +1. **Animation not playing**: Check VAP file exists, verify asset registration +2. **Memory issues**: Dispose controllers, limit concurrent animations +3. **Content not updating**: Verify tag names, set content before playback +4. **iOS build issues**: Ensure iOS 11.0+, run `pod install` + +## Contributing + +We welcome contributions to improve this plugin! Please feel free to: + +- Report bugs and issues +- Suggest new features +- Submit pull requests +- Improve documentation + +## Credits + +This Flutter plugin is built upon the excellent work of Tencent's VAP project: + +- **Original VAP Project**: [https://github.com/Tencent/vap](https://github.com/Tencent/vap) +- **License**: MIT License +- **Tencent VAP Team**: For creating the foundational VAP technology + +We extend our gratitude to the Tencent team for developing and open-sourcing the VAP framework, which makes high-performance video animations accessible to developers worldwide. + +## [Instruction to create the Animation Video](https://github.com/Tencent/vap/blob/master/tool/README_en.md) + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +The original Tencent VAP project is also licensed under the MIT License. + \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/analysis_options.yaml b/local_packages/tancent_vap-1.0.0+1/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/local_packages/tancent_vap-1.0.0+1/android/app/build.gradle.kts b/local_packages/tancent_vap-1.0.0+1/android/app/build.gradle.kts new file mode 100644 index 0000000..e69de29 diff --git a/local_packages/tancent_vap-1.0.0+1/android/build.gradle b/local_packages/tancent_vap-1.0.0+1/android/build.gradle new file mode 100644 index 0000000..9e2f2e6 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/build.gradle @@ -0,0 +1,71 @@ +group = "com.laskarmedia.vap" +version = "1.0-SNAPSHOT" + +buildscript { + ext.kotlin_version = "2.1.0" + repositories { + google() + mavenCentral() + } + + dependencies { + classpath("com.android.tools.build:gradle:8.7.3") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: "com.android.library" +apply plugin: "kotlin-android" + +android { + namespace = "com.laskarmedia.vap" + + compileSdk = 36 + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11 + } + + sourceSets { + main.java.srcDirs += "src/main/kotlin" + test.java.srcDirs += "src/test/kotlin" + } + + defaultConfig { + minSdk = 21 + } + + dependencies { + testImplementation("org.jetbrains.kotlin:kotlin-test") + testImplementation("org.mockito:mockito-core:5.0.0") + } + + testOptions { + unitTests.all { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen {false} + showStandardStreams = true + } + } + } +} + +dependencies { +// implementation("io.github.tencent:vap:2.0.28") + implementation 'com.google.android.gms:play-services-basement:18.7.1' +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/settings.gradle b/local_packages/tancent_vap-1.0.0+1/android/settings.gradle new file mode 100644 index 0000000..8492daf --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'vap' diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/AndroidManifest.xml b/local_packages/tancent_vap-1.0.0+1/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a3bdc68 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/VapPlugin.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/VapPlugin.kt new file mode 100644 index 0000000..7ba2c25 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/VapPlugin.kt @@ -0,0 +1,41 @@ +package com.laskarmedia.vap + +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result + +import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding +import io.flutter.plugin.platform.PlatformViewRegistry + +/** VapPlugin */ +class VapPlugin : FlutterPlugin, MethodCallHandler { + /// The MethodChannel that will the communication between Flutter and native Android + /// + /// This local reference serves to register the plugin with the Flutter Engine and unregister it + /// when the Flutter Engine is detached from the Activity + private lateinit var channel: MethodChannel + + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "vap") + channel.setMethodCallHandler(this) + // Register the platform view + flutterPluginBinding.platformViewRegistry.registerViewFactory( + "vap_view", + VapViewFactory(flutterPluginBinding.binaryMessenger) + ) + } + + override fun onMethodCall(call: MethodCall, result: Result) { + if (call.method == "getPlatformVersion") { + result.success("Android ${android.os.Build.VERSION.RELEASE}") + } else { + result.notImplemented() + } + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } +} diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/VapView.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/VapView.kt new file mode 100644 index 0000000..4e99e50 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/VapView.kt @@ -0,0 +1,530 @@ +package com.laskarmedia.vap + +import android.content.Context +import android.graphics.Bitmap +import android.view.View +import io.flutter.plugin.platform.PlatformView +import io.flutter.plugin.platform.PlatformViewFactory +import io.flutter.plugin.common.StandardMessageCodec +import android.os.Handler +import android.os.Looper +import android.widget.FrameLayout +import android.util.Log +import androidx.annotation.IntegerRes +import com.tencent.qgame.animplayer.AnimConfig +import com.tencent.qgame.animplayer.AnimView +import com.tencent.qgame.animplayer.inter.IAnimListener +import com.tencent.qgame.animplayer.inter.IFetchResource +import com.tencent.qgame.animplayer.mix.Resource +import com.tencent.qgame.animplayer.util.ScaleType +import org.json.JSONObject + +// import com.tencent.vap.player.AnimView // Uncomment when VAP is available + +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MethodCall +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch + +class VapViewFactory(private val messenger: BinaryMessenger) : + PlatformViewFactory(StandardMessageCodec.INSTANCE) { + override fun create(context: Context, id: Int, args: Any?): PlatformView { + val params = args as? Map + return VapView(context, params, messenger, id) + } +} + +@OptIn(DelicateCoroutinesApi::class) +class VapView( + context: Context, params: Map?, messenger: BinaryMessenger, id: Int +) : PlatformView, MethodChannel.MethodCallHandler { + private val frameLayout = FrameLayout(context) + private val animView = AnimView(context) + private val channel = MethodChannel(messenger, "vap_view_$id") + private var playResult: MethodChannel.Result? = null + private val vapTagContents = mutableMapOf>() + + init { + // Set scaleType from params + when (params?.get("scaleType") as? String) { + "fitCenter" -> animView.setScaleType(ScaleType.FIT_CENTER) + "centerCrop" -> animView.setScaleType(ScaleType.CENTER_CROP) + "fitXY" -> animView.setScaleType(ScaleType.FIT_XY) + else -> animView.setScaleType(ScaleType.FIT_CENTER) + } + + channel.setMethodCallHandler(this) + frameLayout.addView(animView) + + // Initial playback if filePath or assetName is provided + (params?.get("filePath") as? String)?.let { playFile(it, null) } + (params?.get("assetName") as? String)?.let { playAsset(it, context, null) } + (params?.get("loop") as? Int)?.let { animView.setLoop(it) } + (params?.get("mute") as? Boolean)?.let { animView.setMute(it) } + (params?.get("decodeScale") as? Double)?.let { animView.decodeScale = it.toFloat() } + + // Set initial VAP tag contents from params + (params?.get("vapTagContents") as? Map>)?.let { contents -> + for ((tag, contentMap) in contents) { + vapTagContents[tag] = contentMap + Log.d("VapView", "Stored tag content for tag: $tag, contentType: ${contentMap["contentType"]} contentValue: ${contentMap["contentValue"]}") + } + } + + // Set the fetch resource callback to provide VAP tag contents + animView.setFetchResource(object : IFetchResource { + override fun fetchImage(resource: Resource, result: (Bitmap?) -> Unit) { + val contentMap = vapTagContents[resource.tag] + val contentValue = contentMap?.get("contentValue") + val contentType = contentMap?.get("contentType") + + if (!contentValue.isNullOrEmpty() && !contentType.isNullOrEmpty()) { + handleVapTagContent(contentValue, contentType, resource.tag) { bitmap -> + result(bitmap) + } + } else { + Log.w("VapView", "No content found for tag: ${resource.tag}") + result(null) + } + } + + override fun fetchText(resource: Resource, result: (String?) -> Unit) { + val contentMap = vapTagContents.get(resource.tag) + val contentValue = contentMap?.get("contentValue") + result(contentValue) + } + + override fun releaseResource(resources: List) { + // Don't clear vapTagContents here as they might be reused + // vapTagContents.clear() + } + }) + animView.setAnimListener(object : IAnimListener { + override fun onVideoConfigReady(config: AnimConfig): Boolean { + GlobalScope.launch(Dispatchers.Main) { + val animConfigMap = mutableMapOf() + config.let { + animConfigMap["width"] = it.width + animConfigMap["height"] = it.height + animConfigMap["fps"] = it.fps + animConfigMap["totalFrames"] = it.totalFrames + animConfigMap["videoHeight"] = it.videoHeight + animConfigMap["videoWidth"] = it.videoWidth + animConfigMap["isMix"] = it.isMix + animConfigMap["orien"] = it.orien + animConfigMap["alphaPointRect"] = mapOf( + "x" to it.alphaPointRect.x, + "y" to it.alphaPointRect.y, + "w" to it.alphaPointRect.w, + "h" to it.alphaPointRect.h + ) + animConfigMap["rgbPointRect"] = mapOf( + "x" to it.rgbPointRect.x, + "y" to it.rgbPointRect.y, + "w" to it.rgbPointRect.w, + "h" to it.rgbPointRect.h + ) + animConfigMap["version"] = it.version + } + val args = mapOf( + "config" to animConfigMap + ) + channel.invokeMethod("onVideoConfigReady", args) + } + return super.onVideoConfigReady(config) + } + + override fun onFailed(errorType: Int, errorMsg: String?) { + GlobalScope.launch(Dispatchers.Main) { + playError(errorType, errorMsg) + val args = mapOf("errorCode" to errorType, "errorMsg" to errorMsg, "errorType" to "VIDEO_PLAYBACK_ERROR") + channel.invokeMethod("onFailed", args) + } + } + + override fun onVideoComplete() { + GlobalScope.launch(Dispatchers.Main) { + playSuccess() + channel.invokeMethod("onVideoComplete", null) + } + } + + override fun onVideoDestroy() { + GlobalScope.launch(Dispatchers.Main) { + channel.invokeMethod("onVideoDestroy", null) + } + } + + override fun onVideoRender(frameIndex: Int, config: AnimConfig?) { + GlobalScope.launch(Dispatchers.Main) { + val animConfigMap = mutableMapOf() + config?.let { + animConfigMap["width"] = it.width + animConfigMap["height"] = it.height + animConfigMap["fps"] = it.fps + animConfigMap["totalFrames"] = it.totalFrames + animConfigMap["videoHeight"] = it.videoHeight + animConfigMap["videoWidth"] = it.videoWidth + animConfigMap["isMix"] = it.isMix + animConfigMap["orien"] = it.orien + animConfigMap["alphaPointRect"] = mapOf( + "x" to it.alphaPointRect.x, + "y" to it.alphaPointRect.y, + "w" to it.alphaPointRect.w, + "h" to it.alphaPointRect.h + ) + animConfigMap["rgbPointRect"] = mapOf( + "x" to it.rgbPointRect.x, + "y" to it.rgbPointRect.y, + "w" to it.rgbPointRect.w, + "h" to it.rgbPointRect.h + ) + animConfigMap["version"] = it.version + } + val args = mapOf( + "frameIndex" to frameIndex, "config" to animConfigMap + ) + channel.invokeMethod("onVideoRender", args) + } + } + + override fun onVideoStart() { + GlobalScope.launch(Dispatchers.Main) { + channel.invokeMethod("onVideoStart", null) + } + } + }) + } + + override fun getView(): View = frameLayout + + private fun playError(errorType: Int, errorMsg: String?) { + if (playResult != null) { + playResult?.error(errorType.toString(), errorMsg, "$errorType: VIDEO_PLAYBACK_ERROR") + playResult = null + } + } + + private fun playSuccess() { + if (playResult != null) { + playResult?.success(null) + playResult = null + } + } + + private fun reset() { + animView.stopPlay() + frameLayout.removeAllViews() + clearVapTagContents() + } + + override fun dispose() { + this.reset() + channel.setMethodCallHandler(null) + } + + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "dispose" -> { + dispose() + } + + "playFile" -> { + val path = call.argument("filePath") + playFile(path, result) + } + + "playAsset" -> { + val asset = call.argument("assetName") + playAsset(asset, frameLayout.context, result) + } + + "stop" -> { + animView.stopPlay() + result.success(null) + } + + "setLoop" -> { + val loop = call.argument("loop") ?: 1 + animView.setLoop(loop) + result.success(null) + } + + "setMute" -> { + val mute = call.argument("mute") ?: false + animView.setMute(mute) + result.success(null) + } + + "setScaleType" -> { + val type = call.argument("scaleType") + setScaleType(type) + result.success(null) + } + + "setVapTagContent" -> { + val tag = call.argument("tag") + val contentMap = call.argument>("content") + if (tag != null && contentMap != null) { + setVapTagContent(tag, contentMap) + } + result.success(null) + } + + "setVapTagContents" -> { + val contents = call.argument>>("contents") + if (contents != null) { + setVapTagContents(contents) + } + result.success(null) + } + + "getVapTagContent" -> { + val tag = call.argument("tag") + val content = if (tag != null) getVapTagContent(tag) else null + result.success(content) + } + + "getAllVapTagContents" -> { + result.success(vapTagContents.toMap()) + } + + "clearVapTagContents"->{ + clearVapTagContents() + result.success(null) + } + + else -> result.notImplemented() + } + } + + private fun playFile(path: String?, result: MethodChannel.Result?) { + if (path != null) { + playResult = result + animView.startPlay(java.io.File(path)) + } + } + + private fun playAsset(asset: String?, context: Context, result: MethodChannel.Result?) { + if (asset != null) { + playResult = result + animView.startPlay(context.assets, "flutter_assets/$asset") + } + } + + private fun setScaleType(type: String?) { + when (type) { + "fitCenter" -> animView.setScaleType(ScaleType.FIT_CENTER) + "centerCrop" -> animView.setScaleType(ScaleType.CENTER_CROP) + "fitXY" -> animView.setScaleType(ScaleType.FIT_XY) + else -> animView.setScaleType(ScaleType.FIT_CENTER) + } + } + + // VAP Tag Content Management + private fun setVapTagContent(tag: String, contentMap: Map) { + vapTagContents[tag] = contentMap + } + + private fun setVapTagContents(contents: Map>) { + vapTagContents.putAll(contents) + } + + private fun getVapTagContent(tag: String): String? { + return vapTagContents[tag]?.get("contentValue") + } + + private fun clearVapTagContents() { + vapTagContents.clear() + } + + // Handle VAP tag content - supports different content types + private fun handleVapTagContent(content: String, contentType: String, tag: String, callback: (Bitmap?) -> Unit) { + Log.d("VapView", "Processing tag: $tag, contentType: $contentType") + + try { + when (contentType) { + "image_base64" -> { + handleBase64Image(content, tag, callback) + } + "image_file" -> { + handleFileImage(content, tag, callback) + } + "image_asset" -> { + handleAssetImage(content, tag, callback) + } + "image_url" -> { + handleUrlImage(content, tag, callback) + } + "text" -> { + // Text content is not handled by image loading, skip + Log.d("VapView", "Text content type for tag: $tag, skipping image loading") + callback(null) + } + else -> { + Log.e("VapView", "Unsupported content type: $contentType for tag: $tag") + callback(null) + } + } + } catch (e: Exception) { + Log.e("VapView", "Error handling VAP tag content for tag '$tag': ${e.message}") + callback(null) + } + } + + private fun handleBase64Image(content: String, tag: String, callback: (Bitmap?) -> Unit) { + GlobalScope.launch(Dispatchers.IO) { + try { + var base64String = content + + // Remove data URL prefix if present (e.g., "data:image/png;base64,") + when { + content.startsWith("data:image/") -> { + val commaIndex = content.indexOf(",") + if (commaIndex != -1) { + base64String = content.substring(commaIndex + 1) + } + } + content.startsWith("base64:") -> { + base64String = content.substring(7) // Remove "base64:" prefix + } + } + + val byteArray = android.util.Base64.decode(base64String, android.util.Base64.DEFAULT) + val bitmap = android.graphics.BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) + + GlobalScope.launch(Dispatchers.Main) { + if (bitmap != null) { + Log.d("VapView", "Successfully decoded base64 image for tag: $tag") + } else { + Log.e("VapView", "Failed to decode base64 image for tag: $tag") + } + callback(bitmap) + } + } catch (e: Exception) { + Log.e("VapView", "Error decoding base64 image for tag '$tag': ${e.message}") + GlobalScope.launch(Dispatchers.Main) { + callback(null) + } + } + } + } + + private fun handleFileImage(filePath: String, tag: String, callback: (Bitmap?) -> Unit) { + GlobalScope.launch(Dispatchers.IO) { + try { + var fullPath = filePath + + // Handle different file path types + when { + filePath.startsWith("/") -> { + // Absolute path + fullPath = filePath + } + filePath.startsWith("file://") -> { + // File URL + fullPath = filePath.substring(7) + } + else -> { + // Relative path - try in different locations + val context = frameLayout.context + + // Try in external files dir + val externalFile = java.io.File(context.getExternalFilesDir(null), filePath) + if (externalFile.exists()) { + fullPath = externalFile.absolutePath + } else { + // Try in internal files dir + val internalFile = java.io.File(context.filesDir, filePath) + if (internalFile.exists()) { + fullPath = internalFile.absolutePath + } else { + // Use as-is and let it fail if invalid + fullPath = filePath + } + } + } + } + + val file = java.io.File(fullPath) + if (!file.exists()) { + Log.e("VapView", "File not found: $fullPath for tag: $tag") + GlobalScope.launch(Dispatchers.Main) { + callback(null) + } + return@launch + } + + val bitmap = android.graphics.BitmapFactory.decodeFile(fullPath) + GlobalScope.launch(Dispatchers.Main) { + if (bitmap != null) { + Log.d("VapView", "Successfully loaded file image for tag: $tag from: $fullPath") + } else { + Log.e("VapView", "Failed to decode image file: $fullPath for tag: $tag") + } + callback(bitmap) + } + } catch (e: Exception) { + Log.e("VapView", "Error loading file image for tag '$tag': ${e.message}") + GlobalScope.launch(Dispatchers.Main) { + callback(null) + } + } + } + } + + private fun handleAssetImage(assetPath: String, tag: String, callback: (Bitmap?) -> Unit) { + GlobalScope.launch(Dispatchers.IO) { + try { + val context = frameLayout.context + val inputStream = context.assets.open("flutter_assets/$assetPath") + val bitmap = android.graphics.BitmapFactory.decodeStream(inputStream) + inputStream.close() + + GlobalScope.launch(Dispatchers.Main) { + if (bitmap != null) { + Log.d("VapView", "Successfully loaded asset image for tag: $tag from: $assetPath") + } else { + Log.e("VapView", "Failed to decode asset image: $assetPath for tag: $tag") + } + callback(bitmap) + } + } catch (e: Exception) { + Log.e("VapView", "Error loading asset image for tag '$tag': ${e.message}") + GlobalScope.launch(Dispatchers.Main) { + callback(null) + } + } + } + } + + private fun handleUrlImage(url: String, tag: String, callback: (Bitmap?) -> Unit) { + GlobalScope.launch(Dispatchers.IO) { + try { + val connection = java.net.URL(url).openConnection() + connection.doInput = true + connection.connect() + val inputStream = connection.getInputStream() + val bitmap = android.graphics.BitmapFactory.decodeStream(inputStream) + inputStream.close() + + GlobalScope.launch(Dispatchers.Main) { + if (bitmap != null) { + Log.d("VapView", "Successfully loaded URL image for tag: $tag from: $url") + } else { + Log.e("VapView", "Failed to decode URL image: $url for tag: $tag") + } + callback(bitmap) + } + } catch (e: Exception) { + Log.e("VapView", "Error loading URL image for tag '$tag': ${e.message}") + GlobalScope.launch(Dispatchers.Main) { + callback(null) + } + } + } + } +} diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/AnimConfig.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/AnimConfig.kt new file mode 100644 index 0000000..bb88f05 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/AnimConfig.kt @@ -0,0 +1,92 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer + +import android.graphics.Bitmap +import android.graphics.MaskFilter +import com.tencent.qgame.animplayer.mask.MaskConfig +import com.tencent.qgame.animplayer.util.ALog +import org.json.JSONException +import org.json.JSONObject + +/** + * vapc里读取出来的基础配置 + */ +class AnimConfig { + + companion object { + private const val TAG = "${Constant.TAG}.AnimConfig" + } + + val version = 2 // 不同版本号不兼容 + var totalFrames = 0 // 总帧数 + var width = 0 // 需要显示视频的真实宽高 + var height = 0 + var videoWidth = 0 // 视频实际宽高 + var videoHeight = 0 + var orien = Constant.ORIEN_DEFAULT // 0-兼容模式 1-竖屏 2-横屏 + var fps = 0 + var isMix = false // 是否为融合动画 + var alphaPointRect = PointRect(0, 0 ,0 ,0) // alpha区域 + var rgbPointRect = PointRect(0, 0, 0, 0) // rgb区域 + var isDefaultConfig = false // 没有vapc配置时默认逻辑 + var defaultVideoMode = Constant.VIDEO_MODE_SPLIT_HORIZONTAL + + var maskConfig: MaskConfig ?= null + var jsonConfig: JSONObject? = null + + + /** + * @return 解析是否成功,失败按默认配置走 + */ + fun parse(json: JSONObject): Boolean { + return try { + json.getJSONObject("info").apply { + val v = getInt("v") + if (version != v) { + ALog.e(TAG, "current version=$version target=$v") + return false + } + totalFrames = getInt("f") + width = getInt("w") + height = getInt("h") + videoWidth = getInt("videoW") + videoHeight = getInt("videoH") + orien = getInt("orien") + fps = getInt("fps") + isMix = getInt("isVapx") == 1 + val a = getJSONArray("aFrame") ?: return false + alphaPointRect = PointRect(a.getInt(0), a.getInt(1), a.getInt(2), a.getInt(3)) + val c = getJSONArray("rgbFrame") ?: return false + rgbPointRect = PointRect(c.getInt(0), c.getInt(1), c.getInt(2), c.getInt(3)) + } + true + } catch (e : JSONException) { + ALog.e(TAG, "json parse fail $e", e) + false + } + } + + override fun toString(): String { + return "AnimConfig(version=$version, totalFrames=$totalFrames, width=$width, height=$height, videoWidth=$videoWidth, videoHeight=$videoHeight, orien=$orien, fps=$fps, isMix=$isMix, alphaPointRect=$alphaPointRect, rgbPointRect=$rgbPointRect, isDefaultConfig=$isDefaultConfig)" + } + + +} + + +data class PointRect(val x: Int, val y: Int, val w: Int, val h: Int) +data class RefVec2(val w: Int, val h: Int) //参考宽&高 \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/AnimConfigManager.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/AnimConfigManager.kt new file mode 100644 index 0000000..2a346df --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/AnimConfigManager.kt @@ -0,0 +1,188 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer + +import android.os.SystemClock +import com.tencent.qgame.animplayer.file.IFileContainer +import com.tencent.qgame.animplayer.util.ALog +import org.json.JSONObject +import java.nio.charset.Charset + +/** + * 配置管理 + */ +class AnimConfigManager(val player: AnimPlayer) { + + companion object { + private const val TAG = "${Constant.TAG}.AnimConfigManager" + } + + var config: AnimConfig? = null + var isParsingConfig = false // 是否正在读取配置 + + /** + * 解析配置 + * @return true 解析成功 false 解析失败 + */ + fun parseConfig(fileContainer: IFileContainer, enableVersion1: Boolean, defaultVideoMode: Int, defaultFps: Int): Int { + try { + isParsingConfig = true + // 解析vapc + val time = SystemClock.elapsedRealtime() + val result = parse(fileContainer, defaultVideoMode, defaultFps) + ALog.i(TAG, "parseConfig cost=${SystemClock.elapsedRealtime() - time}ms enableVersion1=$enableVersion1 result=$result") + if (!result) { + isParsingConfig = false + return Constant.REPORT_ERROR_TYPE_PARSE_CONFIG + } + if (config?.isDefaultConfig == true && !enableVersion1) { + isParsingConfig = false + return Constant.REPORT_ERROR_TYPE_PARSE_CONFIG + } + // 插件解析配置 + val resultCode = config?.let { + player.pluginManager.onConfigCreate(it) + } ?: Constant.OK + isParsingConfig = false + return resultCode + } catch (e : Throwable) { + ALog.e(TAG, "parseConfig error $e", e) + isParsingConfig = false + return Constant.REPORT_ERROR_TYPE_PARSE_CONFIG + } + } + + /** + * 默认配置解析(兼容老视频格式) + */ + fun defaultConfig(_videoWidth: Int, _videoHeight: Int) { + if (config?.isDefaultConfig == false) return + config?.apply { + videoWidth = _videoWidth + videoHeight = _videoHeight + when (defaultVideoMode) { + Constant.VIDEO_MODE_SPLIT_HORIZONTAL -> { + // 视频左右对齐(alpha左\rgb右) + width = _videoWidth / 2 + height = _videoHeight + alphaPointRect = PointRect(0, 0, width, height) + rgbPointRect = PointRect(width, 0, width, height) + } + Constant.VIDEO_MODE_SPLIT_VERTICAL -> { + // 视频上下对齐(alpha上\rgb下) + width = _videoWidth + height = _videoHeight / 2 + alphaPointRect = PointRect(0, 0, width, height) + rgbPointRect = PointRect(0, height, width, height) + } + Constant.VIDEO_MODE_SPLIT_HORIZONTAL_REVERSE -> { + // 视频左右对齐(rgb左\alpha右) + width = _videoWidth / 2 + height = _videoHeight + rgbPointRect = PointRect(0, 0, width, height) + alphaPointRect = PointRect(width, 0, width, height) + } + Constant.VIDEO_MODE_SPLIT_VERTICAL_REVERSE -> { + // 视频上下对齐(rgb上\alpha下) + width = _videoWidth + height = _videoHeight / 2 + rgbPointRect = PointRect(0, 0, width, height) + alphaPointRect = PointRect(0, height, width, height) + } + else -> { + // 默认视频左右对齐(alpha左\rgb右) + width = _videoWidth / 2 + height = _videoHeight + alphaPointRect = PointRect(0, 0, width, height) + rgbPointRect = PointRect(width, 0, width, height) + } + } + } + } + + + private fun parse(fileContainer: IFileContainer, defaultVideoMode: Int, defaultFps: Int): Boolean { + + val config = AnimConfig() + this.config = config + + + // 查找vapc box + fileContainer.startRandomRead() + val boxHead = ByteArray(8) + var head: BoxHead? = null + var vapcStartIndex: Long = 0 + while (fileContainer.read(boxHead, 0, boxHead.size) == 8) { + val h = parseBoxHead(boxHead) ?: break + if ("vapc" == h.type) { + h.startIndex = vapcStartIndex + head = h + break + } + vapcStartIndex += h.length + fileContainer.skip(h.length - 8L) + } + + if (head == null) { + ALog.e(TAG, "vapc box head not found") + // 按照默认配置生成config + config.apply { + isDefaultConfig = true + this.defaultVideoMode = defaultVideoMode + fps = defaultFps + } + player.fps = config.fps + return true + } + + // 读取vapc box + val vapcBuf = ByteArray(head.length - 8) // ps: OOM exception + fileContainer.read(vapcBuf, 0 , vapcBuf.size) + fileContainer.closeRandomRead() + + val json = String(vapcBuf, 0, vapcBuf.size, Charset.forName("UTF-8")) + val jsonObj = JSONObject(json) + config.jsonConfig = jsonObj + val result = config.parse(jsonObj) + if (defaultFps > 0) { + config.fps = defaultFps + } + player.fps = config.fps + return result + } + + private fun parseBoxHead(boxHead: ByteArray): BoxHead? { + if (boxHead.size != 8) return null + val head = BoxHead() + var length: Int = 0 + length = length or (boxHead[0].toInt() and 0xff shl 24) + length = length or (boxHead[1].toInt() and 0xff shl 16) + length = length or (boxHead[2].toInt() and 0xff shl 8) + length = length or (boxHead[3].toInt() and 0xff) + head.length = length + head.type = String(boxHead, 4, 4, Charset.forName("US-ASCII")) + return head + } + + private class BoxHead { + var startIndex: Long = 0 + var length: Int = 0 + var type: String? = null + } + + + +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/AnimPlayer.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/AnimPlayer.kt new file mode 100644 index 0000000..f5294ee --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/AnimPlayer.kt @@ -0,0 +1,160 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer + +import com.tencent.qgame.animplayer.file.IFileContainer +import com.tencent.qgame.animplayer.inter.IAnimListener +import com.tencent.qgame.animplayer.mask.MaskConfig +import com.tencent.qgame.animplayer.plugin.AnimPluginManager +import com.tencent.qgame.animplayer.util.ALog + +class AnimPlayer(val animView: IAnimView) { + + companion object { + private const val TAG = "${Constant.TAG}.AnimPlayer" + } + + var animListener: IAnimListener? = null + var decoder: Decoder? = null + var audioPlayer: AudioPlayer? = null + var fps: Int = 0 + set(value) { + decoder?.fps = value + field = value + } + // 设置默认的fps <= 0 表示以vapc配置为准 > 0 表示以此设置为准 + var defaultFps: Int = 0 + var playLoop: Int = 0 + set(value) { + decoder?.playLoop = value + audioPlayer?.playLoop = value + field = value + } + var supportMaskBoolean : Boolean = false + var maskEdgeBlurBoolean : Boolean = false + // 是否兼容老版本 默认不兼容 + var enableVersion1 : Boolean = false + // 解码分辨率缩放比例 1.0=完整分辨率 0.5=一半分辨率 + var decodeScale: Float = 1.0f + // 视频模式 + var videoMode: Int = Constant.VIDEO_MODE_SPLIT_HORIZONTAL + var isDetachedFromWindow = false + var isSurfaceAvailable = false + var startRunnable: Runnable? = null + var isStartRunning = false // 启动时运行状态 + var isMute = false // 是否静音 + + val configManager = AnimConfigManager(this) + val pluginManager = AnimPluginManager(this) + + fun onSurfaceTextureDestroyed() { + isSurfaceAvailable = false + isStartRunning = false + decoder?.destroy() + audioPlayer?.destroy() + } + + fun onSurfaceTextureAvailable(width: Int, height: Int) { + isSurfaceAvailable = true + startRunnable?.run() + startRunnable = null + } + + + fun onSurfaceTextureSizeChanged(width: Int, height: Int) { + decoder?.onSurfaceSizeChanged(width, height) + } + + fun startPlay(fileContainer: IFileContainer) { + isStartRunning = true + prepareDecoder() + if (decoder?.prepareThread() == false) { + isStartRunning = false + decoder?.onFailed(Constant.REPORT_ERROR_TYPE_CREATE_THREAD, Constant.ERROR_MSG_CREATE_THREAD) + decoder?.onVideoComplete() + return + } + // 在线程中解析配置 + decoder?.renderThread?.handler?.post { + val result = configManager.parseConfig(fileContainer, enableVersion1, videoMode, defaultFps) + if (result != Constant.OK) { + isStartRunning = false + decoder?.onFailed(result, Constant.getErrorMsg(result)) + decoder?.onVideoComplete() + return@post + } + ALog.i(TAG, "parse ${configManager.config}") + val config = configManager.config + // 如果是默认配置,因为信息不完整onVideoConfigReady不会被调用 + if (config != null && (config.isDefaultConfig || animListener?.onVideoConfigReady(config) == true)) { + innerStartPlay(fileContainer) + } else { + ALog.i(TAG, "onVideoConfigReady return false") + } + } + } + + private fun innerStartPlay(fileContainer: IFileContainer) { + synchronized(AnimPlayer::class.java) { + if (isSurfaceAvailable) { + isStartRunning = false + decoder?.start(fileContainer) + if (!isMute) { + audioPlayer?.start(fileContainer) + } + } else { + startRunnable = Runnable { + innerStartPlay(fileContainer) + } + animView.prepareTextureView() + } + } + } + + fun stopPlay() { + decoder?.stop() + audioPlayer?.stop() + } + + fun isRunning(): Boolean { + return isStartRunning // 启动过程运行状态 + || (decoder?.isRunning ?: false) // 解码过程运行状态 + + } + + private fun prepareDecoder() { + if (decoder == null) { + decoder = HardDecoder(this).apply { + playLoop = this@AnimPlayer.playLoop + fps = this@AnimPlayer.fps + this.decodeScale = this@AnimPlayer.decodeScale + } + } + if (audioPlayer == null) { + audioPlayer = AudioPlayer(this).apply { + playLoop = this@AnimPlayer.playLoop + } + } + } + + fun updateMaskConfig(maskConfig: MaskConfig?) { + configManager.config?.maskConfig = configManager.config?.maskConfig ?: MaskConfig() + configManager.config?.maskConfig?.safeSetMaskBitmapAndReleasePre(maskConfig?.alphaMaskBitmap) + configManager.config?.maskConfig?.maskPositionPair = maskConfig?.maskPositionPair + configManager.config?.maskConfig?.maskTexPair = maskConfig?.maskTexPair + } + +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/AnimView.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/AnimView.kt new file mode 100644 index 0000000..09b2abd --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/AnimView.kt @@ -0,0 +1,321 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer + +import android.content.Context +import android.content.res.AssetManager +import android.graphics.SurfaceTexture +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.util.AttributeSet +import android.view.TextureView +import android.view.View +import android.widget.FrameLayout +import com.tencent.qgame.animplayer.file.AssetsFileContainer +import com.tencent.qgame.animplayer.file.FileContainer +import com.tencent.qgame.animplayer.file.IFileContainer +import com.tencent.qgame.animplayer.inter.IAnimListener +import com.tencent.qgame.animplayer.inter.IFetchResource +import com.tencent.qgame.animplayer.inter.OnResourceClickListener +import com.tencent.qgame.animplayer.mask.MaskConfig +import com.tencent.qgame.animplayer.textureview.InnerTextureView +import com.tencent.qgame.animplayer.util.ALog +import com.tencent.qgame.animplayer.util.IScaleType +import com.tencent.qgame.animplayer.util.ScaleType +import com.tencent.qgame.animplayer.util.ScaleTypeUtil +import java.io.File + +open class AnimView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0): + IAnimView, + FrameLayout(context, attrs, defStyleAttr), + TextureView.SurfaceTextureListener { + + companion object { + private const val TAG = "${Constant.TAG}.AnimView" + } + private lateinit var player: AnimPlayer + + private val uiHandler by lazy { Handler(Looper.getMainLooper()) } + private var surface: SurfaceTexture? = null + private var animListener: IAnimListener? = null + private var innerTextureView: InnerTextureView? = null + private var lastFile: IFileContainer? = null + private val scaleTypeUtil = ScaleTypeUtil() + + // 代理监听 + private val animProxyListener by lazy { + object : IAnimListener { + + override fun onVideoConfigReady(config: AnimConfig): Boolean { + scaleTypeUtil.setVideoSize(config.width, config.height) + return animListener?.onVideoConfigReady(config) ?: super.onVideoConfigReady(config) + } + + override fun onVideoStart() { + animListener?.onVideoStart() + } + + override fun onVideoRender(frameIndex: Int, config: AnimConfig?) { + animListener?.onVideoRender(frameIndex, config) + } + + override fun onVideoComplete() { + hide() + animListener?.onVideoComplete() + } + + override fun onVideoDestroy() { + hide() + animListener?.onVideoDestroy() + } + + override fun onFailed(errorType: Int, errorMsg: String?) { + animListener?.onFailed(errorType, errorMsg) + } + + } + } + + // 保证AnimView已经布局完成才加入TextureView + private var onSizeChangedCalled = false + private var needPrepareTextureView = false + private val prepareTextureViewRunnable = Runnable { + removeAllViews() + innerTextureView = InnerTextureView(context).apply { + player = this@AnimView.player + isOpaque = false + surfaceTextureListener = this@AnimView + layoutParams = scaleTypeUtil.getLayoutParam(this) + } + addView(innerTextureView) + } + + + init { + hide() + player = AnimPlayer(this) + player.animListener = animProxyListener + } + + + override fun prepareTextureView() { + if (onSizeChangedCalled) { + uiHandler.post(prepareTextureViewRunnable) + } else { + ALog.e(TAG, "onSizeChanged not called") + needPrepareTextureView = true + } + } + + override fun getSurfaceTexture(): SurfaceTexture? { + return innerTextureView?.surfaceTexture ?: surface + } + + override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) { + ALog.i(TAG, "onSurfaceTextureSizeChanged $width x $height") + player.onSurfaceTextureSizeChanged(width, height) + } + + override fun onSurfaceTextureUpdated(surface: SurfaceTexture) { + } + + override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean { + ALog.i(TAG, "onSurfaceTextureDestroyed") + this.surface = null + player.onSurfaceTextureDestroyed() + uiHandler.post { + innerTextureView?.surfaceTextureListener = null + innerTextureView = null + removeAllViews() + } + return true + } + + override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) { + ALog.i(TAG, "onSurfaceTextureAvailable width=$width height=$height") + this.surface = surface + player.onSurfaceTextureAvailable(width, height) + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + ALog.i(TAG, "onSizeChanged w=$w, h=$h") + scaleTypeUtil.setLayoutSize(w, h) + onSizeChangedCalled = true + // 需要保证onSizeChanged被调用 + if (needPrepareTextureView) { + needPrepareTextureView = false + prepareTextureView() + } + } + + override fun onAttachedToWindow() { + ALog.i(TAG, "onAttachedToWindow") + super.onAttachedToWindow() + player.isDetachedFromWindow = false + // 自动恢复播放 + if (player.playLoop > 0) { + lastFile?.apply { + startPlay(this) + } + } + } + + override fun onDetachedFromWindow() { + ALog.i(TAG, "onDetachedFromWindow") + super.onDetachedFromWindow() + player.isDetachedFromWindow = true + player.onSurfaceTextureDestroyed() + } + + + override fun setAnimListener(animListener: IAnimListener?) { + this.animListener = animListener + } + + override fun setFetchResource(fetchResource: IFetchResource?) { + player.pluginManager.getMixAnimPlugin()?.resourceRequest = fetchResource + } + + override fun setOnResourceClickListener(resourceClickListener: OnResourceClickListener?) { + player.pluginManager.getMixAnimPlugin()?.resourceClickListener = resourceClickListener + } + + /** + * 兼容方案,优先保证表情显示 + */ + open fun enableAutoTxtColorFill(enable: Boolean) { + player.pluginManager.getMixAnimPlugin()?.autoTxtColorFill = enable + } + + override fun setLoop(playLoop: Int) { + player.playLoop = playLoop + } + + override fun supportMask(isSupport : Boolean, isEdgeBlur : Boolean) { + player.supportMaskBoolean = isSupport + player.maskEdgeBlurBoolean = isEdgeBlur + } + + override fun updateMaskConfig(maskConfig: MaskConfig?) { + player.updateMaskConfig(maskConfig) + } + + + @Deprecated("Compatible older version mp4, default false") + fun enableVersion1(enable: Boolean) { + player.enableVersion1 = enable + } + + // 兼容老版本视频模式 + @Deprecated("Compatible older version mp4") + fun setVideoMode(mode: Int) { + player.videoMode = mode + } + + override fun setFps(fps: Int) { + ALog.i(TAG, "setFps=$fps") + player.defaultFps = fps + } + + /** + * Set decode resolution scale factor. + * @param scale 1.0 = full resolution, 0.5 = half resolution + */ + var decodeScale: Float = 1.0f + set(value) { + field = value + player.decodeScale = value + } + + override fun setScaleType(type : ScaleType) { + scaleTypeUtil.currentScaleType = type + } + + override fun setScaleType(scaleType: IScaleType) { + scaleTypeUtil.scaleTypeImpl = scaleType + } + + /** + * @param isMute true 静音 + */ + override fun setMute(isMute: Boolean) { + ALog.e(TAG, "set mute=$isMute") + player.isMute = isMute + } + + override fun startPlay(file: File) { + try { + val fileContainer = FileContainer(file) + startPlay(fileContainer) + } catch (e: Throwable) { + animProxyListener.onFailed(Constant.REPORT_ERROR_TYPE_FILE_ERROR, Constant.ERROR_MSG_FILE_ERROR) + animProxyListener.onVideoComplete() + } + } + + override fun startPlay(assetManager: AssetManager, assetsPath: String) { + try { + val fileContainer = AssetsFileContainer(assetManager, assetsPath) + startPlay(fileContainer) + } catch (e: Throwable) { + animProxyListener.onFailed(Constant.REPORT_ERROR_TYPE_FILE_ERROR, Constant.ERROR_MSG_FILE_ERROR) + animProxyListener.onVideoComplete() + } + } + + + override fun startPlay(fileContainer: IFileContainer) { + ui { + if (visibility != View.VISIBLE) { + ALog.e(TAG, "AnimView is GONE, can't play") + return@ui + } + if (!player.isRunning()) { + lastFile = fileContainer + player.startPlay(fileContainer) + } else { + ALog.e(TAG, "is running can not start") + } + } + } + + + override fun stopPlay() { + player.stopPlay() + } + + override fun isRunning(): Boolean { + return player.isRunning() + } + + override fun getRealSize(): Pair { + return scaleTypeUtil.getRealSize() + } + + private fun hide() { + lastFile?.close() + ui { + removeAllViews() + } + } + + private fun ui(f:()->Unit) { + if (Looper.myLooper() == Looper.getMainLooper()) f() else uiHandler.post { f() } + } + +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/AudioPlayer.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/AudioPlayer.kt new file mode 100644 index 0000000..80b0f9a --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/AudioPlayer.kt @@ -0,0 +1,211 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer + +import android.media.* +import com.tencent.qgame.animplayer.file.IFileContainer +import com.tencent.qgame.animplayer.util.ALog +import com.tencent.qgame.animplayer.util.MediaUtil +import java.lang.RuntimeException + +class AudioPlayer(val player: AnimPlayer) { + + companion object { + private const val TAG = "${Constant.TAG}.AudioPlayer" + } + + var extractor: MediaExtractor? = null + var decoder: MediaCodec? = null + var audioTrack: AudioTrack? = null + val decodeThread = HandlerHolder(null, null) + var isRunning = false + var playLoop = 0 + var isStopReq = false + var needDestroy = false + + + + private fun prepareThread(): Boolean { + return Decoder.createThread(decodeThread, "anim_audio_thread") + } + + fun start(fileContainer: IFileContainer) { + isStopReq = false + needDestroy = false + if (!prepareThread()) return + if (isRunning) { + stop() + } + isRunning = true + decodeThread.handler?.post { + try { + startPlay(fileContainer) + } catch (e: Throwable) { + ALog.e(TAG, "Audio exception=$e", e) + release() + } + } + } + + fun stop() { + isStopReq = true + } + + private fun startPlay(fileContainer: IFileContainer) { + val extractor = MediaUtil.getExtractor(fileContainer) + this.extractor = extractor + val audioIndex = MediaUtil.selectAudioTrack(extractor) + if (audioIndex < 0) { + ALog.e(TAG, "cannot find audio track") + release() + return + } + extractor.selectTrack(audioIndex) + val format = extractor.getTrackFormat(audioIndex) + val mime = format.getString(MediaFormat.KEY_MIME) ?: "" + ALog.i(TAG, "audio mime=$mime") + if (!MediaUtil.checkSupportCodec(mime)) { + ALog.e(TAG, "mime=$mime not support") + release() + return + } + + val decoder = MediaCodec.createDecoderByType(mime).apply { + configure(format, null, null, 0) + start() + } + this.decoder = decoder + + val decodeInputBuffers = decoder.inputBuffers + var decodeOutputBuffers = decoder.outputBuffers + + val bufferInfo = MediaCodec.BufferInfo() + val sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE) + val channelConfig = getChannelConfig(format.getInteger(MediaFormat.KEY_CHANNEL_COUNT)) + + val bufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT) + val audioTrack = AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM) + this.audioTrack = audioTrack + val state = audioTrack.state + if (state != AudioTrack.STATE_INITIALIZED) { + release() + ALog.e(TAG, "init audio track failure") + return + } + audioTrack.play() + val timeOutUs = 1000L + var isEOS = false + while (!isStopReq) { + if (!isEOS) { + val inputIndex = decoder.dequeueInputBuffer(timeOutUs) + if (inputIndex >= 0) { + val inputBuffer = decodeInputBuffers[inputIndex] + inputBuffer.clear() + val sampleSize = extractor.readSampleData(inputBuffer, 0) + if (sampleSize < 0) { + isEOS = true + decoder.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM) + } else { + decoder.queueInputBuffer(inputIndex, 0, sampleSize, 0, 0) + extractor.advance() + } + } + } + val outputIndex = decoder.dequeueOutputBuffer(bufferInfo, 0) + if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + decodeOutputBuffers = decoder.outputBuffers + } + if (outputIndex >= 0) { + val outputBuffer = decodeOutputBuffers[outputIndex] + val chunkPCM = ByteArray(bufferInfo.size) + outputBuffer.get(chunkPCM) + outputBuffer.clear() + audioTrack.write(chunkPCM, 0, bufferInfo.size) + decoder.releaseOutputBuffer(outputIndex, false) + } + + if (isEOS && bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) { + if (--playLoop > 0) { + ALog.d(TAG, "Reached EOS, looping -> playLoop") + extractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC) + decoder.flush() + isEOS = false + } else { + ALog.i(TAG, "decode finish") + release() + break + } + } + } + release() + } + + + private fun release() { + try { + decoder?.apply { + stop() + release() + } + decoder = null + extractor?.release() + extractor = null + audioTrack?.apply { + pause() + flush() + stop() + release() + } + audioTrack = null + } catch (e: Throwable) { + ALog.e(TAG, "release exception=$e", e) + } + isRunning = false + if (needDestroy) { + destroyInner() + } + } + + fun destroy() { + if (isRunning) { + needDestroy = true + stop() + } else { + destroyInner() + } + } + + private fun destroyInner() { + if (player.isDetachedFromWindow) { + ALog.i(TAG, "destroyThread") + decodeThread.handler?.removeCallbacksAndMessages(null) + decodeThread.thread = Decoder.quitSafely(decodeThread.thread) + } + } + + private fun getChannelConfig(channelCount: Int): Int { + return when (channelCount) { + 1 -> AudioFormat.CHANNEL_CONFIGURATION_MONO + 2 -> AudioFormat.CHANNEL_OUT_STEREO + 3 -> AudioFormat.CHANNEL_OUT_STEREO or AudioFormat.CHANNEL_OUT_FRONT_CENTER + 4 -> AudioFormat.CHANNEL_OUT_QUAD + 5 -> AudioFormat.CHANNEL_OUT_QUAD or AudioFormat.CHANNEL_OUT_FRONT_CENTER + 6 -> AudioFormat.CHANNEL_OUT_5POINT1 + 7 -> AudioFormat.CHANNEL_OUT_5POINT1 or AudioFormat.CHANNEL_OUT_BACK_CENTER + else -> throw RuntimeException("Unsupported channel count: $channelCount") + } + } +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/Constant.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/Constant.kt new file mode 100644 index 0000000..cc553e1 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/Constant.kt @@ -0,0 +1,70 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer + +object Constant { + const val TAG = "AnimPlayer" + + // 视频适配的屏幕方向 + const val ORIEN_DEFAULT = 0 // 兼容模式 + const val ORIEN_PORTRAIT = 1 // 适配竖屏的视频 + const val ORIEN_LANDSCAPE = 2 // 适配横屏的视频 + + // 视频对齐方式 (兼容老版本视频模式) + @Deprecated("Compatible older version mp4") + const val VIDEO_MODE_SPLIT_HORIZONTAL = 1 // 视频左右对齐(alpha左\rgb右) + @Deprecated("Compatible older version mp4") + const val VIDEO_MODE_SPLIT_VERTICAL = 2 // 视频上下对齐(alpha上\rgb下) + @Deprecated("Compatible older version mp4") + const val VIDEO_MODE_SPLIT_HORIZONTAL_REVERSE = 3 // 视频左右对齐(rgb左\alpha右) + @Deprecated("Compatible older version mp4") + const val VIDEO_MODE_SPLIT_VERTICAL_REVERSE = 4 // 视频上下对齐(rgb上\alpha下) + + + const val OK = 0 // 成功 + + const val REPORT_ERROR_TYPE_EXTRACTOR_EXC = 10001 // MediaExtractor exception + const val REPORT_ERROR_TYPE_DECODE_EXC = 10002 // MediaCodec exception + const val REPORT_ERROR_TYPE_CREATE_THREAD = 10003 // 线程创建失败 + const val REPORT_ERROR_TYPE_CREATE_RENDER = 10004 // render创建失败 + const val REPORT_ERROR_TYPE_PARSE_CONFIG = 10005 // 配置解析失败 + const val REPORT_ERROR_TYPE_CONFIG_PLUGIN_MIX = 10006 // vapx融合动画资源获取失败 + const val REPORT_ERROR_TYPE_FILE_ERROR = 10007 // 文件无法读取 + const val REPORT_ERROR_TYPE_HEVC_NOT_SUPPORT = 10008 // 不支持h265 + + const val ERROR_MSG_EXTRACTOR_EXC = "0x1 MediaExtractor exception" // MediaExtractor exception + const val ERROR_MSG_DECODE_EXC = "0x2 MediaCodec exception" // MediaCodec exception + const val ERROR_MSG_CREATE_THREAD = "0x3 thread create fail" // 线程创建失败 + const val ERROR_MSG_CREATE_RENDER = "0x4 render create fail" // render创建失败 + const val ERROR_MSG_PARSE_CONFIG = "0x5 parse config fail" // 配置解析失败 + const val ERROR_MSG_CONFIG_PLUGIN_MIX = "0x6 vapx fail" // vapx融合动画资源获取失败 + const val ERROR_MSG_FILE_ERROR = "0x7 file can't read" // 文件无法读取 + const val ERROR_MSG_HEVC_NOT_SUPPORT = "0x8 hevc not support" // 不支持h265 + + + fun getErrorMsg(errorType: Int, errorMsg: String? = null): String { + return when(errorType) { + REPORT_ERROR_TYPE_EXTRACTOR_EXC -> ERROR_MSG_EXTRACTOR_EXC + REPORT_ERROR_TYPE_DECODE_EXC -> ERROR_MSG_DECODE_EXC + REPORT_ERROR_TYPE_CREATE_THREAD -> ERROR_MSG_CREATE_THREAD + REPORT_ERROR_TYPE_CREATE_RENDER -> ERROR_MSG_CREATE_RENDER + REPORT_ERROR_TYPE_PARSE_CONFIG -> ERROR_MSG_PARSE_CONFIG + REPORT_ERROR_TYPE_CONFIG_PLUGIN_MIX -> ERROR_MSG_CONFIG_PLUGIN_MIX + else -> "unknown" + } + " ${errorMsg ?: ""}" + } + +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/Decoder.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/Decoder.kt new file mode 100644 index 0000000..c1b42ee --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/Decoder.kt @@ -0,0 +1,171 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer + +import android.os.Build +import android.os.HandlerThread +import android.os.Handler +import com.tencent.qgame.animplayer.file.IFileContainer +import com.tencent.qgame.animplayer.inter.IAnimListener +import com.tencent.qgame.animplayer.util.ALog +import com.tencent.qgame.animplayer.util.SpeedControlUtil + + +abstract class Decoder(val player: AnimPlayer) : IAnimListener { + + companion object { + private const val TAG = "${Constant.TAG}.Decoder" + + fun createThread(handlerHolder: HandlerHolder, name: String): Boolean { + try { + if (handlerHolder.thread == null || handlerHolder.thread?.isAlive == false) { + handlerHolder.thread = HandlerThread(name).apply { + start() + handlerHolder.handler = Handler(looper) + } + } + return true + } catch (e: OutOfMemoryError) { + ALog.e(TAG, "createThread OOM", e) + } + return false + } + + fun quitSafely(thread: HandlerThread?): HandlerThread? { + thread?.apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + thread.quitSafely() + } else { + thread.quit() + } + } + return null + } + } + + var render: IRenderListener? = null + val renderThread = HandlerHolder(null, null) + val decodeThread = HandlerHolder(null, null) + private var surfaceWidth = 0 + private var surfaceHeight = 0 + var fps: Int = 0 + set(value) { + speedControlUtil.setFixedPlaybackRate(value) + field = value + } + var playLoop = 0 // 循环播放次数 + var isRunning = false // 是否正在运行 + var isStopReq = false // 是否需要停止 + val speedControlUtil by lazy { SpeedControlUtil() } + + abstract fun start(fileContainer: IFileContainer) + + fun stop() { + isStopReq = true + } + + abstract fun destroy() + + fun prepareThread(): Boolean { + return createThread(renderThread, "anim_render_thread") && createThread(decodeThread, "anim_decode_thread") + } + + fun prepareRender(needYUV: Boolean): Boolean { + if (render == null) { + ALog.i(TAG, "prepareRender") + player.animView.getSurfaceTexture()?.apply { + if (needYUV) { + ALog.i(TAG, "use yuv render") + render = YUVRender(this) + } else { + render = Render(this).apply { + updateViewPort(surfaceWidth, surfaceHeight) + } + } + } + } + return render != null + } + + fun preparePlay(videoWidth: Int, videoHeight: Int) { + player.configManager.defaultConfig(videoWidth, videoHeight) + player.configManager.config?.apply { + render?.setAnimConfig(this) + } + player.pluginManager.onRenderCreate() + } + + /** + * decode过程中视频尺寸变化 + * 主要是没有16进制对齐的老视频 + */ + fun videoSizeChange(newWidth: Int, newHeight: Int) { + if (newWidth <= 0 || newHeight <= 0) return + val config = player.configManager.config ?: return + if (config.videoWidth != newWidth || config.videoHeight != newHeight) { + ALog.i(TAG, "videoSizeChange old=(${config.videoWidth},${config.videoHeight}), new=($newWidth,$newHeight)") + config.videoWidth = newWidth + config.videoHeight = newHeight + render?.setAnimConfig(config) + } + } + + + fun destroyThread() { + if (player.isDetachedFromWindow) { + ALog.i(TAG, "destroyThread") + renderThread.handler?.removeCallbacksAndMessages(null) + decodeThread.handler?.removeCallbacksAndMessages(null) + renderThread.thread = quitSafely(renderThread.thread) + decodeThread.thread = quitSafely(decodeThread.thread) + renderThread.handler = null + decodeThread.handler = null + } + } + + fun onSurfaceSizeChanged(width: Int, height: Int) { + surfaceWidth = width + surfaceHeight = height + render?.updateViewPort(width, height) + } + + override fun onVideoStart() { + ALog.i(TAG, "onVideoStart") + player.animListener?.onVideoStart() + } + + override fun onVideoRender(frameIndex: Int, config: AnimConfig?) { + ALog.d(TAG, "onVideoRender") + player.animListener?.onVideoRender(frameIndex, config) + } + + override fun onVideoComplete() { + ALog.i(TAG, "onVideoComplete") + player.animListener?.onVideoComplete() + } + + override fun onVideoDestroy() { + ALog.i(TAG, "onVideoDestroy") + player.animListener?.onVideoDestroy() + } + + override fun onFailed(errorType: Int, errorMsg: String?) { + ALog.e(TAG, "onFailed errorType=$errorType, errorMsg=$errorMsg") + player.animListener?.onFailed(errorType, errorMsg) + } +} + +data class HandlerHolder(var thread: HandlerThread?, var handler: Handler?) \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/EGLUtil.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/EGLUtil.kt new file mode 100644 index 0000000..21d0190 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/EGLUtil.kt @@ -0,0 +1,115 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer + +import android.graphics.SurfaceTexture +import android.opengl.EGL14 +import android.view.Surface +import com.tencent.qgame.animplayer.util.ALog +import javax.microedition.khronos.egl.* + +class EGLUtil { + + companion object { + private const val TAG = "${Constant.TAG}.EGLUtil" + } + + private var egl: EGL10? = null + private var eglDisplay: EGLDisplay? = null + private var eglSurface: EGLSurface? = null + private var eglContext: EGLContext? = null + private var eglConfig: EGLConfig? = null + private var surface: Surface? = null + + init { + eglDisplay = EGL10.EGL_NO_DISPLAY + eglSurface = EGL10.EGL_NO_SURFACE + eglContext = EGL10.EGL_NO_CONTEXT + } + + fun start(surfaceTexture: SurfaceTexture) { + try { + egl = EGLContext.getEGL() as EGL10 + eglDisplay = egl?.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY) + val version = IntArray(2) + egl?.eglInitialize(eglDisplay, version) + eglConfig = chooseConfig() + surface = Surface(surfaceTexture) + eglSurface = egl?.eglCreateWindowSurface(eglDisplay, eglConfig, surface, null) + eglContext = createContext(egl, eglDisplay, eglConfig) + if (eglSurface == null || eglSurface == EGL10.EGL_NO_SURFACE) { + ALog.e(TAG, "error:${Integer.toHexString(egl?.eglGetError() ?: 0)}") + return + } + + if (egl?.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext) == false) { + ALog.e(TAG, "make current error:${Integer.toHexString(egl?.eglGetError() ?: 0)}") + } + } catch (e: Throwable) { + ALog.e(TAG, "error:$e", e) + } + } + + + private fun chooseConfig(): EGLConfig? { + val configsCount = IntArray(1) + val configs = arrayOfNulls(1) + val attributes =getAttributes() + val confSize = 1 + if (egl?.eglChooseConfig(eglDisplay, attributes, configs, confSize, configsCount) == true) { + return configs[0] + } + return null + } + + private fun getAttributes(): IntArray { + return intArrayOf( + EGL10.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, //指定渲染api类别 + EGL10.EGL_RED_SIZE, 8, + EGL10.EGL_GREEN_SIZE, 8, + EGL10.EGL_BLUE_SIZE, 8, + EGL10.EGL_ALPHA_SIZE, 8, + EGL10.EGL_DEPTH_SIZE, 0, + EGL10.EGL_STENCIL_SIZE, 0, + EGL10.EGL_NONE + ) + } + + private fun createContext(egl: EGL10?, eglDisplay: EGLDisplay?, eglConfig: EGLConfig?): EGLContext? { + val attrs = intArrayOf( + EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, + EGL10.EGL_NONE + ) + return egl?.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrs) + } + + fun swapBuffers() { + if (eglDisplay == null || eglSurface == null) return + egl?.eglSwapBuffers(eglDisplay, eglSurface) + } + + fun release() { + egl?.apply { + eglMakeCurrent(eglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT) + eglDestroySurface(eglDisplay, eglSurface) + eglDestroyContext(eglDisplay, eglContext) + eglTerminate(eglDisplay) + surface?.release() + surface = null + } + } + +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/HardDecoder.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/HardDecoder.kt new file mode 100644 index 0000000..368b54e --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/HardDecoder.kt @@ -0,0 +1,406 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer + +import android.graphics.SurfaceTexture +import android.media.MediaCodec +import android.media.MediaCodecInfo +import android.media.MediaExtractor +import android.media.MediaFormat +import android.os.Build +import android.view.Surface +import com.tencent.qgame.animplayer.file.IFileContainer +import com.tencent.qgame.animplayer.util.ALog +import com.tencent.qgame.animplayer.util.MediaUtil + +class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameAvailableListener { + + + companion object { + private const val TAG = "${Constant.TAG}.HardDecoder" + } + + private var surface: Surface? = null + private var glTexture: SurfaceTexture? = null + private val bufferInfo by lazy { MediaCodec.BufferInfo() } + private var needDestroy = false + + // 动画的原始尺寸 + private var videoWidth = 0 + private var videoHeight = 0 + + // 解码分辨率缩放比例(1.0 = 原始分辨率,0.5 = 一半分辨率) + var decodeScale: Float = 1.0f + + // 动画对齐后的尺寸 + private var alignWidth = 0 + private var alignHeight = 0 + + // 动画是否需要走YUV渲染逻辑的标志位 + private var needYUV = false + private var outputFormat: MediaFormat? = null + + override fun start(fileContainer: IFileContainer) { + isStopReq = false + needDestroy = false + isRunning = true + renderThread.handler?.post { + startPlay(fileContainer) + } + } + + override fun onFrameAvailable(surfaceTexture: SurfaceTexture?) { + if (isStopReq) return + ALog.d(TAG, "onFrameAvailable") + renderData() + } + + private fun renderData() { + renderThread.handler?.post { + try { + glTexture?.apply { + updateTexImage() + render?.renderFrame() + player.pluginManager.onRendering() + render?.swapBuffers() + } + } catch (e: Throwable) { + ALog.e(TAG, "render exception=$e", e) + } + } + } + + private fun startPlay(fileContainer: IFileContainer) { + + var extractor: MediaExtractor? = null + var decoder: MediaCodec? = null + var format: MediaFormat? = null + var trackIndex = 0 + + try { + extractor = MediaUtil.getExtractor(fileContainer) + trackIndex = MediaUtil.selectVideoTrack(extractor) + if (trackIndex < 0) { + throw RuntimeException("No video track found") + } + extractor.selectTrack(trackIndex) + format = extractor.getTrackFormat(trackIndex) + if (format == null) throw RuntimeException("format is null") + + // 是否支持h265 + if (MediaUtil.checkIsHevc(format)) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP + || !MediaUtil.checkSupportCodec(MediaUtil.MIME_HEVC)) { + + onFailed(Constant.REPORT_ERROR_TYPE_HEVC_NOT_SUPPORT, + "${Constant.ERROR_MSG_HEVC_NOT_SUPPORT} " + + "sdk:${Build.VERSION.SDK_INT}" + + ",support hevc:" + MediaUtil.checkSupportCodec(MediaUtil.MIME_HEVC)) + release(null, null) + return + } + } + + videoWidth = format.getInteger(MediaFormat.KEY_WIDTH) + videoHeight = format.getInteger(MediaFormat.KEY_HEIGHT) + // 防止没有INFO_OUTPUT_FORMAT_CHANGED时导致alignWidth和alignHeight不会被赋值一直是0 + alignWidth = videoWidth + alignHeight = videoHeight + ALog.i(TAG, "Video size is $videoWidth x $videoHeight") + + // 由于使用mediacodec解码老版本素材时对宽度1500尺寸的视频进行数据对齐,解码后的宽度变成1504,导致采样点出现偏差播放异常 + // 所以当开启兼容老版本视频模式并且老版本视频的宽度不能被16整除时要走YUV渲染逻辑 + // 但是这样直接判断有风险,后期想办法改 + needYUV = videoWidth % 16 != 0 && player.enableVersion1 + + try { + if (!prepareRender(needYUV)) { + throw RuntimeException("render create fail") + } + } catch (t: Throwable) { + onFailed(Constant.REPORT_ERROR_TYPE_CREATE_RENDER, "${Constant.ERROR_MSG_CREATE_RENDER} e=$t") + release(null, null) + return + } + + preparePlay(videoWidth, videoHeight) + + render?.apply { + // 计算解码分辨率:非YUV模式下应用缩放降低GPU内存占用 + val decodeWidth = if (needYUV) videoWidth else maxOf(1, (videoWidth * decodeScale).toInt()) + val decodeHeight = if (needYUV) videoHeight else maxOf(1, (videoHeight * decodeScale).toInt()) + if (decodeScale < 1.0f && !needYUV) { + ALog.i(TAG, "Decode scale: $decodeScale, original=${videoWidth}x${videoHeight}, decode=${decodeWidth}x${decodeHeight}") + } + glTexture = SurfaceTexture(getExternalTexture()).apply { + setOnFrameAvailableListener(this@HardDecoder) + setDefaultBufferSize(decodeWidth, decodeHeight) + } + clearFrame() + } + + } catch (e: Throwable) { + ALog.e(TAG, "MediaExtractor exception e=$e", e) + onFailed(Constant.REPORT_ERROR_TYPE_EXTRACTOR_EXC, "${Constant.ERROR_MSG_EXTRACTOR_EXC} e=$e") + release(decoder, extractor) + return + } + + try { + val mime = format.getString(MediaFormat.KEY_MIME) ?: "" + ALog.i(TAG, "Video MIME is $mime") + decoder = MediaCodec.createDecoderByType(mime).apply { + if (needYUV) { + format.setInteger( + MediaFormat.KEY_COLOR_FORMAT, + MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar + ) + configure(format, null, null, 0) + } else { + surface = Surface(glTexture) + configure(format, surface, null, 0) + } + + start() + decodeThread.handler?.post { + try { + startDecode(extractor, this) + } catch (e: Throwable) { + ALog.e(TAG, "MediaCodec exception e=$e", e) + onFailed(Constant.REPORT_ERROR_TYPE_DECODE_EXC, "${Constant.ERROR_MSG_DECODE_EXC} e=$e") + release(decoder, extractor) + } + } + } + } catch (e: Throwable) { + ALog.e(TAG, "MediaCodec configure exception e=$e", e) + onFailed(Constant.REPORT_ERROR_TYPE_DECODE_EXC, "${Constant.ERROR_MSG_DECODE_EXC} e=$e") + release(decoder, extractor) + return + } + } + + private fun startDecode(extractor: MediaExtractor ,decoder: MediaCodec) { + val TIMEOUT_USEC = 10000L + var inputChunk = 0 + var outputDone = false + var inputDone = false + var frameIndex = 0 + var isLoop = false + + val decoderInputBuffers = decoder.inputBuffers + + while (!outputDone) { + if (isStopReq) { + ALog.i(TAG, "stop decode") + release(decoder, extractor) + return + } + + if (!inputDone) { + val inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC) + if (inputBufIndex >= 0) { + val inputBuf = decoderInputBuffers[inputBufIndex] + val chunkSize = extractor.readSampleData(inputBuf, 0) + if (chunkSize < 0) { + decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM) + inputDone = true + ALog.d(TAG, "decode EOS") + } else { + val presentationTimeUs = extractor.sampleTime + decoder.queueInputBuffer(inputBufIndex, 0, chunkSize, presentationTimeUs, 0) + ALog.d(TAG, "submitted frame $inputChunk to dec, size=$chunkSize") + inputChunk++ + extractor.advance() + } + } else { + ALog.d(TAG, "input buffer not available") + } + } + + if (!outputDone) { + val decoderStatus = decoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC) + when { + decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER -> ALog.d(TAG, "no output from decoder available") + decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED -> ALog.d(TAG, "decoder output buffers changed") + decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> { + outputFormat = decoder.outputFormat + outputFormat?.apply { + try { + // 有可能取到空值,做一层保护 + val stride = getInteger("stride") + val sliceHeight = getInteger("slice-height") + if (stride > 0 && sliceHeight > 0) { + alignWidth = stride + alignHeight = sliceHeight + } + } catch (t: Throwable) { + ALog.e(TAG, "$t", t) + } + } + ALog.i(TAG, "decoder output format changed: $outputFormat") + } + decoderStatus < 0 -> { + throw RuntimeException("unexpected result from decoder.dequeueOutputBuffer: $decoderStatus") + } + else -> { + var loop = 0 + if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) { + loop = --playLoop + player.playLoop = playLoop // 消耗loop次数 自动恢复后能有正确的loop次数 + outputDone = playLoop <= 0 + } + val doRender = !outputDone + if (doRender) { + speedControlUtil.preRender(bufferInfo.presentationTimeUs) + } + + if (needYUV && doRender) { + yuvProcess(decoder, decoderStatus) + } + + // release & render + decoder.releaseOutputBuffer(decoderStatus, doRender && !needYUV) + + if (frameIndex == 0 && !isLoop) { + onVideoStart() + } + player.pluginManager.onDecoding(frameIndex) + onVideoRender(frameIndex, player.configManager.config) + + frameIndex++ + ALog.d(TAG, "decode frameIndex=$frameIndex") + if (loop > 0) { + ALog.d(TAG, "Reached EOD, looping") + player.pluginManager.onLoopStart() + extractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC) + inputDone = false + decoder.flush() + speedControlUtil.reset() + frameIndex = 0 + isLoop = true + } + if (outputDone) { + release(decoder, extractor) + } + } + } + } + } + + } + + /** + * 获取到解码后每一帧的YUV数据,裁剪出正确的尺寸 + */ + private fun yuvProcess(decoder: MediaCodec, outputIndex: Int) { + val outputBuffer = decoder.outputBuffers[outputIndex] + outputBuffer?.let { + it.position(0) + it.limit(bufferInfo.offset + bufferInfo.size) + var yuvData = ByteArray(outputBuffer.remaining()) + outputBuffer.get(yuvData) + + if (yuvData.isNotEmpty()) { + var yData = ByteArray(videoWidth * videoHeight) + var uData = ByteArray(videoWidth * videoHeight / 4) + var vData = ByteArray(videoWidth * videoHeight / 4) + + if (outputFormat?.getInteger(MediaFormat.KEY_COLOR_FORMAT) == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar) { + yuvData = yuv420spTop(yuvData) + } + + yuvCopy(yuvData, 0, alignWidth, alignHeight, yData, videoWidth, videoHeight) + yuvCopy(yuvData, alignWidth * alignHeight, alignWidth / 2, alignHeight / 2, uData, videoWidth / 2, videoHeight / 2) + yuvCopy(yuvData, alignWidth * alignHeight * 5 / 4, alignWidth / 2, alignHeight / 2, vData, videoWidth / 2, videoHeight / 2) + + render?.setYUVData(videoWidth, videoHeight, yData, uData, vData) + renderData() + } + } + } + + private fun yuv420spTop(yuv420sp: ByteArray): ByteArray { + val yuv420p = ByteArray(yuv420sp.size) + val ySize = alignWidth * alignHeight + System.arraycopy(yuv420sp, 0, yuv420p, 0, alignWidth * alignHeight) + var i = ySize + var j = ySize + while (i < ySize * 3 / 2) { + yuv420p[j] = yuv420sp[i] + yuv420p[j + ySize / 4] = yuv420sp[i + 1] + i += 2 + j++ + } + return yuv420p + } + + private fun yuvCopy(src: ByteArray, srcOffset: Int, inWidth: Int, inHeight: Int, dest: ByteArray, outWidth: Int, outHeight: Int) { + for (h in 0 until inHeight) { + if (h < outHeight) { + System.arraycopy(src, srcOffset + h * inWidth, dest, h * outWidth, outWidth) + } + } + } + + private fun release(decoder: MediaCodec?, extractor: MediaExtractor?) { + renderThread.handler?.post { + render?.clearFrame() + try { + ALog.i(TAG, "release") + decoder?.apply { + stop() + release() + } + extractor?.release() + glTexture?.release() + glTexture = null + speedControlUtil.reset() + player.pluginManager.onRelease() + render?.releaseTexture() + surface?.release() + surface = null + } catch (e: Throwable) { + ALog.e(TAG, "release e=$e", e) + } + isRunning = false + onVideoComplete() + if (needDestroy) { + destroyInner() + } + } + } + + override fun destroy() { + if (isRunning) { + needDestroy = true + stop() + } else { + destroyInner() + } + } + + private fun destroyInner() { + ALog.i(TAG, "destroyInner") + renderThread.handler?.post { + player.pluginManager.onDestroy() + render?.destroyRender() + render = null + onVideoDestroy() + destroyThread() + } + } +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/IAnimView.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/IAnimView.kt new file mode 100644 index 0000000..1bfbcaf --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/IAnimView.kt @@ -0,0 +1,66 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer + +import android.content.res.AssetManager +import android.graphics.SurfaceTexture +import com.tencent.qgame.animplayer.file.IFileContainer +import com.tencent.qgame.animplayer.inter.IAnimListener +import com.tencent.qgame.animplayer.inter.IFetchResource +import com.tencent.qgame.animplayer.inter.OnResourceClickListener +import com.tencent.qgame.animplayer.mask.MaskConfig +import com.tencent.qgame.animplayer.util.IScaleType +import com.tencent.qgame.animplayer.util.ScaleType +import java.io.File + +interface IAnimView { + + fun prepareTextureView() + + fun getSurfaceTexture(): SurfaceTexture? + + fun setAnimListener(animListener: IAnimListener?) + + fun setFetchResource(fetchResource: IFetchResource?) + + fun setOnResourceClickListener(resourceClickListener: OnResourceClickListener?) + + fun setLoop(playLoop: Int) + + fun supportMask(isSupport: Boolean, isEdgeBlur: Boolean) + + fun updateMaskConfig(maskConfig: MaskConfig?) + + fun setFps(fps: Int) + + fun setScaleType(type: ScaleType) + + fun setScaleType(scaleType: IScaleType) + + fun setMute(isMute: Boolean) + + fun startPlay(file: File) + + fun startPlay(assetManager: AssetManager, assetsPath: String) + + fun startPlay(fileContainer: IFileContainer) + + fun stopPlay() + + fun isRunning(): Boolean + + fun getRealSize(): Pair +} diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/IRenderListener.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/IRenderListener.kt new file mode 100644 index 0000000..b92d3b9 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/IRenderListener.kt @@ -0,0 +1,54 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer + +interface IRenderListener { + + /** + * 初始化渲染环境,获取shader字段,创建绑定纹理 + */ + fun initRender() + + /** + * 渲染上屏 + */ + fun renderFrame() + + fun clearFrame() + + /** + * 释放纹理 + */ + fun destroyRender() + + /** + * 设置视频配置 + */ + fun setAnimConfig(config: AnimConfig) + + /** + * 显示区域大小变化 + */ + fun updateViewPort(width: Int, height: Int) {} + + fun getExternalTexture(): Int + + fun releaseTexture() + + fun swapBuffers() + + fun setYUVData(width: Int, height: Int, y: ByteArray?, u: ByteArray?, v: ByteArray?) {} +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/Render.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/Render.kt new file mode 100644 index 0000000..a4b5de5 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/Render.kt @@ -0,0 +1,153 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer + +import android.graphics.SurfaceTexture +import android.opengl.GLES11Ext +import android.opengl.GLES20 +import com.tencent.qgame.animplayer.util.GlFloatArray +import com.tencent.qgame.animplayer.util.ShaderUtil +import com.tencent.qgame.animplayer.util.TexCoordsUtil +import com.tencent.qgame.animplayer.util.VertexUtil +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.ShortBuffer + +class Render(surfaceTexture: SurfaceTexture): IRenderListener { + + companion object { + private const val TAG = "${Constant.TAG}.Render" + } + + private val vertexArray = GlFloatArray() + private val alphaArray = GlFloatArray() + private val rgbArray = GlFloatArray() + private var surfaceSizeChanged = false + private var surfaceWidth = 0 + private var surfaceHeight = 0 + private val eglUtil: EGLUtil = EGLUtil() + private var shaderProgram = 0 + private var genTexture = IntArray(1) + private var uTextureLocation: Int = 0 + private var aPositionLocation: Int = 0 + private var aTextureAlphaLocation: Int = 0 + private var aTextureRgbLocation: Int = 0 + + init { + eglUtil.start(surfaceTexture) + initRender() + } + + private fun setVertexBuf(config: AnimConfig) { + vertexArray.setArray(VertexUtil.create(config.width, config.height, PointRect(0, 0, config.width, config.height), vertexArray.array)) + } + + private fun setTexCoords(config: AnimConfig) { + val alpha = TexCoordsUtil.create(config.videoWidth, config.videoHeight, config.alphaPointRect, alphaArray.array) + val rgb = TexCoordsUtil.create(config.videoWidth, config.videoHeight, config.rgbPointRect, rgbArray.array) + alphaArray.setArray(alpha) + rgbArray.setArray(rgb) + } + + override fun initRender() { + shaderProgram = ShaderUtil.createProgram(RenderConstant.VERTEX_SHADER, RenderConstant.FRAGMENT_SHADER) + uTextureLocation = GLES20.glGetUniformLocation(shaderProgram, "texture") + aPositionLocation = GLES20.glGetAttribLocation(shaderProgram, "vPosition") + aTextureAlphaLocation = GLES20.glGetAttribLocation(shaderProgram, "vTexCoordinateAlpha") + aTextureRgbLocation = GLES20.glGetAttribLocation(shaderProgram, "vTexCoordinateRgb") + + GLES20.glGenTextures(genTexture.size, genTexture, 0) + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, genTexture[0]) + GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST.toFloat()) + GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat()) + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE) + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE) + } + + override fun renderFrame() { + GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f) + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) + if (surfaceSizeChanged && surfaceWidth>0 && surfaceHeight>0) { + surfaceSizeChanged = false + GLES20.glViewport(0,0, surfaceWidth, surfaceHeight) + } + draw() + } + + override fun clearFrame() { + GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f) + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) + eglUtil.swapBuffers() + } + + override fun destroyRender() { + releaseTexture() + eglUtil.release() + } + + override fun releaseTexture() { + GLES20.glDeleteTextures(genTexture.size, genTexture, 0) + } + + /** + * 设置视频配置 + */ + override fun setAnimConfig(config: AnimConfig) { + setVertexBuf(config) + setTexCoords(config) + } + + /** + * 显示区域大小变化 + */ + override fun updateViewPort(width: Int, height: Int) { + if (width <=0 || height <=0) return + surfaceSizeChanged = true + surfaceWidth = width + surfaceHeight = height + } + + override fun swapBuffers() { + eglUtil.swapBuffers() + } + + /** + * mediaCodec渲染使用的 + */ + override fun getExternalTexture(): Int { + return genTexture[0] + } + + private fun draw() { + GLES20.glUseProgram(shaderProgram) + // 设置顶点坐标 + vertexArray.setVertexAttribPointer(aPositionLocation) + // 绑定纹理 + GLES20.glActiveTexture(GLES20.GL_TEXTURE0) + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, genTexture[0]) + GLES20.glUniform1i(uTextureLocation, 0) + + // 设置纹理坐标 + // alpha 通道坐标 + alphaArray.setVertexAttribPointer(aTextureAlphaLocation) + // rgb 通道坐标 + rgbArray.setVertexAttribPointer(aTextureRgbLocation) + + // draw + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4) + } + +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/RenderConstant.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/RenderConstant.kt new file mode 100644 index 0000000..a1135fa --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/RenderConstant.kt @@ -0,0 +1,43 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer + + +object RenderConstant { + + const val VERTEX_SHADER = "attribute vec4 vPosition;\n" + + "attribute vec4 vTexCoordinateAlpha;\n" + + "attribute vec4 vTexCoordinateRgb;\n" + + "varying vec2 v_TexCoordinateAlpha;\n" + + "varying vec2 v_TexCoordinateRgb;\n" + + "\n" + + "void main() {\n" + + " v_TexCoordinateAlpha = vec2(vTexCoordinateAlpha.x, vTexCoordinateAlpha.y);\n" + + " v_TexCoordinateRgb = vec2(vTexCoordinateRgb.x, vTexCoordinateRgb.y);\n" + + " gl_Position = vPosition;\n" + + "}" + const val FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "uniform samplerExternalOES texture;\n" + + "varying vec2 v_TexCoordinateAlpha;\n" + + "varying vec2 v_TexCoordinateRgb;\n" + + "\n" + + "void main () {\n" + + " vec4 alphaColor = texture2D(texture, v_TexCoordinateAlpha);\n" + + " vec4 rgbColor = texture2D(texture, v_TexCoordinateRgb);\n" + + " gl_FragColor = vec4(rgbColor.r, rgbColor.g, rgbColor.b, alphaColor.r);\n" + + "}" +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/YUVRender.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/YUVRender.kt new file mode 100644 index 0000000..9bee304 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/YUVRender.kt @@ -0,0 +1,207 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer + +import android.graphics.SurfaceTexture +import android.opengl.GLES20 +import com.tencent.qgame.animplayer.util.GlFloatArray +import com.tencent.qgame.animplayer.util.ShaderUtil.createProgram +import com.tencent.qgame.animplayer.util.TexCoordsUtil +import com.tencent.qgame.animplayer.util.VertexUtil +import java.nio.ByteBuffer +import java.nio.FloatBuffer + +class YUVRender (surfaceTexture: SurfaceTexture): IRenderListener { + + companion object { + private const val TAG = "${Constant.TAG}.YUVRender" + } + + private val vertexArray = GlFloatArray() + private val alphaArray = GlFloatArray() + private val rgbArray = GlFloatArray() + + private var shaderProgram = 0 + + //顶点位置 + private var avPosition = 0 + + //rgb纹理位置 + private var rgbPosition = 0 + + //alpha纹理位置 + private var alphaPosition = 0 + + //shader yuv变量 + private var samplerY = 0 + private var samplerU = 0 + private var samplerV = 0 + private var textureId = IntArray(3) + private var convertMatrixUniform = 0 + private var convertOffsetUniform = 0 + + //YUV数据 + private var widthYUV = 0 + private var heightYUV = 0 + private var y: ByteBuffer? = null + private var u: ByteBuffer? = null + private var v: ByteBuffer? = null + + private val eglUtil: EGLUtil = EGLUtil() + + // 像素数据向GPU传输时默认以4字节对齐 + private var unpackAlign = 4 + + // YUV offset + private val YUV_OFFSET = floatArrayOf( + 0f, -0.501960814f, -0.501960814f + ) + + // RGB coefficients + private val YUV_MATRIX = floatArrayOf( + 1f, 1f, 1f, + 0f, -0.3441f, 1.772f, + 1.402f, -0.7141f, 0f + ) + + init { + eglUtil.start(surfaceTexture) + initRender() + } + + override fun initRender() { + shaderProgram = createProgram(YUVShader.VERTEX_SHADER, YUVShader.FRAGMENT_SHADER) + //获取顶点坐标字段 + avPosition = GLES20.glGetAttribLocation(shaderProgram, "v_Position") + //获取纹理坐标字段 + rgbPosition = GLES20.glGetAttribLocation(shaderProgram, "vTexCoordinateRgb") + alphaPosition = GLES20.glGetAttribLocation(shaderProgram, "vTexCoordinateAlpha") + + //获取yuv字段 + samplerY = GLES20.glGetUniformLocation(shaderProgram, "sampler_y") + samplerU = GLES20.glGetUniformLocation(shaderProgram, "sampler_u") + samplerV = GLES20.glGetUniformLocation(shaderProgram, "sampler_v") + convertMatrixUniform = GLES20.glGetUniformLocation(shaderProgram, "convertMatrix") + convertOffsetUniform = GLES20.glGetUniformLocation(shaderProgram, "offset") + //创建3个纹理 + GLES20.glGenTextures(textureId.size, textureId, 0) + + //绑定纹理 + for (id in textureId) { + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, id) + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT) + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT) + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR) + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR) + } + } + + override fun renderFrame() { + GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f) + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) + draw() + } + + override fun clearFrame() { + GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f) + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) + eglUtil.swapBuffers() + } + + override fun destroyRender() { + releaseTexture() + eglUtil.release() + } + + override fun setAnimConfig(config: AnimConfig) { + vertexArray.setArray(VertexUtil.create(config.width, config.height, PointRect(0, 0, config.width, config.height), vertexArray.array)) + val alpha = TexCoordsUtil.create(config.videoWidth, config.videoHeight, config.alphaPointRect, alphaArray.array) + val rgb = TexCoordsUtil.create(config.videoWidth, config.videoHeight, config.rgbPointRect, rgbArray.array) + alphaArray.setArray(alpha) + rgbArray.setArray(rgb) + } + + override fun getExternalTexture(): Int { + return textureId[0] + } + + override fun releaseTexture() { + GLES20.glDeleteTextures(textureId.size, textureId, 0) + } + + override fun swapBuffers() { + eglUtil.swapBuffers() + } + + override fun setYUVData(width: Int, height: Int, y: ByteArray?, u: ByteArray?, v: ByteArray?) { + widthYUV = width + heightYUV = height + this.y = ByteBuffer.wrap(y) + this.u = ByteBuffer.wrap(u) + this.v = ByteBuffer.wrap(v) + + // 当视频帧的u或者v分量的宽度不能被4整除时,用默认的4字节对齐会导致存取最后一行时越界,所以在向GPU传输数据前指定对齐方式 + if ((widthYUV / 2) % 4 != 0) { + this.unpackAlign = if ((widthYUV / 2) % 2 == 0) 2 else 1 + } + } + + private fun draw() { + if (widthYUV > 0 && heightYUV > 0 && y != null && u != null && v != null) { + GLES20.glUseProgram(shaderProgram) + vertexArray.setVertexAttribPointer(avPosition) + alphaArray.setVertexAttribPointer(alphaPosition) + rgbArray.setVertexAttribPointer(rgbPosition) + + GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, unpackAlign) + + //激活纹理0来绑定y数据 + GLES20.glActiveTexture(GLES20.GL_TEXTURE0) + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[0]) + GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, widthYUV, heightYUV, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, y) + + //激活纹理1来绑定u数据 + GLES20.glActiveTexture(GLES20.GL_TEXTURE1) + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[1]) + GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, widthYUV / 2, heightYUV / 2, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, u) + + //激活纹理2来绑定v数据 + GLES20.glActiveTexture(GLES20.GL_TEXTURE2) + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[2]) + GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, widthYUV / 2, heightYUV / 2, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, v) + + //给fragment_shader里面yuv变量设置值 0 1 标识纹理x + GLES20.glUniform1i(samplerY, 0) + GLES20.glUniform1i(samplerU, 1) + GLES20.glUniform1i(samplerV, 2) + + GLES20.glUniform3fv(convertOffsetUniform, 1, FloatBuffer.wrap(YUV_OFFSET)) + GLES20.glUniformMatrix3fv(convertMatrixUniform, 1, false, YUV_MATRIX, 0) + + //绘制 + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4) + y?.clear() + u?.clear() + v?.clear() + y = null + u = null + v = null + GLES20.glDisableVertexAttribArray(avPosition) + GLES20.glDisableVertexAttribArray(rgbPosition) + GLES20.glDisableVertexAttribArray(alphaPosition) + } + } +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/YUVShader.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/YUVShader.kt new file mode 100644 index 0000000..b120c7e --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/YUVShader.kt @@ -0,0 +1,60 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer + +object YUVShader { + + const val VERTEX_SHADER = "attribute vec4 v_Position;\n" + + "attribute vec2 vTexCoordinateAlpha;\n" + + "attribute vec2 vTexCoordinateRgb;\n" + + "varying vec2 v_TexCoordinateAlpha;\n" + + "varying vec2 v_TexCoordinateRgb;\n" + + "\n" + + "void main() {\n" + + " v_TexCoordinateAlpha = vTexCoordinateAlpha;\n" + + " v_TexCoordinateRgb = vTexCoordinateRgb;\n" + + " gl_Position = v_Position;\n" + + "}" + + const val FRAGMENT_SHADER = "precision mediump float;\n" + + "uniform sampler2D sampler_y;\n" + + "uniform sampler2D sampler_u;\n" + + "uniform sampler2D sampler_v;\n" + + "varying vec2 v_TexCoordinateAlpha;\n" + + "varying vec2 v_TexCoordinateRgb;\n" + + "uniform mat3 convertMatrix;\n" + + "uniform vec3 offset;\n" + + "\n" + + "void main() {\n" + + " highp vec3 yuvColorAlpha;\n" + + " highp vec3 yuvColorRGB;\n" + + " highp vec3 rgbColorAlpha;\n" + + " highp vec3 rgbColorRGB;\n" + + " yuvColorAlpha.x = texture2D(sampler_y,v_TexCoordinateAlpha).r;\n" + + " yuvColorRGB.x = texture2D(sampler_y,v_TexCoordinateRgb).r;\n" + + " yuvColorAlpha.y = texture2D(sampler_u,v_TexCoordinateAlpha).r;\n" + + " yuvColorAlpha.z = texture2D(sampler_v,v_TexCoordinateAlpha).r;\n" + + " yuvColorRGB.y = texture2D(sampler_u,v_TexCoordinateRgb).r;\n" + + " yuvColorRGB.z = texture2D(sampler_v,v_TexCoordinateRgb).r;\n" + + " yuvColorAlpha += offset;\n" + + " yuvColorRGB += offset;\n" + + " rgbColorAlpha = convertMatrix * yuvColorAlpha; \n" + + " rgbColorRGB = convertMatrix * yuvColorRGB; \n" + + " gl_FragColor=vec4(rgbColorRGB, rgbColorAlpha.r);\n" + + "}" + + // RGB2*Alpha+RGB1*(1-Alpha) +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/file/AssetsFileContainer.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/file/AssetsFileContainer.kt new file mode 100644 index 0000000..6adbcd1 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/file/AssetsFileContainer.kt @@ -0,0 +1,65 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.file + +import android.content.res.AssetFileDescriptor +import android.content.res.AssetManager +import android.media.MediaExtractor +import com.tencent.qgame.animplayer.Constant +import com.tencent.qgame.animplayer.util.ALog + +class AssetsFileContainer(assetManager: AssetManager, assetsPath: String): IFileContainer { + + companion object { + private const val TAG = "${Constant.TAG}.FileContainer" + } + + private val assetFd: AssetFileDescriptor = assetManager.openFd(assetsPath) + private val assetsInputStream: AssetManager.AssetInputStream = + assetManager.open(assetsPath, AssetManager.ACCESS_STREAMING) as AssetManager.AssetInputStream + + init { + ALog.i(TAG, "AssetsFileContainer init") + } + + override fun setDataSource(extractor: MediaExtractor) { + if (assetFd.declaredLength < 0) { + extractor.setDataSource(assetFd.fileDescriptor) + } else { + extractor.setDataSource(assetFd.fileDescriptor, assetFd.startOffset, assetFd.declaredLength) + } + } + + override fun startRandomRead() { + } + + override fun read(b: ByteArray, off: Int, len: Int): Int { + return assetsInputStream.read(b, off, len) + } + + override fun skip(pos: Long) { + assetsInputStream.skip(pos) + } + + override fun closeRandomRead() { + assetsInputStream.close() + } + + override fun close() { + assetFd.close() + assetsInputStream.close() + } +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/file/FileContainer.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/file/FileContainer.kt new file mode 100644 index 0000000..ae27f91 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/file/FileContainer.kt @@ -0,0 +1,60 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.file + +import android.media.MediaExtractor +import com.tencent.qgame.animplayer.Constant +import com.tencent.qgame.animplayer.util.ALog +import java.io.File +import java.io.FileNotFoundException +import java.io.RandomAccessFile + +class FileContainer(private val file: File) : IFileContainer { + + companion object { + private const val TAG = "${Constant.TAG}.FileContainer" + } + + private var randomAccessFile: RandomAccessFile? = null + + init { + ALog.i(TAG, "FileContainer init") + if (!(file.exists() && file.isFile && file.canRead())) throw FileNotFoundException("Unable to read $file") + } + + override fun setDataSource(extractor: MediaExtractor) { + extractor.setDataSource(file.toString()) + } + + override fun startRandomRead() { + randomAccessFile = RandomAccessFile(file, "r") + } + + override fun read(b: ByteArray, off: Int, len: Int): Int { + return randomAccessFile?.read(b, off, len) ?: -1 + } + + override fun skip(pos: Long) { + randomAccessFile?.skipBytes(pos.toInt()) + } + + override fun closeRandomRead() { + randomAccessFile?.close() + } + + override fun close() { + } +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/file/IFileContainer.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/file/IFileContainer.kt new file mode 100644 index 0000000..69e2531 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/file/IFileContainer.kt @@ -0,0 +1,34 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.file + +import android.media.MediaExtractor + +interface IFileContainer { + + fun setDataSource(extractor: MediaExtractor) + + fun startRandomRead() + + fun read(b: ByteArray, off: Int, len: Int): Int + + fun skip(pos: Long) + + fun closeRandomRead() + + fun close() + +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/file/StreamContainer.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/file/StreamContainer.kt new file mode 100644 index 0000000..a2858c7 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/file/StreamContainer.kt @@ -0,0 +1,50 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.file + +import android.annotation.TargetApi +import android.media.MediaExtractor +import android.os.Build +import java.io.ByteArrayInputStream + +@TargetApi(Build.VERSION_CODES.M) +class StreamContainer(private val bytes: ByteArray) : IFileContainer { + + private var stream: ByteArrayInputStream = ByteArrayInputStream(bytes) + + override fun setDataSource(extractor: MediaExtractor) { + val dataSource = StreamMediaDataSource(bytes) + extractor.setDataSource(dataSource) + } + + override fun startRandomRead() { + } + + override fun read(b: ByteArray, off: Int, len: Int): Int { + return stream.read(b, off, len) + } + + override fun skip(pos: Long) { + stream.skip(pos) + } + + override fun closeRandomRead() { + } + + override fun close() { + stream.close() + } +} diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/file/StreamMediaDataSource.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/file/StreamMediaDataSource.kt new file mode 100644 index 0000000..27ec428 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/file/StreamMediaDataSource.kt @@ -0,0 +1,49 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.file + +import android.annotation.TargetApi +import android.media.MediaDataSource +import android.os.Build + +@TargetApi(Build.VERSION_CODES.M) +class StreamMediaDataSource(val bytes: ByteArray) : MediaDataSource() { + + override fun close() { + } + + override fun readAt(position: Long, buffer: ByteArray, offset: Int, size: Int): Int { + var newSize = size + synchronized(StreamMediaDataSource::class) { + val length = bytes.size + if (position >= length) { + return -1 + } + if (position + newSize > length) { + newSize -= (position + newSize).toInt() - length + } + System.arraycopy(bytes, position.toInt(), buffer, offset, newSize) + return newSize + } + + } + + override fun getSize(): Long { + synchronized(StreamMediaDataSource::class) { + return bytes.size.toLong() + } + } +} diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/inter/IAnimListener.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/inter/IAnimListener.kt new file mode 100644 index 0000000..cc81275 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/inter/IAnimListener.kt @@ -0,0 +1,74 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.inter + +import com.tencent.qgame.animplayer.AnimConfig + +interface IAnimListener { + + /** + * 配置准备好后回调 + * ps:如果是默认配置(没有发现vapc配置),因为信息不完整onVideoConfigReady不会被调用,默认播放 + * @return true 继续播放 false 结束播放 + */ + fun onVideoConfigReady(config: AnimConfig): Boolean { + return true // 默认继续播放 + } + + /** + * 开始播放 + */ + fun onVideoStart() + + + /** + * 视频渲染每一帧时的回调 + * @param frameIndex 帧索引 + */ + fun onVideoRender(frameIndex: Int, config: AnimConfig?) + + /** + * 视频播放结束 + */ + fun onVideoComplete() + + /** + * 视频被销毁 + */ + fun onVideoDestroy() + + /** + * 失败回调 + * @param errorType 错误类型 + * @param errorMsg 错误消息 + */ + fun onFailed(errorType: Int, errorMsg: String?) +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/inter/IFetchResource.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/inter/IFetchResource.kt new file mode 100644 index 0000000..766ab27 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/inter/IFetchResource.kt @@ -0,0 +1,33 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.inter + +import android.graphics.Bitmap +import com.tencent.qgame.animplayer.mix.Resource + +/** + * 获取资源 + */ +interface IFetchResource { + // 获取图片 (暂时不支持Bitmap.Config.ALPHA_8 主要是因为一些机型opengl兼容问题) + fun fetchImage(resource: Resource, result:(Bitmap?) -> Unit) + + // 获取文字 + fun fetchText(resource: Resource, result:(String?) -> Unit) + + // 资源释放通知 + fun releaseResource(resources: List) +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/inter/OnResourceClickListener.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/inter/OnResourceClickListener.kt new file mode 100644 index 0000000..3e8484d --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/inter/OnResourceClickListener.kt @@ -0,0 +1,24 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.inter + +import com.tencent.qgame.animplayer.mix.Resource + +interface OnResourceClickListener { + + // 返回被点击的资源 + fun onClick(resource: Resource) +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mask/MaskAnimPlugin.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mask/MaskAnimPlugin.kt new file mode 100644 index 0000000..31143af --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mask/MaskAnimPlugin.kt @@ -0,0 +1,66 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.mask + +import com.tencent.qgame.animplayer.AnimConfig +import com.tencent.qgame.animplayer.AnimPlayer +import com.tencent.qgame.animplayer.Constant +import com.tencent.qgame.animplayer.plugin.IAnimPlugin +import com.tencent.qgame.animplayer.util.ALog + +class MaskAnimPlugin(val player: AnimPlayer): IAnimPlugin { + + companion object { + private const val TAG = "${Constant.TAG}.MaskAnimPlugin" + } + + private var maskRender: MaskRender? = null + private var animConfig : AnimConfig ?= null + + + override fun onConfigCreate(config: AnimConfig): Int { + return Constant.OK + } + + override fun onRenderCreate() { + ALog.i(TAG, "mask render init") + maskRender = if(player.supportMaskBoolean) MaskRender(this) else return + maskRender?.initMaskShader(player.maskEdgeBlurBoolean) + } + + override fun onRendering(frameIndex: Int) { + animConfig = if(player.supportMaskBoolean && player.configManager.config is AnimConfig) player.configManager.config else return + animConfig?.let {config -> + maskRender?.renderFrame(config) + } + } + + + override fun onRelease() { + destroy() + } + + override fun onDestroy() { + destroy() + } + + + private fun destroy() { + // 强制结束等待 + animConfig?.maskConfig?.release() + } + +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mask/MaskConfig.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mask/MaskConfig.kt new file mode 100644 index 0000000..421f5d2 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mask/MaskConfig.kt @@ -0,0 +1,74 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.mask + +import android.graphics.Bitmap +import com.tencent.qgame.animplayer.PointRect +import com.tencent.qgame.animplayer.RefVec2 +import com.tencent.qgame.animplayer.util.TextureLoadUtil + +class MaskConfig() { + var maskTexPair: Pair? = null //遮罩坐标矩形 + var maskPositionPair: Pair? = null //内容坐标矩形 + + constructor(bitmap: Bitmap?, positionPair :Pair?, texPair: Pair?) : this() { + maskPositionPair = positionPair + maskTexPair = texPair + alphaMaskBitmap = bitmap + } + + private var maskTexId = 0 + fun getMaskTexId() : Int { + return maskTexId + } + fun updateMaskTex() : Int { + maskTexId = TextureLoadUtil.loadTexture(alphaMaskBitmap) + return maskTexId + } + + var alphaMaskBitmap: Bitmap? = null //遮罩 + private set(value) { + field = value + } + + fun safeSetMaskBitmapAndReleasePre(bitmap: Bitmap?) { + if (maskTexId > 0) { //释放 + TextureLoadUtil.releaseTexure(maskTexId) + maskTexId = 0 + } + alphaMaskBitmap = bitmap + } + + + fun release() { + alphaMaskBitmap = null + maskTexPair = null + maskPositionPair = null + } + + override fun equals(other: Any?): Boolean { + return other is MaskConfig && this.alphaMaskBitmap != other.alphaMaskBitmap + && this.maskTexPair?.first != other.maskTexPair?.first && this.maskTexPair?.second != other.maskTexPair?.second + && this.maskPositionPair?.first != other.maskPositionPair?.first && this.maskPositionPair?.second != other.maskPositionPair?.second + } + + override fun hashCode(): Int { + var result = alphaMaskBitmap?.hashCode() ?: 0 + result = 31 * result + (maskTexPair?.hashCode() ?: 0) + result = 31 * result + (maskPositionPair?.hashCode() ?: 0) + return result + } +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mask/MaskRender.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mask/MaskRender.kt new file mode 100644 index 0000000..efd685f --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mask/MaskRender.kt @@ -0,0 +1,130 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.mask + +import android.opengl.GLES11Ext +import android.opengl.GLES20 +import com.tencent.qgame.animplayer.AnimConfig +import com.tencent.qgame.animplayer.Constant +import com.tencent.qgame.animplayer.PointRect +import com.tencent.qgame.animplayer.RefVec2 +import com.tencent.qgame.animplayer.util.GlFloatArray +import com.tencent.qgame.animplayer.util.TexCoordsUtil +import com.tencent.qgame.animplayer.util.VertexUtil + +/** + * vapx 渲染 + */ +class MaskRender(private val maskAnimPlugin: MaskAnimPlugin) { + companion object { + private const val TAG = "${Constant.TAG}.MaskRender" + } + + var maskShader: MaskShader? = null + var vertexArray = GlFloatArray() + private var maskArray = GlFloatArray() + /** + * shader 与 texture初始化 + */ + fun initMaskShader(edgeBlur: Boolean) { + // shader 初始化 + maskShader = MaskShader(edgeBlur) + GLES20.glDisable(GLES20.GL_DEPTH_TEST) // 关闭深度测试 + } + + fun renderFrame(config: AnimConfig) { + val videoTextureId = maskAnimPlugin.player.decoder?.render?.getExternalTexture() ?: return + if (videoTextureId <= 0) return + val shader = this.maskShader ?: return + var maskTexId: Int = config.maskConfig?.getMaskTexId() ?: return + val maskBitmap = config.maskConfig?.alphaMaskBitmap ?: return + val maskTexRect = config.maskConfig?.maskTexPair?.first ?: return + val maskTexRefVec2 = config.maskConfig?.maskTexPair?.second ?: return + val maskPositionRect = config.maskConfig?.maskPositionPair?.first ?: PointRect( + 0, + 0, + config.width, + config.height + ) + val maskPositionRefVec2 = + config.maskConfig?.maskPositionPair?.second ?: RefVec2(config.width, config.height) + shader.useProgram() + // 顶点坐标 + vertexArray.setArray( + VertexUtil.create( + maskPositionRefVec2.w, + maskPositionRefVec2.h, + maskPositionRect, + vertexArray.array + ) + ) + vertexArray.setVertexAttribPointer(shader.aPositionLocation) + + if (maskTexId <= 0 && !maskBitmap.isRecycled) { + maskTexId = config.maskConfig?.updateMaskTex() ?: 0 + } + if (maskTexId > 0) { + maskArray.setArray( + TexCoordsUtil.create( + maskTexRefVec2.w, + maskTexRefVec2.h, + maskTexRect, + maskArray.array + ) + ) + maskArray.setVertexAttribPointer(shader.aTextureMaskCoordinatesLocation) + // 绑定alpha纹理 + GLES20.glActiveTexture(GLES20.GL_TEXTURE0) + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, maskTexId) + GLES20.glTexParameterf( + GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_NEAREST.toFloat() + ) + GLES20.glTexParameterf( + GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_MAG_FILTER, + GLES20.GL_LINEAR.toFloat() + ) + GLES20.glTexParameteri( + GLES11Ext.GL_TEXTURE_EXTERNAL_OES, + GLES20.GL_TEXTURE_WRAP_S, + GLES20.GL_CLAMP_TO_EDGE + ) + GLES20.glTexParameteri( + GLES11Ext.GL_TEXTURE_EXTERNAL_OES, + GLES20.GL_TEXTURE_WRAP_T, + GLES20.GL_CLAMP_TO_EDGE + ) + GLES20.glUniform1i(shader.uTextureMaskUnitLocation, 0) + + GLES20.glEnable(GLES20.GL_BLEND) + // 基于源象素alpha通道值的半透明混合函数 + GLES20.glBlendFuncSeparate( + GLES20.GL_ONE, + GLES20.GL_SRC_ALPHA, + GLES20.GL_ZERO, + GLES20.GL_SRC_ALPHA + ) + // draw + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4) + + GLES20.glDisable(GLES20.GL_BLEND) + } + + } + +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mask/MaskShader.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mask/MaskShader.kt new file mode 100644 index 0000000..29baddd --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mask/MaskShader.kt @@ -0,0 +1,114 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.mask + +import android.opengl.GLES20 +import android.util.Log +import com.tencent.qgame.animplayer.util.ShaderUtil + +class MaskShader(edgeBlurBoolean : Boolean) { + companion object { + + private const val VERTEX = + "attribute vec4 vPosition;\n" + + "attribute vec4 vTexCoordinateAlphaMask;\n" + + "varying vec2 v_TexCoordinateAlphaMask;\n" + + "\n" + + "void main() {\n" + + " v_TexCoordinateAlphaMask = vec2(vTexCoordinateAlphaMask.x, vTexCoordinateAlphaMask.y);\n" + + " gl_Position = vPosition;\n" + + "}" + //边缘做高斯模糊 + private const val FRAGMENT_BLUR_EDGE = + "precision mediump float;\n" + + "uniform sampler2D uTextureAlphaMask;\n" + + "varying vec2 v_TexCoordinateAlphaMask;\n" + + "mat3 weight = mat3(0.0625,0.125,0.0625,0.125,0.25,0.125,0.0625,0.125,0.0625);\n " + + "int coreSize=3;\n" + + "float texelOffset = .01;\n" + + "\n" + + "void main() {\n" + + " float alphaResult = 0.;\n" + + " for(int y = 0; y < coreSize; y++) {\n" + + " for(int x = 0;x < coreSize; x++) {\n" + + " alphaResult += texture2D(uTextureAlphaMask, vec2(v_TexCoordinateAlphaMask.x + (-1.0 + float(x)) * texelOffset,v_TexCoordinateAlphaMask.y + (-1.0 + float(y)) * texelOffset)).a * weight[x][y];\n" + + " }\n" + + " }\n" + + " gl_FragColor = vec4(0, 0, 0, alphaResult);\n" + + "}" + + private const val FRAGMENT_NO_BLUR_EDGE = + "precision mediump float;\n" + + "uniform sampler2D uTextureAlphaMask;\n" + + "varying vec2 v_TexCoordinateAlphaMask;\n" + + "\n" + + "void main () {\n" + + " vec4 alphaMaskColor = texture2D(uTextureAlphaMask, v_TexCoordinateAlphaMask);\n" + + " gl_FragColor = vec4(0, 0, 0, alphaMaskColor.a);\n" + + "}" + + private const val FRAGMENT_ROW = + "precision mediump float;\n" + + "uniform sampler2D uTextureAlphaMask;\n" + + "varying vec2 v_TexCoordinateAlphaMask;\n" + + "vec3 weight = vec3(0.4026,0.2442,0.0545);\n " + + "\n" + + "void main() {\n" + + " float texelOffset = .01;\n" + + " vec2 uv[5];\n" + + " uv[0]= v_TexCoordinateAlphaMask;\n" + + " uv[1]=vec2(uv[0].x+texelOffset*1.0, uv[0].y);\n" + + " uv[2]=vec2(uv[0].x-texelOffset*1.0, uv[0].y);\n" + + " uv[3]=vec2(uv[0].x+texelOffset*2.0, uv[0].y);\n" + + " uv[4]=vec2(uv[0].x-texelOffset*2.0, uv[0].y);\n" + + " float alphaResult = texture2D(uTextureAlphaMask, uv[0]).a * weight[0];\n" + + " for(int i = 1; i < 3; ++i) {\n" + + " alphaResult += texture2D(uTextureAlphaMask, uv[2*i-1]).a * weight[i];\n" + + " alphaResult += texture2D(uTextureAlphaMask, uv[2*i]).a * weight[i];\n" + + " }\n" + + " gl_FragColor = vec4(0, 0, 0, alphaResult);\n" + + "}" + + // Uniform constants + private const val U_TEXTURE_ALPHA_MASK_UNIT = "uTextureAlphaMask" + + // Attribute constants + private const val A_POSITION = "vPosition" + private const val A_TEXTURE_MASK_COORDINATES = "vTexCoordinateAlphaMask" + } + + // Shader program + private val program: Int + + // Uniform locations + val uTextureMaskUnitLocation: Int + // Attribute locations + val aPositionLocation: Int + val aTextureMaskCoordinatesLocation: Int + + init { + program = if(edgeBlurBoolean) ShaderUtil.createProgram(VERTEX, FRAGMENT_BLUR_EDGE) else ShaderUtil.createProgram(VERTEX, FRAGMENT_NO_BLUR_EDGE) + uTextureMaskUnitLocation = GLES20.glGetUniformLocation(program, U_TEXTURE_ALPHA_MASK_UNIT) + + aPositionLocation = GLES20.glGetAttribLocation(program, A_POSITION) + aTextureMaskCoordinatesLocation = GLES20.glGetAttribLocation(program, A_TEXTURE_MASK_COORDINATES) + } + + fun useProgram() { + GLES20.glUseProgram(program) + } + +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/Frame.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/Frame.kt new file mode 100644 index 0000000..0eaff04 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/Frame.kt @@ -0,0 +1,83 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.mix + +import android.util.SparseArray +import com.tencent.qgame.animplayer.PointRect +import org.json.JSONObject + +/** + * 单帧 + */ +class Frame(val index: Int, json: JSONObject) { + var srcId = "" + var z = 0 + var frame: PointRect + var mFrame: PointRect + var mt = 0 // 遮罩旋转角度v2 版本只支持 0 与 90度 + + init { + srcId = json.getString("srcId") + z = json.getInt("z") + + val f = json.getJSONArray("frame") + frame = PointRect(f.getInt(0), f.getInt(1), f.getInt(2), f.getInt(3)) + + val m = json.getJSONArray("mFrame") + mFrame = PointRect(m.getInt(0), m.getInt(1), m.getInt(2), m.getInt(3)) + + mt = json.getInt("mt") + } +} + + +/** + * 一帧的集合 + */ +class FrameSet(json: JSONObject) { + var index = 0 // 哪一帧 + val list = ArrayList() + init { + index = json.getInt("i") + val objJsonArray = json.getJSONArray("obj") + val objLen = objJsonArray?.length() ?: 0 + for (i in 0 until objLen) { + val frameJson = objJsonArray?.getJSONObject(i) ?: continue + val frame = Frame(index, frameJson) + list.add(frame) + } + // 绘制顺序排序 + list.sortBy {it.z} + } +} + +/** + * 所有帧集合 + */ +class FrameAll(json: JSONObject) { + // 每一帧的集合 + val map = SparseArray() + + init { + val frameJsonArray = json.getJSONArray("frame") + val frameLen = frameJsonArray?.length() ?: 0 + for (i in 0 until frameLen) { + val frameSetJson = frameJsonArray?.getJSONObject(i) ?: continue + val frameSet = FrameSet(frameSetJson) + map.put(frameSet.index, frameSet) + } + } +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/MixAnimPlugin.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/MixAnimPlugin.kt new file mode 100644 index 0000000..780b2da --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/MixAnimPlugin.kt @@ -0,0 +1,229 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.mix + +import android.graphics.Bitmap +import android.os.Handler +import android.os.Looper +import android.os.SystemClock +import android.view.MotionEvent +import com.tencent.qgame.animplayer.AnimConfig +import com.tencent.qgame.animplayer.AnimPlayer +import com.tencent.qgame.animplayer.Constant +import com.tencent.qgame.animplayer.inter.IFetchResource +import com.tencent.qgame.animplayer.inter.OnResourceClickListener +import com.tencent.qgame.animplayer.plugin.IAnimPlugin +import com.tencent.qgame.animplayer.util.ALog +import com.tencent.qgame.animplayer.util.BitmapUtil + +class MixAnimPlugin(val player: AnimPlayer): IAnimPlugin { + + companion object { + private const val TAG = "${Constant.TAG}.MixAnimPlugin" + } + var resourceRequest: IFetchResource? = null + var resourceClickListener: OnResourceClickListener? = null + var srcMap: SrcMap? = null + var frameAll: FrameAll? = null + var curFrameIndex = -1 // 当前帧 + private var resultCbCount = 0 // 回调次数 + private var mixRender:MixRender? = null + private val mixTouch by lazy { MixTouch(this) } + var autoTxtColorFill = true // 是否启动自动文字填充 默认开启 + + // 同步锁 + private val lock = Object() + private var forceStopLock = false + + + override fun onConfigCreate(config: AnimConfig): Int { + if (!config.isMix) return Constant.OK + if (resourceRequest == null) { + ALog.e(TAG, "IFetchResource is empty") + // 没有设置IFetchResource 当成普通视频播放 + return Constant.OK + } + // step 1 parse src + parseSrc(config) + + // step 2 parse frame + parseFrame(config) + + // step 3 fetch resource + fetchResourceSync() + + // step 4 生成文字bitmap + val result = createBitmap() + if (!result) { + return Constant.REPORT_ERROR_TYPE_CONFIG_PLUGIN_MIX + } + + // step 5 check resource + ALog.i(TAG, "load resource $resultCbCount") + srcMap?.map?.values?.forEach { + if (it.bitmap == null) { + ALog.e(TAG, "missing src $it") + return Constant.REPORT_ERROR_TYPE_CONFIG_PLUGIN_MIX + } else if (it.bitmap?.config == Bitmap.Config.ALPHA_8) { + ALog.e(TAG, "src $it bitmap must not be ALPHA_8") + return Constant.REPORT_ERROR_TYPE_CONFIG_PLUGIN_MIX + } + } + return Constant.OK + } + + override fun onRenderCreate() { + if (player.configManager.config?.isMix == false) return + ALog.i(TAG, "mix render init") + mixRender = MixRender(this) + mixRender?.init() + } + + override fun onRendering(frameIndex: Int) { + val config = player.configManager.config ?: return + if (!config.isMix) return + curFrameIndex = frameIndex + val list = frameAll?.map?.get(frameIndex)?.list ?: return + list.forEach {frame -> + val src = srcMap?.map?.get(frame.srcId) ?: return@forEach + mixRender?.renderFrame(config, frame, src) + } + } + + + override fun onRelease() { + destroy() + } + + override fun onDestroy() { + destroy() + } + + override fun onDispatchTouchEvent(ev: MotionEvent): Boolean { + if (player.configManager.config?.isMix == false || resourceClickListener == null) { + return super.onDispatchTouchEvent(ev) + } + mixTouch.onTouchEvent(ev)?.let {resource -> + Handler(Looper.getMainLooper()).post { + resourceClickListener?.onClick(resource) + } + } + // 只要注册监听则拦截所有事件 + return true + } + + private fun destroy() { + // 强制结束等待 + forceStopLockThread() + if (player.configManager.config?.isMix == false) return + val resources = ArrayList() + srcMap?.map?.values?.forEach {src -> + mixRender?.release(src.srcTextureId) + when(src.srcType) { + Src.SrcType.IMG -> resources.add(Resource(src)) + Src.SrcType.TXT -> src.bitmap?.recycle() + else -> {} + } + } + resourceRequest?.releaseResource(resources) + + // 清理 + curFrameIndex = -1 + srcMap?.map?.clear() + frameAll?.map?.clear() + } + + private fun parseSrc(config: AnimConfig) { + config.jsonConfig?.apply { + srcMap = SrcMap(this) + } + } + + + private fun parseFrame(config: AnimConfig) { + config.jsonConfig?.apply { + frameAll = FrameAll(this) + } + } + + + private fun fetchResourceSync() { + synchronized(lock) { + forceStopLock = false // 开始时不会强制关闭 + } + val time = SystemClock.elapsedRealtime() + val totalSrc = srcMap?.map?.size ?: 0 + ALog.i(TAG, "load resource totalSrc = $totalSrc") + + resultCbCount = 0 + srcMap?.map?.values?.forEach {src -> + if (src.srcType == Src.SrcType.IMG) { + ALog.i(TAG, "fetch image ${src.srcId}") + resourceRequest?.fetchImage(Resource(src)) { + src.bitmap = if (it == null) { + ALog.e(TAG, "fetch image ${src.srcId} bitmap return null") + BitmapUtil.createEmptyBitmap() + } else it + ALog.i(TAG, "fetch image ${src.srcId} finish bitmap is ${it?.hashCode()}") + resultCall() + } + } else if (src.srcType == Src.SrcType.TXT) { + ALog.i(TAG, "fetch txt ${src.srcId}") + resourceRequest?.fetchText(Resource(src)) { + src.txt = it ?: "" + ALog.i(TAG, "fetch text ${src.srcId} finish txt is $it") + resultCall() + } + } + } + + // 同步等待所有资源完成 + synchronized(lock) { + while (resultCbCount < totalSrc && !forceStopLock) { + lock.wait() + } + } + ALog.i(TAG, "fetchResourceSync cost=${SystemClock.elapsedRealtime() - time}ms") + } + + private fun forceStopLockThread() { + synchronized(lock) { + forceStopLock = true + lock.notifyAll() + } + } + + private fun resultCall() { + synchronized(lock) { + resultCbCount++ + lock.notifyAll() + } + } + + private fun createBitmap(): Boolean { + return try { + srcMap?.map?.values?.forEach { src -> + if (src.srcType == Src.SrcType.TXT) { + src.bitmap = BitmapUtil.createTxtBitmap(src) + } + } + true + } catch (e: OutOfMemoryError) { + ALog.e(TAG, "draw text OOM $e", e) + false + } + } +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/MixRender.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/MixRender.kt new file mode 100644 index 0000000..1f82da4 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/MixRender.kt @@ -0,0 +1,151 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.mix + +import android.opengl.GLES11Ext +import android.opengl.GLES20 +import com.tencent.qgame.animplayer.AnimConfig +import com.tencent.qgame.animplayer.Constant +import com.tencent.qgame.animplayer.PointRect +import com.tencent.qgame.animplayer.util.* + +/** + * vapx 渲染 + */ +class MixRender(private val mixAnimPlugin: MixAnimPlugin) { + companion object { + private const val TAG = "${Constant.TAG}.MixRender" + } + var shader: MixShader? = null + var vertexArray = GlFloatArray() + var srcArray = GlFloatArray() + var maskArray = GlFloatArray() + + /** + * shader 与 texture初始化 + */ + fun init() { + // shader 初始化 + shader = MixShader() + GLES20.glDisable(GLES20.GL_DEPTH_TEST) // 关闭深度测试 + + mixAnimPlugin.srcMap?.map?.values?.forEach {src-> + ALog.i(TAG, "init srcId=${src.srcId}") + src.srcTextureId = TextureLoadUtil.loadTexture(src.bitmap) + ALog.i(TAG, "textureProgram=${shader?.program},textureId=${src.srcTextureId}") + } + + } + + fun renderFrame(config:AnimConfig, frame:Frame, src: Src) { + val videoTextureId = mixAnimPlugin.player.decoder?.render?.getExternalTexture() ?: return + if (videoTextureId <= 0) return + val shader = this.shader ?: return + shader.useProgram() + // 定点坐标 + vertexArray.setArray(VertexUtil.create(config.width, config.height, frame.frame, vertexArray.array)) + vertexArray.setVertexAttribPointer(shader.aPositionLocation) + + // src 纹理坐标 + srcArray.setArray(genSrcCoordsArray(srcArray.array, frame.frame.w, frame.frame.h, src.drawWidth, src.drawHeight, src.fitType)) + srcArray.setVertexAttribPointer(shader.aTextureSrcCoordinatesLocation) + // 绑定 src纹理 + GLES20.glActiveTexture(GLES20.GL_TEXTURE0) + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, src.srcTextureId) + GLES20.glUniform1i(shader.uTextureSrcUnitLocation, 0) + + // mask 纹理 + maskArray.setArray(TexCoordsUtil.create(config.videoWidth, config.videoHeight, frame.mFrame, maskArray.array)) + if (frame.mt == 90) { + maskArray.setArray(TexCoordsUtil.rotate90(maskArray.array)) + } + maskArray.setVertexAttribPointer(shader.aTextureMaskCoordinatesLocation) + // 绑定 mask纹理 + GLES20.glActiveTexture(GLES20.GL_TEXTURE1) + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, videoTextureId) + GLES20.glUniform1i(shader.uTextureMaskUnitLocation, 1) + + // 属性处理 + if (src.srcType == Src.SrcType.TXT && mixAnimPlugin.autoTxtColorFill) { // // 文字需要颜色填充 + GLES20.glUniform1i(shader.uIsFillLocation, 1) + val argb = transColor(src.color) + GLES20.glUniform4f(shader.uColorLocation, argb[1], argb[2], argb[3], argb[0]) + } else { + GLES20.glUniform1i(shader.uIsFillLocation, 0) + GLES20.glUniform4f(shader.uColorLocation, 0f, 0f, 0f, 0f) + } + + GLES20.glEnable(GLES20.GL_BLEND) + // 基于源象素alpha通道值的半透明混合函数 + GLES20.glBlendFuncSeparate(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA, GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA) + // draw + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4) + + GLES20.glDisable(GLES20.GL_BLEND) + + } + + fun release(textureId: Int) { + if (textureId != 0) { + GLES20.glDeleteTextures(1, intArrayOf(textureId), 0) + } + } + + /** + * CENTER_FULL 并不是严格的centerCrop(centerCrop已经前置处理),此处主要是为防抖动做处理,复杂遮罩情况下需要固定src大小进行绘制防止抖动 + */ + private fun genSrcCoordsArray(array: FloatArray, fw: Int, fh: Int, sw: Int, sh: Int, fitType: Src.FitType): FloatArray { + return if (fitType == Src.FitType.CENTER_FULL) { + if (fw <= sw && fh <= sh) { + // 中心对齐,不拉伸 + val gw = (sw - fw) / 2 + val gh = (sh - fh) / 2 + TexCoordsUtil.create(sw, sh, PointRect(gw, gh, fw, fh), array) + } else { // centerCrop + val fScale = fw * 1.0f / fh + val sScale = sw * 1.0f / sh + val srcRect = if (fScale > sScale) { + val w = sw + val x = 0 + val h = (sw / fScale).toInt() + val y = (sh - h) / 2 + + PointRect(x, y, w, h) + } else { + val h = sh + val y = 0 + val w = (sh * fScale).toInt() + val x = (sw - w) / 2 + PointRect(x, y, w, h) + } + TexCoordsUtil.create(sw, sh, srcRect, array) + } + } else { // 默认 fitXY + TexCoordsUtil.create(fw, fh, PointRect(0, 0, fw, fh), array) + } + } + + private fun transColor(color: Int): FloatArray { + val argb = FloatArray(4) + argb[0] = (color.ushr(24) and 0x000000ff) / 255f + argb[1] = (color.ushr(16) and 0x000000ff) / 255f + argb[2] = (color.ushr(8) and 0x000000ff) / 255f + argb[3] = (color and 0x000000ff) / 255f + return argb + } + + +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/MixShader.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/MixShader.kt new file mode 100644 index 0000000..a5c5610 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/MixShader.kt @@ -0,0 +1,94 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.mix + +import android.opengl.GLES20 +import com.tencent.qgame.animplayer.util.ShaderUtil + +class MixShader { + companion object { + private const val VERTEX = "attribute vec4 a_Position; \n" + + "attribute vec2 a_TextureSrcCoordinates;\n" + + "attribute vec2 a_TextureMaskCoordinates;\n" + + "varying vec2 v_TextureSrcCoordinates;\n" + + "varying vec2 v_TextureMaskCoordinates;\n" + + "void main()\n" + + "{\n" + + " v_TextureSrcCoordinates = a_TextureSrcCoordinates;\n" + + " v_TextureMaskCoordinates = a_TextureMaskCoordinates;\n" + + " gl_Position = a_Position;\n" + + "}" + + private const val FRAGMENT = "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float; \n" + + "uniform sampler2D u_TextureSrcUnit;\n" + + "uniform samplerExternalOES u_TextureMaskUnit;\n" + + "uniform int u_isFill;\n" + + "uniform vec4 u_Color;\n" + + "varying vec2 v_TextureSrcCoordinates;\n" + + "varying vec2 v_TextureMaskCoordinates;\n" + + "void main()\n" + + "{\n" + + " vec4 srcRgba = texture2D(u_TextureSrcUnit, v_TextureSrcCoordinates);\n" + + " vec4 maskRgba = texture2D(u_TextureMaskUnit, v_TextureMaskCoordinates);\n" + + " float isFill = step(0.5, float(u_isFill));\n" + + " vec4 srcRgbaCal = isFill * vec4(u_Color.r, u_Color.g, u_Color.b, srcRgba.a) + (1.0 - isFill) * srcRgba;\n" + + " gl_FragColor = vec4(srcRgbaCal.r, srcRgbaCal.g, srcRgbaCal.b, srcRgba.a * maskRgba.r);\n" + + "}" + + // Uniform constants + private const val U_TEXTURE_SRC_UNIT = "u_TextureSrcUnit" + private const val U_TEXTURE_MASK_UNIT = "u_TextureMaskUnit" + private const val U_IS_FILL = "u_isFill" + private const val U_COLOR = "u_Color" + + // Attribute constants + private const val A_POSITION = "a_Position" + private const val A_TEXTURE_SRC_COORDINATES = "a_TextureSrcCoordinates" + private const val A_TEXTURE_MASK_COORDINATES = "a_TextureMaskCoordinates" + } + + // Shader program + val program: Int + + // Uniform locations + val uTextureSrcUnitLocation: Int + val uTextureMaskUnitLocation: Int + val uIsFillLocation: Int + val uColorLocation: Int + + // Attribute locations + val aPositionLocation: Int + val aTextureSrcCoordinatesLocation: Int + val aTextureMaskCoordinatesLocation: Int + + init { + program = ShaderUtil.createProgram(VERTEX, FRAGMENT) + uTextureSrcUnitLocation = GLES20.glGetUniformLocation(program, U_TEXTURE_SRC_UNIT) + uTextureMaskUnitLocation = GLES20.glGetUniformLocation(program, U_TEXTURE_MASK_UNIT) + uIsFillLocation = GLES20.glGetUniformLocation(program, U_IS_FILL) + uColorLocation = GLES20.glGetUniformLocation(program, U_COLOR) + + aPositionLocation = GLES20.glGetAttribLocation(program, A_POSITION) + aTextureSrcCoordinatesLocation = GLES20.glGetAttribLocation(program, A_TEXTURE_SRC_COORDINATES) + aTextureMaskCoordinatesLocation = GLES20.glGetAttribLocation(program, A_TEXTURE_MASK_COORDINATES) + } + + fun useProgram() { + GLES20.glUseProgram(program) + } + +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/MixTouch.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/MixTouch.kt new file mode 100644 index 0000000..70f9a6f --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/MixTouch.kt @@ -0,0 +1,56 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.mix + +import android.view.MotionEvent +import com.tencent.qgame.animplayer.PointRect + +/** + * 触摸事件 + */ +class MixTouch(private val mixAnimPlugin: MixAnimPlugin) { + + fun onTouchEvent(ev: MotionEvent): Resource? { + val (viewWith, viewHeight) = mixAnimPlugin.player.animView.getRealSize() + val videoWith = mixAnimPlugin.player.configManager.config?.width ?: return null + val videoHeight = mixAnimPlugin.player.configManager.config?.height ?: return null + + if (viewWith == 0 || viewHeight == 0) return null + + when(ev.action) { + MotionEvent.ACTION_UP -> { + val x = ev.x * videoWith / viewWith.toFloat() + val y = ev.y * videoHeight / viewHeight.toFloat() + val list = mixAnimPlugin.frameAll?.map?.get(mixAnimPlugin.curFrameIndex)?.list + list?.forEach {frame -> + val src = mixAnimPlugin.srcMap?.map?.get(frame.srcId) ?: return@forEach + if (calClick(x.toInt(), y.toInt(), frame.frame)) { + return Resource(src).apply { + curPoint = frame.frame + } + } + } + } + } + return null + } + + + private fun calClick(x: Int, y: Int, frame: PointRect): Boolean { + return x >= frame.x && x <= (frame.x + frame.w) + && y >= frame.y && y <= (frame.y + frame.h) + } +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/Resource.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/Resource.kt new file mode 100644 index 0000000..10de17e --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/Resource.kt @@ -0,0 +1,39 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.mix + +import android.graphics.Bitmap +import com.tencent.qgame.animplayer.PointRect + +/** + * 资源描述 + */ +class Resource(src: Src) { + var id = "" + var type = Src.SrcType.UNKNOWN + var loadType = Src.LoadType.UNKNOWN + var tag = "" + var bitmap: Bitmap? = null + var curPoint: PointRect? = null // src在当前帧的位置信息 + + init { + id = src.srcId + type = src.srcType + loadType = src.loadType + tag = src.srcTag + bitmap = src.bitmap + } +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/Src.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/Src.kt new file mode 100644 index 0000000..0b78f0c --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/mix/Src.kt @@ -0,0 +1,152 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.mix + +import android.graphics.Bitmap +import android.graphics.Color +import com.tencent.qgame.animplayer.Constant +import com.tencent.qgame.animplayer.util.ALog +import org.json.JSONObject + +class Src { + companion object { + private const val TAG = "${Constant.TAG}.Src" + } + + enum class SrcType(val type: String) { + UNKNOWN("unknown"), + IMG("img"), + TXT("txt"), + } + + enum class LoadType(val type: String) { + UNKNOWN("unknown"), + NET("net"), // 网络加载的图片 + LOCAL("local"), // 本地加载的图片 + } + + enum class FitType(val type: String) { + FIT_XY("fitXY"), // 按原始大小填充纹理 + CENTER_FULL("centerFull"), // 以纹理中心点放置 + } + + enum class Style(val style: String) { + DEFAULT("default"), + BOLD("b"), // 文字粗体 + } + + var srcId = "" + var w = 0 + var h = 0 + var drawWidth = 0 + var drawHeight = 0 + var srcType = SrcType.UNKNOWN + var loadType = LoadType.UNKNOWN + var srcTag = "" + var txt = "" + var style = Style.DEFAULT + var color: Int = 0 + var fitType = FitType.FIT_XY + var srcTextureId = 0 + var bitmap: Bitmap? = null + set(value) { + field = value + genDrawSize(value) + } + + constructor(json: JSONObject) { + srcId = json.getString("srcId") + w = json.getInt("w") + h = json.getInt("h") + // 可选 + var colorStr = json.optString("color", "#000000") + if (colorStr.isEmpty()) { + colorStr = "#000000" + } + color = Color.parseColor(colorStr) + srcTag = json.getString("srcTag") + txt = srcTag + + srcType = when(json.getString("srcType")) { + SrcType.IMG.type -> SrcType.IMG + SrcType.TXT.type -> SrcType.TXT + else -> SrcType.UNKNOWN + } + loadType = when(json.getString("loadType")) { + LoadType.NET.type -> LoadType.NET + LoadType.LOCAL.type -> LoadType.LOCAL + else -> LoadType.UNKNOWN + } + fitType = when(json.getString("fitType")) { + FitType.CENTER_FULL.type -> FitType.CENTER_FULL + else -> FitType.FIT_XY + } + + // 可选 + style = when(json.optString("style", "")) { + Style.BOLD.style -> Style.BOLD + else -> Style.DEFAULT + } + ALog.i(TAG, "${toString()} color=$colorStr") + } + + + private fun genDrawSize(bitmap: Bitmap?) { + val bw = bitmap?.width?: w + val bh = bitmap?.height?: h + drawWidth = bw + drawHeight = bh + if (fitType == FitType.CENTER_FULL) { + if (w == 0 || h == 0) { + return + } + // 按src w h进行centerCrop处理 + val srcRate = w.toFloat() / h.toFloat() + val bitmapRate = bw.toFloat() / bh.toFloat() + + if (bitmapRate >= srcRate) { + drawHeight = h + drawWidth = (h * bitmapRate).toInt() + } else { + drawWidth = w + drawHeight = (w / bitmapRate).toInt() + } + } + } + + + override fun toString(): String { + return "Src(srcId='$srcId', srcType=$srcType, loadType=$loadType, srcTag='$srcTag', bitmap=$bitmap, txt='$txt')" + } + +} + + +class SrcMap(json: JSONObject) { + val map = HashMap() + + init { + val srcJsonArray = json.getJSONArray("src") + val srcLen = srcJsonArray?.length() ?: 0 + for (i in 0 until srcLen) { + val srcJson = srcJsonArray?.getJSONObject(i) ?: continue + val src = Src(srcJson) + if (src.srcType != Src.SrcType.UNKNOWN) { // 不认识的srcType丢弃 + map[src.srcId] = src + } + } + } +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/plugin/AnimPluginManager.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/plugin/AnimPluginManager.kt new file mode 100644 index 0000000..25d6490 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/plugin/AnimPluginManager.kt @@ -0,0 +1,127 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.plugin + +import android.view.MotionEvent +import com.tencent.qgame.animplayer.AnimConfig +import com.tencent.qgame.animplayer.AnimPlayer +import com.tencent.qgame.animplayer.Constant +import com.tencent.qgame.animplayer.mask.MaskAnimPlugin +import com.tencent.qgame.animplayer.mix.MixAnimPlugin +import com.tencent.qgame.animplayer.util.ALog + +/** + * 动画插件管理 + */ +class AnimPluginManager(val player: AnimPlayer) { + companion object { + private const val TAG = "${Constant.TAG}.AnimPluginManager" + private const val DIFF_TIMES = 4 + } + + private val mixAnimPlugin = MixAnimPlugin(player) + private val maskAnimPlugin = MaskAnimPlugin(player) + + private val plugins = listOf(mixAnimPlugin, maskAnimPlugin) + + // 当前渲染的帧 + private var frameIndex = 0 + // 当前解码的帧 + private var decodeIndex = 0 + // 帧不相同的次数, 连续多次不同则直接使用decodeIndex + private var frameDiffTimes = 0 + + fun getMixAnimPlugin(): MixAnimPlugin? { + return mixAnimPlugin + } + + fun getMaskAnimPlugin() : MaskAnimPlugin? { + return maskAnimPlugin + } + + fun onConfigCreate(config: AnimConfig): Int { + ALog.i(TAG, "onConfigCreate") + plugins.forEach { + val res = it.onConfigCreate(config) + if (res != Constant.OK) { + return res + } + } + return Constant.OK + } + + fun onRenderCreate() { + ALog.i(TAG, "onRenderCreate") + frameIndex = 0 + decodeIndex = 0 + plugins.forEach { + it.onRenderCreate() + } + } + + fun onDecoding(decodeIndex: Int) { + ALog.d(TAG, "onDecoding decodeIndex=$decodeIndex") + this.decodeIndex = decodeIndex + plugins.forEach { + it.onDecoding(decodeIndex) + } + } + + // 开始循环调用 + fun onLoopStart() { + ALog.i(TAG, "onLoopStart") + frameIndex = 0 + decodeIndex = 0 + } + + fun onRendering() { + if (decodeIndex > frameIndex + 1 || frameDiffTimes >= DIFF_TIMES) { + ALog.i(TAG, "jump frameIndex= $frameIndex,decodeIndex=$decodeIndex,frameDiffTimes=$frameDiffTimes") + frameIndex = decodeIndex + } + if (decodeIndex != frameIndex) { + frameDiffTimes++ + } else { + frameDiffTimes = 0 + } + ALog.d(TAG, "onRendering frameIndex=$frameIndex") + plugins.forEach { + it.onRendering(frameIndex) // 第一帧 0 + } + frameIndex++ + } + + fun onRelease() { + ALog.i(TAG, "onRelease") + plugins.forEach { + it.onRelease() + } + } + + fun onDestroy() { + ALog.i(TAG, "onDestroy") + plugins.forEach { + it.onDestroy() + } + } + + fun onDispatchTouchEvent(ev: MotionEvent): Boolean { + plugins.forEach { + if (it.onDispatchTouchEvent(ev)) return true + } + return false + } +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/plugin/IAnimPlugin.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/plugin/IAnimPlugin.kt new file mode 100644 index 0000000..3af7155 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/plugin/IAnimPlugin.kt @@ -0,0 +1,48 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.plugin + +import android.view.MotionEvent +import com.tencent.qgame.animplayer.AnimConfig +import com.tencent.qgame.animplayer.Constant + +interface IAnimPlugin { + + // 配置生成 + fun onConfigCreate(config: AnimConfig): Int { + return Constant.OK + } + + // 渲染初始化 + fun onRenderCreate() {} + + // 解码通知 + fun onDecoding(decodeIndex: Int) {} + + // 每一帧渲染 + fun onRendering(frameIndex: Int) {} + + // 每次播放完毕 + fun onRelease() {} + + // 销毁 + fun onDestroy() {} + + /** 触摸事件 + * @return false 不拦截 true 拦截 + */ + fun onDispatchTouchEvent(ev: MotionEvent): Boolean {return false} +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/textureview/InnerTextureView.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/textureview/InnerTextureView.kt new file mode 100644 index 0000000..43f828e --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/textureview/InnerTextureView.kt @@ -0,0 +1,21 @@ +package com.tencent.qgame.animplayer.textureview + +import android.content.Context +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.TextureView +import com.tencent.qgame.animplayer.AnimPlayer + +class InnerTextureView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 +) : TextureView(context, attrs, defStyleAttr) { + + var player: AnimPlayer? = null + + override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { + val res = player?.isRunning() == true + && ev != null + && player?.pluginManager?.onDispatchTouchEvent(ev) == true + return if (!res) super.dispatchTouchEvent(ev) else true + } +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/ALog.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/ALog.kt new file mode 100644 index 0000000..7c74f7f --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/ALog.kt @@ -0,0 +1,54 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.util + +object ALog { + + var isDebug = false + + var log: IALog? = null + + fun i(tag: String, msg: String) { + log?.i(tag, msg) + } + + fun d(tag: String, msg: String) { + if (isDebug) { + log?.d(tag, msg) + } + } + + fun e(tag: String, msg: String) { + log?.e(tag, msg) + } + + fun e(tag: String, msg: String, tr: Throwable) { + log?.e(tag, msg, tr) + } +} + + +interface IALog { + fun i(tag: String, msg: String) {} + + fun d(tag: String, msg: String) {} + + fun e(tag: String, msg: String) {} + + fun e(tag: String, msg: String, tr: Throwable) {} +} + + diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/BitmapUtil.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/BitmapUtil.kt new file mode 100644 index 0000000..f55e38a --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/BitmapUtil.kt @@ -0,0 +1,68 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.util + +import android.graphics.* +import android.text.TextPaint +import com.tencent.qgame.animplayer.mix.Src + +object BitmapUtil { + + fun createEmptyBitmap() : Bitmap { + return Bitmap.createBitmap(16, 16, Bitmap.Config.ARGB_8888).apply { + eraseColor(Color.TRANSPARENT) + } + } + + fun createTxtBitmap(src: Src): Bitmap { + val w = src.w + val h = src.h + // 这里使用ALPHA_8 在opengl渲染的时候图像出现错位 + val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + val rect = Rect(0, 0, w, h) + val bounds = Rect() + var sizeR = 0.8f + val paint = TextPaint().apply { + textSize = h * sizeR + textAlign = Paint.Align.CENTER + style = Paint.Style.FILL + isAntiAlias = true + if (src.style == Src.Style.BOLD) { + typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD) + } + color = src.color + } + val text = src.txt + while (sizeR > 0.1f) { + paint.getTextBounds(text, 0, text.length, bounds) + if (bounds.width() <= rect.width()) { + break + } + sizeR -= 0.1f + paint.textSize = h * sizeR + } + val fontMetrics = paint.fontMetricsInt + val top = fontMetrics.top + val bottom = fontMetrics.bottom + val baseline = rect.centerY() - top/2 - bottom/2 + + canvas.drawText(text, rect.centerX().toFloat(), baseline.toFloat(), paint) + + return bitmap + } + +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/GlFloatArray.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/GlFloatArray.kt new file mode 100644 index 0000000..3a28e0b --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/GlFloatArray.kt @@ -0,0 +1,50 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.util + +import android.opengl.GLES20 +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.FloatBuffer +import kotlin.FloatArray + +class GlFloatArray { + + + val array = FloatArray(8) + + private var floatBuffer: FloatBuffer + + init { + floatBuffer = ByteBuffer + .allocateDirect(array.size * 4) + .order(ByteOrder.nativeOrder()) + .asFloatBuffer() + .put(array) + } + + fun setArray(array: FloatArray) { + floatBuffer.position(0) + floatBuffer.put(array) + } + + + fun setVertexAttribPointer(attributeLocation: Int) { + floatBuffer.position(0) + GLES20.glVertexAttribPointer(attributeLocation, 2, GLES20.GL_FLOAT, false, 0, floatBuffer) + GLES20.glEnableVertexAttribArray(attributeLocation) + } +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/MediaUtil.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/MediaUtil.kt new file mode 100644 index 0000000..427e41a --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/MediaUtil.kt @@ -0,0 +1,107 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.util + +import android.media.MediaCodecList +import android.media.MediaExtractor +import android.media.MediaFormat +import com.tencent.qgame.animplayer.Constant +import com.tencent.qgame.animplayer.file.IFileContainer +import kotlin.collections.HashMap + + +object MediaUtil { + + private const val TAG = "${Constant.TAG}.MediaUtil" + + private var isTypeMapInit = false + private val supportTypeMap = HashMap() + + const val MIME_HEVC = "video/hevc" + + fun getExtractor(file: IFileContainer): MediaExtractor { + val extractor = MediaExtractor() + file.setDataSource(extractor) + return extractor + } + + /** + * 是否为h265的视频 + */ + fun checkIsHevc(videoFormat: MediaFormat):Boolean { + val mime = videoFormat.getString(MediaFormat.KEY_MIME) ?: "" + return mime.contains("hevc") + } + + fun selectVideoTrack(extractor: MediaExtractor): Int { + val numTracks = extractor.trackCount + for (i in 0 until numTracks) { + val format = extractor.getTrackFormat(i) + val mime = format.getString(MediaFormat.KEY_MIME) ?: "" + if (mime.startsWith("video/")) { + ALog.i(TAG, "Extractor selected track $i ($mime): $format") + return i + } + } + return -1 + } + + fun selectAudioTrack(extractor: MediaExtractor): Int { + val numTracks = extractor.trackCount + for (i in 0 until numTracks) { + val format = extractor.getTrackFormat(i) + val mime = format.getString(MediaFormat.KEY_MIME) ?: "" + if (mime.startsWith("audio/")) { + ALog.i(TAG, "Extractor selected track $i ($mime): $format") + return i + } + } + return -1 + } + + /** + * 检查设备解码支持类型 + */ + @Synchronized + fun checkSupportCodec(mimeType: String): Boolean { + if (!isTypeMapInit) { + isTypeMapInit = true + getSupportType() + } + return supportTypeMap.containsKey(mimeType.lowercase()) + } + + + private fun getSupportType() { + try { + val numCodecs = MediaCodecList.getCodecCount() + for (i in 0 until numCodecs) { + val codecInfo = MediaCodecList.getCodecInfoAt(i) + if (codecInfo.isEncoder) { + continue + } + val types = codecInfo.supportedTypes + for (j in types.indices) { + supportTypeMap[types[j].lowercase()] = true + } + } + ALog.i(TAG, "supportType=${supportTypeMap.keys}") + } catch (t: Throwable) { + ALog.e(TAG, "getSupportType $t") + } + } + +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/ScaleTypeUtil.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/ScaleTypeUtil.kt new file mode 100644 index 0000000..9298c11 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/ScaleTypeUtil.kt @@ -0,0 +1,252 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.util + +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import com.tencent.qgame.animplayer.Constant + + +enum class ScaleType { + FIT_XY, // 完整填充整个布局 default + FIT_CENTER, // 按视频比例在布局中间完整显示 + CENTER_CROP, // 按视频比例完整填充布局(多余部分不显示) +} + +interface IScaleType { + + fun getLayoutParam( + layoutWidth: Int, + layoutHeight: Int, + videoWidth: Int, + videoHeight: Int, + layoutParams: FrameLayout.LayoutParams + ): FrameLayout.LayoutParams + + fun getRealSize(): Pair +} + +class ScaleTypeFitXY : IScaleType { + + private var realWidth = 0 + private var realHeight = 0 + + override fun getLayoutParam( + layoutWidth: Int, + layoutHeight: Int, + videoWidth: Int, + videoHeight: Int, + layoutParams: FrameLayout.LayoutParams + ): FrameLayout.LayoutParams { + layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT + layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT + realWidth = layoutWidth + realHeight = layoutHeight + return layoutParams + } + + override fun getRealSize(): Pair { + return Pair(realWidth, realHeight) + } +} + +class ScaleTypeFitCenter : IScaleType { + + private var realWidth = 0 + private var realHeight = 0 + + override fun getLayoutParam( + layoutWidth: Int, + layoutHeight: Int, + videoWidth: Int, + videoHeight: Int, + layoutParams: FrameLayout.LayoutParams + ): FrameLayout.LayoutParams { + val (w, h) = getFitCenterSize(layoutWidth, layoutHeight, videoWidth, videoHeight) + if (w <= 0 && h <= 0) return layoutParams + realWidth = w + realHeight = h + layoutParams.width = w + layoutParams.height = h + layoutParams.gravity = Gravity.CENTER + return layoutParams + } + + override fun getRealSize(): Pair { + return Pair(realWidth, realHeight) + } + + private fun getFitCenterSize( + layoutWidth: Int, + layoutHeight: Int, + videoWidth: Int, + videoHeight: Int + ): Pair { + + val layoutRatio = layoutWidth.toFloat() / layoutHeight + val videoRatio = videoWidth.toFloat() / videoHeight + + val realWidth: Int + val realHeight: Int + if (layoutRatio > videoRatio) { + realHeight = layoutHeight + realWidth = (videoRatio * realHeight).toInt() + } else { + realWidth = layoutWidth + realHeight = (realWidth / videoRatio).toInt() + } + + return Pair(realWidth, realHeight) + } +} + +class ScaleTypeCenterCrop : IScaleType { + + private var realWidth = 0 + private var realHeight = 0 + + override fun getLayoutParam( + layoutWidth: Int, + layoutHeight: Int, + videoWidth: Int, + videoHeight: Int, + layoutParams: FrameLayout.LayoutParams + ): FrameLayout.LayoutParams { + val (w, h) = getCenterCropSize(layoutWidth, layoutHeight, videoWidth, videoHeight) + if (w <= 0 && h <= 0) return layoutParams + realWidth = w + realHeight = h + layoutParams.width = w + layoutParams.height = h + layoutParams.gravity = Gravity.CENTER + return layoutParams + } + + override fun getRealSize(): Pair { + return Pair(realWidth, realHeight) + } + + private fun getCenterCropSize( + layoutWidth: Int, + layoutHeight: Int, + videoWidth: Int, + videoHeight: Int + ): Pair { + + val layoutRatio = layoutWidth.toFloat() / layoutHeight + val videoRatio = videoWidth.toFloat() / videoHeight + + val realWidth: Int + val realHeight: Int + if (layoutRatio > videoRatio) { + realWidth = layoutWidth + realHeight = (realWidth / videoRatio).toInt() + } else { + realHeight = layoutHeight + realWidth = (videoRatio * realHeight).toInt() + } + + return Pair(realWidth, realHeight) + } +} + + +class ScaleTypeUtil { + + companion object { + private const val TAG = "${Constant.TAG}.ScaleTypeUtil" + } + + private val scaleTypeFitXY by lazy { ScaleTypeFitXY() } + private val scaleTypeFitCenter by lazy { ScaleTypeFitCenter() } + private val scaleTypeCenterCrop by lazy { ScaleTypeCenterCrop() } + private var layoutWidth = 0 + private var layoutHeight = 0 + private var videoWidth = 0 + private var videoHeight = 0 + + var currentScaleType = ScaleType.FIT_XY + var scaleTypeImpl: IScaleType? = null + + fun setLayoutSize(w: Int, h: Int) { + layoutWidth = w + layoutHeight = h + } + + fun setVideoSize(w: Int, h: Int) { + videoWidth = w + videoHeight = h + } + + /** + * 获取实际视频容器宽高 + * @return w h + */ + fun getRealSize(): Pair { + val size = getCurrentScaleType().getRealSize() + ALog.i(TAG, "get real size (${size.first}, ${size.second})") + return size + } + + fun getLayoutParam(view: View?): FrameLayout.LayoutParams { + val layoutParams = (view?.layoutParams as? FrameLayout.LayoutParams) + ?: FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + if (!checkParams()) { + ALog.e( + TAG, + "params error: layoutWidth=$layoutWidth, layoutHeight=$layoutHeight, videoWidth=$videoWidth, videoHeight=$videoHeight" + ) + return layoutParams + } + + return getCurrentScaleType().getLayoutParam( + layoutWidth, + layoutHeight, + videoWidth, + videoHeight, + layoutParams + ) + } + + private fun getCurrentScaleType(): IScaleType { + val tmpScaleType = scaleTypeImpl + return if (tmpScaleType != null) { + ALog.i(TAG, "custom scaleType") + tmpScaleType + } else { + ALog.i(TAG, "scaleType=$currentScaleType") + when (currentScaleType) { + ScaleType.FIT_XY -> scaleTypeFitXY + ScaleType.FIT_CENTER -> scaleTypeFitCenter + ScaleType.CENTER_CROP -> scaleTypeCenterCrop + } + } + } + + + private fun checkParams(): Boolean { + return layoutWidth > 0 + && layoutHeight > 0 + && videoWidth > 0 + && videoHeight > 0 + } + +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/ShaderUtil.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/ShaderUtil.kt new file mode 100644 index 0000000..b2d99f6 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/ShaderUtil.kt @@ -0,0 +1,74 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.util + +import android.opengl.GLES20 +import com.tencent.qgame.animplayer.Constant + +object ShaderUtil { + private const val TAG = "${Constant.TAG}.ShaderUtil" + + + fun createProgram(vertexSource: String, fragmentSource: String): Int { + val vertexShaderHandle = compileShader(GLES20.GL_VERTEX_SHADER, vertexSource) + val fragmentShaderHandle = compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource) + return createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle) + } + + + private fun compileShader(shaderType: Int, shaderSource: String): Int { + var shaderHandle = GLES20.glCreateShader(shaderType) + + if (shaderHandle != 0) { + GLES20.glShaderSource(shaderHandle, shaderSource) + GLES20.glCompileShader(shaderHandle) + val compileStatus = IntArray(1) + GLES20.glGetShaderiv(shaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus, 0) + if (compileStatus[0] == 0) { + ALog.e(TAG, "Error compiling shader: " + GLES20.glGetShaderInfoLog(shaderHandle)) + GLES20.glDeleteShader(shaderHandle) + shaderHandle = 0 + } + } + if (shaderHandle == 0) { + throw RuntimeException("Error creating shader.") + } + return shaderHandle + } + + + private fun createAndLinkProgram(vertexShaderHandle: Int, fragmentShaderHandle: Int): Int { + var programHandle = GLES20.glCreateProgram() + + if (programHandle != 0) { + GLES20.glAttachShader(programHandle, vertexShaderHandle) + GLES20.glAttachShader(programHandle, fragmentShaderHandle) + GLES20.glLinkProgram(programHandle) + val linkStatus = IntArray(1) + GLES20.glGetProgramiv(programHandle, GLES20.GL_LINK_STATUS, linkStatus, 0) + if (linkStatus[0] == 0) { + ALog.e(TAG, "Error compiling program: " + GLES20.glGetProgramInfoLog(programHandle)) + GLES20.glDeleteProgram(programHandle) + programHandle = 0 + } + } + if (programHandle == 0) { + throw RuntimeException("Error creating program.") + } + return programHandle + } + +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/SpeedControlUtil.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/SpeedControlUtil.kt new file mode 100644 index 0000000..332377d --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/SpeedControlUtil.kt @@ -0,0 +1,81 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.util + +import com.tencent.qgame.animplayer.Constant + +class SpeedControlUtil { + + companion object { + private const val TAG = "${Constant.TAG}.SpeedControlUtil" + } + private val ONE_MILLION = 1000000L + + private var prevPresentUsec: Long = 0 + private var prevMonoUsec: Long = 0 + private var fixedFrameDurationUsec: Long = 0 + private var loopReset = true + + fun setFixedPlaybackRate(fps: Int) { + if (fps <=0) return + fixedFrameDurationUsec = ONE_MILLION / fps + } + + fun preRender(presentationTimeUsec: Long) { + if (prevMonoUsec == 0L) { + prevMonoUsec = System.nanoTime() / 1000 + prevPresentUsec = presentationTimeUsec + } else { + var frameDelta: Long + if (loopReset) { + prevPresentUsec = presentationTimeUsec - ONE_MILLION / 30 + loopReset = false + } + frameDelta = if (fixedFrameDurationUsec != 0L) { + fixedFrameDurationUsec + } else { + presentationTimeUsec - prevPresentUsec + } + when { + frameDelta < 0 -> frameDelta = 0 + frameDelta > 10 * ONE_MILLION -> frameDelta = 5 * ONE_MILLION + } + + val desiredUsec = prevMonoUsec + frameDelta + var nowUsec = System.nanoTime() / 1000 + while (nowUsec < desiredUsec - 100 ) { + var sleepTimeUsec = desiredUsec - nowUsec + if (sleepTimeUsec > 500000) { + sleepTimeUsec = 500000 + } + try { + Thread.sleep(sleepTimeUsec / 1000, (sleepTimeUsec % 1000).toInt() * 1000) + } catch (e: InterruptedException) { + ALog.e(TAG, "e=$e", e) + } + nowUsec = System.nanoTime() / 1000 + } + + prevMonoUsec += frameDelta + prevPresentUsec += frameDelta + } + } + + fun reset() { + prevPresentUsec = 0 + prevMonoUsec = 0 + } +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/TexCoordsUtil.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/TexCoordsUtil.kt new file mode 100644 index 0000000..a1c47c5 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/TexCoordsUtil.kt @@ -0,0 +1,84 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.util + +import com.tencent.qgame.animplayer.PointRect + +/** + * 纹理坐标工具 + * 坐标顺序是倒N + */ +object TexCoordsUtil { + + /** + * @param width 纹理的宽高 + * @param height + */ + fun create(width: Int, height: Int, rect: PointRect, array: FloatArray): FloatArray { + + // x0 + array[0] = rect.x.toFloat() / width + // y0 + array[1] = rect.y.toFloat() / height + + // x1 + array[2] = rect.x.toFloat() / width + // y1 + array[3] = (rect.y.toFloat() + rect.h) / height + + // x2 + array[4] = (rect.x.toFloat() + rect.w) / width + // y2 + array[5] = rect.y.toFloat() / height + + // x3 + array[6] = (rect.x.toFloat() + rect.w) / width + // y3 + array[7] = (rect.y.toFloat() + rect.h) / height + + return array + } + + + + + /** + * 顺时针90度 + */ + fun rotate90(array: FloatArray): FloatArray { + // 0->2 1->0 3->1 2->3 + val tx = array[0] + val ty = array[1] + + // 1->0 + array[0] = array[2] + array[1] = array[3] + + // 3->1 + array[2] = array[6] + array[3] = array[7] + + // 2->3 + array[6] = array[4] + array[7] = array[5] + + // 0->2 + array[4] = tx + array[5] = ty + return array + } + +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/TextureLoadUtil.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/TextureLoadUtil.kt new file mode 100644 index 0000000..d898175 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/TextureLoadUtil.kt @@ -0,0 +1,58 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.util + +import android.graphics.Bitmap +import android.opengl.GLES20 +import android.opengl.GLUtils + +object TextureLoadUtil { + private const val TAG = "TextureUtil" + fun loadTexture(bitmap: Bitmap?): Int { + val textureObjectIds = IntArray(1) + GLES20.glGenTextures(1, textureObjectIds, 0) + + if (textureObjectIds[0] == 0) { + return 0 + } + + if (bitmap == null) { + GLES20.glDeleteTextures(1, textureObjectIds, 0) + return 0 + } + + if (bitmap.isRecycled) { + ALog.e(TAG, "bitmap isRecycled") + return 0 + } + + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureObjectIds[0]) + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR_MIPMAP_LINEAR) + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR) + GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0) + GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D) + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0) + + return textureObjectIds[0] + } + + + fun releaseTexure(textureId: Int) { + if (textureId != 0) { + GLES20.glDeleteTextures(1, intArrayOf(textureId), 0) + } + } +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/VertexUtil.kt b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/VertexUtil.kt new file mode 100644 index 0000000..1cb0bc6 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/android/src/main/kotlin/com/laskarmedia/vap/animplayer/util/VertexUtil.kt @@ -0,0 +1,64 @@ +/* + * Tencent is pleased to support the open source community by making vap available. + * + * Copyright (C) 2020 Tencent. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tencent.qgame.animplayer.util + +import com.tencent.qgame.animplayer.PointRect + +/** + * 顶点坐标工具 + * 坐标顺序是倒N + */ +object VertexUtil { + + /** + * @param width 画布大大小 + * @param height + */ + fun create(width: Int, height: Int, rect: PointRect, array: FloatArray): FloatArray { + + // x0 + array[0] = switchX(rect.x.toFloat() / width) + // y0 + array[1] = switchY(rect.y.toFloat() / height) + + // x1 + array[2] = switchX(rect.x.toFloat() / width) + // y1 + array[3] = switchY((rect.y.toFloat() + rect.h) / height) + + // x2 + array[4] = switchX((rect.x.toFloat() + rect.w) / width) + // y2 + array[5] = switchY(rect.y.toFloat() / height) + + // x3 + array[6] = switchX((rect.x.toFloat() + rect.w) / width) + // y3 + array[7] = switchY((rect.y.toFloat() + rect.h) / height) + + return array + } + + + private fun switchX(x: Float): Float { + return x * 2f -1f + } + + private fun switchY(y: Float): Float { + return ((y * 2f - 2f) * -1f) - 1f + } + +} \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/example/README.md b/local_packages/tancent_vap-1.0.0+1/example/README.md new file mode 100644 index 0000000..ff7639e --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/README.md @@ -0,0 +1,16 @@ +# vap_example + +Demonstrates how to use the vap plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/local_packages/tancent_vap-1.0.0+1/example/analysis_options.yaml b/local_packages/tancent_vap-1.0.0+1/example/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/local_packages/tancent_vap-1.0.0+1/example/android/app/build.gradle.kts b/local_packages/tancent_vap-1.0.0+1/example/android/app/build.gradle.kts new file mode 100644 index 0000000..6beb752 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.laskarmedia.vap_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.laskarmedia.vap_example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/local_packages/tancent_vap-1.0.0+1/example/android/app/src/debug/AndroidManifest.xml b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/AndroidManifest.xml b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..51c6997 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/kotlin/com/laskarmedia/vap_example/MainActivity.kt b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/kotlin/com/laskarmedia/vap_example/MainActivity.kt new file mode 100644 index 0000000..dee11d5 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/kotlin/com/laskarmedia/vap_example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.laskarmedia.vap_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/drawable-v21/launch_background.xml b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/drawable/launch_background.xml b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/values-night/styles.xml b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/values/styles.xml b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/local_packages/tancent_vap-1.0.0+1/example/android/app/src/profile/AndroidManifest.xml b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/local_packages/tancent_vap-1.0.0+1/example/android/build.gradle.kts b/local_packages/tancent_vap-1.0.0+1/example/android/build.gradle.kts new file mode 100644 index 0000000..89176ef --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/local_packages/tancent_vap-1.0.0+1/example/android/gradle.properties b/local_packages/tancent_vap-1.0.0+1/example/android/gradle.properties new file mode 100644 index 0000000..f018a61 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/local_packages/tancent_vap-1.0.0+1/example/android/gradle/wrapper/gradle-wrapper.properties b/local_packages/tancent_vap-1.0.0+1/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ac3b479 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/local_packages/tancent_vap-1.0.0+1/example/android/settings.gradle.kts b/local_packages/tancent_vap-1.0.0+1/example/android/settings.gradle.kts new file mode 100644 index 0000000..ab39a10 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.3" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false +} + +include(":app") diff --git a/local_packages/tancent_vap-1.0.0+1/example/assets/user.png b/local_packages/tancent_vap-1.0.0+1/example/assets/user.png new file mode 100644 index 0000000..ce1761e Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/assets/user.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/assets/vapx.mp4 b/local_packages/tancent_vap-1.0.0+1/example/assets/vapx.mp4 new file mode 100644 index 0000000..76f3923 Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/assets/vapx.mp4 differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Flutter/AppFrameworkInfo.plist b/local_packages/tancent_vap-1.0.0+1/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Flutter/Debug.xcconfig b/local_packages/tancent_vap-1.0.0+1/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Flutter/Release.xcconfig b/local_packages/tancent_vap-1.0.0+1/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Podfile b/local_packages/tancent_vap-1.0.0+1/example/ios/Podfile new file mode 100644 index 0000000..e549ee2 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Podfile.lock b/local_packages/tancent_vap-1.0.0+1/example/ios/Podfile.lock new file mode 100644 index 0000000..751b14a --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Podfile.lock @@ -0,0 +1,35 @@ +PODS: + - Flutter (1.0.0) + - integration_test (0.0.1): + - Flutter + - QGVAPlayer (1.0.19) + - tancent_vap (0.0.1): + - Flutter + - QGVAPlayer (= 1.0.19) + +DEPENDENCIES: + - Flutter (from `Flutter`) + - integration_test (from `.symlinks/plugins/integration_test/ios`) + - tancent_vap (from `.symlinks/plugins/tancent_vap/ios`) + +SPEC REPOS: + trunk: + - QGVAPlayer + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + integration_test: + :path: ".symlinks/plugins/integration_test/ios" + tancent_vap: + :path: ".symlinks/plugins/tancent_vap/ios" + +SPEC CHECKSUMS: + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e + QGVAPlayer: a0bca68c9bd6f1c8de5ac2d10ddf98be6038cce9 + tancent_vap: 4917210cc7e916023fd112655170d8e3d074f482 + +PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5 + +COCOAPODS: 1.16.2 diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcodeproj/project.pbxproj b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..094379f --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,731 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 6F8ADB862F8E60C4AB97DA28 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 566311037EC0CAAFB24687F5 /* Pods_Runner.framework */; }; + 744984D6F56DEB89DB72B30F /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C4817119AE4459FC02DD87C /* Pods_RunnerTests.framework */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 062EE43FDE538D0CC0FD8B46 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3C4817119AE4459FC02DD87C /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 464B48BA098671256B7AABFD /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 566311037EC0CAAFB24687F5 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 58C4B2D487313B2935432E1C /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 59B6F0786928D4354B29DB32 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D11162BBEA7C33570402D418 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + D55AA79CB14BDC498FC7B2EE /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6F8ADB862F8E60C4AB97DA28 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D9E91B65B0EC3E54A4F18446 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 744984D6F56DEB89DB72B30F /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + E3430BFEF77AD2B92D3746DF /* Pods */, + A56E98C5D62987F53044077A /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + A56E98C5D62987F53044077A /* Frameworks */ = { + isa = PBXGroup; + children = ( + 566311037EC0CAAFB24687F5 /* Pods_Runner.framework */, + 3C4817119AE4459FC02DD87C /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + E3430BFEF77AD2B92D3746DF /* Pods */ = { + isa = PBXGroup; + children = ( + D11162BBEA7C33570402D418 /* Pods-Runner.debug.xcconfig */, + 062EE43FDE538D0CC0FD8B46 /* Pods-Runner.release.xcconfig */, + D55AA79CB14BDC498FC7B2EE /* Pods-Runner.profile.xcconfig */, + 58C4B2D487313B2935432E1C /* Pods-RunnerTests.debug.xcconfig */, + 464B48BA098671256B7AABFD /* Pods-RunnerTests.release.xcconfig */, + 59B6F0786928D4354B29DB32 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + DFC471707750343963518BE1 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + D9E91B65B0EC3E54A4F18446 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 2F8FE29D194725917C994C4B /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 59EDB7A442327DC29F0223C4 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 2F8FE29D194725917C994C4B /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 59EDB7A442327DC29F0223C4 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + DFC471707750343963518BE1 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = NAFNLUB99G; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.laskarmedia.vapExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 58C4B2D487313B2935432E1C /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.laskarmedia.vapExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 464B48BA098671256B7AABFD /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.laskarmedia.vapExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 59B6F0786928D4354B29DB32 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.laskarmedia.vapExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = NAFNLUB99G; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.laskarmedia.vapExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = NAFNLUB99G; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.laskarmedia.vapExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..e3773d4 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/AppDelegate.swift b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..6266644 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +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. \ No newline at end of file diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Base.lproj/Main.storyboard b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Info.plist b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Info.plist new file mode 100644 index 0000000..0007be7 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Vap + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + vap_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Runner-Bridging-Header.h b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/local_packages/tancent_vap-1.0.0+1/example/ios/RunnerTests/RunnerTests.swift b/local_packages/tancent_vap-1.0.0+1/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..d66857a --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,27 @@ +import Flutter +import UIKit +import XCTest + + +@testable import vap + +// This demonstrates a simple unit test of the Swift portion of this plugin's implementation. +// +// See https://developer.apple.com/documentation/xctest for more information about using XCTest. + +class RunnerTests: XCTestCase { + + func testGetPlatformVersion() { + let plugin = VapPlugin() + + let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: []) + + let resultExpectation = expectation(description: "result block must be called.") + plugin.handle(call) { result in + XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion) + resultExpectation.fulfill() + } + waitForExpectations(timeout: 1) + } + +} diff --git a/local_packages/tancent_vap-1.0.0+1/example/lib/main.dart b/local_packages/tancent_vap-1.0.0+1/example/lib/main.dart new file mode 100644 index 0000000..a54f676 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/lib/main.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:tancent_vap/tancent_vap.dart'; + +main() { + runApp(MaterialApp(home: Application())); +} + +class Application extends StatefulWidget { + const Application({super.key}); + + @override + State createState() => _ApplicationState(); +} + +class _ApplicationState extends State { + late VapController controller; + final TextEditingController textController = TextEditingController(); + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + fit: StackFit.expand, + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + spacing: 20, + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: textController, + decoration: InputDecoration( + labelText: "Enter text to replace [sTxt1]", + border: OutlineInputBorder(), + ), + ), + ElevatedButton( + onPressed: () { + controller.setVapTagContent( + "[sTxt1]", TextContent(textController.text)); + controller.playAsset("assets/vapx.mp4"); + }, + child: Text("Play Animation"), + ), + ], + ), + ), + ), + + // IgnorePointer to prevent user interaction with the VapView + // This is useful if you want to display the animation without allowing user interaction. + // You can remove this IgnorePointer if you want the VapView to be interactive. + IgnorePointer( + child: VapView( + repeat: 0, // 0 means play once, -1 means loop infinitely + mute: true, // Mute the audio + scaleType: ScaleType.centerCrop, // Scale type for the video + vapTagContents: { + "[sTxt1]": TextContent("Nizwar win the game!"), + "[sImg1]": ImageAssetContent("assets/user.png"), + }, + onViewCreated: (ctl) async { + controller = ctl; + }, + ), + ), + ], + ), + ); + } +} diff --git a/local_packages/tancent_vap-1.0.0+1/example/pubspec.yaml b/local_packages/tancent_vap-1.0.0+1/example/pubspec.yaml new file mode 100644 index 0000000..0c45541 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/example/pubspec.yaml @@ -0,0 +1,29 @@ +name: vap_example +description: "Demonstrates how to use the vap plugin." + +publish_to: 'none' + +environment: + sdk: ^3.6.0 + +dependencies: + flutter: + sdk: flutter + + tancent_vap: + path: ../ + + cupertino_icons: ^1.0.8 + +dev_dependencies: + integration_test: + sdk: flutter + flutter_test: + sdk: flutter + + flutter_lints: ^5.0.0 + +flutter: + uses-material-design: true + assets: + - assets/ diff --git a/local_packages/tancent_vap-1.0.0+1/ios/Classes/Header.h b/local_packages/tancent_vap-1.0.0+1/ios/Classes/Header.h new file mode 100644 index 0000000..8d1c8b6 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/ios/Classes/Header.h @@ -0,0 +1 @@ + diff --git a/local_packages/tancent_vap-1.0.0+1/ios/Classes/VapPlugin.swift b/local_packages/tancent_vap-1.0.0+1/ios/Classes/VapPlugin.swift new file mode 100644 index 0000000..2830865 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/ios/Classes/VapPlugin.swift @@ -0,0 +1,18 @@ +import Flutter +import UIKit + +/** VapPlugin */ +public class VapPlugin: NSObject, FlutterPlugin { + + public static func register(with registrar: FlutterPluginRegistrar) { + let instance = VapPlugin() + instance.onAttachedToEngine(registrar: registrar) + } + + private func onAttachedToEngine(registrar: FlutterPluginRegistrar) { + // Register the platform view factory for VapView + let factory = VapViewFactory(messenger: registrar.messenger()) + registrar.register(factory, withId: "vap_view") + } + +} diff --git a/local_packages/tancent_vap-1.0.0+1/ios/Classes/VapView.swift b/local_packages/tancent_vap-1.0.0+1/ios/Classes/VapView.swift new file mode 100644 index 0000000..9a26292 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/ios/Classes/VapView.swift @@ -0,0 +1,723 @@ +import Flutter +import UIKit +import QGVAPlayer +import Darwin.Mach + +public class VapFlutterView: NSObject, FlutterPlatformView { + private let _view: UIView + private let channel: FlutterMethodChannel + private var vapView: QGVAPWrapView? + private var repeatCount: Int = 0 + private var playResult: FlutterResult? + private var vapTagContents: [String: [String: Any]] = [:] + + + init( + context: CGRect, + params: [String: Any]?, + messenger: FlutterBinaryMessenger, + id: Int64 + ) { + _view = UIView(frame: context) + channel = FlutterMethodChannel(name: "vap_view_\(id)", binaryMessenger: messenger) + + super.init() + + // Initialize VAP view with proper configuration + vapView = QGVAPWrapView(frame: context) + + vapView?.center = _view.center + // Set scaleType from params (matching Kotlin logic) + if let scaleType = params?["scaleType"] as? String { + switch scaleType { + case "fitCenter": + vapView?.contentMode = .aspectFit + break + case "centerCrop": + vapView?.contentMode = .aspectFill + break + case "fitXY": + vapView?.contentMode = .scaleToFill + break + default: + vapView?.contentMode = .aspectFit + break + } + } else { + // Default scale type (matching Kotlin's default) + vapView?.contentMode = .aspectFit + } + + // Configure background color to prevent visual glitches + vapView?.backgroundColor = UIColor.clear + + _view.backgroundColor = UIColor.clear + + channel.setMethodCallHandler(onMethodCall) + + // Add vapView to container + if let vapView = vapView { + vapView.translatesAutoresizingMaskIntoConstraints = false + _view.addSubview(vapView) + + // Set up auto layout constraints + NSLayoutConstraint.activate([ + vapView.topAnchor.constraint(equalTo: _view.topAnchor), + vapView.leadingAnchor.constraint(equalTo: _view.leadingAnchor), + vapView.trailingAnchor.constraint(equalTo: _view.trailingAnchor), + vapView.bottomAnchor.constraint(equalTo: _view.bottomAnchor) + ]) + } + + // Initial playback if filePath or assetName is provided (matching Kotlin) + if let filePath = params?["filePath"] as? String { + playFile(filePath) + } + if let assetName = params?["assetName"] as? String { + playAsset(assetName) + } + if let loop = params?["loop"] as? Int { + setLoop(loop) + } + if let mute = params?["mute"] as? Bool { + setMute(mute) + } + + // Store tag contents as Maps + if let tagContents = params?["vapTagContents"] as? [String: [String: Any]] { + for (tag, content) in tagContents { + vapTagContents[tag] = content + NSLog("Stored tag content for tag: \(tag), contentType: \(content["contentType"] ?? "unknown")") + } + } + } + + public func view() -> UIView { + return _view + } + + private func reset() { + // Ensure cleanup happens on main thread + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + clearVapTagContents() + + // Stop VAP playback and clean up resources + self.vapView?.stopHWDMP4() + + // Give a moment for VideoToolbox to clean up + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + // Remove all subviews + self._view.subviews.forEach { $0.removeFromSuperview() } + + // Clear the VAP view reference + self.vapView = nil + + // Force garbage collection hint + if #available(iOS 13.0, *) { + // Modern iOS versions handle this automatically + } else { + // For older iOS versions, hint at memory cleanup + DispatchQueue.global(qos: .background).async { + // Trigger background cleanup + } + } + } + } + } + + private func dispose() { + reset() + channel.setMethodCallHandler(nil) + } + + private func onMethodCall(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "dispose": + dispose() + result(nil) + + case "playFile": + if let args = call.arguments as? [String: Any], + let filePath = args["filePath"] as? String { + playFile(filePath, result) + } + + case "playAsset": + if let args = call.arguments as? [String: Any], + let assetName = args["assetName"] as? String { + playAsset(assetName, result) + } + + case "stop": + stop() + result(nil) + + case "setLoop": + if let args = call.arguments as? [String: Any], + let loop = args["loop"] as? Int { + setLoop(loop) + } else { + setLoop(0) + } + result(nil) + + case "setMute": + if let args = call.arguments as? [String: Any], + let mute = args["mute"] as? Bool { + setMute(mute) + } else { + setMute(false) + } + result(nil) + + case "setScaleType": + if let args = call.arguments as? [String: Any], + let scaleType = args["scaleType"] as? String { + setScaleType(scaleType) + } + result(nil) + + case "setVapTagContent": + if let args = call.arguments as? [String: Any], + let tag = args["tag"] as? String, + let contentMap = args["content"] as? [String: Any] { + setVapTagContent(tag: tag, contentMap: contentMap) + } + result(nil) + + case "setVapTagContents": + if let args = call.arguments as? [String: Any], + let contents = args["contents"] as? [String: [String: Any]] { + setVapTagContents(contents) + } + result(nil) + + case "getVapTagContent": + if let args = call.arguments as? [String: Any], + let tag = args["tag"] as? String { + result(getVapTagContent(tag: tag)) + } else { + result(nil) + } + + case "getAllVapTagContents": + result(vapTagContents) + + case "clearVapTagContents": + clearVapTagContents() + result(nil) + + default: + result(FlutterMethodNotImplemented) + } + } + + private func playFile(_ filePath: String, _ result: FlutterResult? = nil) { + // Ensure we're on the main thread for UI operations + self.playResult = result + DispatchQueue.main.async { [weak self] in + guard let self = self, let vapView = self.vapView else { return } + + // Stop any existing playback first + vapView.stopHWDMP4() + + // Configure VAP player with better error handling + do { + guard FileManager.default.fileExists(atPath: filePath) else { + self.sendFailedEvent(errorCode: -1, errorType: "FILE_NOT_FOUND", errorMsg: "VAP file not found: \(filePath)") + return + } + + // Check file size (VAP files shouldn't be too large for memory) + let fileAttributes = try FileManager.default.attributesOfItem(atPath: filePath) + if let fileSize = fileAttributes[.size] as? NSNumber { + let fileSizeInMB = fileSize.doubleValue / (1024 * 1024) + // Reject extremely large files that will definitely cause issues + if fileSizeInMB > 100 { + self.sendFailedEvent(errorCode: -1006, errorType: "FILE_TOO_LARGE", errorMsg: "VAP file too large (\(fileSizeInMB) MB), maximum size is 100MB") + playError(-1006, "FILE_TOO_LARGE", "VAP file too large (\(fileSizeInMB) MB), maximum size is 100MB") + return + } + } + // Start playback with delegate + vapView.playHWDMP4(filePath, repeatCount: self.repeatCount, delegate: self) + + + } catch { + self.sendFailedEvent(errorCode: -2, errorType: "FILE_PLAYBACK_ERROR", errorMsg: "Failed to play VAP file: \(error.localizedDescription)") + } + } + } + + private func playAsset(_ assetName: String, _ result: FlutterResult? = nil) { + let key = FlutterDartProject.lookupKey(forAsset: assetName) + if let bundlePath = Bundle.main.path(forResource: key, ofType: nil) { + playFile(bundlePath) + } else { + sendFailedEvent(errorCode: -1, errorType: "FILE_NOT_FOUND", errorMsg: "Asset not found: \(assetName)") + } + } + + private func stop() { + vapView?.stopHWDMP4() + DispatchQueue.main.async { [weak self] in + self?.channel.invokeMethod("onVideoDestroy", arguments: nil) + } + } + + private func setLoop(_ loop: Int) { + repeatCount = loop + } + + private func setMute(_ mute: Bool) { + vapView?.setMute(mute) + } + + private func setScaleType(_ scaleType: String) { + switch scaleType { + case "fitCenter": + vapView?.contentMode = .aspectFit + break + case "centerCrop": + vapView?.contentMode = .aspectFill + break + case "fitXY": + vapView?.contentMode = .scaleToFill + break + default: + vapView?.contentMode = .aspectFit + break + } + } + + // MARK: - VAP Tag Content Management + private func setVapTagContent(tag: String, contentMap: [String: Any]) { + vapTagContents[tag] = contentMap + } + + private func setVapTagContents(_ contents: [String: [String: Any]]) { + vapTagContents.merge(contents) { (_, new) in new } + } + + private func getVapTagContent(tag: String) -> String? { + guard let contentMap = vapTagContents[tag], + let contentValue = contentMap["contentValue"] as? String else { + return nil + } + return contentValue + } + + private func clearVapTagContents() { + vapTagContents.removeAll() + } + + // MARK: - Event Handling + private func sendFailedEvent(errorCode: Int, errorType: String, errorMsg: String?) { + let args: [String: Any?] = [ + "errorCode": errorCode, + "errorType": errorType, + "errorMsg": errorMsg + ] + DispatchQueue.main.async { [weak self] in + self?.channel.invokeMethod("onFailed", arguments: args) + } + } + + private func sendVideoCompleteEvent() { + DispatchQueue.main.async { [weak self] in + self?.channel.invokeMethod("onVideoComplete", arguments: nil) + } + } + + private func sendVideoRenderEvent(frameIndex: Int, config: [String: Any]?) { + let args: [String: Any?] = [ + "frameIndex": frameIndex, + "config": config + ] + DispatchQueue.main.async { [weak self] in + self?.channel.invokeMethod("onVideoRender", arguments: args) + } + } + + private func sendVideoConfigReadyEvent(config: [String: Any]) { + let args = ["config": config] + DispatchQueue.main.async { [weak self] in + self?.channel.invokeMethod("onVideoConfigReady", arguments: args) + } + } + + // MARK: - Objective-C 兼容性桥接方法 + @objc public func vapWrap_viewDidFinishPlayMP4(_ totalFrameCount: Int, view: UIView) { + // 注意:参数 `view` 是普通的 UIView,不是 QGVAPWrapView + // 我们直接调用已有方法,并使用 self.vapView 作为参数 + if let vapView = self.vapView { + self.viewDidFinishPlayMP4(totalFrameCount, view: vapView) + } + } + + @objc public func vapWrap_viewDidStartPlayMP4(_ totalFrameCount: Int, view: UIView) { + // 你原来的 viewDidStartPlayMP4 方法只接受一个 QGVAPWrapView 参数 + // 忽略 totalFrameCount 参数,只传递 vapView + if let vapView = self.vapView { + self.viewDidStartPlayMP4(vapView) + } + } + + @objc public func vapWrap_viewDidStopPlayMP4(_ frameIndex: Int, view: UIView) { + if let vapView = self.vapView { + self.viewDidStopPlayMP4(frameIndex, view: vapView) + } + } + + @objc public func vapWrap_onVAPStopWithError(_ error: NSError?, view: UIView) { + // viewDidFailPlayMP4 方法只需要 error 参数 + self.viewDidFailPlayMP4(error ?? NSError(domain: "VAP", code: -1, userInfo: nil)) + } +} + +// MARK: - HWDMP4PlayDelegate +extension VapFlutterView: VAPWrapViewDelegate { + + func shouldStartPlayMP4(_ container: QGVAPWrapView, config: QGVAPConfigModel) -> Bool { + var animConfigMap: [String: Any] = [:] + animConfigMap["width"] = config.info.size.width + animConfigMap["height"] = config.info.size.height + animConfigMap["fps"] = config.info.fps + animConfigMap["totalFrames"] = config.info.framesCount + animConfigMap["videoHeight"] = config.info.videoSize.height + animConfigMap["videoWidth"] = config.info.videoSize.width + animConfigMap["isMix"] = config.info.isMerged + animConfigMap["orien"] = config.info.targetOrientaion.rawValue + animConfigMap["alphaPointRect"] = [ + "x": config.info.alphaAreaRect.minX, + "y": config.info.alphaAreaRect.minY, + "w": config.info.alphaAreaRect.maxX, + "h": config.info.alphaAreaRect.maxY + ] + animConfigMap["rgbPointRect"] = [ + "x": config.info.rgbAreaRect.minX, + "y": config.info.rgbAreaRect.minY, + "w": config.info.rgbAreaRect.maxX, + "h": config.info.rgbAreaRect.maxY + ] + animConfigMap["version"] = config.info.version + sendVideoConfigReadyEvent(config: animConfigMap) + return true + } + + func viewDidStartPlayMP4(_ container: QGVAPWrapView) { + + DispatchQueue.main.async { [weak self] in + self?.channel.invokeMethod("onVideoStart", arguments: nil) + } + } + + func viewDidPlayMP4AtFrame(_ frame: QGMP4AnimatedImageFrame, view container: QGVAPWrapView) { + let args: [String: Any] = ["frameIndex": frame.index] + DispatchQueue.main.async { [weak self] in + self?.channel.invokeMethod("onVideoRender", arguments: args) + } + } + + func viewDidStopPlayMP4(_ lastFrameIndex: Int, view container: QGVAPWrapView) { + playSuccess() + DispatchQueue.main.async { [weak self] in + self?.channel.invokeMethod("onVideoDestroy", arguments: nil) + } + } + + func viewDidFinishPlayMP4(_ totalFrameCount: Int, view container: QGVAPWrapView) { + playSuccess() + sendVideoCompleteEvent() + } + + func viewDidFailPlayMP4(_ error: NSError) { + // Handle specific VideoToolbox errors + var errorMsg = error.localizedDescription + var errorCode = error.code + + // Check for common VideoToolbox errors + if error.domain == "com.apple.videotoolbox" || error.domain.contains("VT") { + switch error.code { + case -12909: // kVTVideoDecoderBadDataErr + errorMsg = "Invalid or corrupted video data. Please check your VAP file encoding." + errorCode = -1001 + case -12911: // kVTVideoDecoderMalfunctionErr + errorMsg = "Video decoder malfunction. Try restarting the app." + errorCode = -1002 + case -12912: // kVTVideoDecoderNotAvailableNowErr + errorMsg = "Video decoder not available. Device may be under memory pressure." + errorCode = -1003 + case -12913: // kVTInvalidSessionErr + errorMsg = "Invalid video session. Please try playing the file again." + errorCode = -1004 + default: + errorMsg = "VideoToolbox error: \(error.localizedDescription)" + errorCode = -1000 + } + } + playError(errorCode, "VIDEO_PLAYBACK_ERROR", errorMsg) + sendFailedEvent(errorCode: errorCode, errorType:"VIDEO_PLAYBACK_ERROR", errorMsg: errorMsg) + } + + func playError(_ code:Int, _ errorType:String, _ errorMsg:String){ + if(playResult != nil){ + playResult?(FlutterError(code: String(code),message: errorMsg,details: ["errorType": errorType])) + playResult = nil + } + } + + func playSuccess(){ + if(playResult != nil){ + playResult?(nil) + playResult = nil + } + } + // MARK: - Resource Management Delegate Methods + public func vapWrapview_content(forVapTag tag: String, resource info: QGVAPSourceInfo) -> String { + guard let contentMap = vapTagContents[tag], + let contentValue = contentMap["contentValue"] as? String else { + return tag + } + + // If the resource type is text, return the content value + if info.type == .text || info.type == .textStr { + return contentValue + } + + // For other resource types (images, etc.), return the tag + return tag + } + + public func vapWrapView_loadVapImage(withURL urlStr: String, context: [AnyHashable : Any], completion completionBlock: @escaping VAPImageCompletionBlock) { + print("URLNYA : " + urlStr + " INI VAPIMAGECONTENT : " + String(describing: vapTagContents) + "\n") + // First check if we have content for this tag in vapTagContents + if let contentMap = vapTagContents[urlStr], + let contentValue = contentMap["contentValue"] as? String, + let contentType = contentMap["contentType"] as? String { + handleVapTagContent(content: contentValue, contentType: contentType, tag: urlStr, completion: completionBlock) + return + } + + // Fallback to original URL loading if no tag content is found + guard let url = URL(string: urlStr) else { + completionBlock(nil, NSError(domain: "VAPImageLoader", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL and no tag content found"]), urlStr) + return + } + + // Simple URLSession implementation - replace with your preferred image loading library + URLSession.shared.dataTask(with: url) { data, response, error in + DispatchQueue.main.async { + if let error = error { + completionBlock(nil, error as NSError, urlStr) + return + } + + guard let data = data, let image = UIImage(data: data) else { + completionBlock(nil, NSError(domain: "VAPImageLoader", code: -2, userInfo: [NSLocalizedDescriptionKey: "Failed to create image from data"]), urlStr) + return + } + + completionBlock(image, nil, urlStr) + } + }.resume() + } + + func loadVapImageWithURL(_ urlStr: String, context: [String: Any], completion: @escaping VAPImageCompletionBlock) { + // First check if we have content for this tag in vapTagContents + if let contentMap = vapTagContents[urlStr], + let contentValue = contentMap["contentValue"] as? String, + let contentType = contentMap["contentType"] as? String { + handleVapTagContent(content: contentValue, contentType: contentType, tag: urlStr, completion: completion) + return + } + + // Fallback to original URL loading if no tag content is found + guard let url = URL(string: urlStr) else { + completion(nil, NSError(domain: "VAPImageLoader", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL and no tag content found"]), urlStr) + return + } + + // Simple URLSession implementation - replace with your preferred image loading library + URLSession.shared.dataTask(with: url) { data, response, error in + DispatchQueue.main.async { + if let error = error { + completion(nil, error as NSError, urlStr) + return + } + + guard let data = data, let image = UIImage(data: data) else { + completion(nil, NSError(domain: "VAPImageLoader", code: -2, userInfo: [NSLocalizedDescriptionKey: "Failed to create image from data"]), urlStr) + return + } + + completion(image, nil, urlStr) + } + }.resume() + } + + private func handleVapTagContent(content: String, contentType: String, tag: String, completion: @escaping VAPImageCompletionBlock) { + NSLog("Processing tag: \(tag), contentType: \(contentType)") + + switch contentType { + case "text": + handleTextContent(content: content, tag: tag, completion: completion) + case "image_base64": + handleImageBase64Content(content: content, tag: tag, completion: completion) + case "image_file": + handleImageFileContent(content: content, tag: tag, completion: completion) + case "image_asset": + handleImageAssetContent(content: content, tag: tag, completion: completion) + case "image_url": + handleImageUrlContent(content: content, tag: tag, completion: completion) + default: + NSLog("Unsupported content type: \(contentType) for tag: \(tag)") + completion(nil, NSError(domain: "VAPImageLoader", code: -4, userInfo: [NSLocalizedDescriptionKey: "Unsupported content type: \(contentType)"]), tag) + } + } + + // MARK: - Content Type Handlers + + private func handleTextContent(content: String, tag: String, completion: @escaping VAPImageCompletionBlock) { + // Text content is not handled by image loading, skip + NSLog("Text content type for tag: \(tag), skipping image loading") + completion(nil, nil, tag) + } + + private func handleImageBase64Content(content: String, tag: String, completion: @escaping VAPImageCompletionBlock) { + DispatchQueue.global(qos: .userInitiated).async { + var base64String = content + + // Remove data URL prefix if present (e.g., "data:image/png;base64,") + if content.hasPrefix("data:image/") { + if let commaRange = content.range(of: ",") { + base64String = String(content[commaRange.upperBound...]) + } + } else if content.hasPrefix("base64:") { + base64String = String(content.dropFirst(7)) // Remove "base64:" prefix + } + + guard let imageData = Data(base64Encoded: base64String), + let image = UIImage(data: imageData) else { + DispatchQueue.main.async { + completion(nil, NSError(domain: "VAPImageLoader", code: -3, userInfo: [NSLocalizedDescriptionKey: "Failed to decode base64 image for tag: \(tag)"]), tag) + } + return + } + + DispatchQueue.main.async { + NSLog("Successfully decoded base64 image for tag: \(tag)") + completion(image, nil, tag) + } + } + } + + private func handleImageFileContent(content: String, tag: String, completion: @escaping VAPImageCompletionBlock) { + DispatchQueue.global(qos: .userInitiated).async { + var fullPath = content + + // Handle different file path types + if content.hasPrefix("/") { + // Absolute path + fullPath = content + } else if content.hasPrefix("file://") { + // File URL + fullPath = String(content.dropFirst(7)) + } else { + // Relative path - check in Documents directory + let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + let documentFilePath = documentsPath + "/" + content + + if FileManager.default.fileExists(atPath: documentFilePath) { + fullPath = documentFilePath + } else { + // Use as-is and let it fail if invalid + fullPath = content + } + } + + guard FileManager.default.fileExists(atPath: fullPath) else { + DispatchQueue.main.async { + completion(nil, NSError(domain: "VAPImageLoader", code: -4, userInfo: [NSLocalizedDescriptionKey: "File not found: \(fullPath) for tag: \(tag)"]), tag) + } + return + } + + guard let image = UIImage(contentsOfFile: fullPath) else { + DispatchQueue.main.async { + completion(nil, NSError(domain: "VAPImageLoader", code: -5, userInfo: [NSLocalizedDescriptionKey: "Failed to load image from file: \(fullPath) for tag: \(tag)"]), tag) + } + return + } + + DispatchQueue.main.async { + NSLog("Successfully loaded file image for tag: \(tag) from: \(fullPath)") + completion(image, nil, tag) + } + } + } + + private func handleImageAssetContent(content: String, tag: String, completion: @escaping VAPImageCompletionBlock) { + DispatchQueue.global(qos: .userInitiated).async { + // Try as Flutter asset first + let assetKey = FlutterDartProject.lookupKey(forAsset: content) + var fullPath: String? + + if let assetPath = Bundle.main.path(forResource: assetKey, ofType: nil) { + fullPath = assetPath + } else if let bundlePath = Bundle.main.path(forResource: content, ofType: nil) { + // Try as direct bundle resource + fullPath = bundlePath + } + + guard let validPath = fullPath, FileManager.default.fileExists(atPath: validPath) else { + DispatchQueue.main.async { + completion(nil, NSError(domain: "VAPImageLoader", code: -4, userInfo: [NSLocalizedDescriptionKey: "Asset not found: \(content) for tag: \(tag)"]), tag) + } + return + } + + guard let image = UIImage(contentsOfFile: validPath) else { + DispatchQueue.main.async { + completion(nil, NSError(domain: "VAPImageLoader", code: -5, userInfo: [NSLocalizedDescriptionKey: "Failed to load asset image: \(content) for tag: \(tag)"]), tag) + } + return + } + + DispatchQueue.main.async { + NSLog("Successfully loaded asset image for tag: \(tag) from: \(validPath)") + completion(image, nil, tag) + } + } + } + + private func handleImageUrlContent(content: String, tag: String, completion: @escaping VAPImageCompletionBlock) { + guard let url = URL(string: content) else { + completion(nil, NSError(domain: "VAPImageLoader", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL: \(content) for tag: \(tag)"]), tag) + return + } + + NSLog("Loading image from URL: \(content) for tag: \(tag)") + + URLSession.shared.dataTask(with: url) { data, response, error in + DispatchQueue.main.async { + if let error = error { + NSLog("Failed to load image from URL: \(content) for tag: \(tag), error: \(error.localizedDescription)") + completion(nil, error as NSError, tag) + return + } + + guard let data = data, let image = UIImage(data: data) else { + completion(nil, NSError(domain: "VAPImageLoader", code: -2, userInfo: [NSLocalizedDescriptionKey: "Failed to create image from URL data: \(content) for tag: \(tag)"]), tag) + return + } + + NSLog("Successfully loaded image from URL: \(content) for tag: \(tag)") + completion(image, nil, tag) + } + }.resume() + } + +} diff --git a/local_packages/tancent_vap-1.0.0+1/ios/Classes/VapViewFactory.swift b/local_packages/tancent_vap-1.0.0+1/ios/Classes/VapViewFactory.swift new file mode 100644 index 0000000..2d2b14a --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/ios/Classes/VapViewFactory.swift @@ -0,0 +1,24 @@ +import Flutter +import UIKit + +public class VapViewFactory: NSObject, FlutterPlatformViewFactory { + private let messenger: FlutterBinaryMessenger + + public init(messenger: FlutterBinaryMessenger) { + self.messenger = messenger + super.init() + } + + public func create( + withFrame frame: CGRect, + viewIdentifier viewId: Int64, + arguments args: Any? + ) -> FlutterPlatformView { + let params = args as? [String: Any] + return VapFlutterView(context: frame, params: params, messenger: messenger, id: viewId) + } + + public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol { + return FlutterStandardMessageCodec.sharedInstance() + } +} diff --git a/local_packages/tancent_vap-1.0.0+1/ios/Resources/PrivacyInfo.xcprivacy b/local_packages/tancent_vap-1.0.0+1/ios/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..a34b7e2 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/ios/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,14 @@ + + + + + NSPrivacyTrackingDomains + + NSPrivacyAccessedAPITypes + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + + diff --git a/local_packages/tancent_vap-1.0.0+1/ios/tancent_vap.podspec b/local_packages/tancent_vap-1.0.0+1/ios/tancent_vap.podspec new file mode 100644 index 0000000..5c1f835 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/ios/tancent_vap.podspec @@ -0,0 +1,35 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint vap.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'tancent_vap' + s.version = '0.0.1' + s.summary = 'A Flutter plugin for VAP (Video Animation Player) integration.' + s.description = <<-DESC +A Flutter plugin that provides VAP (Video Animation Player) integration for iOS and Android. +VAP is a high-performance animation framework developed by Tencent for playing transparent video animations. + DESC + s.homepage = 'https://laskarmedia.id' + s.license = { :file => '../LICENSE' } + s.author = { 'Laskarmedia' => 'support@laskarmedia.id' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'Flutter' + s.platform = :ios, '12.0' + s.dependency 'QGVAPlayer', '= 1.0.19' + + + # VAP iOS SDK dependency (users need to add the git source in their Podfile) + # s.dependency 'QGVAPlayer' + + # Flutter.framework does not contain a i386 slice. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } + s.swift_version = '5.0' + + # If your plugin requires a privacy manifest, for example if it uses any + # required reason APIs, update the PrivacyInfo.xcprivacy file to describe your + # plugin's privacy impact, and then uncomment this line. For more information, + # see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files + # s.resource_bundles = {'vap_privacy' => ['Resources/PrivacyInfo.xcprivacy']} +end diff --git a/local_packages/tancent_vap-1.0.0+1/lib/tancent_vap.dart b/local_packages/tancent_vap-1.0.0+1/lib/tancent_vap.dart new file mode 100644 index 0000000..f8a3c33 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/lib/tancent_vap.dart @@ -0,0 +1,27 @@ +/// Tencent VAP Flutter Plugin +/// +/// A Flutter plugin for playing VAP (Video Animation Player) animations from Tencent. +/// VAP is a high-performance video animation format that supports transparent backgrounds +/// and dynamic content injection. +/// +/// This library provides: +/// - [VapView] widget for displaying VAP animations +/// - [VapController] for programmatic control of animations +/// - [VAPContent] classes for dynamic content injection +/// - [VAPConfigs] for animation configuration data +/// +/// Example usage: +/// ```dart +/// VapView( +/// scaleType: ScaleType.fitCenter, +/// repeat: -1, +/// onViewCreated: (controller) { +/// controller.playAsset('animations/sample.mp4'); +/// }, +/// ) +/// ``` +library; + +export 'widgets/vap_view.dart'; +export 'utils/anim_configs.dart'; +export 'utils/constant.dart'; diff --git a/local_packages/tancent_vap-1.0.0+1/lib/utils/anim_configs.dart b/local_packages/tancent_vap-1.0.0+1/lib/utils/anim_configs.dart new file mode 100644 index 0000000..b2c8963 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/lib/utils/anim_configs.dart @@ -0,0 +1,136 @@ +import 'package:flutter/painting.dart'; +import 'package:tancent_vap/utils/constant.dart'; + +/// Configuration data for VAP animations. +/// +/// VAPConfigs contains all the technical details about a VAP animation file, +/// including dimensions, timing, orientation, and channel information. +/// This data is typically provided by the native VAP player when an animation +/// is loaded and ready to play. +/// +/// The configuration is automatically parsed from native data and provided +/// through the [OnVideoConfigReady] callback. +/// +/// Example usage: +/// ```dart +/// controller.setAnimListener( +/// onVideoConfigReady: (config) { +/// print('Animation size: ${config.width}x${config.height}'); +/// print('FPS: ${config.fps}'); +/// print('Total frames: ${config.totalFrames}'); +/// }, +/// ); +/// ``` +class VAPConfigs { + /// The logical width of the animation in pixels. + final int width; + + /// The logical height of the animation in pixels. + final int height; + + /// Frames per second of the animation. + final int fps; + + /// Total number of frames in the animation. + final int totalFrames; + + /// Whether this is a mixed content animation (has both video and alpha channels). + final bool isMix; + + /// The orientation of the video content. + final VideoOrientation orien; + + /// The actual video height in pixels. + final int videoHeight; + + /// The actual video width in pixels. + final int videoWidth; + + /// Rectangle defining the alpha channel area in the video. + /// Used for transparency information in mixed content animations. + final Rect alphaPointRect; + + /// Rectangle defining the RGB channel area in the video. + /// Contains the actual video content in mixed content animations. + final Rect rgbPointRect; + + /// VAP format version number. + final int version; + + /// Creates a VAPConfigs instance. + /// + /// All parameters are required as they represent essential animation properties. + /// This constructor is typically not used directly - instead, use [VAPConfigs.fromMap] + /// to create instances from native data. + VAPConfigs({ + required this.width, + required this.height, + required this.fps, + required this.totalFrames, + required this.isMix, + required this.orien, + required this.videoHeight, + required this.videoWidth, + required this.alphaPointRect, + required this.rgbPointRect, + required this.version, + }); + + /// Creates a VAPConfigs instance from a map of native data. + /// + /// This factory constructor is used internally to parse configuration data + /// received from the native VAP player. The map should contain all required + /// fields with appropriate types. + /// + /// Parameters: + /// - [json]: Map containing configuration data from native code + /// + /// Throws [TypeError] if required fields are missing or have wrong types. + factory VAPConfigs.fromMap(Map json) { + return VAPConfigs( + width: json['width'] as int, + height: json['height'] as int, + fps: json['fps'] as int, + totalFrames: json['totalFrames'] as int, + videoHeight: json['videoHeight'] as int, + videoWidth: json['videoWidth'] as int, + isMix: json['isMix'] as bool, + orien: VideoOrientation.fromValue(json['orien'] as int), + alphaPointRect: Rect.fromLTRB( + (json['alphaPointRect']["x"] as int).toDouble(), + (json['alphaPointRect']["y"] as int).toDouble(), + (json['alphaPointRect']["w"] as int).toDouble(), + (json['alphaPointRect']["h"] as int).toDouble(), + ), + rgbPointRect: Rect.fromLTRB( + (json['rgbPointRect']["x"] as int).toDouble(), + (json['rgbPointRect']["y"] as int).toDouble(), + (json['rgbPointRect']["w"] as int).toDouble(), + (json['rgbPointRect']["h"] as int).toDouble(), + ), + version: json['version'] as int, + ); + } + + /// Converts this VAPConfigs instance to a map. + /// + /// This method is primarily used for debugging and serialization purposes. + /// The returned map contains all configuration properties with their current values. + /// + /// Returns a map with string keys and dynamic values representing the configuration. + Map toMap() { + return { + 'width': width, + 'height': height, + 'fps': fps, + 'totalFrames': totalFrames, + 'isMix': isMix, + 'orien': orien, + 'videoHeight': videoHeight, + 'videoWidth': videoWidth, + 'alphaPointRect': alphaPointRect, + 'rgbPointRect': rgbPointRect, + 'version': version, + }; + } +} diff --git a/local_packages/tancent_vap-1.0.0+1/lib/utils/constant.dart b/local_packages/tancent_vap-1.0.0+1/lib/utils/constant.dart new file mode 100644 index 0000000..4a3a427 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/lib/utils/constant.dart @@ -0,0 +1,319 @@ +import 'package:tancent_vap/utils/anim_configs.dart'; + +/// Defines how video content should be scaled within its container. +/// +/// These scale types determine how the VAP animation is displayed when the +/// animation dimensions don't exactly match the widget size. +/// +/// Example usage: +/// ```dart +/// VapView( +/// scaleType: ScaleType.fitCenter, // Maintain aspect ratio, fit within bounds +/// ) +/// ``` +enum ScaleType { + /// Scales the video to fit within the container while maintaining aspect ratio. + /// The entire video will be visible, but there may be empty space if the + /// aspect ratios don't match. + fitCenter("fitCenter"), + + /// Scales the video to fill the container while maintaining aspect ratio. + /// The video may be cropped if aspect ratios don't match, but no empty space + /// will be visible. + centerCrop("centerCrop"), + + /// Scales the video to exactly fill the container bounds. + /// This may distort the video if aspect ratios don't match, but ensures + /// the entire container is filled. + fitXY("fitXY"); + + /// The string key used for native platform communication. + final String key; + + const ScaleType(this.key); +} + +/// Defines the orientation of video content. +/// +/// This enum represents the orientation metadata of the VAP animation, +/// which can be used to determine how the content should be displayed +/// or processed. +enum VideoOrientation { + /// No specific orientation or orientation is unknown. + none(0), + + /// Video content is in portrait orientation (height > width). + portrait(1), + + /// Video content is in landscape orientation (width > height). + landscape(2); + + /// The integer value used for native platform communication. + final int value; + + const VideoOrientation(this.value); + + /// Creates a VideoOrientation from an integer value. + /// + /// This method is used when parsing orientation data from native platforms. + /// If the provided [value] doesn't match any known orientation, returns [none]. + /// + /// Parameters: + /// - [value]: Integer value representing the orientation (0=none, 1=portrait, 2=landscape) + static VideoOrientation fromValue(int value) { + return VideoOrientation.values + .firstWhere((e) => e.value == value, orElse: () => none); + } +} + +/// Callback function for animation failure events. +/// +/// Called when an animation fails to load or play. +/// +/// Parameters: +/// - [code]: Error code indicating the type of failure +/// - [type]: String description of the error type +/// - [message]: Optional detailed error message +typedef OnFailed = void Function(int code, String type, String? message); + +/// Callback function for animation completion events. +/// +/// Called when an animation finishes playing completely. +typedef OnVideoComplete = void Function(); + +/// Callback function for animation destruction events. +/// +/// Called when an animation is destroyed and resources are cleaned up. +typedef OnVideoDestroy = void Function(); + +/// Callback function for frame rendering events. +/// +/// Called for each frame that is rendered during animation playback. +/// This callback is only available on Android. +/// +/// Parameters: +/// - [frameInfo]: Index of the current frame being rendered +/// - [configs]: Animation configuration data (may be null) +typedef OnVideoRender = void Function(int frameInfo, VAPConfigs? configs); + +/// Callback function for animation configuration ready events. +/// +/// Called when the animation file has been loaded and configuration +/// data is available. +/// +/// Parameters: +/// - [configs]: Complete animation configuration data +typedef OnVideoConfigReady = void Function(VAPConfigs configs); + +/// Callback function for animation start events. +/// +/// Called when an animation begins playing. +typedef OnVideoStart = void Function(); + +/// Abstract base class for dynamic content that can be injected into VAP animations. +/// +/// VAPContent represents different types of content (text, images) that can be +/// dynamically inserted into VAP animations at runtime. This allows for +/// personalized animations with user-specific data. +/// +/// Subclasses include: +/// - [TextContent]: For dynamic text content +/// - [ImageBase64Content]: For base64-encoded images +/// - [ImageFileContent]: For local file images +/// - [ImageAssetContent]: For Flutter asset images +/// - [ImageURLContent]: For remote images +/// +/// Example usage: +/// ```dart +/// Map contents = { +/// 'username': TextContent('John Doe'), +/// 'avatar': ImageURLContent('https://example.com/avatar.jpg'), +/// 'logo': ImageAssetContent('assets/images/logo.png'), +/// }; +/// ``` +abstract class VAPContent { + /// The type of content (e.g., 'text', 'image_url', 'image_file'). + String get contentType; + + /// The actual content value (text string, file path, URL, etc.). + String get contentValue; + + /// Converts this content to a map for native platform communication. + /// + /// Returns a map containing the content type and value that can be + /// serialized and sent to native platforms. + Map get toMap { + return {'contentType': contentType, 'contentValue': contentValue}; + } + + /// Creates a VAPContent instance from a map received from native platforms. + /// + /// This factory method automatically determines the appropriate subclass + /// based on the content type and value in the map. + /// + /// Parameters: + /// - [value]: Map containing 'contentType' and 'contentValue' keys + /// + /// Returns the appropriate VAPContent subclass instance. + /// Throws [ArgumentError] if the content type is unknown. + static VAPContent fromMap(Map value) { + final contentType = value['contentType'] as String; + final contentValue = value['contentValue'] as String; + + switch (contentType) { + case 'text': + return TextContent(contentValue); + case 'image': + if (contentValue.startsWith('http://') || + contentValue.startsWith('https://')) { + return ImageURLContent(contentValue); + } else if (contentValue.startsWith('data:image/')) { + return ImageBase64Content(contentValue); + } else if (contentValue.startsWith('assets/')) { + return ImageAssetContent(contentValue); + } else { + return ImageFileContent(contentValue); + } + default: + throw ArgumentError('Unknown content type: $contentType'); + } + } +} + +/// Content type for dynamic text injection into VAP animations. +/// +/// TextContent allows you to inject dynamic text into VAP animations at runtime. +/// The text will be rendered using the styling defined in the VAP file. +/// +/// Example usage: +/// ```dart +/// TextContent username = TextContent('John Doe'); +/// await controller.setVapTagContent('username', username); +/// ``` +class TextContent extends VAPContent { + /// The text content to display in the animation. + final String text; + + /// Creates a TextContent with the specified text. + /// + /// Parameters: + /// - [text]: The text string to inject into the animation + TextContent(this.text); + + @override + String get contentType => 'text'; + + @override + String get contentValue => text; +} + +/// Content type for base64-encoded image injection into VAP animations. +/// +/// ImageBase64Content allows you to inject images that are encoded as base64 +/// strings directly into VAP animations. This is useful when you have image +/// data in memory or received from an API. +/// +/// Example usage: +/// ```dart +/// String base64Data = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=='; +/// ImageBase64Content image = ImageBase64Content(base64Data); +/// await controller.setVapTagContent('avatar', image); +/// ``` +class ImageBase64Content extends VAPContent { + /// The base64-encoded image data. + final String base64; + + /// Creates an ImageBase64Content with the specified base64 data. + /// + /// Parameters: + /// - [base64]: Base64-encoded image string + ImageBase64Content(this.base64); + + @override + String get contentType => 'image_base64'; + + @override + String get contentValue => base64; +} + +/// Content type for local file image injection into VAP animations. +/// +/// ImageFileContent allows you to inject images from the device's file system +/// into VAP animations. The file path should point to a valid image file +/// accessible to the app. +/// +/// Example usage: +/// ```dart +/// ImageFileContent image = ImageFileContent('/storage/emulated/0/Download/avatar.jpg'); +/// await controller.setVapTagContent('avatar', image); +/// ``` +class ImageFileContent extends VAPContent { + /// The file system path to the image file. + final String filePath; + + /// Creates an ImageFileContent with the specified file path. + /// + /// Parameters: + /// - [filePath]: Absolute path to the image file on the device + ImageFileContent(this.filePath); + + @override + String get contentType => 'image_file'; + + @override + String get contentValue => filePath; +} + +/// Content type for Flutter asset image injection into VAP animations. +/// +/// ImageAssetContent allows you to inject images from your app's assets +/// into VAP animations. The asset must be declared in your pubspec.yaml file. +/// +/// Example usage: +/// ```dart +/// ImageAssetContent logo = ImageAssetContent('assets/images/logo.png'); +/// await controller.setVapTagContent('logo', logo); +/// ``` +class ImageAssetContent extends VAPContent { + /// The asset path as declared in pubspec.yaml. + final String assetPath; + + /// Creates an ImageAssetContent with the specified asset path. + /// + /// Parameters: + /// - [assetPath]: Path to the asset image (e.g., 'assets/images/logo.png') + ImageAssetContent(this.assetPath); + + @override + String get contentType => 'image_asset'; + + @override + String get contentValue => assetPath; +} + +/// Content type for remote URL image injection into VAP animations. +/// +/// ImageURLContent allows you to inject images from remote URLs into VAP +/// animations. The image will be downloaded and cached automatically. +/// +/// Example usage: +/// ```dart +/// ImageURLContent avatar = ImageURLContent('https://example.com/avatar.jpg'); +/// await controller.setVapTagContent('avatar', avatar); +/// ``` +class ImageURLContent extends VAPContent { + /// The URL of the remote image. + final String url; + + /// Creates an ImageURLContent with the specified URL. + /// + /// Parameters: + /// - [url]: HTTP/HTTPS URL pointing to an image file + ImageURLContent(this.url); + + @override + String get contentType => 'image_url'; + + @override + String get contentValue => url; +} diff --git a/local_packages/tancent_vap-1.0.0+1/lib/widgets/vap_view.dart b/local_packages/tancent_vap-1.0.0+1/lib/widgets/vap_view.dart new file mode 100644 index 0000000..741792d --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/lib/widgets/vap_view.dart @@ -0,0 +1,608 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:tancent_vap/utils/constant.dart'; + +import '../utils/anim_configs.dart'; + +/// A widget that displays Tencent VAP (Video Animation Player) animations. +/// +/// VapView is a Flutter widget that renders VAP animations with support for: +/// - Multiple scale types (fitCenter, centerCrop, fitXY) +/// - Loop control (no repeat, finite repeats, infinite loop) +/// - Audio control (mute/unmute) +/// - Dynamic content injection via VAP tags +/// - Cross-platform support (iOS and Android) +/// +/// The widget provides a [VapController] through the [onViewCreated] callback +/// for programmatic control of animation playback and content management. +/// +/// Example usage: +/// ```dart +/// VapView( +/// scaleType: ScaleType.fitCenter, +/// repeat: -1, // Infinite loop +/// mute: false, +/// vapTagContents: { +/// 'username': TextContent('John Doe'), +/// 'avatar': ImageURLContent('https://example.com/avatar.jpg'), +/// }, +/// onViewCreated: (controller) { +/// controller.setAnimListener( +/// onVideoStart: () => print('Animation started'), +/// onVideoComplete: () => print('Animation completed'), +/// ); +/// controller.playAsset('animations/sample.mp4'); +/// }, +/// ) +/// ``` +class VapView extends StatefulWidget { + /// How the video should be scaled within the view bounds. + /// + /// Defaults to [ScaleType.fitCenter]. + final ScaleType scaleType; + + /// Number of times the animation should repeat. + /// + /// - `0`: Play once (no repeat) + /// - Positive number: Repeat that many times + /// - `-1`: Infinite loop + /// + /// Defaults to `0`. + final int repeat; + + /// Whether the animation should be muted during playback. + /// + /// Defaults to `false`. + final bool mute; + + /// Initial dynamic content for VAP tags. + /// + /// This map allows you to provide content that will be injected into + /// the VAP animation at runtime. Keys are tag names defined in the VAP file, + /// values are [VAPContent] objects containing the actual content. + /// + /// Can be `null` if no initial content is needed. + final Map? vapTagContents; + + /// Callback invoked when the platform view is created. + /// + /// Provides a [VapController] instance that can be used to control + /// animation playback, set content, and listen to events. + /// + /// This is the recommended way to obtain a controller reference. + final Function(VapController)? onViewCreated; + + /// Scale factor for decode resolution. + /// + /// Controls the resolution at which video frames are decoded. + /// - `1.0`: Full resolution (default, no change from original behavior) + /// - `0.5`: Half resolution (reduces GPU memory by ~75%) + /// - `0.75`: 75% resolution + /// + /// Lower values significantly reduce memory and GPU bandwidth but may + /// affect visual quality for fine details. + final double decodeScale; + + /// Creates a VapView widget. + /// + /// All parameters are optional and have sensible defaults: + /// - [scaleType]: How to scale the video (defaults to [ScaleType.fitCenter]) + /// - [repeat]: Number of repetitions (defaults to 0 for no repeat) + /// - [mute]: Whether to mute audio (defaults to false) + /// - [vapTagContents]: Initial content for VAP tags (optional) + /// - [onViewCreated]: Callback to receive the controller (optional) + /// - [decodeScale]: Decode resolution scale (defaults to 1.0 for full resolution) + const VapView( + {super.key, + this.scaleType = ScaleType.fitCenter, + this.repeat = 0, + this.mute = false, + this.vapTagContents, + this.onViewCreated, + this.decodeScale = 1.0}); + + @override + State createState() => _VapViewState(); +} + +class _VapViewState extends State { + final VapController _controller = VapController(); + + @override + void dispose() { + // Dispose the controller when the widget is disposed + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final creationParams = { + 'scaleType': widget.scaleType.key, + 'loop': widget.repeat, + 'mute': widget.mute, + 'decodeScale': widget.decodeScale, + }; + + // Add VAP tag contents to creation params if provided + if (widget.vapTagContents != null && widget.vapTagContents!.isNotEmpty) { + creationParams['vapTagContents'] = widget.vapTagContents! + .map((key, value) => MapEntry(key, value.toMap)); + } + + if (defaultTargetPlatform == TargetPlatform.iOS) { + return UiKitView( + viewType: 'vap_view', + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + onPlatformViewCreated: _onPlatformViewCreated); + } else { + return AndroidView( + viewType: 'vap_view', + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + onPlatformViewCreated: _onPlatformViewCreated); + } + } + + void _onPlatformViewCreated(int id) { + final channel = MethodChannel('vap_view_$id'); + _controller._setChannel(channel); + widget.onViewCreated?.call(_controller); + } +} + +/// Controller for programmatic management of VAP animations. +/// +/// VapController provides methods to: +/// - Control playback (play, stop, loop settings) +/// - Manage dynamic content (set, get, clear VAP tag contents) +/// - Listen to animation events (start, complete, error, etc.) +/// - Configure playback settings (mute, scale type) +/// +/// The controller is obtained through [VapView.onViewCreated] callback +/// and should be disposed when no longer needed to prevent memory leaks. +/// +/// Example usage: +/// ```dart +/// VapController controller; +/// +/// // Set event listeners +/// controller.setAnimListener( +/// onVideoStart: () => print('Started'), +/// onVideoComplete: () => print('Completed'), +/// onFailed: (code, type, msg) => print('Error: $code'), +/// ); +/// +/// // Play animation +/// await controller.playAsset('animations/sample.mp4'); +/// +/// // Set dynamic content +/// await controller.setVapTagContent('username', TextContent('John')); +/// +/// // Clean up +/// controller.dispose(); +/// ``` +class VapController { + /// Internal method channel for communication with native platforms. + /// + /// This channel is set when the platform view is created and is used + /// for all communication between Dart and native code. + MethodChannel? _channel; + + /// Gets the method channel, throwing an exception if the controller is disposed. + /// + /// Throws [Exception] if the controller has been disposed. + MethodChannel get __channel { + if (_channel == null) { + throw Exception("Controller already disposed"); + } + return _channel!; + } + + /// Callback for animation failure events. + OnFailed? _onFailed; + + /// Callback for animation completion events. + OnVideoComplete? _onVideoComplete; + + /// Callback for animation destruction events. + OnVideoDestroy? _onVideoDestroy; + + /// Callback for frame rendering events. + OnVideoRender? _onVideoRender; + + /// Callback for animation start events. + OnVideoStart? _onVideoStart; + + /// Callback for animation configuration ready events. + OnVideoConfigReady? _onVideoConfigReady; + + /// Internal method to set the method channel. + /// + /// Called automatically when the platform view is created. + /// Sets up the method call handler for receiving events from native code. + void _setChannel(MethodChannel channel) { + _channel = channel; + _channel + ?.setMethodCallHandler((MethodCall call) => _handleNativeEvent(call)); + } + + /// Sets event listeners for various animation events. + /// + /// All parameters are optional - only set listeners for events you need to handle. + /// This method should typically be called before starting animation playback + /// to ensure all events are captured. + /// + /// Parameters: + /// - [onFailed]: Called when animation fails with error code, type, and message + /// - [onVideoComplete]: Called when animation playback completes + /// - [onVideoDestroy]: Called when animation is destroyed/cleaned up + /// - [onVideoRender]: Called for each frame render with frame index and config + /// - [onVideoStart]: Called when animation starts playing + /// - [onVideoConfigReady]: Called when animation configuration is loaded + /// + /// Example: + /// ```dart + /// controller.setAnimListener( + /// onFailed: (code, type, message) { + /// print("Animation failed with code $code: $message"); + /// }, + /// onVideoComplete: () { + /// print("Video playback completed"); + /// }, + /// onVideoStart: () { + /// print("Video started"); + /// }, + /// ); + /// ``` + void setAnimListener({ + OnFailed? onFailed, + OnVideoComplete? onVideoComplete, + OnVideoDestroy? onVideoDestroy, + OnVideoRender? onVideoRender, + OnVideoStart? onVideoStart, + OnVideoConfigReady? onVideoConfigReady, + }) { + _onFailed = onFailed; + _onVideoComplete = onVideoComplete; + _onVideoDestroy = onVideoDestroy; + _onVideoRender = onVideoRender; + _onVideoStart = onVideoStart; + _onVideoConfigReady = onVideoConfigReady; + } + + /// Internal method to handle events from native platforms. + /// + /// Automatically routes native events to the appropriate Dart callbacks + /// that were set via [setAnimListener]. + Future _handleNativeEvent(MethodCall call) async { + switch (call.method) { + case 'onFailed': + if (_onFailed != null) { + final args = call.arguments as Map; + final code = args['errorCode'] as int; + final type = args['errorType'] as String; + final msg = args['errorMsg'] as String?; + _onFailed!(code, type, msg); + } + break; + case 'onVideoComplete': + _onVideoComplete?.call(); + break; + case 'onVideoDestroy': + _onVideoDestroy?.call(); + break; + case 'onVideoRender': + if (_onVideoRender != null) { + final args = call.arguments as Map; + final frameIndex = args['frameIndex'] as int; + final config = args['config'] as Map?; + _onVideoRender!( + frameIndex, config != null ? VAPConfigs.fromMap(config) : null); + } + break; + case 'onVideoStart': + _onVideoStart?.call(); + break; + case 'onVideoConfigReady': + if (_onVideoConfigReady != null) { + final args = Map.from(call.arguments as Map); + final config = Map.from(args['config'] as Map); + _onVideoConfigReady!(VAPConfigs.fromMap(config)); + } + break; + default: + throw UnimplementedError('Unhandled method: ${call.method}'); + } + } + + /// Plays a VAP animation from a file path. + /// + /// The [filePath] should point to a valid VAP file accessible to the app. + /// This is typically used for files downloaded to the device or copied + /// to the app's documents directory. + /// + /// Throws an exception if the file cannot be found or played. + /// + /// Example: + /// ```dart + /// await controller.playFile('/path/to/animation.mp4'); + /// ``` + Future playFile(String filePath) async { + await __channel.invokeMethod('playFile', {'filePath': filePath}); + } + + /// Plays a VAP animation from a Flutter asset. + /// + /// The [assetName] should correspond to an asset declared in `pubspec.yaml`. + /// This is the recommended way to bundle VAP animations with your app. + /// + /// Throws an exception if the asset cannot be found or played. + /// + /// Example: + /// ```dart + /// await controller.playAsset('assets/animations/sample.mp4'); + /// ``` + Future playAsset(String assetName) async { + await __channel.invokeMethod('playAsset', {'assetName': assetName}); + } + + /// Stops the current animation playback. + /// + /// This will immediately stop the animation and reset the playback state. + /// The animation can be restarted by calling [playFile] or [playAsset] again. + /// + /// Example: + /// ```dart + /// await controller.stop(); + /// ``` + Future stop() async { + await __channel.invokeMethod('stop'); + } + + /// Sets the loop count for animation playback. + /// + /// Parameters: + /// - [loop]: Number of times to repeat the animation + /// - `0`: Play once (no repeat) + /// - Positive number: Repeat that many times + /// - `-1`: Infinite loop + /// + /// Example: + /// ```dart + /// await controller.setLoop(-1); // Infinite loop + /// await controller.setLoop(3); // Play 3 times + /// ``` + Future setLoop(int loop) async { + await __channel.invokeMethod('setLoop', {'loop': loop}); + } + + /// Sets the mute state for animation playback. + /// + /// Parameters: + /// - [mute]: `true` to mute audio, `false` to enable audio + /// + /// Example: + /// ```dart + /// await controller.setMute(true); // Mute audio + /// await controller.setMute(false); // Enable audio + /// ``` + Future setMute(bool mute) async { + await __channel.invokeMethod('setMute', {'mute': mute}); + } + + /// Sets the scale type for animation display. + /// + /// Parameters: + /// - [scaleType]: How the animation should be scaled within its container + /// - `'fitCenter'`: Scale to fit within bounds, maintaining aspect ratio + /// - `'centerCrop'`: Scale to fill bounds, cropping excess, maintaining aspect ratio + /// - `'fitXY'`: Scale to exactly fill bounds, potentially distorting aspect ratio + /// + /// Example: + /// ```dart + /// await controller.setScaleType('centerCrop'); + /// ``` + Future setScaleType(String scaleType) async { + await __channel.invokeMethod('setScaleType', {'scaleType': scaleType}); + } + + /// Sets content for a specific VAP tag. + /// + /// VAP tags are placeholders in the animation file that can be replaced + /// with dynamic content at runtime. This allows for personalized animations + /// with user-specific data. + /// + /// Parameters: + /// - [tag]: The tag name defined in the VAP file (cannot be empty) + /// - [content]: The content to inject ([VAPContent] subclass) + /// + /// Throws [ArgumentError] if [tag] is empty. + /// + /// Example: + /// ```dart + /// await controller.setVapTagContent('username', TextContent('John Doe')); + /// await controller.setVapTagContent('avatar', ImageURLContent('https://...')); + /// ``` + Future setVapTagContent(String tag, VAPContent content) async { + if (tag.isEmpty) { + throw ArgumentError('Tag cannot be empty'); + } + await __channel.invokeMethod( + 'setVapTagContent', {'tag': tag, 'content': content.toMap}); + } + + /// Sets multiple VAP tag contents at once. + /// + /// This method is more efficient than calling [setVapTagContent] multiple times + /// as it batches all updates in a single native call. + /// + /// Parameters: + /// - [contents]: Map of tag names to content objects + /// + /// Throws [ArgumentError] if any tag name is empty. + /// Returns immediately if [contents] is empty. + /// + /// Example: + /// ```dart + /// await controller.setVapTagContents({ + /// 'username': TextContent('John Doe'), + /// 'avatar': ImageURLContent('https://example.com/avatar.jpg'), + /// 'message': TextContent('Hello World!') + /// }); + /// ``` + Future setVapTagContents(Map contents) async { + if (contents.isEmpty) { + return; // Nothing to set + } + + // Validate that no tags are empty + for (final tag in contents.keys) { + if (tag.isEmpty) { + throw ArgumentError('Tag cannot be empty'); + } + } + + await __channel.invokeMethod('setVapTagContents', + {'contents': contents.map((key, value) => MapEntry(key, value.toMap))}); + } + + /// Gets the content for a specific VAP tag. + /// + /// Parameters: + /// - [tag]: The tag name to retrieve content for (cannot be empty) + /// + /// Returns the [VAPContent] associated with the tag, or `null` if no content is set. + /// Throws [ArgumentError] if [tag] is empty. + /// + /// Example: + /// ```dart + /// VAPContent? content = await controller.getVapTagContent('username'); + /// if (content is TextContent) { + /// print('Username: ${content.text}'); + /// } + /// ``` + Future getVapTagContent(String tag) async { + if (tag.isEmpty) { + throw ArgumentError('Tag cannot be empty'); + } + return await __channel + .invokeMethod('getVapTagContent', {'tag': tag}).then((result) { + if (result == null) return null; + return VAPContent.fromMap(Map.from(result as Map)); + }); + } + + /// Gets all currently set VAP tag contents. + /// + /// Returns a map containing all tag names and their associated content. + /// The map will be empty if no content has been set. + /// + /// Example: + /// ```dart + /// Map allContents = await controller.getAllVapTagContents(); + /// for (final entry in allContents.entries) { + /// print('Tag ${entry.key}: ${entry.value.contentValue}'); + /// } + /// ``` + Future> getAllVapTagContents() async { + final result = await __channel.invokeMethod('getAllVapTagContents'); + return Map.from((result ?? {}) as Map) + .map((key, value) { + return MapEntry(key, VAPContent.fromMap(value as Map)); + }); + } + + /// Clears all VAP tag contents. + /// + /// This removes all previously set tag content mappings. + /// The animation will continue to play but without any dynamic content. + /// + /// Example: + /// ```dart + /// await controller.clearVapTagContents(); + /// ``` + Future clearVapTagContents() async { + await __channel.invokeMethod('clearVapTagContents'); + } + + /// Checks if a specific VAP tag has content set. + /// + /// Parameters: + /// - [tag]: The tag name to check + /// + /// Returns `true` if content is set for the tag, `false` otherwise. + /// Returns `false` if [tag] is empty. + /// + /// Example: + /// ```dart + /// if (await controller.hasVapTagContent('username')) { + /// print('Username content is set'); + /// } + /// ``` + Future hasVapTagContent(String tag) async { + if (tag.isEmpty) { + return false; + } + final content = await getVapTagContent(tag); + return content != null; + } + + /// Removes content for a specific VAP tag. + /// + /// Parameters: + /// - [tag]: The tag name to remove content for (cannot be empty) + /// + /// Throws [ArgumentError] if [tag] is empty. + /// Has no effect if the tag doesn't have content set. + /// + /// Example: + /// ```dart + /// await controller.removeVapTagContent('username'); + /// ``` + Future removeVapTagContent(String tag) async { + if (tag.isEmpty) { + throw ArgumentError('Tag cannot be empty'); + } + + final allContents = await getAllVapTagContents(); + allContents.remove(tag); + await setVapTagContents(allContents); + } + + /// Checks if the controller has been disposed. + /// + /// Returns `true` if [dispose] has been called, `false` otherwise. + /// A disposed controller cannot be used for any operations. + bool get isDisposed => _channel == null; + + /// Disposes the controller and releases associated resources. + /// + /// This method should be called when the controller is no longer needed + /// to prevent memory leaks. After calling this method, the controller + /// cannot be used for any operations. + /// + /// It's safe to call this method multiple times. + /// + /// Example: + /// ```dart + /// @override + /// void dispose() { + /// controller.dispose(); + /// super.dispose(); + /// } + /// ``` + Future dispose() async { + try { + await _channel?.invokeMethod('dispose'); + } catch (e) { + // Ignore disposal errors + } finally { + _channel?.setMethodCallHandler(null); + _channel = null; + } + } +} diff --git a/local_packages/tancent_vap-1.0.0+1/pubspec.yaml b/local_packages/tancent_vap-1.0.0+1/pubspec.yaml new file mode 100644 index 0000000..0dd2e66 --- /dev/null +++ b/local_packages/tancent_vap-1.0.0+1/pubspec.yaml @@ -0,0 +1,29 @@ +name: tancent_vap +description: "Video Animation Player from Tencent for Flutter, supports both Android and iOS platforms." +version: 1.0.0+1 +homepage: https://github.com/nizwar/tancent_vap_flutter.git + +environment: + sdk: ^3.6.0 + flutter: '>=3.3.0' + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: + + plugin: + platforms: + android: + package: com.laskarmedia.vap + pluginClass: VapPlugin + ios: + package: com.laskarmedia.vap + pluginClass: VapPlugin diff --git a/local_packages/webview_flutter-4.13.0/AUTHORS b/local_packages/webview_flutter-4.13.0/AUTHORS new file mode 100644 index 0000000..85628e4 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/AUTHORS @@ -0,0 +1,68 @@ +# Below is a list of people and organizations that have contributed +# to the Flutter project. Names should be added to the list like so: +# +# Name/Organization + +Google Inc. +The Chromium Authors +German Saprykin +Benjamin Sauer +larsenthomasj@gmail.com +Ali Bitek +Pol Batlló +Anatoly Pulyaevskiy +Hayden Flinner +Stefano Rodriguez +Salvatore Giordano +Brian Armstrong +Paul DeMarco +Fabricio Nogueira +Simon Lightfoot +Ashton Thomas +Thomas Danner +Diego Velásquez +Hajime Nakamura +Tuyển Vũ Xuân +Miguel Ruivo +Sarthak Verma +Mike Diarmid +Invertase +Elliot Hesp +Vince Varga +Aawaz Gyawali +EUI Limited +Katarina Sheremet +Thomas Stockx +Sarbagya Dhaubanjar +Ozkan Eksi +Rishab Nayak +ko2ic +Jonathan Younger +Jose Sanchez +Debkanchan Samadder +Audrius Karosevicius +Lukasz Piliszczuk +SoundReply Solutions GmbH +Rafal Wachol +Pau Picas +Christian Weder +Alexandru Tuca +Christian Weder +Rhodes Davis Jr. +Luigi Agosti +Quentin Le Guennec +Koushik Ravikumar +Nissim Dsilva +Giancarlo Rocha +Ryo Miyake +Théo Champion +Kazuki Yamaguchi +Eitan Schwartz +Chris Rutkowski +Juan Alvarez +Aleksandr Yurkovskiy +Anton Borries +Alex Li +Rahul Raj <64.rahulraj@gmail.com> +Nick Bradshaw +Antonino Di Natale diff --git a/local_packages/webview_flutter-4.13.0/CHANGELOG.md b/local_packages/webview_flutter-4.13.0/CHANGELOG.md new file mode 100644 index 0000000..50311c6 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/CHANGELOG.md @@ -0,0 +1,681 @@ +## 4.13.0 + +* Adds support to respond to recoverable SSL certificate errors. See `NavigationDelegate(onSSlAuthError)`. + +## 4.12.0 + +* Adds support to set whether to draw the scrollbar. See + `WebViewController.setVerticalScrollBarEnabled`, + `WebViewController.setHorizontalScrollBarEnabled`, + `WebViewController.supportsSetScrollBarsEnabled`. +* Updates README to indicate that Android SDK <21 is no longer supported. + +## 4.11.0 + +* Adds support to set the over-scroll mode for the WebView. See `WebViewController.setOverScrollMode`. +* Updates minimum supported SDK version to Flutter 3.27/Dart 3.6. + +## 4.10.0 + +* Updates minimum supported `webview_flutter_android` from 3.16.0 to 4.0.0. + +## 4.9.0 + +* Adds endorsed macOS support. +* Updates minimum supported SDK version to Flutter 3.24/Dart 3.5. + +## 4.8.0 + +* Adds `onHttpError` callback to `NavigationDelegate` to catch HTTP error status codes. + +## 4.7.0 + +* Adds support to track scroll position changes. +* Updates minimum supported SDK version to Flutter 3.16.6/Dart 3.2.3. + +## 4.6.0 + +* Adds support for custom handling of JavaScript dialogs. See + `WebViewController.setOnJavaScriptAlertDialog`, `WebViewController.setOnJavaScriptConfirmDialog` + and `WebViewController.setOnJavaScriptTextInputDialog`. +* Updates minimum Dart version to 3.2.3 and minimum Flutter version to 3.16.6. + +## 4.5.0 + +* Adds support for HTTP basic authentication. See `NavigationDelegate(onReceivedHttpAuthRequest)`. +* Updates support matrix in README to indicate that iOS 11 is no longer supported. +* Clients on versions of Flutter that still support iOS 11 can continue to use this + package with iOS 11, but will not receive any further updates to the iOS implementation. + +## 4.4.4 + +* Updates minimum required plugin_platform_interface version to 2.1.7. + +## 4.4.3 + +* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0. +* Fixes new lint warnings. + +## 4.4.2 + +* Fixes `use_build_context_synchronously` lint violations in the example app. + +## 4.4.1 + +* Exposes `JavaScriptLogLevel` from platform interface. + +## 4.4.0 + +* Adds support to register a callback to receive JavaScript console messages. See `WebViewController.setOnConsoleMessage`. + +## 4.3.0 + +* Adds support to retrieve the user agent. See `WebViewController.getUserAgent`. + +## 4.2.4 + +* Adds pub topics to package metadata. + +## 4.2.3 + +* Fixes the code sample in the dartdocs for `WebViewController.addJavaScriptChannel`. +* Updates minimum supported SDK version to Flutter 3.7/Dart 2.19. + +## 4.2.2 + +* Fixes documentation typo. + +## 4.2.1 + +* Removes obsolete null checks on non-nullable values. + +## 4.2.0 + +* Adds support to receive permission requests. See `WebViewController(onPermissionRequest)`. + +## 4.1.0 + +* Adds support to track URL changes. See `NavigationDelegate(onUrlChange)`. +* Updates minimum Flutter version to 3.3. +* Fixes common typos in tests and documentation. +* Fixes documentation for `WebViewController` and `WebViewCookieManager`. + +## 4.0.7 + +* Updates the README with the migration of `WebView.initialCookies` and Hybrid Composition on + Android. + +## 4.0.6 + +* Updates iOS minimum version in README. + +## 4.0.5 + +* Updates links for the merge of flutter/plugins into flutter/packages. + +## 4.0.4 + +* Adds examples of accessing platform-specific features for each class. + +## 4.0.3 + +* Updates example code for `use_build_context_synchronously` lint. + +## 4.0.2 + +* Updates code for stricter lint checks. + +## 4.0.1 + +* Exposes `WebResourceErrorType` from platform interface. + +## 4.0.0 + +* **BREAKING CHANGE** Updates implementation to use the `2.0.0` release of + `webview_flutter_platform_interface`. See `Usage` section in the README for updated usage. See + `Migrating from 3.0 to 4.0` section in the README for details on migrating to this version. +* Updates minimum Flutter version to 3.0.0. +* Updates code for new analysis options. +* Updates references to the obsolete master branch. + +## 3.0.4 + +* Minor fixes for new analysis options. + +## 3.0.3 + +* Removes unnecessary imports. +* Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors + lint warnings. + +## 3.0.2 + +* Migrates deprecated `Scaffold.showSnackBar` to `ScaffoldMessenger` in example app. +* Adds OS version support information to README. + +## 3.0.1 + +* Removes a duplicate Android-specific integration test. +* Fixes an integration test race condition. +* Fixes comments (accidentally mixed // with ///). + +## 3.0.0 + +* **BREAKING CHANGE**: On Android, hybrid composition (SurfaceAndroidWebView) + is now the default. The previous default, virtual display, can be specified + with `WebView.platform = AndroidWebView()` + +## 2.8.0 + +* Adds support for the `loadFlutterAsset` method. + +## 2.7.0 + +* Adds `setCookie` to CookieManager. +* CreationParams now supports setting `initialCookies`. + +## 2.6.0 + +* Adds support for the `loadRequest` method. + +## 2.5.0 + +* Adds an option to set the background color of the webview. + +## 2.4.0 + +* Adds support for the `loadFile` and `loadHtmlString` methods. +* Updates example app Android compileSdkVersion to 31. +* Integration test fixes. +* Updates code for new analysis options. + +## 2.3.1 + +* Add iOS-specific note to set `JavascriptMode.unrestricted` in order to set `zoomEnabled: false`. + +## 2.3.0 + +* Add ability to enable/disable zoom functionality. + +## 2.2.0 + +* Added `runJavascript` and `runJavascriptForResult` to supersede `evaluateJavascript`. +* Deprecated `evaluateJavascript`. + +## 2.1.2 + +* Fix typos in the README. + +## 2.1.1 + +* Fixed `_CastError` that was thrown when running the example App. + +## 2.1.0 + +* Migrated to fully federated architecture. + +## 2.0.14 + +* Update minimum Flutter SDK to 2.5 and iOS deployment target to 9.0. + +## 2.0.13 + +* Send URL of File to download to the NavigationDelegate on Android just like it is already done on iOS. +* Updated Android lint settings. + +## 2.0.12 + +* Improved the documentation on using the different Android Platform View modes. +* So that Android and iOS behave the same, `onWebResourceError` is now only called for the main + page. + +## 2.0.11 + +* Remove references to the Android V1 embedding. + +## 2.0.10 + +* Fix keyboard issues link in the README. + +## 2.0.9 + +* Add iOS UI integration test target. +* Suppress deprecation warning for iOS APIs deprecated in iOS 9. + +## 2.0.8 + +* Migrate maven repository from jcenter to mavenCentral. + +## 2.0.7 + +* Republished 2.0.6 with Flutter 2.2 to avoid https://github.com/dart-lang/pub/issues/3001 + +## 2.0.6 + +* WebView requires at least Android 19 if you are using +hybrid composition ([flutter/issues/59894](https://github.com/flutter/flutter/issues/59894)). + +## 2.0.5 + +* Example app observes `uiMode`, so the WebView isn't reattached when the UI mode changes. (e.g. switching to Dark mode). + +## 2.0.4 + +* Fix a bug where `allowsInlineMediaPlayback` is not respected on iOS. + +## 2.0.3 + +* Fixes bug where scroll bars on the Android non-hybrid WebView are rendered on +the wrong side of the screen. + +## 2.0.2 + +* Fixes bug where text fields are hidden behind the keyboard +when hybrid composition is used [flutter/issues/75667](https://github.com/flutter/flutter/issues/75667). + +## 2.0.1 + +* Run CocoaPods iOS tests in RunnerUITests target + +## 2.0.0 + +* Migration to null-safety. +* Added support for progress tracking. +* Add section to the wiki explaining how to use Material components. +* Update integration test to workaround an iOS 14 issue with `evaluateJavascript`. +* Fix `onWebResourceError` on iOS. +* Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) +* Added `allowsInlineMediaPlayback` property. + +## 1.0.8 + +* Update Flutter SDK constraint. + +## 1.0.7 + +* Minor documentation update to indicate known issue on iOS 13.4 and 13.5. + * See: https://github.com/flutter/flutter/issues/53490 + +## 1.0.6 + +* Invoke the WebView.onWebResourceError on iOS when the webview content process crashes. + +## 1.0.5 + +* Fix example in the readme. + +## 1.0.4 + +* Suppress the `deprecated_member_use` warning in the example app for `ScaffoldMessenger.showSnackBar`. + +## 1.0.3 + +* Update android compileSdkVersion to 29. + +## 1.0.2 + +* Android Code Inspection and Clean up. + +## 1.0.1 + +* Add documentation for `WebViewPlatformCreatedCallback`. + +## 1.0.0 - Out of developer preview 🎉. + +* Bumped the minimal Flutter SDK to 1.22 where platform views are out of developer preview, and +performing better on iOS. Flutter 1.22 no longer requires adding the +`io.flutter.embedded_views_preview` flag to `Info.plist`. + +* Added support for Hybrid Composition on Android (see opt-in instructions in [README](https://github.com/flutter/plugins/blob/main/packages/webview_flutter/README.md#android)) + * Lowered the required Android API to 19 (was previously 20): [#23728](https://github.com/flutter/flutter/issues/23728). + * Fixed the following issues: + * 🎹 Keyboard: [#41089](https://github.com/flutter/flutter/issues/41089), [#36478](https://github.com/flutter/flutter/issues/36478), [#51254](https://github.com/flutter/flutter/issues/51254), [#50716](https://github.com/flutter/flutter/issues/50716), [#55724](https://github.com/flutter/flutter/issues/55724), [#56513](https://github.com/flutter/flutter/issues/56513), [#56515](https://github.com/flutter/flutter/issues/56515), [#61085](https://github.com/flutter/flutter/issues/61085), [#62205](https://github.com/flutter/flutter/issues/62205), [#62547](https://github.com/flutter/flutter/issues/62547), [#58943](https://github.com/flutter/flutter/issues/58943), [#56361](https://github.com/flutter/flutter/issues/56361), [#56361](https://github.com/flutter/flutter/issues/42902), [#40716](https://github.com/flutter/flutter/issues/40716), [#37989](https://github.com/flutter/flutter/issues/37989), [#27924](https://github.com/flutter/flutter/issues/27924). + * ♿️ Accessibility: [#50716](https://github.com/flutter/flutter/issues/50716). + * ⚡️ Performance: [#61280](https://github.com/flutter/flutter/issues/61280), [#31243](https://github.com/flutter/flutter/issues/31243), [#52211](https://github.com/flutter/flutter/issues/52211). + * 📹 Video: [#5191](https://github.com/flutter/flutter/issues/5191). + +## 0.3.24 + +* Keep handling deprecated Android v1 classes for backward compatibility. + +## 0.3.23 + +* Handle WebView multi-window support. + +## 0.3.22+2 + +* Update package:e2e reference to use the local version in the flutter/plugins + repository. + +## 0.3.22+1 + +* Update the `setAndGetScrollPosition` to use hard coded values and add a `pumpAndSettle` call. + +## 0.3.22 + +* Add support for passing a failing url. + +## 0.3.21 + +* Enable programmatic scrolling using Android's WebView.scrollTo & iOS WKWebView.scrollView.contentOffset. + +## 0.3.20+2 + +* Fix CocoaPods podspec lint warnings. + +## 0.3.20+1 + +* OCMock module import -> #import, unit tests compile generated as library. +* Fix select drop down crash on old Android tablets (https://github.com/flutter/flutter/issues/54164). + +## 0.3.20 + +* Added support for receiving web resource loading errors. See `WebView.onWebResourceError`. + +## 0.3.19+10 + +* Replace deprecated `getFlutterEngine` call on Android. + +## 0.3.19+9 + +* Remove example app's iOS workspace settings. + +## 0.3.19+8 + +* Make the pedantic dev_dependency explicit. + +## 0.3.19+7 + +* Remove the Flutter SDK constraint upper bound. + +## 0.3.19+6 + +* Enable opening links that target the "_blank" window (links open in same window). + +## 0.3.19+5 + +* On iOS, always keep contentInsets of the WebView to be 0. +* Fix XCTest case to follow XCTest naming convention. + +## 0.3.19+4 + +* On iOS, fix the scroll view content inset is automatically adjusted. After the fix, the content position of the WebView is customizable by Flutter. +* Fix an iOS 13 bug where the scroll indicator shows at random location. + +## 0.3.19+3 + +* Setup XCTests. + +## 0.3.19+2 + +* Migrate from deprecated BinaryMessages to ServicesBinding.instance.defaultBinaryMessenger. + +## 0.3.19+1 + +* Raise min Flutter SDK requirement to the latest stable. v2 embedding apps no + longer need to special case their Flutter SDK requirement like they have + since v0.3.15+3. + +## 0.3.19 + +* Add setting for iOS to allow gesture based navigation. + +## 0.3.18+1 + +* Be explicit that keyboard is not ready for production in README.md. + +## 0.3.18 + +* Add support for onPageStarted event. +* Remove the deprecated `author:` field from pubspec.yaml +* Migrate to the new pubspec platforms manifest. +* Require Flutter SDK 1.10.0 or greater. + +## 0.3.17 + +* Fix pedantic lint errors. Added missing documentation and awaited some futures + in tests and the example app. + +## 0.3.16 + +* Add support for async NavigationDelegates. Synchronous NavigationDelegates + should still continue to function without any change in behavior. + +## 0.3.15+3 + +* Re-land support for the v2 Android embedding. This correctly sets the minimum + SDK to the latest stable and avoid any compile errors. *WARNING:* the V2 + embedding itself still requires the current Flutter master channel + (flutter/flutter@1d4d63a) for text input to work properly on all Android + versions. + +## 0.3.15+2 + +* Remove AndroidX warnings. + +## 0.3.15+1 + +* Revert the prior embedding support add since it requires an API that hasn't + rolled to stable. + +## 0.3.15 + +* Add support for the v2 Android embedding. This shouldn't affect existing + functionality. Plugin authors who use the V2 embedding can now register the + plugin and expect that it correctly responds to app lifecycle changes. + +## 0.3.14+2 + +* Define clang module for iOS. + +## 0.3.14+1 + +* Allow underscores anywhere for Javascript Channel name. + +## 0.3.14 + +* Added a getTitle getter to WebViewController. + +## 0.3.13 + +* Add an optional `userAgent` property to set a custom User Agent. + +## 0.3.12+1 + +* Temporarily revert getTitle (doing this as a patch bump shortly after publishing). + +## 0.3.12 + +* Added a getTitle getter to WebViewController. + +## 0.3.11+6 + +* Calling destroy on Android webview when flutter webview is getting disposed. + +## 0.3.11+5 + +* Reduce compiler warnings regarding iOS9 compatibility by moving a single + method back into a `@available` block. + +## 0.3.11+4 + +* Removed noisy log messages on iOS. + +## 0.3.11+3 + +* Apply the display listeners workaround that was shipped in 0.3.11+1 on + all Android versions prior to P. + +## 0.3.11+2 + +* Add fix for input connection being dropped after a screen resize on certain + Android devices. + +## 0.3.11+1 + +* Work around a bug in old Android WebView versions that was causing a crash + when resizing the webview on old devices. + +## 0.3.11 + +* Add an initialAutoMediaPlaybackPolicy setting for controlling how auto media + playback is restricted. + +## 0.3.10+5 + +* Add dependency on `androidx.annotation:annotation:1.0.0`. + +## 0.3.10+4 + +* Add keyboard text to README. + +## 0.3.10+3 + +* Don't log an unknown setting key error for 'debuggingEnabled' on iOS. + +## 0.3.10+2 + +* Fix InputConnection being lost when combined with route transitions. + +## 0.3.10+1 + +* Add support for simultaenous Flutter `TextInput` and WebView text fields. + +## 0.3.10 + +* Add partial WebView keyboard support for Android versions prior to N. Support + for UIs that also have Flutter `TextInput` fields is still pending. This basic + support currently only works with Flutter `master`. The keyboard will still + appear when it previously did not when run with older versions of Flutter. But + if the WebView is resized while showing the keyboard the text field will need + to be focused multiple times for any input to be registered. + +## 0.3.9+2 + +* Update Dart code to conform to current Dart formatter. + +## 0.3.9+1 + +* Add missing template type parameter to `invokeMethod` calls. +* Bump minimum Flutter version to 1.5.0. +* Replace invokeMethod with invokeMapMethod wherever necessary. + +## 0.3.9 + +* Allow external packages to provide webview implementations for new platforms. + +## 0.3.8+1 + +* Suppress deprecation warning for BinaryMessages. See: https://github.com/flutter/flutter/issues/33446 + +## 0.3.8 + +* Add `debuggingEnabled` property. + +## 0.3.7+1 + +* Fix an issue where JavaScriptChannel messages weren't sent from the platform thread on Android. + +## 0.3.7 + +* Fix loadUrlWithHeaders flaky test. + +## 0.3.6+1 + +* Remove un-used method params in webview\_flutter + +## 0.3.6 + +* Add an optional `headers` field to the controller. + +## 0.3.5+5 + +* Fixed error in documentation of `javascriptChannels`. + +## 0.3.5+4 + +* Fix bugs in the example app by updating it to use a `StatefulWidget`. + +## 0.3.5+3 + +* Make sure to post javascript channel messages from the platform thread. + +## 0.3.5+2 + +* Fix crash from `NavigationDelegate` on later versions of Android. + +## 0.3.5+1 + +* Fix a bug where updates to onPageFinished were ignored. + +## 0.3.5 + +* Added an onPageFinished callback. + +## 0.3.4 + +* Support specifying navigation delegates that can prevent navigations from being executed. + +## 0.3.3+2 + +* Exclude LongPress handler from semantics tree since it does nothing. + +## 0.3.3+1 + +* Fixed a memory leak on Android - the WebView was not properly disposed. + +## 0.3.3 + +* Add clearCache method to WebView controller. + +## 0.3.2+1 + +* Log a more detailed warning at build time about the previous AndroidX + migration. + +## 0.3.2 + +* Added CookieManager to interface with WebView cookies. Currently has the ability to clear cookies. + +## 0.3.1 + +* Added JavaScript channels to facilitate message passing from JavaScript code running inside + the WebView to the Flutter app's Dart code. + +## 0.3.0 + +* **Breaking change**. Migrate from the deprecated original Android Support + Library to AndroidX. This shouldn't result in any functional changes, but it + requires any Android apps using this plugin to [also + migrate](https://developer.android.com/jetpack/androidx/migrate) if they're + using the original support library. + +## 0.2.0 + +* Added a evaluateJavascript method to WebView controller. +* (BREAKING CHANGE) Renamed the `JavaScriptMode` enum to `JavascriptMode`, and the WebView `javasScriptMode` parameter to `javascriptMode`. + +## 0.1.2 + +* Added a reload method to the WebView controller. + +## 0.1.1 + +* Added a `currentUrl` accessor for the WebView controller to look up what URL + is being displayed. + +## 0.1.0+1 + +* Fix null crash when initialUrl is unset on iOS. + +## 0.1.0 + +* Add goBack, goForward, canGoBack, and canGoForward methods to the WebView controller. + +## 0.0.1+1 + +* Fix case for "FLTWebViewFlutterPlugin" (iOS was failing to buld on case-sensitive file systems). + +## 0.0.1 + +* Initial release. diff --git a/local_packages/webview_flutter-4.13.0/LICENSE b/local_packages/webview_flutter-4.13.0/LICENSE new file mode 100644 index 0000000..c6823b8 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/LICENSE @@ -0,0 +1,25 @@ +Copyright 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/local_packages/webview_flutter-4.13.0/README.md b/local_packages/webview_flutter-4.13.0/README.md new file mode 100644 index 0000000..eaab720 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/README.md @@ -0,0 +1,213 @@ +# WebView for Flutter + + + +[![pub package](https://img.shields.io/pub/v/webview_flutter.svg)](https://pub.dev/packages/webview_flutter) + +A Flutter plugin that provides a WebView widget. + +On iOS the WebView widget is backed by a [WKWebView](https://developer.apple.com/documentation/webkit/wkwebview). +On Android the WebView widget is backed by a [WebView](https://developer.android.com/reference/android/webkit/WebView). + +| | Android | iOS | macOS | +|-------------|---------|-------|--------| +| **Support** | SDK 21+ | 12.0+ | 10.14+ | + +## Usage + +You can now display a WebView by: + +1. Instantiating a [WebViewController](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController-class.html). + + +```dart +controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate( + onProgress: (int progress) { + // Update loading bar. + }, + onPageStarted: (String url) {}, + onPageFinished: (String url) {}, + onHttpError: (HttpResponseError error) {}, + onWebResourceError: (WebResourceError error) {}, + onNavigationRequest: (NavigationRequest request) { + if (request.url.startsWith('https://www.youtube.com/')) { + return NavigationDecision.prevent; + } + return NavigationDecision.navigate; + }, + ), + ) + ..loadRequest(Uri.parse('https://flutter.dev')); +``` + +2. Passing the controller to a [WebViewWidget](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewWidget-class.html). + + +```dart +@override +Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Flutter Simple Example')), + body: WebViewWidget(controller: controller), + ); +} +``` + +See the Dartdocs for [WebViewController](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController-class.html) +and [WebViewWidget](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewWidget-class.html) +for more details. + +### Platform-Specific Features + +Many classes have a subclass or an underlying implementation that provides access to platform-specific +features. + +To access platform-specific features, start by adding the platform implementation packages to your +app or package: + +* **Android**: [webview_flutter_android](https://pub.dev/packages/webview_flutter_android/install) +* **iOS/macOS**: [webview_flutter_wkwebview](https://pub.dev/packages/webview_flutter_wkwebview/install) + +Next, add the imports of the implementation packages to your app or package: + + +```dart +// Import for Android features. +import 'package:webview_flutter_android/webview_flutter_android.dart'; +// Import for iOS/macOS features. +import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; +``` + +Now, additional features can be accessed through the platform implementations. Classes +[WebViewController], [WebViewWidget], [NavigationDelegate], and [WebViewCookieManager] pass their +functionality to a class provided by the current platform. Below are a couple of ways to access +additional functionality provided by the platform and is followed by an example. + +1. Pass a creation params class provided by a platform implementation to a `fromPlatformCreationParams` + constructor (e.g. `WebViewController.fromPlatformCreationParams`, + `WebViewWidget.fromPlatformCreationParams`, etc.). +2. Call methods on a platform implementation of a class by using the `platform` field (e.g. + `WebViewController.platform`, `WebViewWidget.platform`, etc.). + +Below is an example of setting additional iOS/macOS and Android parameters on the `WebViewController`. + + +```dart +late final PlatformWebViewControllerCreationParams params; +if (WebViewPlatform.instance is WebKitWebViewPlatform) { + params = WebKitWebViewControllerCreationParams( + allowsInlineMediaPlayback: true, + mediaTypesRequiringUserAction: const {}, + ); +} else { + params = const PlatformWebViewControllerCreationParams(); +} + +final WebViewController controller = + WebViewController.fromPlatformCreationParams(params); +// ··· +if (controller.platform is AndroidWebViewController) { + AndroidWebViewController.enableDebugging(true); + (controller.platform as AndroidWebViewController) + .setMediaPlaybackRequiresUserGesture(false); +} +``` + +See https://pub.dev/documentation/webview_flutter_android/latest/webview_flutter_android/webview_flutter_android-library.html +for more details on Android features. + +See https://pub.dev/documentation/webview_flutter_wkwebview/latest/webview_flutter_wkwebview/webview_flutter_wkwebview-library.html +for more details on iOS/macOS features. + +### Enable Material Components for Android + +To use Material Components when the user interacts with input elements in the WebView, +follow the steps described in the [Enabling Material Components instructions](https://docs.flutter.dev/deployment/android#enable-material-components). + +### Setting custom headers on POST requests + +Currently, setting custom headers when making a post request with the WebViewController's `loadRequest` method is not supported on Android. +If you require this functionality, a workaround is to make the request manually, and then load the response data using `loadHtmlString` instead. + +## Migrating from 3.0 to 4.0 + +### Instantiating WebViewController + +In version 3.0 and below, `WebViewController` could only be retrieved in a callback after the +`WebView` was added to the widget tree. Now, `WebViewController` must be instantiated and can be +used before it is added to the widget tree. See `Usage` section above for an example. + +### Replacing WebView Functionality + +The `WebView` class has been removed and its functionality has been split into `WebViewController` +and `WebViewWidget`. + +`WebViewController` handles all functionality that is associated with the underlying web view +provided by each platform. (e.g., loading a url, setting the background color of the underlying +platform view, or clearing the cache). + +`WebViewWidget` takes a `WebViewController` and handles all Flutter widget related functionality +(e.g., layout direction, gesture recognizers). + +See the Dartdocs for [WebViewController](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController-class.html) +and [WebViewWidget](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewWidget-class.html) +for more details. + +### PlatformView Implementation on Android + +The PlatformView implementation for Android uses Texture Layer Hybrid Composition on versions 23+ +and automatically fallbacks to Hybrid Composition for version 21-23. See section +`Platform-Specific Features` and [AndroidWebViewWidgetCreationParams.displayWithHybridComposition](https://pub.dev/documentation/webview_flutter_android/latest/webview_flutter_android/AndroidWebViewWidgetCreationParams/displayWithHybridComposition.html) +to manually switch to Hybrid Composition on versions 23+. + +### API Changes + +Below is a non-exhaustive list of changes to the API: + +* `WebViewController.clearCache` no longer clears local storage. Please use + `WebViewController.clearLocalStorage`. +* `WebViewController.clearCache` no longer reloads the page. +* `WebViewController.loadUrl` has been removed. Please use `WebViewController.loadRequest`. +* `WebViewController.evaluateJavascript` has been removed. Please use + `WebViewController.runJavaScript` or `WebViewController.runJavaScriptReturningResult`. +* `WebViewController.getScrollX` and `WebViewController.getScrollY` have been removed and have + been replaced by `WebViewController.getScrollPosition`. +* `WebViewController.runJavaScriptReturningResult` now returns an `Object` and not a `String`. This + will attempt to return a `bool` or `num` if the return value can be parsed. +* `WebView.initialCookies` has been removed. Use `WebViewCookieManager.setCookie` before calling + `WebViewController.loadRequest`. +* `CookieManager` is replaced by `WebViewCookieManager`. +* `NavigationDelegate.onWebResourceError` callback includes errors that are not from the main frame. + Use the `WebResourceError.isForMainFrame` field to filter errors. +* The following fields from `WebView` have been moved to `NavigationDelegate`. They can be added to + a WebView with `WebViewController.setNavigationDelegate`. + * `WebView.navigationDelegate` -> `NavigationDelegate.onNavigationRequest` + * `WebView.onPageStarted` -> `NavigationDelegate.onPageStarted` + * `WebView.onPageFinished` -> `NavigationDelegate.onPageFinished` + * `WebView.onProgress` -> `NavigationDelegate.onProgress` + * `WebView.onWebResourceError` -> `NavigationDelegate.onWebResourceError` +* The following fields from `WebView` have been moved to `WebViewController`: + * `WebView.javascriptMode` -> `WebViewController.setJavaScriptMode` + * `WebView.javascriptChannels` -> + `WebViewController.addJavaScriptChannel`/`WebViewController.removeJavaScriptChannel` + * `WebView.zoomEnabled` -> `WebViewController.enableZoom` + * `WebView.userAgent` -> `WebViewController.setUserAgent` + * `WebView.backgroundColor` -> `WebViewController.setBackgroundColor` +* The following features have been moved to an Android implementation class. See section + `Platform-Specific Features` for details on accessing Android platform-specific features. + * `WebView.debuggingEnabled` -> `static AndroidWebViewController.enableDebugging` + * `WebView.initialMediaPlaybackPolicy` -> `AndroidWebViewController.setMediaPlaybackRequiresUserGesture` +* The following features have been moved to an iOS implementation class. See section + `Platform-Specific Features` for details on accessing iOS platform-specific features. + * `WebView.gestureNavigationEnabled` -> `WebKitWebViewController.setAllowsBackForwardNavigationGestures` + * `WebView.initialMediaPlaybackPolicy` -> `WebKitWebViewControllerCreationParams.mediaTypesRequiringUserAction` + * `WebView.allowsInlineMediaPlayback` -> `WebKitWebViewControllerCreationParams.allowsInlineMediaPlayback` + + +[WebViewController]: https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController-class.html +[WebViewWidget]: https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewWidget-class.html +[NavigationDelegate]: https://pub.dev/documentation/webview_flutter/latest/webview_flutter/NavigationDelegate-class.html +[WebViewCookieManager]: https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewCookieManager-class.html diff --git a/local_packages/webview_flutter-4.13.0/example/README.md b/local_packages/webview_flutter-4.13.0/example/README.md new file mode 100644 index 0000000..e5bd6e2 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/README.md @@ -0,0 +1,3 @@ +# webview_flutter_example + +Demonstrates how to use the webview_flutter plugin. diff --git a/local_packages/webview_flutter-4.13.0/example/android/app/build.gradle b/local_packages/webview_flutter-4.13.0/example/android/app/build.gradle new file mode 100644 index 0000000..471f408 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/android/app/build.gradle @@ -0,0 +1,61 @@ +plugins { + id "com.android.application" + id "org.jetbrains.kotlin.android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + namespace 'io.flutter.plugins.webviewflutterexample' + compileSdk = flutter.compileSdkVersion + + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "io.flutter.plugins.webviewflutterexample" + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } + lint { + disable 'InvalidPackage' + } +} + +flutter { + source '../..' +} + +dependencies { + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + api 'androidx.test:core:1.4.0' +} diff --git a/local_packages/webview_flutter-4.13.0/example/android/app/gradle/wrapper/gradle-wrapper.properties b/local_packages/webview_flutter-4.13.0/example/android/app/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..d951fac --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/android/app/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/local_packages/webview_flutter-4.13.0/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/local_packages/webview_flutter-4.13.0/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java new file mode 100644 index 0000000..21923b5 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -0,0 +1,21 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/* + * Annotation to aid repository tooling in determining if a test is + * a native java unit test or a java class with a dart integration. + * + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests + * for more infomation. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface DartIntegrationTest {} diff --git a/local_packages/webview_flutter-4.13.0/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/MainActivityTest.java b/local_packages/webview_flutter-4.13.0/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/MainActivityTest.java new file mode 100644 index 0000000..a32aaeb --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/MainActivityTest.java @@ -0,0 +1,19 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutterexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.integration_test.FlutterTestRunner; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.plugins.DartIntegrationTest; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@DartIntegrationTest +@RunWith(FlutterTestRunner.class) +public class MainActivityTest { + @Rule + public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); +} diff --git a/local_packages/webview_flutter-4.13.0/example/android/app/src/debug/AndroidManifest.xml b/local_packages/webview_flutter-4.13.0/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..2879220 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/local_packages/webview_flutter-4.13.0/example/android/app/src/main/AndroidManifest.xml b/local_packages/webview_flutter-4.13.0/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..484b588 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + diff --git a/local_packages/webview_flutter-4.13.0/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/WebViewTestActivity.java b/local_packages/webview_flutter-4.13.0/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/WebViewTestActivity.java new file mode 100644 index 0000000..cb53a7a --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/WebViewTestActivity.java @@ -0,0 +1,20 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutterexample; + +import androidx.annotation.NonNull; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; + +// Extends FlutterActivity to make the FlutterEngine accessible for testing. +public class WebViewTestActivity extends FlutterActivity { + public FlutterEngine engine; + + @Override + public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { + super.configureFlutterEngine(flutterEngine); + engine = flutterEngine; + } +} diff --git a/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/drawable/launch_background.xml b/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/values/styles.xml b/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..00fa441 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/local_packages/webview_flutter-4.13.0/example/android/build.gradle b/local_packages/webview_flutter-4.13.0/example/android/build.gradle new file mode 100644 index 0000000..b9db570 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/android/build.gradle @@ -0,0 +1,24 @@ +allprojects { + repositories { + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. + def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' + if (System.getenv().containsKey(artifactRepoKey)) { + println "Using artifact hub" + maven { url System.getenv(artifactRepoKey) } + } + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/local_packages/webview_flutter-4.13.0/example/android/gradle.properties b/local_packages/webview_flutter-4.13.0/example/android/gradle.properties new file mode 100644 index 0000000..598d13f --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G +android.useAndroidX=true +android.enableJetifier=true diff --git a/local_packages/webview_flutter-4.13.0/example/android/gradle/wrapper/gradle-wrapper.properties b/local_packages/webview_flutter-4.13.0/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..7aeeb11 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip diff --git a/local_packages/webview_flutter-4.13.0/example/android/settings.gradle b/local_packages/webview_flutter-4.13.0/example/android/settings.gradle new file mode 100644 index 0000000..0da20da --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/android/settings.gradle @@ -0,0 +1,27 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.5.2" apply false + id "org.jetbrains.kotlin.android" version "2.0.21" apply false + id "com.google.cloud.artifactregistry.gradle-plugin" version "2.2.1" +} + +include ":app" diff --git a/local_packages/webview_flutter-4.13.0/example/assets/sample_audio.ogg b/local_packages/webview_flutter-4.13.0/example/assets/sample_audio.ogg new file mode 100644 index 0000000..27e1710 Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/assets/sample_audio.ogg differ diff --git a/local_packages/webview_flutter-4.13.0/example/assets/sample_video.mp4 b/local_packages/webview_flutter-4.13.0/example/assets/sample_video.mp4 new file mode 100644 index 0000000..a203d0c Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/assets/sample_video.mp4 differ diff --git a/local_packages/webview_flutter-4.13.0/example/assets/www/index.html b/local_packages/webview_flutter-4.13.0/example/assets/www/index.html new file mode 100644 index 0000000..9895dd3 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/assets/www/index.html @@ -0,0 +1,20 @@ + + + + +Load file or HTML string example + + + + +

Local demo page

+

+ This is an example page used to demonstrate how to load a local file or HTML + string using the Flutter + webview plugin. +

+ + + \ No newline at end of file diff --git a/local_packages/webview_flutter-4.13.0/example/assets/www/styles/style.css b/local_packages/webview_flutter-4.13.0/example/assets/www/styles/style.css new file mode 100644 index 0000000..c2140b8 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/assets/www/styles/style.css @@ -0,0 +1,3 @@ +h1 { + color: blue; +} \ No newline at end of file diff --git a/local_packages/webview_flutter-4.13.0/example/integration_test/webview_flutter_test.dart b/local_packages/webview_flutter-4.13.0/example/integration_test/webview_flutter_test.dart new file mode 100644 index 0000000..6307f31 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/integration_test/webview_flutter_test.dart @@ -0,0 +1,1063 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This test is run using `flutter drive` by the CI (see /script/tool/README.md +// in this repository for details on driving that tooling manually), but can +// also be run using `flutter test` directly during development. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter_android/webview_flutter_android.dart'; +import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; + +Future main() async { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + final HttpServer server = + await HttpServer.bind(InternetAddress.loopbackIPv4, 0); + unawaited(server.forEach((HttpRequest request) { + if (request.uri.path == '/hello.txt') { + request.response.writeln('Hello, world.'); + } else if (request.uri.path == '/secondary.txt') { + request.response.writeln('How are you today?'); + } else if (request.uri.path == '/headers') { + request.response.writeln('${request.headers}'); + } else if (request.uri.path == '/favicon.ico') { + request.response.statusCode = HttpStatus.notFound; + } else if (request.uri.path == '/http-basic-authentication') { + final List? authHeader = + request.headers[HttpHeaders.authorizationHeader]; + if (authHeader != null) { + final String encodedCredential = authHeader.first.split(' ')[1]; + final String credential = + String.fromCharCodes(base64Decode(encodedCredential)); + if (credential == 'user:password') { + request.response.writeln('Authorized'); + } else { + request.response.headers.add( + HttpHeaders.wwwAuthenticateHeader, 'Basic realm="Test realm"'); + request.response.statusCode = HttpStatus.unauthorized; + } + } else { + request.response.headers + .add(HttpHeaders.wwwAuthenticateHeader, 'Basic realm="Test realm"'); + request.response.statusCode = HttpStatus.unauthorized; + } + } else { + fail('unexpected request: ${request.method} ${request.uri}'); + } + request.response.close(); + })); + final String prefixUrl = 'http://${server.address.address}:${server.port}'; + final String primaryUrl = '$prefixUrl/hello.txt'; + final String secondaryUrl = '$prefixUrl/secondary.txt'; + final String headersUrl = '$prefixUrl/headers'; + final String basicAuthUrl = '$prefixUrl/http-basic-authentication'; + + testWidgets('loadRequest', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + + final WebViewController controller = WebViewController(); + await controller.setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageFinished.complete()), + ); + await controller.loadRequest(Uri.parse(primaryUrl)); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + await pageFinished.future; + + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, primaryUrl); + }); + + testWidgets('runJavaScriptReturningResult', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + + final WebViewController controller = WebViewController(); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageFinished.complete()), + ); + await controller.loadRequest(Uri.parse(primaryUrl)); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await pageFinished.future; + + await expectLater( + controller.runJavaScriptReturningResult('1 + 1'), + completion(2), + ); + }); + + testWidgets('loadRequest with headers', (WidgetTester tester) async { + final Map headers = { + 'test_header': 'flutter_test_header' + }; + + final StreamController pageLoads = StreamController(); + + final WebViewController controller = WebViewController(); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate( + NavigationDelegate(onPageFinished: (String url) => pageLoads.add(url)), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await controller.loadRequest(Uri.parse(headersUrl), headers: headers); + + await pageLoads.stream.firstWhere((String url) => url == headersUrl); + + final String content = await controller.runJavaScriptReturningResult( + 'document.documentElement.innerText', + ) as String; + expect(content.contains('flutter_test_header'), isTrue); + }); + + testWidgets('JavascriptChannel', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + final WebViewController controller = WebViewController(); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageFinished.complete()), + ); + + final Completer channelCompleter = Completer(); + await controller.addJavaScriptChannel( + 'Echo', + onMessageReceived: (JavaScriptMessage message) { + channelCompleter.complete(message.message); + }, + ); + + await controller.loadHtmlString( + 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await pageFinished.future; + + await controller.runJavaScript('Echo.postMessage("hello");'); + await expectLater(channelCompleter.future, completion('hello')); + }); + + testWidgets('resize webview', (WidgetTester tester) async { + final Completer initialResizeCompleter = Completer(); + final Completer buttonTapResizeCompleter = Completer(); + final Completer onPageFinished = Completer(); + + bool resizeButtonTapped = false; + await tester.pumpWidget(ResizableWebView( + onResize: () { + if (resizeButtonTapped) { + buttonTapResizeCompleter.complete(); + } else { + initialResizeCompleter.complete(); + } + }, + onPageFinished: () => onPageFinished.complete(), + )); + + await onPageFinished.future; + // Wait for a potential call to resize after page is loaded. + await initialResizeCompleter.future.timeout( + const Duration(seconds: 3), + onTimeout: () => null, + ); + + resizeButtonTapped = true; + + await tester.tap(find.byKey(const ValueKey('resizeButton'))); + await tester.pumpAndSettle(); + + await expectLater(buttonTapResizeCompleter.future, completes); + }); + + testWidgets('set custom userAgent', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + + final WebViewController controller = WebViewController(); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageFinished.complete(), + )); + await controller.setUserAgent('Custom_User_Agent1'); + await controller.loadRequest(Uri.parse('about:blank')); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await pageFinished.future; + + final String? customUserAgent = await controller.getUserAgent(); + expect(customUserAgent, 'Custom_User_Agent1'); + }); + + group('Video playback policy', () { + testWidgets('Auto media playback', (WidgetTester tester) async { + final String videoTestBase64 = await getTestVideoBase64(); + Completer pageLoaded = Completer(); + + late PlatformWebViewControllerCreationParams params; + if (defaultTargetPlatform == TargetPlatform.iOS) { + params = WebKitWebViewControllerCreationParams( + mediaTypesRequiringUserAction: const {}, + ); + } else { + params = const PlatformWebViewControllerCreationParams(); + } + + WebViewController controller = + WebViewController.fromPlatformCreationParams(params); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + + await controller.setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), + ); + + if (controller.platform is AndroidWebViewController) { + await (controller.platform as AndroidWebViewController) + .setMediaPlaybackRequiresUserGesture(false); + } + + await controller.loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,$videoTestBase64'), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await tester.pumpAndSettle(); + + await pageLoaded.future; + + bool isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + expect(isPaused, false); + + pageLoaded = Completer(); + controller = WebViewController(); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + + await controller.setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), + ); + + await controller.loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,$videoTestBase64'), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await tester.pumpAndSettle(); + + await pageLoaded.future; + + isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + expect(isPaused, true); + }); + + testWidgets('Video plays inline', (WidgetTester tester) async { + final String videoTestBase64 = await getTestVideoBase64(); + final Completer pageLoaded = Completer(); + final Completer videoPlaying = Completer(); + + late PlatformWebViewControllerCreationParams params; + if (defaultTargetPlatform == TargetPlatform.iOS) { + params = WebKitWebViewControllerCreationParams( + mediaTypesRequiringUserAction: const {}, + allowsInlineMediaPlayback: true, + ); + } else { + params = const PlatformWebViewControllerCreationParams(); + } + final WebViewController controller = + WebViewController.fromPlatformCreationParams(params); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + + await controller.setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), + ); + + await controller.addJavaScriptChannel( + 'VideoTestTime', + onMessageReceived: (JavaScriptMessage message) { + final double currentTime = double.parse(message.message); + // Let it play for at least 1 second to make sure the related video's properties are set. + if (currentTime > 1 && !videoPlaying.isCompleted) { + videoPlaying.complete(null); + } + }, + ); + + if (controller.platform is AndroidWebViewController) { + await (controller.platform as AndroidWebViewController) + .setMediaPlaybackRequiresUserGesture(false); + } + + await controller.loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,$videoTestBase64'), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + await tester.pumpAndSettle(); + + await pageLoaded.future; + + // Makes sure we get the correct event that indicates the video is actually playing. + await videoPlaying.future; + + final bool fullScreen = await controller + .runJavaScriptReturningResult('isFullScreen();') as bool; + expect(fullScreen, false); + }); + }, + // TODO(bparrishMines): Stop skipping once https://github.com/flutter/flutter/issues/148487 is resolved + skip: true); + + group('Audio playback policy', () { + late String audioTestBase64; + setUpAll(() async { + final ByteData audioData = + await rootBundle.load('assets/sample_audio.ogg'); + final String base64AudioData = + base64Encode(Uint8List.view(audioData.buffer)); + final String audioTest = ''' + + Audio auto play + + + + + + + '''; + audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest)); + }); + + testWidgets('Auto media playback', (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + late PlatformWebViewControllerCreationParams params; + if (defaultTargetPlatform == TargetPlatform.iOS) { + params = WebKitWebViewControllerCreationParams( + mediaTypesRequiringUserAction: const {}, + ); + } else { + params = const PlatformWebViewControllerCreationParams(); + } + + WebViewController controller = + WebViewController.fromPlatformCreationParams(params); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), + ); + + if (controller.platform is AndroidWebViewController) { + await (controller.platform as AndroidWebViewController) + .setMediaPlaybackRequiresUserGesture(false); + } + + await controller.loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,$audioTestBase64'), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + await tester.pumpAndSettle(); + + await pageLoaded.future; + + bool isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + expect(isPaused, false); + + pageLoaded = Completer(); + controller = WebViewController(); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate( + NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), + ); + + await controller.loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,$audioTestBase64'), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await tester.pumpAndSettle(); + await pageLoaded.future; + + isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + expect(isPaused, true); + }); + }, + // OGG playback is not supported on macOS, so the test data would need + // to be changed to support macOS. + skip: Platform.isMacOS); + + testWidgets('getTitle', (WidgetTester tester) async { + const String getTitleTest = ''' + + Some title + + + + + '''; + final String getTitleTestBase64 = + base64Encode(const Utf8Encoder().convert(getTitleTest)); + final Completer pageLoaded = Completer(); + + final WebViewController controller = WebViewController(); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + )); + await controller.loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,$getTitleTestBase64'), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await pageLoaded.future; + + // On at least iOS, it does not appear to be guaranteed that the native + // code has the title when the page load completes. Execute some JavaScript + // before checking the title to ensure that the page has been fully parsed + // and processed. + await controller.runJavaScript('1;'); + + final String? title = await controller.getTitle(); + expect(title, 'Some title'); + }); + + group('Programmatic Scroll', () { + testWidgets('setAndGetScrollPosition', (WidgetTester tester) async { + const String scrollTestPage = ''' + + + + + + +
+ + + '''; + + final String scrollTestPageBase64 = + base64Encode(const Utf8Encoder().convert(scrollTestPage)); + + final Completer pageLoaded = Completer(); + final WebViewController controller = WebViewController(); + ScrollPositionChange? recordedPosition; + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + )); + await controller.setOnScrollPositionChange( + (ScrollPositionChange contentOffsetChange) { + recordedPosition = contentOffsetChange; + }); + + await controller.loadRequest(Uri.parse( + 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', + )); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await pageLoaded.future; + + await tester.pumpAndSettle(const Duration(seconds: 3)); + + Offset scrollPos = await controller.getScrollPosition(); + + // Check scrollTo() + const int X_SCROLL = 123; + const int Y_SCROLL = 321; + // Get the initial position; this ensures that scrollTo is actually + // changing something, but also gives the native view's scroll position + // time to settle. + expect(scrollPos.dx, isNot(X_SCROLL)); + expect(scrollPos.dy, isNot(Y_SCROLL)); + expect(recordedPosition?.x, isNot(X_SCROLL)); + expect(recordedPosition?.y, isNot(Y_SCROLL)); + + await controller.scrollTo(X_SCROLL, Y_SCROLL); + scrollPos = await controller.getScrollPosition(); + expect(scrollPos.dx, X_SCROLL); + expect(scrollPos.dy, Y_SCROLL); + expect(recordedPosition?.x, X_SCROLL); + expect(recordedPosition?.y, Y_SCROLL); + + // Check scrollBy() (on top of scrollTo()) + await controller.scrollBy(X_SCROLL, Y_SCROLL); + scrollPos = await controller.getScrollPosition(); + expect(scrollPos.dx, X_SCROLL * 2); + expect(scrollPos.dy, Y_SCROLL * 2); + expect(recordedPosition?.x, X_SCROLL * 2); + expect(recordedPosition?.y, Y_SCROLL * 2); + }); + }, + // Scroll position is currently not implemented for macOS. + // Flakes on iOS: https://github.com/flutter/flutter/issues/154826 + skip: Platform.isMacOS || Platform.isIOS); + + group('NavigationDelegate', () { + const String blankPage = ''; + final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' + '${base64Encode(const Utf8Encoder().convert(blankPage))}'; + + testWidgets('can allow requests', (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + final WebViewController controller = WebViewController(); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + onNavigationRequest: (NavigationRequest navigationRequest) { + return navigationRequest.url.contains('youtube.com') + ? NavigationDecision.prevent + : NavigationDecision.navigate; + }, + )); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await controller.loadRequest(Uri.parse(blankPageEncoded)); + + await pageLoaded.future; // Wait for initial page load. + + pageLoaded = Completer(); + await controller.runJavaScript('location.href = "$secondaryUrl"'); + await pageLoaded.future; // Wait for the next page load. + + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, secondaryUrl); + }); + + testWidgets('onWebResourceError', (WidgetTester tester) async { + final Completer errorCompleter = + Completer(); + + final WebViewController controller = WebViewController(); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate( + NavigationDelegate(onWebResourceError: (WebResourceError error) { + errorCompleter.complete(error); + })); + await controller.loadRequest(Uri.parse('https://www.notawebsite..com')); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + final WebResourceError error = await errorCompleter.future; + expect(error, isNotNull); + }); + + testWidgets('onWebResourceError is not called with valid url', + (WidgetTester tester) async { + final Completer errorCompleter = + Completer(); + final Completer pageFinishCompleter = Completer(); + + final WebViewController controller = WebViewController(); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageFinishCompleter.complete(), + onWebResourceError: (WebResourceError error) { + errorCompleter.complete(error); + }, + )); + await controller.loadRequest( + Uri.parse('data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+'), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + expect(errorCompleter.future, doesNotComplete); + await pageFinishCompleter.future; + }); + + testWidgets('can block requests', (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + final WebViewController controller = WebViewController(); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + onNavigationRequest: (NavigationRequest navigationRequest) { + return navigationRequest.url.contains('youtube.com') + ? NavigationDecision.prevent + : NavigationDecision.navigate; + })); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await controller.loadRequest(Uri.parse(blankPageEncoded)); + + await pageLoaded.future; // Wait for initial page load. + + pageLoaded = Completer(); + await controller + .runJavaScript('location.href = "https://www.youtube.com/"'); + + // There should never be any second page load, since our new URL is + // blocked. Still wait for a potential page change for some time in order + // to give the test a chance to fail. + await pageLoaded.future + .timeout(const Duration(milliseconds: 500), onTimeout: () => ''); + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, isNot(contains('youtube.com'))); + }); + + testWidgets('onHttpError', (WidgetTester tester) async { + final Completer errorCompleter = + Completer(); + + final WebViewController controller = WebViewController(); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + + final NavigationDelegate delegate = NavigationDelegate( + onHttpError: (HttpResponseError error) { + errorCompleter.complete(error); + }, + ); + unawaited(controller.setNavigationDelegate(delegate)); + + unawaited(controller.loadRequest( + Uri.parse('$prefixUrl/favicon.ico'), + )); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + final HttpResponseError error = await errorCompleter.future; + + expect(error, isNotNull); + expect(error.response?.statusCode, 404); + }); + + testWidgets('onHttpError is not called when no HTTP error is received', + (WidgetTester tester) async { + const String testPage = ''' + + + + + + '''; + + final Completer errorCompleter = + Completer(); + final Completer pageFinishCompleter = Completer(); + + final WebViewController controller = WebViewController(); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + + final NavigationDelegate delegate = NavigationDelegate( + onPageFinished: pageFinishCompleter.complete, + onHttpError: (HttpResponseError error) { + errorCompleter.complete(error); + }, + ); + unawaited(controller.setNavigationDelegate(delegate)); + + unawaited(controller.loadHtmlString(testPage)); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + expect(errorCompleter.future, doesNotComplete); + await pageFinishCompleter.future; + }); + + testWidgets('supports asynchronous decisions', (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + final WebViewController controller = WebViewController(); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + onNavigationRequest: (NavigationRequest navigationRequest) async { + NavigationDecision decision = NavigationDecision.prevent; + decision = await Future.delayed( + const Duration(milliseconds: 10), + () => NavigationDecision.navigate); + return decision; + })); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await controller.loadRequest(Uri.parse(blankPageEncoded)); + + await pageLoaded.future; // Wait for initial page load. + + pageLoaded = Completer(); + await controller.runJavaScript('location.href = "$secondaryUrl"'); + await pageLoaded.future; // Wait for second page to load. + + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, secondaryUrl); + }); + + testWidgets('can receive url changes', (WidgetTester tester) async { + final Completer pageLoaded = Completer(); + + final WebViewController controller = WebViewController(); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + )); + await controller.loadRequest(Uri.parse(blankPageEncoded)); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await pageLoaded.future; + + final Completer urlChangeCompleter = Completer(); + await controller.setNavigationDelegate(NavigationDelegate( + onUrlChange: (UrlChange change) { + urlChangeCompleter.complete(change.url); + }, + )); + + await controller.runJavaScript('location.href = "$primaryUrl"'); + + await expectLater(urlChangeCompleter.future, completion(primaryUrl)); + }); + + testWidgets('can receive updates to history state', + (WidgetTester tester) async { + final Completer pageLoaded = Completer(); + + final NavigationDelegate navigationDelegate = NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + ); + + final WebViewController controller = WebViewController(); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(navigationDelegate); + await controller.loadRequest(Uri.parse(primaryUrl)); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await pageLoaded.future; + + final Completer urlChangeCompleter = Completer(); + await controller.setNavigationDelegate(NavigationDelegate( + onUrlChange: (UrlChange change) { + urlChangeCompleter.complete(change.url); + }, + )); + + await controller.runJavaScript( + 'window.history.pushState({}, "", "secondary.txt");', + ); + + await expectLater(urlChangeCompleter.future, completion(secondaryUrl)); + }); + + testWidgets('can receive HTTP basic auth requests', + (WidgetTester tester) async { + final Completer authRequested = Completer(); + final WebViewController controller = WebViewController(); + + await controller.setNavigationDelegate( + NavigationDelegate( + onHttpAuthRequest: (HttpAuthRequest request) => + authRequested.complete(), + ), + ); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await controller.loadRequest(Uri.parse(basicAuthUrl)); + + await expectLater(authRequested.future, completes); + }); + + testWidgets('can authenticate to HTTP basic auth requests', + (WidgetTester tester) async { + final WebViewController controller = WebViewController(); + final Completer pageFinished = Completer(); + + await controller.setNavigationDelegate(NavigationDelegate( + onHttpAuthRequest: (HttpAuthRequest request) => request.onProceed( + const WebViewCredential( + user: 'user', + password: 'password', + ), + ), + onPageFinished: (_) => pageFinished.complete(), + onWebResourceError: (_) => fail('Authentication failed'), + )); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await controller.loadRequest(Uri.parse(basicAuthUrl)); + + await expectLater(pageFinished.future, completes); + }); + }); + + testWidgets('target _blank opens in same window', + (WidgetTester tester) async { + final Completer pageLoaded = Completer(); + + final WebViewController controller = WebViewController(); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + )); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await controller.runJavaScript('window.open("$primaryUrl", "_blank")'); + await pageLoaded.future; + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, primaryUrl); + }); + + testWidgets( + 'can open new window and go back', + (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + final WebViewController controller = WebViewController(); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoaded.complete(), + )); + await controller.loadRequest(Uri.parse(primaryUrl)); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + expect(controller.currentUrl(), completion(primaryUrl)); + await pageLoaded.future; + pageLoaded = Completer(); + + await controller.runJavaScript('window.open("$secondaryUrl")'); + await pageLoaded.future; + pageLoaded = Completer(); + expect(controller.currentUrl(), completion(secondaryUrl)); + + expect(controller.canGoBack(), completion(true)); + await controller.goBack(); + await pageLoaded.future; + await expectLater(controller.currentUrl(), completion(primaryUrl)); + }, + ); + + testWidgets( + 'clearLocalStorage', + (WidgetTester tester) async { + Completer pageLoadCompleter = Completer(); + + final WebViewController controller = WebViewController(); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => pageLoadCompleter.complete(), + )); + await controller.loadRequest(Uri.parse(primaryUrl)); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + await pageLoadCompleter.future; + pageLoadCompleter = Completer(); + + await controller.runJavaScript('localStorage.setItem("myCat", "Tom");'); + final String myCatItem = await controller.runJavaScriptReturningResult( + 'localStorage.getItem("myCat");', + ) as String; + expect(myCatItem, _webViewString('Tom')); + + await controller.clearLocalStorage(); + + // Reload page to have changes take effect. + await controller.reload(); + await pageLoadCompleter.future; + + late final String? nullItem; + try { + nullItem = await controller.runJavaScriptReturningResult( + 'localStorage.getItem("myCat");', + ) as String; + } catch (exception) { + if (_isWKWebView() && + exception is ArgumentError && + (exception.message as String).contains( + 'Result of JavaScript execution returned a `null` value.')) { + nullItem = ''; + } + } + expect(nullItem, _webViewNull()); + }, + ); +} + +// JavaScript `null` evaluate to different string values per platform. +// This utility method returns the string boolean value of the current platform. +String _webViewNull() { + if (_isWKWebView()) { + return ''; + } + return 'null'; +} + +// JavaScript String evaluates to different strings depending on the platform. +// This utility method returns the string boolean value of the current platform. +String _webViewString(String value) { + if (_isWKWebView()) { + return value; + } + return '"$value"'; +} + +bool _isWKWebView() { + return defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform == TargetPlatform.macOS; +} + +class ResizableWebView extends StatefulWidget { + const ResizableWebView({ + super.key, + required this.onResize, + required this.onPageFinished, + }); + + final VoidCallback onResize; + final VoidCallback onPageFinished; + + @override + State createState() => ResizableWebViewState(); +} + +class ResizableWebViewState extends State { + late final WebViewController controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (_) => widget.onPageFinished(), + )) + ..addJavaScriptChannel( + 'Resize', + onMessageReceived: (_) { + widget.onResize(); + }, + ) + ..loadRequest( + Uri.parse( + 'data:text/html;charset=utf-8;base64,${base64Encode(const Utf8Encoder().convert(resizePage))}', + ), + ); + + double webViewWidth = 200; + double webViewHeight = 200; + + static const String resizePage = ''' + + Resize test + + + + + + '''; + + @override + Widget build(BuildContext context) { + return Directionality( + textDirection: TextDirection.ltr, + child: Column( + children: [ + SizedBox( + width: webViewWidth, + height: webViewHeight, + child: WebViewWidget(controller: controller)), + TextButton( + key: const Key('resizeButton'), + onPressed: () { + setState(() { + webViewWidth += 100.0; + webViewHeight += 100.0; + }); + }, + child: const Text('ResizeButton'), + ), + ], + ), + ); + } +} + +Future getTestVideoBase64() async { + final ByteData videoData = await rootBundle.load('assets/sample_video.mp4'); + final String base64VideoData = base64Encode(Uint8List.view(videoData.buffer)); + final String videoTest = ''' + + Video auto play + + + + + + + '''; + return base64Encode(const Utf8Encoder().convert(videoTest)); +} diff --git a/local_packages/webview_flutter-4.13.0/example/integration_test/webview_flutter_test_legacy.dart b/local_packages/webview_flutter-4.13.0/example/integration_test/webview_flutter_test_legacy.dart new file mode 100644 index 0000000..734fc10 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/integration_test/webview_flutter_test_legacy.dart @@ -0,0 +1,1340 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This test is run using `flutter drive` by the CI (see /script/tool/README.md +// in this repository for details on driving that tooling manually), but can +// also be run using `flutter test` directly during development. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:webview_flutter/src/webview_flutter_legacy.dart'; + +Future main() async { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + final HttpServer server = + await HttpServer.bind(InternetAddress.loopbackIPv4, 0); + unawaited(server.forEach((HttpRequest request) { + if (request.uri.path == '/hello.txt') { + request.response.writeln('Hello, world.'); + } else if (request.uri.path == '/secondary.txt') { + request.response.writeln('How are you today?'); + } else if (request.uri.path == '/headers') { + request.response.writeln('${request.headers}'); + } else if (request.uri.path == '/favicon.ico') { + request.response.statusCode = HttpStatus.notFound; + } else { + fail('unexpected request: ${request.method} ${request.uri}'); + } + request.response.close(); + })); + final String prefixUrl = 'http://${server.address.address}:${server.port}'; + final String primaryUrl = '$prefixUrl/hello.txt'; + final String secondaryUrl = '$prefixUrl/secondary.txt'; + final String headersUrl = '$prefixUrl/headers'; + + testWidgets('initialUrl', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final Completer pageFinishedCompleter = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + onPageFinished: pageFinishedCompleter.complete, + ), + ), + ); + + final WebViewController controller = await controllerCompleter.future; + await pageFinishedCompleter.future; + + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, primaryUrl); + }); + + testWidgets('loadUrl', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageLoads = StreamController(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + onPageFinished: (String url) { + pageLoads.add(url); + }, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + + await controller.loadUrl(secondaryUrl); + await expectLater( + pageLoads.stream.firstWhere((String url) => url == secondaryUrl), + completion(secondaryUrl), + ); + }); + + testWidgets('evaluateJavascript', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + // ignore: deprecated_member_use + final String result = await controller.evaluateJavascript('1 + 1'); + expect(result, equals('2')); + }); + + testWidgets('loadUrl with headers', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageStarts = StreamController(); + final StreamController pageLoads = StreamController(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageStarted: (String url) { + pageStarts.add(url); + }, + onPageFinished: (String url) { + pageLoads.add(url); + }, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + final Map headers = { + 'test_header': 'flutter_test_header' + }; + await controller.loadUrl(headersUrl, headers: headers); + + await pageStarts.stream.firstWhere((String url) => url == headersUrl); + await pageLoads.stream.firstWhere((String url) => url == headersUrl); + + final String content = await controller + .runJavascriptReturningResult('document.documentElement.innerText'); + expect(content.contains('flutter_test_header'), isTrue); + }); + + testWidgets('JavascriptChannel', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final Completer pageStarted = Completer(); + final Completer pageLoaded = Completer(); + final Completer channelCompleter = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + // This is the data URL for: '' + initialUrl: + 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + javascriptChannels: { + JavascriptChannel( + name: 'Echo', + onMessageReceived: (JavascriptMessage message) { + channelCompleter.complete(message.message); + }, + ), + }, + onPageStarted: (String url) { + pageStarted.complete(null); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + await pageStarted.future; + await pageLoaded.future; + + expect(channelCompleter.isCompleted, isFalse); + await controller.runJavascript('Echo.postMessage("hello");'); + + await expectLater(channelCompleter.future, completion('hello')); + }); + + testWidgets('resize webview', (WidgetTester tester) async { + final Completer initialResizeCompleter = Completer(); + final Completer buttonTapResizeCompleter = Completer(); + final Completer onPageFinished = Completer(); + + bool resizeButtonTapped = false; + await tester.pumpWidget(ResizableWebView( + onResize: (_) { + if (resizeButtonTapped) { + buttonTapResizeCompleter.complete(); + } else { + initialResizeCompleter.complete(); + } + }, + onPageFinished: () => onPageFinished.complete(), + )); + await onPageFinished.future; + // Wait for a potential call to resize after page is loaded. + await initialResizeCompleter.future.timeout( + const Duration(seconds: 3), + onTimeout: () => null, + ); + + resizeButtonTapped = true; + await tester.tap(find.byKey(const ValueKey('resizeButton'))); + await tester.pumpAndSettle(); + expect(buttonTapResizeCompleter.future, completes); + }); + + testWidgets('set custom userAgent', (WidgetTester tester) async { + final Completer controllerCompleter1 = + Completer(); + final GlobalKey globalKey = GlobalKey(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: globalKey, + initialUrl: 'about:blank', + javascriptMode: JavascriptMode.unrestricted, + userAgent: 'Custom_User_Agent1', + onWebViewCreated: (WebViewController controller) { + controllerCompleter1.complete(controller); + }, + ), + ), + ); + final WebViewController controller1 = await controllerCompleter1.future; + final String customUserAgent1 = await _getUserAgent(controller1); + expect(customUserAgent1, 'Custom_User_Agent1'); + // rebuild the WebView with a different user agent. + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: globalKey, + initialUrl: 'about:blank', + javascriptMode: JavascriptMode.unrestricted, + userAgent: 'Custom_User_Agent2', + ), + ), + ); + + final String customUserAgent2 = await _getUserAgent(controller1); + expect(customUserAgent2, 'Custom_User_Agent2'); + }); + + testWidgets('use default platform userAgent after webView is rebuilt', + (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final GlobalKey globalKey = GlobalKey(); + // Build the webView with no user agent to get the default platform user agent. + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: globalKey, + initialUrl: primaryUrl, + javascriptMode: JavascriptMode.unrestricted, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + final String defaultPlatformUserAgent = await _getUserAgent(controller); + // rebuild the WebView with a custom user agent. + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: globalKey, + initialUrl: 'about:blank', + javascriptMode: JavascriptMode.unrestricted, + userAgent: 'Custom_User_Agent', + ), + ), + ); + final String customUserAgent = await _getUserAgent(controller); + expect(customUserAgent, 'Custom_User_Agent'); + // rebuilds the WebView with no user agent. + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: globalKey, + initialUrl: 'about:blank', + javascriptMode: JavascriptMode.unrestricted, + ), + ), + ); + + final String customUserAgent2 = await _getUserAgent(controller); + expect(customUserAgent2, defaultPlatformUserAgent); + }); + + group('Video playback policy', () { + testWidgets( + 'Auto media playback', + (WidgetTester tester) async { + final String videoTestBase64 = await getTestVideoBase64(); + Completer controllerCompleter = + Completer(); + Completer pageLoaded = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: + 'data:text/html;charset=utf-8;base64,$videoTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + ), + ), + ); + WebViewController controller = await controllerCompleter.future; + await pageLoaded.future; + + String isPaused = + await controller.runJavascriptReturningResult('isPaused();'); + expect(isPaused, _webviewBool(false)); + + controllerCompleter = Completer(); + pageLoaded = Completer(); + + // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: + 'data:text/html;charset=utf-8;base64,$videoTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + + controller = await controllerCompleter.future; + await pageLoaded.future; + + isPaused = await controller.runJavascriptReturningResult('isPaused();'); + expect(isPaused, _webviewBool(true)); + }, + // Flakes on iOS: https://github.com/flutter/flutter/issues/164632 + skip: Platform.isIOS, + ); + + testWidgets( + 'Changes to initialMediaPlaybackPolicy are ignored', + (WidgetTester tester) async { + final String videoTestBase64 = await getTestVideoBase64(); + final Completer controllerCompleter = + Completer(); + Completer pageLoaded = Completer(); + + final GlobalKey key = GlobalKey(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: key, + initialUrl: + 'data:text/html;charset=utf-8;base64,$videoTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + await pageLoaded.future; + + String isPaused = + await controller.runJavascriptReturningResult('isPaused();'); + expect(isPaused, _webviewBool(false)); + + pageLoaded = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: key, + initialUrl: + 'data:text/html;charset=utf-8;base64,$videoTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + + await controller.reload(); + + await pageLoaded.future; + + isPaused = await controller.runJavascriptReturningResult('isPaused();'); + expect(isPaused, _webviewBool(false)); + }, + // Flakes on iOS: https://github.com/flutter/flutter/issues/164632 + skip: Platform.isIOS, + ); + + testWidgets('Video plays inline when allowsInlineMediaPlayback is true', + (WidgetTester tester) async { + final String videoTestBase64 = await getTestVideoBase64(); + final Completer controllerCompleter = + Completer(); + final Completer pageLoaded = Completer(); + final Completer videoPlaying = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + javascriptChannels: { + JavascriptChannel( + name: 'VideoTestTime', + onMessageReceived: (JavascriptMessage message) { + final double currentTime = double.parse(message.message); + // Let it play for at least 1 second to make sure the related video's properties are set. + if (currentTime > 1 && !videoPlaying.isCompleted) { + videoPlaying.complete(null); + } + }, + ), + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + allowsInlineMediaPlayback: true, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + await pageLoaded.future; + + // Pump once to trigger the video play. + await tester.pump(); + + // Makes sure we get the correct event that indicates the video is actually playing. + await videoPlaying.future; + + final String fullScreen = + await controller.runJavascriptReturningResult('isFullScreen();'); + expect(fullScreen, _webviewBool(false)); + }); + }); + + group('Audio playback policy', () { + late String audioTestBase64; + setUpAll(() async { + final ByteData audioData = + await rootBundle.load('assets/sample_audio.ogg'); + final String base64AudioData = + base64Encode(Uint8List.view(audioData.buffer)); + final String audioTest = ''' + + Audio auto play + + + + + + + '''; + audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest)); + }); + + testWidgets('Auto media playback', (WidgetTester tester) async { + Completer controllerCompleter = + Completer(); + Completer pageStarted = Completer(); + Completer pageLoaded = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageStarted: (String url) { + pageStarted.complete(null); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + ), + ), + ); + WebViewController controller = await controllerCompleter.future; + await pageStarted.future; + await pageLoaded.future; + + String isPaused = + await controller.runJavascriptReturningResult('isPaused();'); + expect(isPaused, _webviewBool(false)); + + controllerCompleter = Completer(); + pageStarted = Completer(); + pageLoaded = Completer(); + + // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageStarted: (String url) { + pageStarted.complete(null); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + + controller = await controllerCompleter.future; + await pageStarted.future; + await pageLoaded.future; + + isPaused = await controller.runJavascriptReturningResult('isPaused();'); + expect(isPaused, _webviewBool(true)); + }); + + testWidgets('Changes to initialMediaPlaybackPolicy are ignored', + (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + Completer pageStarted = Completer(); + Completer pageLoaded = Completer(); + + final GlobalKey key = GlobalKey(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: key, + initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageStarted: (String url) { + pageStarted.complete(null); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + await pageStarted.future; + await pageLoaded.future; + + String isPaused = + await controller.runJavascriptReturningResult('isPaused();'); + expect(isPaused, _webviewBool(false)); + + pageStarted = Completer(); + pageLoaded = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: key, + initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageStarted: (String url) { + pageStarted.complete(null); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + + await controller.reload(); + + await pageStarted.future; + await pageLoaded.future; + + isPaused = await controller.runJavascriptReturningResult('isPaused();'); + expect(isPaused, _webviewBool(false)); + }); + }); + + testWidgets('getTitle', (WidgetTester tester) async { + const String getTitleTest = ''' + + Some title + + + + + '''; + final String getTitleTestBase64 = + base64Encode(const Utf8Encoder().convert(getTitleTest)); + final Completer pageStarted = Completer(); + final Completer pageLoaded = Completer(); + final Completer controllerCompleter = + Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + initialUrl: 'data:text/html;charset=utf-8;base64,$getTitleTestBase64', + javascriptMode: JavascriptMode.unrestricted, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + onPageStarted: (String url) { + pageStarted.complete(null); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + + final WebViewController controller = await controllerCompleter.future; + await pageStarted.future; + await pageLoaded.future; + + // On at least iOS, it does not appear to be guaranteed that the native + // code has the title when the page load completes. Execute some JavaScript + // before checking the title to ensure that the page has been fully parsed + // and processed. + await controller.runJavascript('1;'); + + final String? title = await controller.getTitle(); + expect(title, 'Some title'); + }); + + group('Programmatic Scroll', () { + testWidgets('setAndGetScrollPosition', (WidgetTester tester) async { + const String scrollTestPage = ''' + + + + + + +
+ + + '''; + + final String scrollTestPageBase64 = + base64Encode(const Utf8Encoder().convert(scrollTestPage)); + + final Completer pageLoaded = Completer(); + final Completer controllerCompleter = + Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + initialUrl: + 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + + final WebViewController controller = await controllerCompleter.future; + await pageLoaded.future; + + await tester.pumpAndSettle(const Duration(seconds: 3)); + + int scrollPosX = await controller.getScrollX(); + int scrollPosY = await controller.getScrollY(); + + // Check scrollTo() + const int X_SCROLL = 123; + const int Y_SCROLL = 321; + // Get the initial position; this ensures that scrollTo is actually + // changing something, but also gives the native view's scroll position + // time to settle. + expect(scrollPosX, isNot(X_SCROLL)); + expect(scrollPosX, isNot(Y_SCROLL)); + + await controller.scrollTo(X_SCROLL, Y_SCROLL); + scrollPosX = await controller.getScrollX(); + scrollPosY = await controller.getScrollY(); + expect(scrollPosX, X_SCROLL); + expect(scrollPosY, Y_SCROLL); + + // Check scrollBy() (on top of scrollTo()) + await controller.scrollBy(X_SCROLL, Y_SCROLL); + scrollPosX = await controller.getScrollX(); + scrollPosY = await controller.getScrollY(); + expect(scrollPosX, X_SCROLL * 2); + expect(scrollPosY, Y_SCROLL * 2); + }); + }); + + // Minimal end-to-end testing of the legacy Android implementation. + group('AndroidWebView (virtual display)', () { + setUpAll(() { + WebView.platform = AndroidWebView(); + }); + + tearDownAll(() { + WebView.platform = null; + }); + + testWidgets('initialUrl', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final Completer pageFinishedCompleter = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + onPageFinished: pageFinishedCompleter.complete, + ), + ), + ); + + final WebViewController controller = await controllerCompleter.future; + await pageFinishedCompleter.future; + + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, primaryUrl); + }); + }, skip: !Platform.isAndroid); + + group('NavigationDelegate', () { + const String blankPage = ''; + final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' + '${base64Encode(const Utf8Encoder().convert(blankPage))}'; + + testWidgets('can allow requests', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageLoads = + StreamController.broadcast(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: blankPageEncoded, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + navigationDelegate: (NavigationRequest request) { + return request.url.contains('youtube.com') + ? NavigationDecision.prevent + : NavigationDecision.navigate; + }, + onPageFinished: (String url) => pageLoads.add(url), + ), + ), + ); + + await pageLoads.stream.first; // Wait for initial page load. + final WebViewController controller = await controllerCompleter.future; + await controller.runJavascript('location.href = "$secondaryUrl"'); + + await pageLoads.stream.first; // Wait for the next page load. + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, secondaryUrl); + }); + + testWidgets('onWebResourceError', (WidgetTester tester) async { + final Completer errorCompleter = + Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: 'https://www.notawebsite..com', + onWebResourceError: (WebResourceError error) { + errorCompleter.complete(error); + }, + ), + ), + ); + + final WebResourceError error = await errorCompleter.future; + expect(error, isNotNull); + + if (Platform.isIOS) { + expect(error.domain, isNotNull); + expect(error.failingUrl, isNull); + } else if (Platform.isAndroid) { + expect(error.errorType, isNotNull); + expect(error.failingUrl?.startsWith('https://www.notawebsite..com'), + isTrue); + } + }); + + testWidgets('onWebResourceError is not called with valid url', + (WidgetTester tester) async { + final Completer errorCompleter = + Completer(); + final Completer pageFinishCompleter = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: + 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', + onWebResourceError: (WebResourceError error) { + errorCompleter.complete(error); + }, + onPageFinished: (_) => pageFinishCompleter.complete(), + ), + ), + ); + + expect(errorCompleter.future, doesNotComplete); + await pageFinishCompleter.future; + }); + + testWidgets( + 'onWebResourceError only called for main frame', + (WidgetTester tester) async { + const String iframeTest = ''' + + + + WebResourceError test + + + + + + '''; + final String iframeTestBase64 = + base64Encode(const Utf8Encoder().convert(iframeTest)); + + final Completer errorCompleter = + Completer(); + final Completer pageFinishCompleter = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: + 'data:text/html;charset=utf-8;base64,$iframeTestBase64', + onWebResourceError: (WebResourceError error) { + errorCompleter.complete(error); + }, + onPageFinished: (_) => pageFinishCompleter.complete(), + ), + ), + ); + + expect(errorCompleter.future, doesNotComplete); + await pageFinishCompleter.future; + }, + ); + + testWidgets('can block requests', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageLoads = + StreamController.broadcast(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: blankPageEncoded, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + navigationDelegate: (NavigationRequest request) { + return request.url.contains('youtube.com') + ? NavigationDecision.prevent + : NavigationDecision.navigate; + }, + onPageFinished: (String url) => pageLoads.add(url), + ), + ), + ); + + await pageLoads.stream.first; // Wait for initial page load. + final WebViewController controller = await controllerCompleter.future; + await controller + .runJavascript('location.href = "https://www.youtube.com/"'); + + // There should never be any second page load, since our new URL is + // blocked. Still wait for a potential page change for some time in order + // to give the test a chance to fail. + await pageLoads.stream.first + .timeout(const Duration(milliseconds: 500), onTimeout: () => ''); + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, isNot(contains('youtube.com'))); + }); + + testWidgets('supports asynchronous decisions', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageLoads = + StreamController.broadcast(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: blankPageEncoded, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + navigationDelegate: (NavigationRequest request) async { + NavigationDecision decision = NavigationDecision.prevent; + decision = await Future.delayed( + const Duration(milliseconds: 10), + () => NavigationDecision.navigate); + return decision; + }, + onPageFinished: (String url) => pageLoads.add(url), + ), + ), + ); + + await pageLoads.stream.first; // Wait for initial page load. + final WebViewController controller = await controllerCompleter.future; + await controller.runJavascript('location.href = "$secondaryUrl"'); + + await pageLoads.stream.first; // Wait for second page to load. + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, secondaryUrl); + }); + }); + + testWidgets('launches with gestureNavigationEnabled on iOS', + (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: SizedBox( + width: 400, + height: 300, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + gestureNavigationEnabled: true, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + ), + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, primaryUrl); + }); + + testWidgets('target _blank opens in same window', + (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final Completer pageLoaded = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + await controller.runJavascript('window.open("$primaryUrl", "_blank")'); + await pageLoaded.future; + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, primaryUrl); + }); + + testWidgets( + 'can open new window and go back', + (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + Completer pageLoaded = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (String url) { + pageLoaded.complete(); + }, + initialUrl: primaryUrl, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + expect(controller.currentUrl(), completion(primaryUrl)); + await pageLoaded.future; + pageLoaded = Completer(); + + await controller.runJavascript('window.open("$secondaryUrl")'); + await pageLoaded.future; + pageLoaded = Completer(); + expect(controller.currentUrl(), completion(secondaryUrl)); + + expect(controller.canGoBack(), completion(true)); + await controller.goBack(); + await pageLoaded.future; + await expectLater(controller.currentUrl(), completion(primaryUrl)); + }, + ); + + testWidgets( + 'clearCache should clear local storage', + (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + + Completer pageLoadCompleter = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (_) => pageLoadCompleter.complete(), + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + ), + ), + ); + + await pageLoadCompleter.future; + pageLoadCompleter = Completer(); + + final WebViewController controller = await controllerCompleter.future; + await controller.runJavascript('localStorage.setItem("myCat", "Tom");'); + final String myCatItem = await controller.runJavascriptReturningResult( + 'localStorage.getItem("myCat");', + ); + expect(myCatItem, _webviewString('Tom')); + + await controller.clearCache(); + await pageLoadCompleter.future; + + late final String? nullItem; + try { + nullItem = await controller.runJavascriptReturningResult( + 'localStorage.getItem("myCat");', + ); + } catch (exception) { + if (defaultTargetPlatform == TargetPlatform.iOS && + exception is ArgumentError && + (exception.message as String).contains( + 'Result of JavaScript execution returned a `null` value.')) { + nullItem = ''; + } + } + expect(nullItem, _webviewNull()); + }, + ); +} + +// JavaScript booleans evaluate to different string values on different devices. +// This utility method returns a matcher that match on either representation. +Matcher _webviewBool(bool value) { + if (value) { + return anyOf('true', '1'); + } + return anyOf('false', '0'); +} + +// JavaScript `null` evaluate to different string values on Android and iOS. +// This utility method returns the string boolean value of the current platform. +String _webviewNull() { + if (defaultTargetPlatform == TargetPlatform.iOS) { + return ''; + } + return 'null'; +} + +// JavaScript String evaluate to different string values on Android and iOS. +// This utility method returns the string boolean value of the current platform. +String _webviewString(String value) { + if (defaultTargetPlatform == TargetPlatform.iOS) { + return value; + } + return '"$value"'; +} + +/// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests. +Future _getUserAgent(WebViewController controller) async { + return _runJavascriptReturningResult(controller, 'navigator.userAgent;'); +} + +Future _runJavascriptReturningResult( + WebViewController controller, String js) async { + if (defaultTargetPlatform == TargetPlatform.iOS) { + return controller.runJavascriptReturningResult(js); + } + return jsonDecode(await controller.runJavascriptReturningResult(js)) + as String; +} + +class ResizableWebView extends StatefulWidget { + const ResizableWebView({ + super.key, + required this.onResize, + required this.onPageFinished, + }); + + final JavascriptMessageHandler onResize; + final VoidCallback onPageFinished; + + @override + State createState() => ResizableWebViewState(); +} + +class ResizableWebViewState extends State { + double webViewWidth = 200; + double webViewHeight = 200; + + static const String resizePage = ''' + + Resize test + + + + + + '''; + + @override + Widget build(BuildContext context) { + final String resizeTestBase64 = + base64Encode(const Utf8Encoder().convert(resizePage)); + return Directionality( + textDirection: TextDirection.ltr, + child: Column( + children: [ + SizedBox( + width: webViewWidth, + height: webViewHeight, + child: WebView( + initialUrl: + 'data:text/html;charset=utf-8;base64,$resizeTestBase64', + javascriptChannels: { + JavascriptChannel( + name: 'Resize', + onMessageReceived: widget.onResize, + ), + }, + onPageFinished: (_) => widget.onPageFinished(), + javascriptMode: JavascriptMode.unrestricted, + ), + ), + TextButton( + key: const Key('resizeButton'), + onPressed: () { + setState(() { + webViewWidth += 100.0; + webViewHeight += 100.0; + }); + }, + child: const Text('ResizeButton'), + ), + ], + ), + ); + } +} + +Future getTestVideoBase64() async { + final ByteData videoData = await rootBundle.load('assets/sample_video.mp4'); + final String base64VideoData = base64Encode(Uint8List.view(videoData.buffer)); + final String videoTest = ''' + + Video auto play + + + + + + + '''; + return base64Encode(const Utf8Encoder().convert(videoTest)); +} diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Flutter/AppFrameworkInfo.plist b/local_packages/webview_flutter-4.13.0/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Flutter/Debug.xcconfig b/local_packages/webview_flutter-4.13.0/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..e8efba1 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Flutter/Release.xcconfig b/local_packages/webview_flutter-4.13.0/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..399e934 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Podfile b/local_packages/webview_flutter-4.13.0/example/ios/Podfile new file mode 100644 index 0000000..414ba51 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/ios/Podfile @@ -0,0 +1,38 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner.xcodeproj/project.pbxproj b/local_packages/webview_flutter-4.13.0/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..0952275 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,506 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; + 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + E6159E2B6496F35B1D4F4096 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C0ABA59F25635F077C9EA161 /* libPods-Runner.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 11DF059E983DF25F078B44CC /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3CEFE8F0E91B9792E4EE427B /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 4D2B3F45D8E6CA81EA52591E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 5D19D984A61169BB95DB0FED /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + BEC8CD326B252E47ABE6C037 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + C0ABA59F25635F077C9EA161 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + E6159E2B6496F35B1D4F4096 /* libPods-Runner.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 00D2395F7DDFEE571DF3C0B1 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C0ABA59F25635F077C9EA161 /* libPods-Runner.a */, + BEC8CD326B252E47ABE6C037 /* libPods-RunnerTests.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + EA36D6F90B795550E32A139A /* Pods */, + 00D2395F7DDFEE571DF3C0B1 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 97C146F21CF9000F007C117D /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + EA36D6F90B795550E32A139A /* Pods */ = { + isa = PBXGroup; + children = ( + 4D2B3F45D8E6CA81EA52591E /* Pods-Runner.debug.xcconfig */, + 11DF059E983DF25F078B44CC /* Pods-Runner.release.xcconfig */, + 3CEFE8F0E91B9792E4EE427B /* Pods-RunnerTests.debug.xcconfig */, + 5D19D984A61169BB95DB0FED /* Pods-RunnerTests.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 1B3EA6BF26F6D525A8503093 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + DefaultBuildSystemTypeForWorkspace = Original; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = "The Flutter Authors"; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 1B3EA6BF26F6D525A8503093 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, + 97C146F31CF9000F007C117D /* main.m in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPat_icon_NAME = AppIcon; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.webviewFlutterExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPat_icon_NAME = AppIcon; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.webviewFlutterExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/local_packages/webview_flutter-4.13.0/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/local_packages/webview_flutter-4.13.0/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..37ba710 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/local_packages/webview_flutter-4.13.0/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/webview_flutter-4.13.0/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/AppDelegate.h b/local_packages/webview_flutter-4.13.0/example/ios/Runner/AppDelegate.h new file mode 100644 index 0000000..0681d28 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/ios/Runner/AppDelegate.h @@ -0,0 +1,10 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import + +@interface AppDelegate : FlutterAppDelegate + +@end diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/AppDelegate.m b/local_packages/webview_flutter-4.13.0/example/ios/Runner/AppDelegate.m new file mode 100644 index 0000000..30b8796 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/ios/Runner/AppDelegate.m @@ -0,0 +1,17 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "AppDelegate.h" +#include "GeneratedPluginRegistrant.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +@end diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..3d43d11 Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..28c6bf0 Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..f091b6b Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cde121 Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d0ef06e Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..dcdc230 Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..c8f9ed8 Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..75b2d16 Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..c4df70d Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..6a84f41 Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..d0e1f58 Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +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. \ No newline at end of file diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Base.lproj/Main.storyboard b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/Info.plist b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Info.plist new file mode 100644 index 0000000..6e0d80c --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/ios/Runner/Info.plist @@ -0,0 +1,52 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + webview_flutter_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + NSAppTransportSecurity + + NSAllowsArbitraryLoadsInWebContent + + + + diff --git a/local_packages/webview_flutter-4.13.0/example/ios/Runner/main.m b/local_packages/webview_flutter-4.13.0/example/ios/Runner/main.m new file mode 100644 index 0000000..f143297 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/ios/Runner/main.m @@ -0,0 +1,13 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import +#import "AppDelegate.h" + +int main(int argc, char *argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/local_packages/webview_flutter-4.13.0/example/lib/main.dart b/local_packages/webview_flutter-4.13.0/example/lib/main.dart new file mode 100644 index 0000000..c73749d --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/lib/main.dart @@ -0,0 +1,655 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: public_member_api_docs + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +// #docregion platform_imports +// Import for Android features. +import 'package:webview_flutter_android/webview_flutter_android.dart'; +// Import for iOS/macOS features. +import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; +// #enddocregion platform_imports + +void main() => runApp(const MaterialApp(home: WebViewExample())); + +const String kNavigationExamplePage = ''' + +Navigation Delegate Example + +

+The navigation delegate is set to block navigation to the youtube website. +

+ + + +'''; + +const String kLocalExamplePage = ''' + + + +Load file or HTML string example + + + +

Local demo page

+

+ This is an example page used to demonstrate how to load a local file or HTML + string using the Flutter + webview plugin. +

+ + + +'''; + +const String kTransparentBackgroundPage = ''' + + + + Transparent background test + + + +
+

Transparent background test

+
+
+ + +'''; + +const String kLogExamplePage = ''' + + + +Load file or HTML string example + + + +

Local demo page

+

+ This page is used to test the forwarding of console logs to Dart. +

+ + + +
+ + + + + +
+ + + +'''; + +class WebViewExample extends StatefulWidget { + const WebViewExample({super.key}); + + @override + State createState() => _WebViewExampleState(); +} + +class _WebViewExampleState extends State { + late final WebViewController _controller; + + @override + void initState() { + super.initState(); + + // #docregion platform_features + late final PlatformWebViewControllerCreationParams params; + if (WebViewPlatform.instance is WebKitWebViewPlatform) { + params = WebKitWebViewControllerCreationParams( + allowsInlineMediaPlayback: true, + mediaTypesRequiringUserAction: const {}, + ); + } else { + params = const PlatformWebViewControllerCreationParams(); + } + + final WebViewController controller = + WebViewController.fromPlatformCreationParams(params); + // #enddocregion platform_features + + controller + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate( + onProgress: (int progress) { + debugPrint('WebView is loading (progress : $progress%)'); + }, + onPageStarted: (String url) { + debugPrint('Page started loading: $url'); + }, + onPageFinished: (String url) { + debugPrint('Page finished loading: $url'); + }, + onWebResourceError: (WebResourceError error) { + debugPrint(''' +Page resource error: + code: ${error.errorCode} + description: ${error.description} + errorType: ${error.errorType} + isForMainFrame: ${error.isForMainFrame} + '''); + }, + onNavigationRequest: (NavigationRequest request) { + if (request.url.startsWith('https://www.youtube.com/')) { + debugPrint('blocking navigation to ${request.url}'); + return NavigationDecision.prevent; + } + debugPrint('allowing navigation to ${request.url}'); + return NavigationDecision.navigate; + }, + onHttpError: (HttpResponseError error) { + debugPrint('Error occurred on page: ${error.response?.statusCode}'); + }, + onUrlChange: (UrlChange change) { + debugPrint('url change to ${change.url}'); + }, + onHttpAuthRequest: (HttpAuthRequest request) { + openDialog(request); + }, + ), + ) + ..addJavaScriptChannel( + 'Toaster', + onMessageReceived: (JavaScriptMessage message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message.message)), + ); + }, + ) + ..loadRequest(Uri.parse('https://flutter.dev')); + + // setBackgroundColor is not currently supported on macOS. + if (kIsWeb || !Platform.isMacOS) { + controller.setBackgroundColor(const Color(0x80000000)); + } + + // #docregion platform_features + if (controller.platform is AndroidWebViewController) { + AndroidWebViewController.enableDebugging(true); + (controller.platform as AndroidWebViewController) + .setMediaPlaybackRequiresUserGesture(false); + } + // #enddocregion platform_features + + _controller = controller; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.green, + appBar: AppBar( + title: const Text('Flutter WebView example'), + // This drop down menu demonstrates that Flutter widgets can be shown over the web view. + actions: [ + NavigationControls(webViewController: _controller), + SampleMenu(webViewController: _controller), + ], + ), + body: WebViewWidget(controller: _controller), + floatingActionButton: favoriteButton(), + ); + } + + Widget favoriteButton() { + return FloatingActionButton( + onPressed: () async { + final String? url = await _controller.currentUrl(); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Favorited $url')), + ); + } + }, + child: const Icon(Icons.favorite), + ); + } + + Future openDialog(HttpAuthRequest httpRequest) async { + final TextEditingController usernameTextController = + TextEditingController(); + final TextEditingController passwordTextController = + TextEditingController(); + + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: Text('${httpRequest.host}: ${httpRequest.realm ?? '-'}'), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + decoration: const InputDecoration(labelText: 'Username'), + autofocus: true, + controller: usernameTextController, + ), + TextField( + decoration: const InputDecoration(labelText: 'Password'), + controller: passwordTextController, + ), + ], + ), + ), + actions: [ + // Explicitly cancel the request on iOS as the OS does not emit new + // requests when a previous request is pending. + TextButton( + onPressed: () { + httpRequest.onCancel(); + Navigator.of(context).pop(); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + httpRequest.onProceed( + WebViewCredential( + user: usernameTextController.text, + password: passwordTextController.text, + ), + ); + Navigator.of(context).pop(); + }, + child: const Text('Authenticate'), + ), + ], + ); + }, + ); + } +} + +enum MenuOptions { + showUserAgent, + listCookies, + clearCookies, + addToCache, + listCache, + clearCache, + navigationDelegate, + doPostRequest, + loadLocalFile, + loadFlutterAsset, + loadHtmlString, + transparentBackground, + setCookie, + logExample, + basicAuthentication, +} + +class SampleMenu extends StatelessWidget { + SampleMenu({ + super.key, + required this.webViewController, + }); + + final WebViewController webViewController; + late final WebViewCookieManager cookieManager = WebViewCookieManager(); + + @override + Widget build(BuildContext context) { + return PopupMenuButton( + key: const ValueKey('ShowPopupMenu'), + onSelected: (MenuOptions value) { + switch (value) { + case MenuOptions.showUserAgent: + _onShowUserAgent(); + case MenuOptions.listCookies: + _onListCookies(context); + case MenuOptions.clearCookies: + _onClearCookies(context); + case MenuOptions.addToCache: + _onAddToCache(context); + case MenuOptions.listCache: + _onListCache(); + case MenuOptions.clearCache: + _onClearCache(context); + case MenuOptions.navigationDelegate: + _onNavigationDelegateExample(); + case MenuOptions.doPostRequest: + _onDoPostRequest(); + case MenuOptions.loadLocalFile: + _onLoadLocalFileExample(); + case MenuOptions.loadFlutterAsset: + _onLoadFlutterAssetExample(); + case MenuOptions.loadHtmlString: + _onLoadHtmlStringExample(); + case MenuOptions.transparentBackground: + _onTransparentBackground(); + case MenuOptions.setCookie: + _onSetCookie(); + case MenuOptions.logExample: + _onLogExample(); + case MenuOptions.basicAuthentication: + _promptForUrl(context); + } + }, + itemBuilder: (BuildContext context) => >[ + const PopupMenuItem( + value: MenuOptions.showUserAgent, + child: Text('Show user agent'), + ), + const PopupMenuItem( + value: MenuOptions.listCookies, + child: Text('List cookies'), + ), + const PopupMenuItem( + value: MenuOptions.clearCookies, + child: Text('Clear cookies'), + ), + const PopupMenuItem( + value: MenuOptions.addToCache, + child: Text('Add to cache'), + ), + const PopupMenuItem( + value: MenuOptions.listCache, + child: Text('List cache'), + ), + const PopupMenuItem( + value: MenuOptions.clearCache, + child: Text('Clear cache'), + ), + const PopupMenuItem( + value: MenuOptions.navigationDelegate, + child: Text('Navigation Delegate example'), + ), + const PopupMenuItem( + value: MenuOptions.doPostRequest, + child: Text('Post Request'), + ), + const PopupMenuItem( + value: MenuOptions.loadHtmlString, + child: Text('Load HTML string'), + ), + const PopupMenuItem( + value: MenuOptions.loadLocalFile, + child: Text('Load local file'), + ), + const PopupMenuItem( + value: MenuOptions.loadFlutterAsset, + child: Text('Load Flutter Asset'), + ), + const PopupMenuItem( + key: ValueKey('ShowTransparentBackgroundExample'), + value: MenuOptions.transparentBackground, + child: Text('Transparent background example'), + ), + const PopupMenuItem( + value: MenuOptions.setCookie, + child: Text('Set cookie'), + ), + const PopupMenuItem( + value: MenuOptions.logExample, + child: Text('Log example'), + ), + const PopupMenuItem( + value: MenuOptions.basicAuthentication, + child: Text('Basic Authentication Example'), + ), + ], + ); + } + + Future _onShowUserAgent() { + // Send a message with the user agent string to the Toaster JavaScript channel we registered + // with the WebView. + return webViewController.runJavaScript( + 'Toaster.postMessage("User Agent: " + navigator.userAgent);', + ); + } + + Future _onListCookies(BuildContext context) async { + final String cookies = await webViewController + .runJavaScriptReturningResult('document.cookie') as String; + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Column( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Cookies:'), + _getCookieList(cookies), + ], + ), + )); + } + } + + Future _onAddToCache(BuildContext context) async { + await webViewController.runJavaScript( + 'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";', + ); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Added a test entry to cache.'), + )); + } + } + + Future _onListCache() { + return webViewController.runJavaScript('caches.keys()' + // ignore: missing_whitespace_between_adjacent_strings + '.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))' + '.then((caches) => Toaster.postMessage(caches))'); + } + + Future _onClearCache(BuildContext context) async { + await webViewController.clearCache(); + await webViewController.clearLocalStorage(); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Cache cleared.'), + )); + } + } + + Future _onClearCookies(BuildContext context) async { + final bool hadCookies = await cookieManager.clearCookies(); + String message = 'There were cookies. Now, they are gone!'; + if (!hadCookies) { + message = 'There are no cookies.'; + } + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(message), + )); + } + } + + Future _onNavigationDelegateExample() { + final String contentBase64 = base64Encode( + const Utf8Encoder().convert(kNavigationExamplePage), + ); + return webViewController.loadRequest( + Uri.parse('data:text/html;base64,$contentBase64'), + ); + } + + Future _onSetCookie() async { + await cookieManager.setCookie( + const WebViewCookie( + name: 'foo', + value: 'bar', + domain: 'httpbin.org', + path: '/anything', + ), + ); + await webViewController.loadRequest(Uri.parse( + 'https://httpbin.org/anything', + )); + } + + Future _onDoPostRequest() { + return webViewController.loadRequest( + Uri.parse('https://httpbin.org/post'), + method: LoadRequestMethod.post, + headers: {'foo': 'bar', 'Content-Type': 'text/plain'}, + body: Uint8List.fromList('Test Body'.codeUnits), + ); + } + + Future _onLoadLocalFileExample() async { + final String pathToIndex = await _prepareLocalFile(); + await webViewController.loadFile(pathToIndex); + } + + Future _onLoadFlutterAssetExample() { + return webViewController.loadFlutterAsset('assets/www/index.html'); + } + + Future _onLoadHtmlStringExample() { + return webViewController.loadHtmlString(kLocalExamplePage); + } + + Future _onTransparentBackground() { + return webViewController.loadHtmlString(kTransparentBackgroundPage); + } + + Widget _getCookieList(String cookies) { + if (cookies == '""') { + return Container(); + } + final List cookieList = cookies.split(';'); + final Iterable cookieWidgets = + cookieList.map((String cookie) => Text(cookie)); + return Column( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: cookieWidgets.toList(), + ); + } + + static Future _prepareLocalFile() async { + final String tmpDir = (await getTemporaryDirectory()).path; + final File indexFile = File( + {tmpDir, 'www', 'index.html'}.join(Platform.pathSeparator)); + + await indexFile.create(recursive: true); + await indexFile.writeAsString(kLocalExamplePage); + + return indexFile.path; + } + + Future _onLogExample() { + webViewController + .setOnConsoleMessage((JavaScriptConsoleMessage consoleMessage) { + debugPrint( + '== JS == ${consoleMessage.level.name}: ${consoleMessage.message}'); + }); + + return webViewController.loadHtmlString(kLogExamplePage); + } + + Future _promptForUrl(BuildContext context) { + final TextEditingController urlTextController = TextEditingController(); + + return showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Input URL to visit'), + content: TextField( + decoration: const InputDecoration(labelText: 'URL'), + autofocus: true, + controller: urlTextController, + ), + actions: [ + TextButton( + onPressed: () { + if (urlTextController.text.isNotEmpty) { + final Uri? uri = Uri.tryParse(urlTextController.text); + if (uri != null && uri.scheme.isNotEmpty) { + webViewController.loadRequest(uri); + Navigator.pop(context); + } + } + }, + child: const Text('Visit'), + ), + ], + ); + }, + ); + } +} + +class NavigationControls extends StatelessWidget { + const NavigationControls({super.key, required this.webViewController}); + + final WebViewController webViewController; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + IconButton( + icon: const Icon(Icons.arrow_back_ios), + onPressed: () async { + if (await webViewController.canGoBack()) { + await webViewController.goBack(); + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No back history item')), + ); + } + } + }, + ), + IconButton( + icon: const Icon(Icons.arrow_forward_ios), + onPressed: () async { + if (await webViewController.canGoForward()) { + await webViewController.goForward(); + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No forward history item')), + ); + } + } + }, + ), + IconButton( + icon: const Icon(Icons.replay), + onPressed: () => webViewController.reload(), + ), + ], + ); + } +} diff --git a/local_packages/webview_flutter-4.13.0/example/lib/simple_example.dart b/local_packages/webview_flutter-4.13.0/example/lib/simple_example.dart new file mode 100644 index 0000000..9bfe41f --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/lib/simple_example.dart @@ -0,0 +1,59 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: public_member_api_docs + +import 'package:flutter/material.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +void main() => runApp(const MaterialApp(home: WebViewExample())); + +class WebViewExample extends StatefulWidget { + const WebViewExample({super.key}); + + @override + State createState() => _WebViewExampleState(); +} + +class _WebViewExampleState extends State { + late final WebViewController controller; + + @override + void initState() { + super.initState(); + + // #docregion webview_controller + controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate( + onProgress: (int progress) { + // Update loading bar. + }, + onPageStarted: (String url) {}, + onPageFinished: (String url) {}, + onHttpError: (HttpResponseError error) {}, + onWebResourceError: (WebResourceError error) {}, + onNavigationRequest: (NavigationRequest request) { + if (request.url.startsWith('https://www.youtube.com/')) { + return NavigationDecision.prevent; + } + return NavigationDecision.navigate; + }, + ), + ) + ..loadRequest(Uri.parse('https://flutter.dev')); + // #enddocregion webview_controller + } + + // #docregion webview_widget + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Flutter Simple Example')), + body: WebViewWidget(controller: controller), + ); + } + // #enddocregion webview_widget +} diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Flutter/Flutter-Debug.xcconfig b/local_packages/webview_flutter-4.13.0/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Flutter/Flutter-Release.xcconfig b/local_packages/webview_flutter-4.13.0/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Podfile b/local_packages/webview_flutter-4.13.0/example/macos/Podfile new file mode 100644 index 0000000..ae77cc1 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/macos/Podfile @@ -0,0 +1,39 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner.xcodeproj/project.pbxproj b/local_packages/webview_flutter-4.13.0/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..d4d8cfd --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,654 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 2EB4149157BBD3A430F11F07 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8F0018C81FCBA5B69ABBF5D1 /* Pods_Runner.framework */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* webview_flutter_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = webview_flutter_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 5F8C8F28798E2C13759B0247 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 667DA8CACF6C1923CDF6309D /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 7AF549A34A225DB69555B81E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 8F0018C81FCBA5B69ABBF5D1 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9161E5D15A39CAD8BDCA3648 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + EA616C3C53C91DA835A1F62D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + F3B4A758F2F46A632FB5B88A /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F927ED1AFD55FE3E39FF121E /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + 2EB4149157BBD3A430F11F07 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 394ADD5AAA21B9C50952CBB6 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* webview_flutter_example.app */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + 394ADD5AAA21B9C50952CBB6 /* Pods */ = { + isa = PBXGroup; + children = ( + 7AF549A34A225DB69555B81E /* Pods-Runner.debug.xcconfig */, + 9161E5D15A39CAD8BDCA3648 /* Pods-Runner.release.xcconfig */, + EA616C3C53C91DA835A1F62D /* Pods-Runner.profile.xcconfig */, + F927ED1AFD55FE3E39FF121E /* Pods-RunnerTests.debug.xcconfig */, + 5F8C8F28798E2C13759B0247 /* Pods-RunnerTests.release.xcconfig */, + 667DA8CACF6C1923CDF6309D /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 8F0018C81FCBA5B69ABBF5D1 /* Pods_Runner.framework */, + F3B4A758F2F46A632FB5B88A /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 25920929D4636EAA1B54D439 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* webview_flutter_example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 25920929D4636EAA1B54D439 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPat_icon_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPat_icon_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPat_icon_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/webview_flutter-4.13.0/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/local_packages/webview_flutter-4.13.0/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..d8fafbb --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/local_packages/webview_flutter-4.13.0/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_packages/webview_flutter-4.13.0/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner/AppDelegate.swift b/local_packages/webview_flutter-4.13.0/example/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..21fbd02 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,17 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2f59618 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_at_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_at_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_at_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_at_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_at_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_at_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_at_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_at_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_at_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_at_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner/Base.lproj/MainMenu.xib b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner/Configs/AppInfo.xcconfig b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..aaf1573 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = webview_flutter_example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.webview_flutter_example + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2024 dev.flutter.plugins. All rights reserved. diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner/Configs/Debug.xcconfig b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner/Configs/Release.xcconfig b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner/Configs/Warnings.xcconfig b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner/DebugProfile.entitlements b/local_packages/webview_flutter-4.13.0/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..08c3ab1 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + com.apple.security.network.client + + + diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner/Info.plist b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner/MainFlutterWindow.swift b/local_packages/webview_flutter-4.13.0/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..f219089 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,19 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/local_packages/webview_flutter-4.13.0/example/macos/Runner/Release.entitlements b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Release.entitlements new file mode 100644 index 0000000..ee95ab7 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/macos/Runner/Release.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/local_packages/webview_flutter-4.13.0/example/pubspec.yaml b/local_packages/webview_flutter-4.13.0/example/pubspec.yaml new file mode 100644 index 0000000..ca1e3b9 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/pubspec.yaml @@ -0,0 +1,38 @@ +name: webview_flutter_example +description: Demonstrates how to use the webview_flutter plugin. +publish_to: none + +environment: + sdk: ^3.4.0 + flutter: ">=3.22.0" + +dependencies: + flutter: + sdk: flutter + path_provider: ^2.0.6 + webview_flutter: + # When depending on this package from a real application you should use: + # webview_flutter: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + webview_flutter_android: ^4.7.0 + webview_flutter_wkwebview: ^3.22.0 + +dev_dependencies: + build_runner: ^2.1.5 + espresso: ^0.4.0 + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + webview_flutter_platform_interface: ^2.13.0 + +flutter: + uses-material-design: true + assets: + - assets/sample_audio.ogg + - assets/sample_video.mp4 + - assets/www/index.html + - assets/www/styles/style.css diff --git a/local_packages/webview_flutter-4.13.0/example/test/main_test.dart b/local_packages/webview_flutter-4.13.0/example/test/main_test.dart new file mode 100644 index 0000000..4bb7885 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/test/main_test.dart @@ -0,0 +1,127 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:webview_flutter_example/main.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +void main() { + setUp(() { + WebViewPlatform.instance = FakeWebViewPlatform(); + }); + + testWidgets('Test snackbar from ScaffoldMessenger', + (WidgetTester tester) async { + await tester.pumpWidget(const MaterialApp(home: WebViewExample())); + expect(find.byIcon(Icons.favorite), findsOneWidget); + await tester.tap(find.byIcon(Icons.favorite)); + await tester.pump(); + expect(find.byType(SnackBar), findsOneWidget); + }); +} + +class FakeWebViewPlatform extends WebViewPlatform { + @override + PlatformWebViewController createPlatformWebViewController( + PlatformWebViewControllerCreationParams params, + ) { + return FakeWebViewController(params); + } + + @override + PlatformWebViewWidget createPlatformWebViewWidget( + PlatformWebViewWidgetCreationParams params, + ) { + return FakeWebViewWidget(params); + } + + @override + PlatformWebViewCookieManager createPlatformCookieManager( + PlatformWebViewCookieManagerCreationParams params, + ) { + return FakeCookieManager(params); + } + + @override + PlatformNavigationDelegate createPlatformNavigationDelegate( + PlatformNavigationDelegateCreationParams params, + ) { + return FakeNavigationDelegate(params); + } +} + +class FakeWebViewController extends PlatformWebViewController { + FakeWebViewController(super.params) : super.implementation(); + + @override + Future setJavaScriptMode(JavaScriptMode javaScriptMode) async {} + + @override + Future setBackgroundColor(Color color) async {} + + @override + Future setPlatformNavigationDelegate( + PlatformNavigationDelegate handler, + ) async {} + + @override + Future addJavaScriptChannel( + JavaScriptChannelParams javaScriptChannelParams) async {} + + @override + Future loadRequest(LoadRequestParams params) async {} + + @override + Future currentUrl() async { + return 'https://www.google.com'; + } +} + +class FakeCookieManager extends PlatformWebViewCookieManager { + FakeCookieManager(super.params) : super.implementation(); +} + +class FakeWebViewWidget extends PlatformWebViewWidget { + FakeWebViewWidget(super.params) : super.implementation(); + + @override + Widget build(BuildContext context) { + return Container(); + } +} + +class FakeNavigationDelegate extends PlatformNavigationDelegate { + FakeNavigationDelegate(super.params) : super.implementation(); + + @override + Future setOnNavigationRequest( + NavigationRequestCallback onNavigationRequest, + ) async {} + + @override + Future setOnPageFinished(PageEventCallback onPageFinished) async {} + + @override + Future setOnPageStarted(PageEventCallback onPageStarted) async {} + + @override + Future setOnProgress(ProgressCallback onProgress) async {} + + @override + Future setOnWebResourceError( + WebResourceErrorCallback onWebResourceError, + ) async {} + + @override + Future setOnUrlChange(UrlChangeCallback onUrlChange) async {} + + @override + Future setOnHttpAuthRequest( + HttpAuthRequestCallback handler, + ) async {} + + @override + Future setOnHttpError(HttpResponseErrorCallback onHttpError) async {} +} diff --git a/local_packages/webview_flutter-4.13.0/example/test_driver/integration_test.dart b/local_packages/webview_flutter-4.13.0/example/test_driver/integration_test.dart new file mode 100644 index 0000000..4f10f2a --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/example/test_driver/integration_test.dart @@ -0,0 +1,7 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); diff --git a/local_packages/webview_flutter-4.13.0/lib/src/legacy/platform_interface.dart b/local_packages/webview_flutter-4.13.0/lib/src/legacy/platform_interface.dart new file mode 100644 index 0000000..f346d35 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/lib/src/legacy/platform_interface.dart @@ -0,0 +1,29 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// Re-export the classes from the webview_flutter_platform_interface through +/// the `platform_interface.dart` file so we don't accidentally break any +/// non-endorsed existing implementations of the interface. +library; + +export 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart' + show + AutoMediaPlaybackPolicy, + CreationParams, + JavascriptChannel, + JavascriptChannelRegistry, + JavascriptMessage, + JavascriptMessageHandler, + JavascriptMode, + WebResourceError, + WebResourceErrorType, + WebSetting, + WebSettings, + WebViewCookie, + WebViewPlatform, + WebViewPlatformCallbacksHandler, + WebViewPlatformController, + WebViewPlatformCreatedCallback, + WebViewRequest, + WebViewRequestMethod; diff --git a/local_packages/webview_flutter-4.13.0/lib/src/legacy/webview.dart b/local_packages/webview_flutter-4.13.0/lib/src/legacy/webview.dart new file mode 100644 index 0000000..0dddb2d --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/lib/src/legacy/webview.dart @@ -0,0 +1,829 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/widgets.dart'; +// ignore: implementation_imports +import 'package:webview_flutter_android/src/webview_flutter_android_legacy.dart'; +// ignore: implementation_imports +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; +// ignore: implementation_imports +import 'package:webview_flutter_wkwebview/src/webview_flutter_wkwebview_legacy.dart'; + +/// Optional callback invoked when a web view is first created. [controller] is +/// the [WebViewController] for the created web view. +typedef WebViewCreatedCallback = void Function(WebViewController controller); + +/// Information about a navigation action that is about to be executed. +class NavigationRequest { + NavigationRequest._({required this.url, required this.isForMainFrame}); + + /// The URL that will be loaded if the navigation is executed. + final String url; + + /// Whether the navigation request is to be loaded as the main frame. + final bool isForMainFrame; + + @override + String toString() { + return 'NavigationRequest(url: $url, isForMainFrame: $isForMainFrame)'; + } +} + +/// A decision on how to handle a navigation request. +enum NavigationDecision { + /// Prevent the navigation from taking place. + prevent, + + /// Allow the navigation to take place. + navigate, +} + +/// Decides how to handle a specific navigation request. +/// +/// The returned [NavigationDecision] determines how the navigation described by +/// `navigation` should be handled. +/// +/// See also: [WebView.navigationDelegate]. +typedef NavigationDelegate = FutureOr Function( + NavigationRequest navigation); + +/// Signature for when a [WebView] has started loading a page. +typedef PageStartedCallback = void Function(String url); + +/// Signature for when a [WebView] has finished loading a page. +typedef PageFinishedCallback = void Function(String url); + +/// Signature for when a [WebView] is loading a page. +typedef PageLoadingCallback = void Function(int progress); + +/// Signature for when a [WebView] has failed to load a resource. +typedef WebResourceErrorCallback = void Function(WebResourceError error); + +/// A web view widget for showing html content. +/// +/// There is a known issue that on iOS 13.4 and 13.5, other flutter widgets covering +/// the `WebView` is not able to block the `WebView` from receiving touch events. +/// See https://github.com/flutter/flutter/issues/53490. +class WebView extends StatefulWidget { + /// Creates a new web view. + /// + /// The web view can be controlled using a `WebViewController` that is passed to the + /// `onWebViewCreated` callback once the web view is created. + /// + /// The `javascriptMode` and `autoMediaPlaybackPolicy` parameters must not be null. + const WebView({ + super.key, + this.onWebViewCreated, + this.initialUrl, + this.initialCookies = const [], + this.javascriptMode = JavascriptMode.disabled, + this.javascriptChannels, + this.navigationDelegate, + this.gestureRecognizers, + this.onPageStarted, + this.onPageFinished, + this.onProgress, + this.onWebResourceError, + this.debuggingEnabled = false, + this.gestureNavigationEnabled = false, + this.userAgent, + this.zoomEnabled = true, + this.initialMediaPlaybackPolicy = + AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, + this.allowsInlineMediaPlayback = false, + this.backgroundColor, + }); + + static WebViewPlatform? _platform; + + /// Sets a custom [WebViewPlatform]. + /// + /// This property can be set to use a custom platform implementation for WebViews. + /// + /// Setting `platform` doesn't affect [WebView]s that were already created. + /// + /// The default value is [AndroidWebView] on Android and [CupertinoWebView] on iOS. + static set platform(WebViewPlatform? platform) { + _platform = platform; + } + + /// The WebView platform that's used by this WebView. + /// + /// The default value is [SurfaceAndroidWebView] on Android and [CupertinoWebView] on iOS. + static WebViewPlatform get platform { + if (_platform == null) { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + _platform = SurfaceAndroidWebView(); + case TargetPlatform.iOS: + _platform = CupertinoWebView(); + // ignore: no_default_cases + default: + throw UnsupportedError( + "Trying to use the default webview implementation for $defaultTargetPlatform but there isn't a default one"); + } + } + return _platform!; + } + + /// If not null invoked once the web view is created. + final WebViewCreatedCallback? onWebViewCreated; + + /// Which gestures should be consumed by the web view. + /// + /// It is possible for other gesture recognizers to be competing with the web view on pointer + /// events, e.g. if the web view is inside a [ListView] the [ListView] will want to handle + /// vertical drags. The web view will claim gestures that are recognized by any of the + /// recognizers on this list. + /// + /// When this set is empty or null, the web view will only handle pointer events for gestures that + /// were not claimed by any other gesture recognizer. + final Set>? gestureRecognizers; + + /// The initial URL to load. + final String? initialUrl; + + /// The initial cookies to set. + final List initialCookies; + + /// Whether JavaScript execution is enabled. + final JavascriptMode javascriptMode; + + /// The set of [JavascriptChannel]s available to JavaScript code running in the web view. + /// + /// For each [JavascriptChannel] in the set, a channel object is made available for the + /// JavaScript code in a window property named [JavascriptChannel.name]. + /// The JavaScript code can then call `postMessage` on that object to send a message that will be + /// passed to [JavascriptChannel.onMessageReceived]. + /// + /// For example for the following JavascriptChannel: + /// + /// ```dart + /// JavascriptChannel(name: 'Print', onMessageReceived: (JavascriptMessage message) { print(message.message); }); + /// ``` + /// + /// JavaScript code can call: + /// + /// ```javascript + /// Print.postMessage('Hello'); + /// ``` + /// + /// To asynchronously invoke the message handler which will print the message to standard output. + /// + /// Adding a new JavaScript channel only takes effect after the next page is loaded. + /// + /// Set values must not be null. A [JavascriptChannel.name] cannot be the same for multiple + /// channels in the list. + /// + /// A null value is equivalent to an empty set. + final Set? javascriptChannels; + + /// A delegate function that decides how to handle navigation actions. + /// + /// When a navigation is initiated by the WebView (e.g when a user clicks a link) + /// this delegate is called and has to decide how to proceed with the navigation. + /// + /// See [NavigationDecision] for possible decisions the delegate can take. + /// + /// When null all navigation actions are allowed. + /// + /// Caveats on Android: + /// + /// * Navigation actions targeted to the main frame can be intercepted, + /// navigation actions targeted to subframes are allowed regardless of the value + /// returned by this delegate. + /// * Setting a navigationDelegate makes the WebView treat all navigations as if they were + /// triggered by a user gesture, this disables some of Chromium's security mechanisms. + /// A navigationDelegate should only be set when loading trusted content. + /// * On Android WebView versions earlier than 67(most devices running at least Android L+ should have + /// a later version): + /// * When a navigationDelegate is set pages with frames are not properly handled by the + /// webview, and frames will be opened in the main frame. + /// * When a navigationDelegate is set HTTP requests do not include the HTTP referer header. + final NavigationDelegate? navigationDelegate; + + /// Controls whether inline playback of HTML5 videos is allowed on iOS. + /// + /// This field is ignored on Android because Android allows it by default. + /// + /// By default `allowsInlineMediaPlayback` is false. + final bool allowsInlineMediaPlayback; + + /// Invoked when a page starts loading. + final PageStartedCallback? onPageStarted; + + /// Invoked when a page has finished loading. + /// + /// This is invoked only for the main frame. + /// + /// When [onPageFinished] is invoked on Android, the page being rendered may + /// not be updated yet. + /// + /// When invoked on iOS or Android, any JavaScript code that is embedded + /// directly in the HTML has been loaded and code injected with + /// [WebViewController.runJavascript] or [WebViewController.runJavascriptReturningResult] can assume this. + final PageFinishedCallback? onPageFinished; + + /// Invoked when a page is loading. + final PageLoadingCallback? onProgress; + + /// Invoked when a web resource has failed to load. + /// + /// This callback is only called for the main page. + final WebResourceErrorCallback? onWebResourceError; + + /// Controls whether WebView debugging is enabled. + /// + /// Setting this to true enables [WebView debugging on Android](https://developers.google.com/web/tools/chrome-devtools/remote-debugging/). + /// + /// WebView debugging is enabled by default in dev builds on iOS. + /// + /// To debug WebViews on iOS: + /// - Enable developer options (Open Safari, go to Preferences -> Advanced and make sure "Show Develop Menu in Menubar" is on.) + /// - From the Menu-bar (of Safari) select Develop -> iPhone Simulator -> + /// + /// By default `debuggingEnabled` is false. + final bool debuggingEnabled; + + /// A Boolean value indicating whether horizontal swipe gestures will trigger back-forward list navigations. + /// + /// This only works on iOS. + /// + /// By default `gestureNavigationEnabled` is false. + final bool gestureNavigationEnabled; + + /// The value used for the HTTP User-Agent: request header. + /// + /// When null the platform's webview default is used for the User-Agent header. + /// + /// When the [WebView] is rebuilt with a different `userAgent`, the page reloads and the request uses the new User Agent. + /// + /// When [WebViewController.goBack] is called after changing `userAgent` the previous `userAgent` value is used until the page is reloaded. + /// + /// This field is ignored on iOS versions prior to 9 as the platform does not support a custom + /// user agent. + /// + /// By default `userAgent` is null. + final String? userAgent; + + /// A Boolean value indicating whether the WebView should support zooming + /// using its on-screen zoom controls and gestures. + /// + /// *Note: On iOS [javascriptMode] must be set to + /// [JavascriptMode.unrestricted] in order to set [zoomEnabled] to false + /// + /// By default 'zoomEnabled' is true + final bool zoomEnabled; + + /// Which restrictions apply on automatic media playback. + /// + /// This initial value is applied to the platform's webview upon creation. Any following + /// changes to this parameter are ignored (as long as the state of the [WebView] is preserved). + /// + /// The default policy is [AutoMediaPlaybackPolicy.require_user_action_for_all_media_types]. + final AutoMediaPlaybackPolicy initialMediaPlaybackPolicy; + + /// The background color of the [WebView]. + /// + /// When `null` the platform's webview default background color is used. By + /// default [backgroundColor] is `null`. + final Color? backgroundColor; + + @override + State createState() => _WebViewState(); +} + +class _WebViewState extends State { + final Completer _controller = + Completer(); + + late JavascriptChannelRegistry _javascriptChannelRegistry; + late _PlatformCallbacksHandler _platformCallbacksHandler; + + @override + Widget build(BuildContext context) { + return WebView.platform.build( + context: context, + onWebViewPlatformCreated: _onWebViewPlatformCreated, + webViewPlatformCallbacksHandler: _platformCallbacksHandler, + javascriptChannelRegistry: _javascriptChannelRegistry, + gestureRecognizers: widget.gestureRecognizers, + creationParams: _creationParamsFromWidget(widget), + ); + } + + @override + void initState() { + super.initState(); + _assertJavascriptChannelNamesAreUnique(); + _platformCallbacksHandler = _PlatformCallbacksHandler(widget); + _javascriptChannelRegistry = + JavascriptChannelRegistry(widget.javascriptChannels); + } + + @override + void didUpdateWidget(WebView oldWidget) { + super.didUpdateWidget(oldWidget); + _assertJavascriptChannelNamesAreUnique(); + _controller.future.then((WebViewController controller) { + _platformCallbacksHandler._widget = widget; + controller._updateWidget(widget); + }); + } + + void _onWebViewPlatformCreated(WebViewPlatformController? webViewPlatform) { + final WebViewController controller = WebViewController._( + widget, + webViewPlatform!, + _javascriptChannelRegistry, + ); + _controller.complete(controller); + if (widget.onWebViewCreated != null) { + widget.onWebViewCreated!(controller); + } + } + + void _assertJavascriptChannelNamesAreUnique() { + if (widget.javascriptChannels == null || + widget.javascriptChannels!.isEmpty) { + return; + } + assert(_extractChannelNames(widget.javascriptChannels).length == + widget.javascriptChannels!.length); + } +} + +CreationParams _creationParamsFromWidget(WebView widget) { + return CreationParams( + initialUrl: widget.initialUrl, + webSettings: _webSettingsFromWidget(widget), + javascriptChannelNames: _extractChannelNames(widget.javascriptChannels), + userAgent: widget.userAgent, + autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy, + backgroundColor: widget.backgroundColor, + cookies: widget.initialCookies, + ); +} + +WebSettings _webSettingsFromWidget(WebView widget) { + return WebSettings( + javascriptMode: widget.javascriptMode, + hasNavigationDelegate: widget.navigationDelegate != null, + hasProgressTracking: widget.onProgress != null, + debuggingEnabled: widget.debuggingEnabled, + gestureNavigationEnabled: widget.gestureNavigationEnabled, + allowsInlineMediaPlayback: widget.allowsInlineMediaPlayback, + userAgent: WebSetting.of(widget.userAgent), + zoomEnabled: widget.zoomEnabled, + ); +} + +// This method assumes that no fields in `currentValue` are null. +WebSettings _clearUnchangedWebSettings( + WebSettings currentValue, WebSettings newValue) { + assert(currentValue.javascriptMode != null); + assert(currentValue.hasNavigationDelegate != null); + assert(currentValue.hasProgressTracking != null); + assert(currentValue.debuggingEnabled != null); + assert(newValue.javascriptMode != null); + assert(newValue.hasNavigationDelegate != null); + assert(newValue.debuggingEnabled != null); + assert(newValue.zoomEnabled != null); + + JavascriptMode? javascriptMode; + bool? hasNavigationDelegate; + bool? hasProgressTracking; + bool? debuggingEnabled; + WebSetting userAgent = const WebSetting.absent(); + bool? zoomEnabled; + if (currentValue.javascriptMode != newValue.javascriptMode) { + javascriptMode = newValue.javascriptMode; + } + if (currentValue.hasNavigationDelegate != newValue.hasNavigationDelegate) { + hasNavigationDelegate = newValue.hasNavigationDelegate; + } + if (currentValue.hasProgressTracking != newValue.hasProgressTracking) { + hasProgressTracking = newValue.hasProgressTracking; + } + if (currentValue.debuggingEnabled != newValue.debuggingEnabled) { + debuggingEnabled = newValue.debuggingEnabled; + } + if (currentValue.userAgent != newValue.userAgent) { + userAgent = newValue.userAgent; + } + if (currentValue.zoomEnabled != newValue.zoomEnabled) { + zoomEnabled = newValue.zoomEnabled; + } + + return WebSettings( + javascriptMode: javascriptMode, + hasNavigationDelegate: hasNavigationDelegate, + hasProgressTracking: hasProgressTracking, + debuggingEnabled: debuggingEnabled, + userAgent: userAgent, + zoomEnabled: zoomEnabled, + ); +} + +Set _extractChannelNames(Set? channels) { + final Set channelNames = channels == null + ? {} + : channels.map((JavascriptChannel channel) => channel.name).toSet(); + return channelNames; +} + +class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { + _PlatformCallbacksHandler(this._widget); + + WebView _widget; + + @override + FutureOr onNavigationRequest({ + required String url, + required bool isForMainFrame, + }) async { + final NavigationRequest request = + NavigationRequest._(url: url, isForMainFrame: isForMainFrame); + final bool allowNavigation = _widget.navigationDelegate == null || + await _widget.navigationDelegate!(request) == + NavigationDecision.navigate; + return allowNavigation; + } + + @override + void onPageStarted(String url) { + if (_widget.onPageStarted != null) { + _widget.onPageStarted!(url); + } + } + + @override + void onPageFinished(String url) { + if (_widget.onPageFinished != null) { + _widget.onPageFinished!(url); + } + } + + @override + void onProgress(int progress) { + if (_widget.onProgress != null) { + _widget.onProgress!(progress); + } + } + + @override + void onWebResourceError(WebResourceError error) { + if (_widget.onWebResourceError != null) { + _widget.onWebResourceError!(error); + } + } +} + +/// Controls a [WebView]. +/// +/// A [WebViewController] instance can be obtained by setting the [WebView.onWebViewCreated] +/// callback for a [WebView] widget. +class WebViewController { + WebViewController._( + this._widget, + this._webViewPlatformController, + this._javascriptChannelRegistry, + ) { + _settings = _webSettingsFromWidget(_widget); + } + + final WebViewPlatformController _webViewPlatformController; + final JavascriptChannelRegistry _javascriptChannelRegistry; + + late WebSettings _settings; + + WebView _widget; + + /// Loads the file located at the specified [absoluteFilePath]. + /// + /// The [absoluteFilePath] parameter should contain the absolute path to the + /// file as it is stored on the device. For example: + /// `/Users/username/Documents/www/index.html`. + /// + /// Throws an ArgumentError if the [absoluteFilePath] does not exist. + Future loadFile( + String absoluteFilePath, + ) { + assert(absoluteFilePath.isNotEmpty); + return _webViewPlatformController.loadFile(absoluteFilePath); + } + + /// Loads the Flutter asset specified in the pubspec.yaml file. + /// + /// Throws an ArgumentError if [key] is not part of the specified assets + /// in the pubspec.yaml file. + Future loadFlutterAsset(String key) { + assert(key.isNotEmpty); + return _webViewPlatformController.loadFlutterAsset(key); + } + + /// Loads the supplied HTML string. + /// + /// The [baseUrl] parameter is used when resolving relative URLs within the + /// HTML string. + Future loadHtmlString( + String html, { + String? baseUrl, + }) { + assert(html.isNotEmpty); + return _webViewPlatformController.loadHtmlString( + html, + baseUrl: baseUrl, + ); + } + + /// Loads the specified URL. + /// + /// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will + /// be added as key value pairs of HTTP headers for the request. + /// + /// `url` must not be null. + /// + /// Throws an ArgumentError if `url` is not a valid URL string. + Future loadUrl( + String url, { + Map? headers, + }) async { + _validateUrlString(url); + return _webViewPlatformController.loadUrl(url, headers); + } + + /// Makes a specific HTTP request and loads the response in the webview. + /// + /// [WebViewRequest.method] must be one of the supported HTTP methods + /// in [WebViewRequestMethod]. + /// + /// If [WebViewRequest.headers] is not empty, its key-value pairs will be + /// added as the headers for the request. + /// + /// If [WebViewRequest.body] is not null, it will be added as the body + /// for the request. + /// + /// Throws an ArgumentError if [WebViewRequest.uri] has empty scheme. + /// + /// Android only: + /// When making a POST request, headers are ignored. As a workaround, make + /// the request manually and load the response data using [loadHTMLString]. + Future loadRequest(WebViewRequest request) async { + return _webViewPlatformController.loadRequest(request); + } + + /// Accessor to the current URL that the WebView is displaying. + /// + /// If [WebView.initialUrl] was never specified, returns `null`. + /// Note that this operation is asynchronous, and it is possible that the + /// current URL changes again by the time this function returns (in other + /// words, by the time this future completes, the WebView may be displaying a + /// different URL). + Future currentUrl() { + return _webViewPlatformController.currentUrl(); + } + + /// Checks whether there's a back history item. + /// + /// Note that this operation is asynchronous, and it is possible that the "canGoBack" state has + /// changed by the time the future completed. + Future canGoBack() { + return _webViewPlatformController.canGoBack(); + } + + /// Checks whether there's a forward history item. + /// + /// Note that this operation is asynchronous, and it is possible that the "canGoForward" state has + /// changed by the time the future completed. + Future canGoForward() { + return _webViewPlatformController.canGoForward(); + } + + /// Goes back in the history of this WebView. + /// + /// If there is no back history item this is a no-op. + Future goBack() { + return _webViewPlatformController.goBack(); + } + + /// Goes forward in the history of this WebView. + /// + /// If there is no forward history item this is a no-op. + Future goForward() { + return _webViewPlatformController.goForward(); + } + + /// Reloads the current URL. + Future reload() { + return _webViewPlatformController.reload(); + } + + /// Clears all caches used by the [WebView]. + /// + /// The following caches are cleared: + /// 1. Browser HTTP Cache. + /// 2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) caches. + /// These are not yet supported in iOS WkWebView. Service workers tend to use this cache. + /// 3. Application cache. + /// 4. Local Storage. + /// + /// Note: Calling this method also triggers a reload. + Future clearCache() async { + await _webViewPlatformController.clearCache(); + return reload(); + } + + Future _updateJavascriptChannels( + Set? newChannels) async { + final Set currentChannels = + _javascriptChannelRegistry.channels.keys.toSet(); + final Set newChannelNames = _extractChannelNames(newChannels); + final Set channelsToAdd = + newChannelNames.difference(currentChannels); + final Set channelsToRemove = + currentChannels.difference(newChannelNames); + if (channelsToRemove.isNotEmpty) { + await _webViewPlatformController + .removeJavascriptChannels(channelsToRemove); + } + if (channelsToAdd.isNotEmpty) { + await _webViewPlatformController.addJavascriptChannels(channelsToAdd); + } + _javascriptChannelRegistry.updateJavascriptChannelsFromSet(newChannels); + } + + Future _updateWidget(WebView widget) async { + _widget = widget; + await _updateSettings(_webSettingsFromWidget(widget)); + await _updateJavascriptChannels(widget.javascriptChannels); + } + + Future _updateSettings(WebSettings newSettings) { + final WebSettings update = + _clearUnchangedWebSettings(_settings, newSettings); + _settings = newSettings; + return _webViewPlatformController.updateSettings(update); + } + + /// Evaluates a JavaScript expression in the context of the current page. + /// + /// On Android returns the evaluation result as a JSON formatted string. + /// + /// On iOS depending on the value type the return value would be one of: + /// + /// - For primitive JavaScript types: the value string formatted + /// (e.g JavaScript 100 returns '100'). + /// - For JavaScript arrays of supported types: a string formatted NSArray + /// (e.g '(1,2,3), note that the string for NSArray is formatted and might + /// contain newlines and extra spaces.'). + /// - Other non-primitive types are not supported on iOS and will complete + /// the Future with an error. + /// + /// The Future completes with an error if a JavaScript error occurred, + /// or on iOS, if the type of the evaluated expression is + /// not supported as described above. + /// + /// When evaluating JavaScript in a [WebView], it is best practice to wait for + /// the [WebView.onPageFinished] callback. This guarantees all the JavaScript + /// embedded in the main frame HTML has been loaded. + @Deprecated('Use [runJavascript] or [runJavascriptReturningResult]') + Future evaluateJavascript(String javascriptString) { + if (_settings.javascriptMode == JavascriptMode.disabled) { + return Future.error(FlutterError( + 'JavaScript mode must be enabled/unrestricted when calling evaluateJavascript.')); + } + return _webViewPlatformController.evaluateJavascript(javascriptString); + } + + /// Runs the given JavaScript in the context of the current page. + /// If you are looking for the result, use [runJavascriptReturningResult] instead. + /// The Future completes with an error if a JavaScript error occurred. + /// + /// When running JavaScript in a [WebView], it is best practice to wait for + /// the [WebView.onPageFinished] callback. This guarantees all the JavaScript + /// embedded in the main frame HTML has been loaded. + Future runJavascript(String javaScriptString) { + if (_settings.javascriptMode == JavascriptMode.disabled) { + return Future.error(FlutterError( + 'JavaScript mode must be enabled/unrestricted when calling runJavascript.')); + } + return _webViewPlatformController.runJavascript(javaScriptString); + } + + /// Runs the given JavaScript in the context of the current page, + /// and returns the result. + /// + /// On Android returns the evaluation result as a JSON formatted string. + /// + /// On iOS depending on the value type the return value would be one of: + /// + /// - For primitive JavaScript types: the value string formatted + /// (e.g JavaScript 100 returns '100'). + /// - For JavaScript arrays of supported types: a string formatted NSArray + /// (e.g '(1,2,3), note that the string for NSArray is formatted and might + /// contain newlines and extra spaces.'). + /// + /// The Future completes with an error if a JavaScript error occurred, + /// or if the type the given expression evaluates to is unsupported. + /// Unsupported values include certain non primitive types on iOS, as well as + /// `undefined` or `null` on iOS 14+. + /// + /// When evaluating JavaScript in a [WebView], it is best practice to wait + /// for the [WebView.onPageFinished] callback. This guarantees all the + /// JavaScript embedded in the main frame HTML has been loaded. + Future runJavascriptReturningResult(String javaScriptString) { + if (_settings.javascriptMode == JavascriptMode.disabled) { + return Future.error(FlutterError( + 'JavaScript mode must be enabled/unrestricted when calling runJavascriptReturningResult.')); + } + return _webViewPlatformController + .runJavascriptReturningResult(javaScriptString); + } + + /// Returns the title of the currently loaded page. + Future getTitle() { + return _webViewPlatformController.getTitle(); + } + + /// Sets the WebView's content scroll position. + /// + /// The parameters `x` and `y` specify the scroll position in WebView pixels. + Future scrollTo(int x, int y) { + return _webViewPlatformController.scrollTo(x, y); + } + + /// Move the scrolled position of this view. + /// + /// The parameters `x` and `y` specify the amount of WebView pixels to scroll by horizontally and vertically respectively. + Future scrollBy(int x, int y) { + return _webViewPlatformController.scrollBy(x, y); + } + + /// Return the horizontal scroll position, in WebView pixels, of this view. + /// + /// Scroll position is measured from left. + Future getScrollX() { + return _webViewPlatformController.getScrollX(); + } + + /// Return the vertical scroll position, in WebView pixels, of this view. + /// + /// Scroll position is measured from top. + Future getScrollY() { + return _webViewPlatformController.getScrollY(); + } +} + +/// Manages cookies pertaining to all [WebView]s. +class CookieManager { + /// Creates a [CookieManager] -- returns the instance if it's already been called. + factory CookieManager() { + return _instance ??= CookieManager._(); + } + + CookieManager._() { + if (WebViewCookieManagerPlatform.instance == null) { + if (Platform.isAndroid) { + WebViewCookieManagerPlatform.instance = WebViewAndroidCookieManager(); + } else if (Platform.isIOS) { + WebViewCookieManagerPlatform.instance = WKWebViewCookieManager(); + } else { + throw AssertionError( + 'This platform is currently unsupported by webview_flutter.'); + } + } + } + + static CookieManager? _instance; + + /// Clears all cookies for all [WebView] instances. + /// + /// Returns true if cookies were present before clearing, else false. + Future clearCookies() => + WebViewCookieManagerPlatform.instance!.clearCookies(); + + /// Sets a cookie for all [WebView] instances. + /// + /// This is a no op on iOS versions below 11. + Future setCookie(WebViewCookie cookie) => + WebViewCookieManagerPlatform.instance!.setCookie(cookie); +} + +// Throws an ArgumentError if `url` is not a valid URL string. +void _validateUrlString(String url) { + try { + final Uri uri = Uri.parse(url); + if (uri.scheme.isEmpty) { + throw ArgumentError('Missing scheme in URL string: "$url"'); + } + } on FormatException catch (e) { + throw ArgumentError(e); + } +} diff --git a/local_packages/webview_flutter-4.13.0/lib/src/navigation_delegate.dart b/local_packages/webview_flutter-4.13.0/lib/src/navigation_delegate.dart new file mode 100644 index 0000000..0256aec --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/lib/src/navigation_delegate.dart @@ -0,0 +1,252 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'webview_controller.dart'; + +/// Callbacks for accepting or rejecting navigation changes, and for tracking +/// the progress of navigation requests. +/// +/// See [WebViewController.setNavigationDelegate]. +/// +/// ## Platform-Specific Features +/// This class contains an underlying implementation provided by the current +/// platform. Once a platform implementation is imported, the examples below +/// can be followed to use features provided by a platform's implementation. +/// +/// {@macro webview_flutter.NavigationDelegate.fromPlatformCreationParams} +/// +/// Below is an example of accessing the platform-specific implementation for +/// iOS and Android: +/// +/// ```dart +/// final NavigationDelegate navigationDelegate = NavigationDelegate(); +/// +/// if (WebViewPlatform.instance is WebKitWebViewPlatform) { +/// final WebKitNavigationDelegate webKitDelegate = +/// navigationDelegate.platform as WebKitNavigationDelegate; +/// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { +/// final AndroidNavigationDelegate androidDelegate = +/// navigationDelegate.platform as AndroidNavigationDelegate; +/// } +/// ``` +class NavigationDelegate { + /// Constructs a [NavigationDelegate]. + /// + /// {@template webview_fluttter.NavigationDelegate.constructor} + /// **`onUrlChange`:** invoked when the underlying web view changes to a new url. + /// **`onHttpAuthRequest`:** invoked when the web view is requesting authentication. + /// **`onSslAuthError`:** Invoked when the web view receives a recoverable SSL + /// error for a certificate. The host application must call either + /// [SslAuthError.cancel] or [SslAuthError.proceed]. + /// {@endtemplate} + NavigationDelegate({ + FutureOr Function(NavigationRequest request)? + onNavigationRequest, + void Function(String url)? onPageStarted, + void Function(String url)? onPageFinished, + void Function(int progress)? onProgress, + void Function(WebResourceError error)? onWebResourceError, + void Function(UrlChange change)? onUrlChange, + void Function(HttpAuthRequest request)? onHttpAuthRequest, + void Function(HttpResponseError error)? onHttpError, + void Function(SslAuthError request)? onSslAuthError, + }) : this.fromPlatformCreationParams( + const PlatformNavigationDelegateCreationParams(), + onNavigationRequest: onNavigationRequest, + onPageStarted: onPageStarted, + onPageFinished: onPageFinished, + onProgress: onProgress, + onWebResourceError: onWebResourceError, + onUrlChange: onUrlChange, + onHttpAuthRequest: onHttpAuthRequest, + onHttpError: onHttpError, + onSslAuthError: onSslAuthError, + ); + + /// Constructs a [NavigationDelegate] from creation params for a specific + /// platform. + /// + /// {@macro webview_fluttter.NavigationDelegate.constructor} + /// + /// {@template webview_flutter.NavigationDelegate.fromPlatformCreationParams} + /// Below is an example of setting platform-specific creation parameters for + /// iOS and Android: + /// + /// ```dart + /// PlatformNavigationDelegateCreationParams params = + /// const PlatformNavigationDelegateCreationParams(); + /// + /// if (WebViewPlatform.instance is WebKitWebViewPlatform) { + /// params = WebKitNavigationDelegateCreationParams + /// .fromPlatformNavigationDelegateCreationParams( + /// params, + /// ); + /// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { + /// params = AndroidNavigationDelegateCreationParams + /// .fromPlatformNavigationDelegateCreationParams( + /// params, + /// ); + /// } + /// + /// final NavigationDelegate navigationDelegate = + /// NavigationDelegate.fromPlatformCreationParams( + /// params, + /// ); + /// ``` + /// {@endtemplate} + NavigationDelegate.fromPlatformCreationParams( + PlatformNavigationDelegateCreationParams params, { + FutureOr Function(NavigationRequest request)? + onNavigationRequest, + void Function(String url)? onPageStarted, + void Function(String url)? onPageFinished, + void Function(int progress)? onProgress, + void Function(WebResourceError error)? onWebResourceError, + void Function(UrlChange change)? onUrlChange, + void Function(HttpAuthRequest request)? onHttpAuthRequest, + void Function(HttpResponseError error)? onHttpError, + void Function(SslAuthError request)? onSslAuthError, + }) : this.fromPlatform( + PlatformNavigationDelegate(params), + onNavigationRequest: onNavigationRequest, + onPageStarted: onPageStarted, + onPageFinished: onPageFinished, + onProgress: onProgress, + onWebResourceError: onWebResourceError, + onUrlChange: onUrlChange, + onHttpAuthRequest: onHttpAuthRequest, + onHttpError: onHttpError, + onSslAuthError: onSslAuthError, + ); + + /// Constructs a [NavigationDelegate] from a specific platform implementation. + /// + /// {@macro webview_fluttter.NavigationDelegate.constructor} + NavigationDelegate.fromPlatform( + this.platform, { + this.onNavigationRequest, + this.onPageStarted, + this.onPageFinished, + this.onProgress, + this.onWebResourceError, + void Function(UrlChange change)? onUrlChange, + HttpAuthRequestCallback? onHttpAuthRequest, + void Function(HttpResponseError error)? onHttpError, + void Function(SslAuthError request)? onSslAuthError, + }) { + if (onNavigationRequest != null) { + platform.setOnNavigationRequest(onNavigationRequest!); + } + if (onPageStarted != null) { + platform.setOnPageStarted(onPageStarted!); + } + if (onPageFinished != null) { + platform.setOnPageFinished(onPageFinished!); + } + if (onProgress != null) { + platform.setOnProgress(onProgress!); + } + if (onWebResourceError != null) { + platform.setOnWebResourceError(onWebResourceError!); + } + if (onUrlChange != null) { + platform.setOnUrlChange(onUrlChange); + } + if (onHttpAuthRequest != null) { + platform.setOnHttpAuthRequest(onHttpAuthRequest); + } + if (onHttpError != null) { + platform.setOnHttpError(onHttpError); + } + if (onSslAuthError != null) { + platform.setOnSSlAuthError( + (PlatformSslAuthError error) { + onSslAuthError(SslAuthError._fromPlatform(error)); + }, + ); + } + } + + /// Implementation of [PlatformNavigationDelegate] for the current platform. + final PlatformNavigationDelegate platform; + + /// Invoked when a decision for a navigation request is pending. + /// + /// When a navigation is initiated by the WebView (e.g when a user clicks a + /// link) this delegate is called and has to decide how to proceed with the + /// navigation. + /// + /// *Important*: Some platforms may also trigger this callback from calls to + /// [WebViewController.loadRequest]. + /// + /// See [NavigationDecision]. + final NavigationRequestCallback? onNavigationRequest; + + /// Invoked when a page has started loading. + final PageEventCallback? onPageStarted; + + /// Invoked when a page has finished loading. + final PageEventCallback? onPageFinished; + + /// Invoked when a page is loading to report the progress. + final ProgressCallback? onProgress; + + /// Invoked when a resource loading error occurred. + final WebResourceErrorCallback? onWebResourceError; +} + +/// Represents an SSL error with the associated certificate. +/// +/// The host application must call [cancel] or, contrary to secure web +/// communication standards, [proceed] to provide the web view's response to the +/// error. [proceed] should generally only be used in test environments, as +/// using it in production can expose users to security and privacy risks. +/// +/// ## Platform-Specific Features +/// This class contains an underlying implementation provided by the current +/// platform. Once a platform implementation is imported, the examples below +/// can be followed to use features provided by a platform's implementation. +/// +/// Below is an example of accessing the platform-specific implementation for +/// iOS and Android: +/// +/// ```dart +/// final SslAuthError error = ...; +/// +/// if (WebViewPlatform.instance is WebKitWebViewPlatform) { +/// final WebKitSslAuthError webKitError = +/// error.platform as WebKitSslAuthError; +/// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { +/// final AndroidSslAuthError androidError = +/// error.platform as AndroidSslAuthError; +/// } +/// ``` +class SslAuthError { + SslAuthError._fromPlatform(this.platform); + + /// An implementation of [PlatformSslAuthError] for the current platform. + final PlatformSslAuthError platform; + + /// The certificate associated with this error. + X509Certificate? get certificate => platform.certificate; + + /// Instructs the WebView that encountered the SSL certificate error to + /// terminate communication with the server. + /// + /// The host application must call this method to prevent a resource from + /// loading when an SSL certificate is invalid. + Future cancel() => platform.cancel(); + + /// Instructs the WebView that encountered the SSL certificate error to ignore + /// the error and continue communicating with the server. + /// + /// **Warning:** Calling [proceed] in a production environment is strongly + /// discouraged, as an invalid SSL certificate means that the connection is + /// not secure, so proceeding can expose users to security and privacy risks. + Future proceed() => platform.proceed(); +} diff --git a/local_packages/webview_flutter-4.13.0/lib/src/webview_controller.dart b/local_packages/webview_flutter-4.13.0/lib/src/webview_controller.dart new file mode 100644 index 0000000..e5c1361 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/lib/src/webview_controller.dart @@ -0,0 +1,479 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'navigation_delegate.dart'; +import 'webview_widget.dart'; + +/// Controls a WebView provided by the host platform. +/// +/// Pass this to a [WebViewWidget] to display the WebView. +/// +/// A [WebViewController] can only be used by a single [WebViewWidget] at a +/// time. +/// +/// ## Platform-Specific Features +/// This class contains an underlying implementation provided by the current +/// platform. Once a platform implementation is imported, the examples below +/// can be followed to use features provided by a platform's implementation. +/// +/// {@macro webview_flutter.WebViewController.fromPlatformCreationParams} +/// +/// Below is an example of accessing the platform-specific implementation for +/// iOS and Android: +/// +/// ```dart +/// final WebViewController webViewController = WebViewController(); +/// +/// if (WebViewPlatform.instance is WebKitWebViewPlatform) { +/// final WebKitWebViewController webKitController = +/// webViewController.platform as WebKitWebViewController; +/// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { +/// final AndroidWebViewController androidController = +/// webViewController.platform as AndroidWebViewController; +/// } +/// ``` +class WebViewController { + /// Constructs a [WebViewController]. + /// + /// {@template webview_fluttter.WebViewController.constructor} + /// `onPermissionRequest`: A callback that notifies the host application that + /// web content is requesting permission to access the specified resources. + /// To grant access for a device resource, most platforms will need to update + /// their app configurations for the relevant system resource. + /// + /// For Android, you will need to update your `AndroidManifest.xml`. See + /// https://developer.android.com/training/permissions/declaring + /// + /// For iOS, you will need to update your `Info.plist`. See + /// https://developer.apple.com/documentation/uikit/protecting_the_user_s_privacy/requesting_access_to_protected_resources?language=objc. + /// {@endtemplate} + /// + /// See [WebViewController.fromPlatformCreationParams] for setting parameters + /// for a specific platform. + WebViewController({ + void Function(WebViewPermissionRequest request)? onPermissionRequest, + }) : this.fromPlatformCreationParams( + const PlatformWebViewControllerCreationParams(), + onPermissionRequest: onPermissionRequest, + ); + + /// Constructs a [WebViewController] from creation params for a specific + /// platform. + /// + /// {@macro webview_fluttter.WebViewController.constructor} + /// + /// {@template webview_flutter.WebViewController.fromPlatformCreationParams} + /// Below is an example of setting platform-specific creation parameters for + /// iOS and Android: + /// + /// ```dart + /// PlatformWebViewControllerCreationParams params = + /// const PlatformWebViewControllerCreationParams(); + /// + /// if (WebViewPlatform.instance is WebKitWebViewPlatform) { + /// params = WebKitWebViewControllerCreationParams + /// .fromPlatformWebViewControllerCreationParams( + /// params, + /// ); + /// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { + /// params = AndroidWebViewControllerCreationParams + /// .fromPlatformWebViewControllerCreationParams( + /// params, + /// ); + /// } + /// + /// final WebViewController webViewController = + /// WebViewController.fromPlatformCreationParams( + /// params, + /// ); + /// ``` + /// {@endtemplate} + WebViewController.fromPlatformCreationParams( + PlatformWebViewControllerCreationParams params, { + void Function(WebViewPermissionRequest request)? onPermissionRequest, + }) : this.fromPlatform( + PlatformWebViewController(params), + onPermissionRequest: onPermissionRequest, + ); + + /// Constructs a [WebViewController] from a specific platform implementation. + /// + /// {@macro webview_fluttter.WebViewController.constructor} + WebViewController.fromPlatform( + this.platform, { + void Function(WebViewPermissionRequest request)? onPermissionRequest, + }) { + if (onPermissionRequest != null) { + platform.setOnPlatformPermissionRequest( + (PlatformWebViewPermissionRequest request) { + onPermissionRequest(WebViewPermissionRequest._( + request, + types: request.types, + )); + }, + ); + } + } + + /// Implementation of [PlatformWebViewController] for the current platform. + final PlatformWebViewController platform; + + /// Loads the file located on the specified [absoluteFilePath]. + /// + /// The [absoluteFilePath] parameter should contain the absolute path to the + /// file as it is stored on the device. For example: + /// `/Users/username/Documents/www/index.html`. + /// + /// Throws a `PlatformException` if the [absoluteFilePath] does not exist. + Future loadFile(String absoluteFilePath) { + return platform.loadFile(absoluteFilePath); + } + + /// Loads the Flutter asset specified in the pubspec.yaml file. + /// + /// Throws a `PlatformException` if [key] is not part of the specified assets + /// in the pubspec.yaml file. + Future loadFlutterAsset(String key) { + assert(key.isNotEmpty); + return platform.loadFlutterAsset(key); + } + + /// Loads the supplied HTML string. + /// + /// The [baseUrl] parameter is used when resolving relative URLs within the + /// HTML string. + Future loadHtmlString(String html, {String? baseUrl}) { + assert(html.isNotEmpty); + return platform.loadHtmlString(html, baseUrl: baseUrl); + } + + /// Makes a specific HTTP request and loads the response in the webview. + /// + /// [method] must be one of the supported HTTP methods in [LoadRequestMethod]. + /// + /// If [headers] is not empty, its key-value pairs will be added as the + /// headers for the request. + /// + /// If [body] is not null, it will be added as the body for the request. + /// + /// Throws an ArgumentError if [uri] has an empty scheme. + Future loadRequest( + Uri uri, { + LoadRequestMethod method = LoadRequestMethod.get, + Map headers = const {}, + Uint8List? body, + }) { + if (uri.scheme.isEmpty) { + throw ArgumentError('Missing scheme in uri: $uri'); + } + return platform.loadRequest(LoadRequestParams( + uri: uri, + method: method, + headers: headers, + body: body, + )); + } + + /// Returns the current URL that the WebView is displaying. + /// + /// If no URL was ever loaded, returns `null`. + Future currentUrl() { + return platform.currentUrl(); + } + + /// Checks whether there's a back history item. + Future canGoBack() { + return platform.canGoBack(); + } + + /// Checks whether there's a forward history item. + Future canGoForward() { + return platform.canGoForward(); + } + + /// Goes back in the history of this WebView. + /// + /// If there is no back history item this is a no-op. + Future goBack() { + return platform.goBack(); + } + + /// Goes forward in the history of this WebView. + /// + /// If there is no forward history item this is a no-op. + Future goForward() { + return platform.goForward(); + } + + /// Reloads the current URL. + Future reload() { + return platform.reload(); + } + + /// Sets the [NavigationDelegate] containing the callback methods that are + /// called during navigation events. + Future setNavigationDelegate(NavigationDelegate delegate) { + return platform.setPlatformNavigationDelegate(delegate.platform); + } + + /// Clears all caches used by the WebView. + /// + /// The following caches are cleared: + /// 1. Browser HTTP Cache. + /// 2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) + /// caches. Service workers tend to use this cache. + /// 3. Application cache. + Future clearCache() { + return platform.clearCache(); + } + + /// Clears the local storage used by the WebView. + Future clearLocalStorage() { + return platform.clearLocalStorage(); + } + + /// Runs the given JavaScript in the context of the current page. + /// + /// The Future completes with an error if a JavaScript error occurred. + Future runJavaScript(String javaScript) { + return platform.runJavaScript(javaScript); + } + + /// Runs the given JavaScript in the context of the current page, and returns + /// the result. + /// + /// The Future completes with an error if a JavaScript error occurred, or if + /// the type the given expression evaluates to is unsupported. Unsupported + /// values include certain non-primitive types on iOS, as well as `undefined` + /// or `null` on iOS 14+. + Future runJavaScriptReturningResult(String javaScript) { + return platform.runJavaScriptReturningResult(javaScript); + } + + /// Adds a new JavaScript channel to the set of enabled channels. + /// + /// The JavaScript code can then call `postMessage` on that object to send a + /// message that will be passed to [onMessageReceived]. + /// + /// For example, after adding the following JavaScript channel: + /// + /// ```dart + /// final WebViewController controller = WebViewController(); + /// controller.addJavaScriptChannel( + /// 'Print', + /// onMessageReceived: (JavaScriptMessage message) { + /// print(message.message); + /// }, + /// ); + /// ``` + /// + /// JavaScript code can call: + /// + /// ```javascript + /// Print.postMessage('Hello'); + /// ``` + /// + /// to asynchronously invoke the message handler which will print the message + /// to standard output. + /// + /// Adding a new JavaScript channel only takes effect after the next page is + /// loaded. + /// + /// A channel [name] cannot be the same for multiple channels. + Future addJavaScriptChannel( + String name, { + required void Function(JavaScriptMessage) onMessageReceived, + }) { + assert(name.isNotEmpty); + return platform.addJavaScriptChannel(JavaScriptChannelParams( + name: name, + onMessageReceived: onMessageReceived, + )); + } + + /// Removes the JavaScript channel with the matching name from the set of + /// enabled channels. + /// + /// This disables the channel with the matching name if it was previously + /// enabled through the [addJavaScriptChannel]. + Future removeJavaScriptChannel(String javaScriptChannelName) { + return platform.removeJavaScriptChannel(javaScriptChannelName); + } + + /// The title of the currently loaded page. + Future getTitle() { + return platform.getTitle(); + } + + /// Sets the scrolled position of this view. + /// + /// The parameters `x` and `y` specify the position to scroll to in WebView + /// pixels. + Future scrollTo(int x, int y) { + return platform.scrollTo(x, y); + } + + /// Moves the scrolled position of this view. + /// + /// The parameters `x` and `y` specify the amount of WebView pixels to scroll + /// by. + Future scrollBy(int x, int y) { + return platform.scrollBy(x, y); + } + + /// Returns the current scroll position of this view. + /// + /// Scroll position is measured from the top left. + Future getScrollPosition() { + return platform.getScrollPosition(); + } + + /// Whether to support zooming using the on-screen zoom controls and gestures. + Future enableZoom(bool enabled) { + return platform.enableZoom(enabled); + } + + /// Sets the current background color of this view. + Future setBackgroundColor(Color color) { + return platform.setBackgroundColor(color); + } + + /// Sets the JavaScript execution mode to be used by the WebView. + Future setJavaScriptMode(JavaScriptMode javaScriptMode) { + return platform.setJavaScriptMode(javaScriptMode); + } + + /// Sets the value used for the HTTP `User-Agent:` request header. + Future setUserAgent(String? userAgent) { + return platform.setUserAgent(userAgent); + } + + /// Sets a callback that notifies the host application on any log messages + /// written to the JavaScript console. + /// + /// Platforms may not preserve all the log level information so clients should + /// not rely on a 1:1 mapping between the JavaScript calls. + /// + /// On iOS setting this callback will inject a custom [WKUserScript] which + /// overrides the default implementation of `console.debug`, `console.error`, + /// `console.info`, `console.log` and `console.warning` methods. The iOS + /// WebKit framework unfortunately doesn't provide a built-in method to + /// forward console messages. + Future setOnConsoleMessage( + void Function(JavaScriptConsoleMessage message) onConsoleMessage) { + return platform.setOnConsoleMessage(onConsoleMessage); + } + + /// Sets a callback that notifies the host application that the web page + /// wants to display a JavaScript alert() dialog. + Future setOnJavaScriptAlertDialog( + Future Function(JavaScriptAlertDialogRequest request) + onJavaScriptAlertDialog) async { + return platform.setOnJavaScriptAlertDialog(onJavaScriptAlertDialog); + } + + /// Sets a callback that notifies the host application that the web page + /// wants to display a JavaScript confirm() dialog. + Future setOnJavaScriptConfirmDialog( + Future Function(JavaScriptConfirmDialogRequest request) + onJavaScriptConfirmDialog) async { + return platform.setOnJavaScriptConfirmDialog(onJavaScriptConfirmDialog); + } + + /// Sets a callback that notifies the host application that the web page + /// wants to display a JavaScript prompt() dialog. + Future setOnJavaScriptTextInputDialog( + Future Function(JavaScriptTextInputDialogRequest request) + onJavaScriptTextInputDialog) async { + return platform.setOnJavaScriptTextInputDialog(onJavaScriptTextInputDialog); + } + + /// Gets the value used for the HTTP `User-Agent:` request header. + Future getUserAgent() { + return platform.getUserAgent(); + } + + /// Sets a listener for scroll position changes. + Future setOnScrollPositionChange( + void Function(ScrollPositionChange change)? onScrollPositionChange, + ) { + return platform.setOnScrollPositionChange(onScrollPositionChange); + } + + /// Whether the vertical scrollbar should be drawn or not. + Future setVerticalScrollBarEnabled(bool enabled) { + return platform.setVerticalScrollBarEnabled(enabled); + } + + /// Whether the horizontal scrollbar should be drawn or not. + Future setHorizontalScrollBarEnabled(bool enabled) { + return platform.setHorizontalScrollBarEnabled(enabled); + } + + /// Returns true if the current platform supports setting whether scrollbars + /// should be drawn or not. + /// + /// See [setVerticalScrollBarEnabled] and [setHorizontalScrollBarEnabled]. + Future supportsSetScrollBarsEnabled() async { + return platform.supportsSetScrollBarsEnabled(); + } + + /// Sets the over-scroll mode for the WebView. + /// + /// Default behavior is platform dependent. + Future setOverScrollMode(WebViewOverScrollMode mode) async { + return platform.setOverScrollMode(mode); + } +} + +/// Permissions request when web content requests access to protected resources. +/// +/// A response MUST be provided by calling [grant], [deny], or a method from +/// [platform]. +/// +/// ## Platform-Specific Features +/// This class contains an underlying implementation provided by the current +/// platform. Once a platform implementation is imported, the example below +/// can be followed to use features provided by a platform's implementation. +/// +/// Below is an example of accessing the platform-specific implementation for +/// iOS and Android: +/// +/// ```dart +/// final WebViewPermissionRequest request = ...; +/// +/// if (WebViewPlatform.instance is WebKitWebViewPlatform) { +/// final WebKitWebViewPermissionRequest webKitRequest = +/// request.platform as WebKitWebViewPermissionRequest; +/// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { +/// final AndroidWebViewPermissionRequest androidRequest = +/// request.platform as AndroidWebViewPermissionRequest; +/// } +/// ``` +@immutable +class WebViewPermissionRequest { + const WebViewPermissionRequest._(this.platform, {required this.types}); + + /// All resources access has been requested for. + final Set types; + + /// Implementation of [PlatformWebViewPermissionRequest] for the current + /// platform. + final PlatformWebViewPermissionRequest platform; + + /// Grant permission for the requested resource(s). + Future grant() { + return platform.grant(); + } + + /// Deny permission for the requested resource(s). + Future deny() { + return platform.deny(); + } +} diff --git a/local_packages/webview_flutter-4.13.0/lib/src/webview_cookie_manager.dart b/local_packages/webview_flutter-4.13.0/lib/src/webview_cookie_manager.dart new file mode 100644 index 0000000..353d755 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/lib/src/webview_cookie_manager.dart @@ -0,0 +1,89 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +/// Manages cookies pertaining to all WebViews. +/// +/// ## Platform-Specific Features +/// This class contains an underlying implementation provided by the current +/// platform. Once a platform implementation is imported, the examples below +/// can be followed to use features provided by a platform's implementation. +/// +/// {@macro webview_flutter.WebViewCookieManager.fromPlatformCreationParams} +/// +/// Below is an example of accessing the platform-specific implementation for +/// iOS and Android: +/// +/// ```dart +/// final WebViewCookieManager cookieManager = WebViewCookieManager(); +/// +/// if (WebViewPlatform.instance is WebKitWebViewPlatform) { +/// final WebKitWebViewCookieManager webKitManager = +/// cookieManager.platform as WebKitWebViewCookieManager; +/// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { +/// final AndroidWebViewCookieManager androidManager = +/// cookieManager.platform as AndroidWebViewCookieManager; +/// } +/// ``` +class WebViewCookieManager { + /// Constructs a [WebViewCookieManager]. + /// + /// See [WebViewCookieManager.fromPlatformCreationParams] for setting + /// parameters for a specific platform. + WebViewCookieManager() + : this.fromPlatformCreationParams( + const PlatformWebViewCookieManagerCreationParams(), + ); + + /// Constructs a [WebViewCookieManager] from creation params for a specific + /// platform. + /// + /// {@template webview_flutter.WebViewCookieManager.fromPlatformCreationParams} + /// Below is an example of setting platform-specific creation parameters for + /// iOS and Android: + /// + /// ```dart + /// PlatformWebViewCookieManagerCreationParams params = + /// const PlatformWebViewCookieManagerCreationParams(); + /// + /// if (WebViewPlatform.instance is WebKitWebViewPlatform) { + /// params = WebKitWebViewCookieManagerCreationParams + /// .fromPlatformWebViewCookieManagerCreationParams( + /// params, + /// ); + /// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { + /// params = AndroidWebViewCookieManagerCreationParams + /// .fromPlatformWebViewCookieManagerCreationParams( + /// params, + /// ); + /// } + /// + /// final WebViewCookieManager webViewCookieManager = + /// WebViewCookieManager.fromPlatformCreationParams( + /// params, + /// ); + /// ``` + /// {@endtemplate} + WebViewCookieManager.fromPlatformCreationParams( + PlatformWebViewCookieManagerCreationParams params, + ) : this.fromPlatform(PlatformWebViewCookieManager(params)); + + /// Constructs a [WebViewCookieManager] from a specific platform + /// implementation. + WebViewCookieManager.fromPlatform(this.platform); + + /// Implementation of [PlatformWebViewCookieManager] for the current platform. + final PlatformWebViewCookieManager platform; + + /// Clears all cookies for all WebViews. + /// + /// Returns true if cookies were present before clearing, else false. + Future clearCookies() => platform.clearCookies(); + + /// Sets a cookie for all WebView instances. + /// + /// This is a no op on iOS versions below 11. + Future setCookie(WebViewCookie cookie) => platform.setCookie(cookie); +} diff --git a/local_packages/webview_flutter-4.13.0/lib/src/webview_flutter_legacy.dart b/local_packages/webview_flutter-4.13.0/lib/src/webview_flutter_legacy.dart new file mode 100644 index 0000000..d040fc2 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/lib/src/webview_flutter_legacy.dart @@ -0,0 +1,9 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'package:webview_flutter_android/src/webview_flutter_android_legacy.dart'; +export 'package:webview_flutter_wkwebview/src/webview_flutter_wkwebview_legacy.dart'; + +export 'legacy/platform_interface.dart'; +export 'legacy/webview.dart'; diff --git a/local_packages/webview_flutter-4.13.0/lib/src/webview_widget.dart b/local_packages/webview_flutter-4.13.0/lib/src/webview_widget.dart new file mode 100644 index 0000000..cc252bf --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/lib/src/webview_widget.dart @@ -0,0 +1,122 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'webview_controller.dart'; + +/// Displays a native WebView as a Widget. +/// +/// ## Platform-Specific Features +/// This class contains an underlying implementation provided by the current +/// platform. Once a platform implementation is imported, the examples below +/// can be followed to use features provided by a platform's implementation. +/// +/// {@macro webview_flutter.WebViewWidget.fromPlatformCreationParams} +/// +/// Below is an example of accessing the platform-specific implementation for +/// iOS and Android: +/// +/// ```dart +/// final WebViewController controller = WebViewController(); +/// +/// final WebViewWidget webViewWidget = WebViewWidget(controller: controller); +/// +/// if (WebViewPlatform.instance is WebKitWebViewPlatform) { +/// final WebKitWebViewWidget webKitWidget = +/// webViewWidget.platform as WebKitWebViewWidget; +/// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { +/// final AndroidWebViewWidget androidWidget = +/// webViewWidget.platform as AndroidWebViewWidget; +/// } +/// ``` +class WebViewWidget extends StatelessWidget { + /// Constructs a [WebViewWidget]. + /// + /// See [WebViewWidget.fromPlatformCreationParams] for setting parameters for + /// a specific platform. + WebViewWidget({ + Key? key, + required WebViewController controller, + TextDirection layoutDirection = TextDirection.ltr, + Set> gestureRecognizers = + const >{}, + }) : this.fromPlatformCreationParams( + key: key, + params: PlatformWebViewWidgetCreationParams( + controller: controller.platform, + layoutDirection: layoutDirection, + gestureRecognizers: gestureRecognizers, + ), + ); + + /// Constructs a [WebViewWidget] from creation params for a specific platform. + /// + /// {@template webview_flutter.WebViewWidget.fromPlatformCreationParams} + /// Below is an example of setting platform-specific creation parameters for + /// iOS and Android: + /// + /// ```dart + /// final WebViewController controller = WebViewController(); + /// + /// PlatformWebViewWidgetCreationParams params = + /// PlatformWebViewWidgetCreationParams( + /// controller: controller.platform, + /// layoutDirection: TextDirection.ltr, + /// gestureRecognizers: const >{}, + /// ); + /// + /// if (WebViewPlatform.instance is WebKitWebViewPlatform) { + /// params = WebKitWebViewWidgetCreationParams + /// .fromPlatformWebViewWidgetCreationParams( + /// params, + /// ); + /// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { + /// params = AndroidWebViewWidgetCreationParams + /// .fromPlatformWebViewWidgetCreationParams( + /// params, + /// ); + /// } + /// + /// final WebViewWidget webViewWidget = + /// WebViewWidget.fromPlatformCreationParams( + /// params: params, + /// ); + /// ``` + /// {@endtemplate} + WebViewWidget.fromPlatformCreationParams({ + Key? key, + required PlatformWebViewWidgetCreationParams params, + }) : this.fromPlatform(key: key, platform: PlatformWebViewWidget(params)); + + /// Constructs a [WebViewWidget] from a specific platform implementation. + WebViewWidget.fromPlatform({super.key, required this.platform}); + + /// Implementation of [PlatformWebViewWidget] for the current platform. + final PlatformWebViewWidget platform; + + /// The layout direction to use for the embedded WebView. + late final TextDirection layoutDirection = platform.params.layoutDirection; + + /// Specifies which gestures should be consumed by the web view. + /// + /// It is possible for other gesture recognizers to be competing with the web + /// view on pointer events, e.g. if the web view is inside a [ListView] the + /// [ListView] will want to handle vertical drags. The web view will claim + /// gestures that are recognized by any of the recognizers on this list. + /// + /// When `gestureRecognizers` is empty (default), the web view will only + /// handle pointer events for gestures that were not claimed by any other + /// gesture recognizer. + late final Set> gestureRecognizers = + platform.params.gestureRecognizers; + + @override + Widget build(BuildContext context) { + return platform.build(context); + } +} diff --git a/local_packages/webview_flutter-4.13.0/lib/webview_flutter.dart b/local_packages/webview_flutter-4.13.0/lib/webview_flutter.dart new file mode 100644 index 0000000..573a337 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/lib/webview_flutter.dart @@ -0,0 +1,45 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart' + show + HttpAuthRequest, + HttpResponseError, + HttpResponseErrorCallback, + JavaScriptAlertDialogRequest, + JavaScriptConfirmDialogRequest, + JavaScriptConsoleMessage, + JavaScriptLogLevel, + JavaScriptMessage, + JavaScriptMode, + JavaScriptTextInputDialogRequest, + LoadRequestMethod, + NavigationDecision, + NavigationRequest, + NavigationRequestCallback, + PageEventCallback, + PlatformNavigationDelegateCreationParams, + PlatformWebViewControllerCreationParams, + PlatformWebViewCookieManagerCreationParams, + PlatformWebViewPermissionRequest, + PlatformWebViewWidgetCreationParams, + ProgressCallback, + ScrollPositionChange, + UrlChange, + WebResourceError, + WebResourceErrorCallback, + WebResourceErrorType, + WebResourceRequest, + WebResourceResponse, + WebViewCookie, + WebViewCredential, + WebViewOverScrollMode, + WebViewPermissionResourceType, + WebViewPlatform, + X509Certificate; + +export 'src/navigation_delegate.dart'; +export 'src/webview_controller.dart'; +export 'src/webview_cookie_manager.dart'; +export 'src/webview_widget.dart'; diff --git a/local_packages/webview_flutter-4.13.0/pubspec.yaml b/local_packages/webview_flutter-4.13.0/pubspec.yaml new file mode 100644 index 0000000..ba0b85a --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/pubspec.yaml @@ -0,0 +1,39 @@ +name: webview_flutter +description: A Flutter plugin that provides a WebView widget backed by the system webview. +repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter +issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 +version: 4.13.0 + +environment: + sdk: ^3.6.0 + flutter: ">=3.27.0" + +flutter: + plugin: + platforms: + android: + default_package: webview_flutter_android + ios: + default_package: webview_flutter_wkwebview + macos: + default_package: webview_flutter_wkwebview + +dependencies: + flutter: + sdk: flutter + webview_flutter_android: + path: ../webview_flutter_android-4.10.5 + webview_flutter_platform_interface: ^2.13.0 + webview_flutter_wkwebview: ^3.22.0 + +dev_dependencies: + build_runner: ^2.1.5 + flutter_test: + sdk: flutter + mockito: ^5.4.4 + plugin_platform_interface: ^2.1.7 + +topics: + - html + - webview + - webview-flutter diff --git a/local_packages/webview_flutter-4.13.0/test/legacy/webview_flutter_test.dart b/local_packages/webview_flutter-4.13.0/test/legacy/webview_flutter_test.dart new file mode 100644 index 0000000..e409b88 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/test/legacy/webview_flutter_test.dart @@ -0,0 +1,1366 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:typed_data'; + +import 'package:flutter/src/foundation/basic_types.dart'; +import 'package:flutter/src/gestures/recognizer.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:webview_flutter/src/webview_flutter_legacy.dart'; +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; + +import 'webview_flutter_test.mocks.dart'; + +typedef VoidCallback = void Function(); + +@GenerateMocks([WebViewPlatform, WebViewPlatformController]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late MockWebViewPlatform mockWebViewPlatform; + late MockWebViewPlatformController mockWebViewPlatformController; + late MockWebViewCookieManagerPlatform mockWebViewCookieManagerPlatform; + + setUp(() { + mockWebViewPlatformController = MockWebViewPlatformController(); + mockWebViewPlatform = MockWebViewPlatform(); + mockWebViewCookieManagerPlatform = MockWebViewCookieManagerPlatform(); + when(mockWebViewPlatform.build( + context: anyNamed('context'), + creationParams: anyNamed('creationParams'), + webViewPlatformCallbacksHandler: + anyNamed('webViewPlatformCallbacksHandler'), + javascriptChannelRegistry: anyNamed('javascriptChannelRegistry'), + onWebViewPlatformCreated: anyNamed('onWebViewPlatformCreated'), + gestureRecognizers: anyNamed('gestureRecognizers'), + )).thenAnswer((Invocation invocation) { + final WebViewPlatformCreatedCallback onWebViewPlatformCreated = + invocation.namedArguments[const Symbol('onWebViewPlatformCreated')] + as WebViewPlatformCreatedCallback; + return TestPlatformWebView( + mockWebViewPlatformController: mockWebViewPlatformController, + onWebViewPlatformCreated: onWebViewPlatformCreated, + ); + }); + + WebView.platform = mockWebViewPlatform; + WebViewCookieManagerPlatform.instance = mockWebViewCookieManagerPlatform; + }); + + tearDown(() { + mockWebViewCookieManagerPlatform.reset(); + }); + + testWidgets('Create WebView', (WidgetTester tester) async { + await tester.pumpWidget(const WebView()); + }); + + testWidgets('Initial url', (WidgetTester tester) async { + await tester.pumpWidget(const WebView(initialUrl: 'https://youtube.com')); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.initialUrl, 'https://youtube.com'); + }); + + testWidgets('Javascript mode', (WidgetTester tester) async { + await tester.pumpWidget(const WebView( + javascriptMode: JavascriptMode.unrestricted, + )); + + final CreationParams unrestrictedparams = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect( + unrestrictedparams.webSettings!.javascriptMode, + JavascriptMode.unrestricted, + ); + + await tester.pumpWidget(const WebView()); + + final CreationParams disabledparams = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(disabledparams.webSettings!.javascriptMode, JavascriptMode.disabled); + }); + + testWidgets('Load file', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + await controller!.loadFile('/test/path/index.html'); + + verify(mockWebViewPlatformController.loadFile( + '/test/path/index.html', + )); + }); + + testWidgets('Load file with empty path', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + expect(() => controller!.loadFile(''), throwsAssertionError); + }); + + testWidgets('Load Flutter asset', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + await controller!.loadFlutterAsset('assets/index.html'); + + verify(mockWebViewPlatformController.loadFlutterAsset( + 'assets/index.html', + )); + }); + + testWidgets('Load Flutter asset with empty key', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + expect(() => controller!.loadFlutterAsset(''), throwsAssertionError); + }); + + testWidgets('Load HTML string without base URL', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + await controller!.loadHtmlString('

This is a test paragraph.

'); + + verify(mockWebViewPlatformController.loadHtmlString( + '

This is a test paragraph.

', + )); + }); + + testWidgets('Load HTML string with base URL', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + await controller!.loadHtmlString( + '

This is a test paragraph.

', + baseUrl: 'https://flutter.dev', + ); + + verify(mockWebViewPlatformController.loadHtmlString( + '

This is a test paragraph.

', + baseUrl: 'https://flutter.dev', + )); + }); + + testWidgets('Load HTML string with empty string', + (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + expect(() => controller!.loadHtmlString(''), throwsAssertionError); + }); + + testWidgets('Load url', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + await controller!.loadUrl('https://flutter.io'); + + verify(mockWebViewPlatformController.loadUrl( + 'https://flutter.io', + argThat(isNull), + )); + }); + + testWidgets('Invalid urls', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.initialUrl, isNull); + + expect(() => controller!.loadUrl(''), throwsA(anything)); + expect(() => controller!.loadUrl('flutter.io'), throwsA(anything)); + }); + + testWidgets('Headers in loadUrl', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + final Map headers = { + 'CACHE-CONTROL': 'ABC' + }; + await controller!.loadUrl('https://flutter.io', headers: headers); + + verify(mockWebViewPlatformController.loadUrl( + 'https://flutter.io', + {'CACHE-CONTROL': 'ABC'}, + )); + }); + + testWidgets('loadRequest', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + expect(controller, isNotNull); + + final WebViewRequest req = WebViewRequest( + uri: Uri.parse('https://flutter.dev'), + method: WebViewRequestMethod.post, + headers: {'foo': 'bar'}, + body: Uint8List.fromList('Test Body'.codeUnits), + ); + + await controller!.loadRequest(req); + + verify(mockWebViewPlatformController.loadRequest(req)); + }); + + testWidgets('Clear Cache', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + + await controller!.clearCache(); + + verify(mockWebViewPlatformController.clearCache()); + }); + + testWidgets('Can go back', (WidgetTester tester) async { + when(mockWebViewPlatformController.canGoBack()) + .thenAnswer((_) => Future.value(true)); + + WebViewController? controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://flutter.io', + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + expect(controller!.canGoBack(), completion(true)); + }); + + testWidgets("Can't go forward", (WidgetTester tester) async { + when(mockWebViewPlatformController.canGoForward()) + .thenAnswer((_) => Future.value(false)); + + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + expect(controller!.canGoForward(), completion(false)); + }); + + testWidgets('Go back', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + await controller!.goBack(); + verify(mockWebViewPlatformController.goBack()); + }); + + testWidgets('Go forward', (WidgetTester tester) async { + WebViewController? controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + await controller!.goForward(); + verify(mockWebViewPlatformController.goForward()); + }); + + testWidgets('Current URL', (WidgetTester tester) async { + when(mockWebViewPlatformController.currentUrl()) + .thenAnswer((_) => Future.value('https://youtube.com')); + + WebViewController? controller; + await tester.pumpWidget( + WebView( + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect(controller, isNotNull); + expect(await controller!.currentUrl(), 'https://youtube.com'); + }); + + testWidgets('Reload url', (WidgetTester tester) async { + late WebViewController controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://flutter.io', + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + await controller.reload(); + verify(mockWebViewPlatformController.reload()); + }); + + testWidgets('evaluate Javascript', (WidgetTester tester) async { + when(mockWebViewPlatformController.evaluateJavascript('fake js string')) + .thenAnswer((_) => Future.value('fake js string')); + + late WebViewController controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://flutter.io', + javascriptMode: JavascriptMode.unrestricted, + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + expect( + // ignore: deprecated_member_use_from_same_package + await controller.evaluateJavascript('fake js string'), + 'fake js string', + reason: 'should get the argument'); + }); + + testWidgets('evaluate Javascript with JavascriptMode disabled', + (WidgetTester tester) async { + late WebViewController controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://flutter.io', + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + expect( + // ignore: deprecated_member_use_from_same_package + () => controller.evaluateJavascript('fake js string'), + throwsA(anything), + ); + }); + + testWidgets('runJavaScript', (WidgetTester tester) async { + late WebViewController controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://flutter.io', + javascriptMode: JavascriptMode.unrestricted, + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + await controller.runJavascript('fake js string'); + verify(mockWebViewPlatformController.runJavascript('fake js string')); + }); + + testWidgets('runJavaScript with JavascriptMode disabled', + (WidgetTester tester) async { + late WebViewController controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://flutter.io', + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + expect( + () => controller.runJavascript('fake js string'), + throwsA(anything), + ); + }); + + testWidgets('runJavaScriptReturningResult', (WidgetTester tester) async { + when(mockWebViewPlatformController + .runJavascriptReturningResult('fake js string')) + .thenAnswer((_) => Future.value('fake js string')); + + late WebViewController controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://flutter.io', + javascriptMode: JavascriptMode.unrestricted, + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + expect(await controller.runJavascriptReturningResult('fake js string'), + 'fake js string', + reason: 'should get the argument'); + }); + + testWidgets('runJavaScriptReturningResult with JavascriptMode disabled', + (WidgetTester tester) async { + late WebViewController controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://flutter.io', + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + expect( + () => controller.runJavascriptReturningResult('fake js string'), + throwsA(anything), + ); + }); + + testWidgets('Cookies can be cleared once', (WidgetTester tester) async { + await tester.pumpWidget( + const WebView( + initialUrl: 'https://flutter.io', + ), + ); + final CookieManager cookieManager = CookieManager(); + final bool hasCookies = await cookieManager.clearCookies(); + expect(hasCookies, true); + }); + + testWidgets('Cookies can be set', (WidgetTester tester) async { + const WebViewCookie cookie = + WebViewCookie(name: 'foo', value: 'bar', domain: 'flutter.dev'); + + await tester.pumpWidget( + const WebView( + initialUrl: 'https://flutter.io', + ), + ); + final CookieManager cookieManager = CookieManager(); + await cookieManager.setCookie(cookie); + expect(mockWebViewCookieManagerPlatform.setCookieCalls, + [cookie]); + }); + + testWidgets('Initial JavaScript channels', (WidgetTester tester) async { + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + javascriptChannels: { + JavascriptChannel( + name: 'ATTts', onMessageReceived: (JavascriptMessage msg) {}), + JavascriptChannel( + name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), + }, + ), + ); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.javascriptChannelNames, + unorderedEquals(['ATTts', 'Alarm'])); + }); + + test('Only valid JavaScript channel names are allowed', () { + void noOp(JavascriptMessage msg) {} + JavascriptChannel(name: 'Tts1', onMessageReceived: noOp); + JavascriptChannel(name: '_Alarm', onMessageReceived: noOp); + JavascriptChannel(name: 'foo_bar_', onMessageReceived: noOp); + + VoidCallback createChannel(String name) { + return () { + JavascriptChannel(name: name, onMessageReceived: noOp); + }; + } + + expect(createChannel('1Alarm'), throwsAssertionError); + expect(createChannel('foo.bar'), throwsAssertionError); + expect(createChannel(''), throwsAssertionError); + }); + + testWidgets('Unique JavaScript channel names are required', + (WidgetTester tester) async { + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + javascriptChannels: { + JavascriptChannel( + name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), + JavascriptChannel( + name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), + }, + ), + ); + expect(tester.takeException(), isNot(null)); + }); + + testWidgets('JavaScript channels update', (WidgetTester tester) async { + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + javascriptChannels: { + JavascriptChannel( + name: 'ATTts', onMessageReceived: (JavascriptMessage msg) {}), + JavascriptChannel( + name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), + }, + ), + ); + + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + javascriptChannels: { + JavascriptChannel( + name: 'ATTts', onMessageReceived: (JavascriptMessage msg) {}), + JavascriptChannel( + name: 'Alarm2', onMessageReceived: (JavascriptMessage msg) {}), + JavascriptChannel( + name: 'Alarm3', onMessageReceived: (JavascriptMessage msg) {}), + }, + ), + ); + + final JavascriptChannelRegistry channelRegistry = captureBuildArgs( + mockWebViewPlatform, + javascriptChannelRegistry: true, + ).first as JavascriptChannelRegistry; + + expect( + channelRegistry.channels.keys, + unorderedEquals(['ATTts', 'Alarm2', 'Alarm3']), + ); + }); + + testWidgets('Remove all JavaScript channels and then add', + (WidgetTester tester) async { + // This covers a specific bug we had where after updating javascriptChannels to null, + // updating it again with a subset of the previously registered channels fails as the + // widget's cache of current channel wasn't properly updated when updating javascriptChannels to + // null. + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + javascriptChannels: { + JavascriptChannel( + name: 'ATTts', onMessageReceived: (JavascriptMessage msg) {}), + }, + ), + ); + + await tester.pumpWidget( + const WebView( + initialUrl: 'https://youtube.com', + ), + ); + + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + javascriptChannels: { + JavascriptChannel( + name: 'ATTts', onMessageReceived: (JavascriptMessage msg) {}), + }, + ), + ); + + final JavascriptChannelRegistry channelRegistry = captureBuildArgs( + mockWebViewPlatform, + javascriptChannelRegistry: true, + ).last as JavascriptChannelRegistry; + + expect(channelRegistry.channels.keys, unorderedEquals(['ATTts'])); + }); + + testWidgets('JavaScript channel messages', (WidgetTester tester) async { + final List ttsMessagesReceived = []; + final List alarmMessagesReceived = []; + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + javascriptChannels: { + JavascriptChannel( + name: 'ATTts', + onMessageReceived: (JavascriptMessage msg) { + ttsMessagesReceived.add(msg.message); + }), + JavascriptChannel( + name: 'Alarm', + onMessageReceived: (JavascriptMessage msg) { + alarmMessagesReceived.add(msg.message); + }), + }, + ), + ); + + final JavascriptChannelRegistry channelRegistry = captureBuildArgs( + mockWebViewPlatform, + javascriptChannelRegistry: true, + ).single as JavascriptChannelRegistry; + + expect(ttsMessagesReceived, isEmpty); + expect(alarmMessagesReceived, isEmpty); + + channelRegistry.onJavascriptChannelMessage('ATTts', 'Hello'); + channelRegistry.onJavascriptChannelMessage('ATTts', 'World'); + + expect(ttsMessagesReceived, ['Hello', 'World']); + }); + + group('$PageStartedCallback', () { + testWidgets('onPageStarted is not null', (WidgetTester tester) async { + String? returnedUrl; + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + onPageStarted: (String url) { + returnedUrl = url; + }, + )); + + final WebViewPlatformCallbacksHandler handler = captureBuildArgs( + mockWebViewPlatform, + webViewPlatformCallbacksHandler: true, + ).single as WebViewPlatformCallbacksHandler; + + handler.onPageStarted('https://youtube.com'); + + expect(returnedUrl, 'https://youtube.com'); + }); + + testWidgets('onPageStarted is null', (WidgetTester tester) async { + await tester.pumpWidget(const WebView( + initialUrl: 'https://youtube.com', + )); + + final WebViewPlatformCallbacksHandler handler = captureBuildArgs( + mockWebViewPlatform, + webViewPlatformCallbacksHandler: true, + ).single as WebViewPlatformCallbacksHandler; + + // The platform side will always invoke a call for onPageStarted. This is + // to test that it does not crash on a null callback. + handler.onPageStarted('https://youtube.com'); + }); + + testWidgets('onPageStarted changed', (WidgetTester tester) async { + String? returnedUrl; + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + onPageStarted: (String url) {}, + )); + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + onPageStarted: (String url) { + returnedUrl = url; + }, + )); + + final WebViewPlatformCallbacksHandler handler = captureBuildArgs( + mockWebViewPlatform, + webViewPlatformCallbacksHandler: true, + ).last as WebViewPlatformCallbacksHandler; + handler.onPageStarted('https://youtube.com'); + + expect(returnedUrl, 'https://youtube.com'); + }); + }); + + group('$PageFinishedCallback', () { + testWidgets('onPageFinished is not null', (WidgetTester tester) async { + String? returnedUrl; + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + onPageFinished: (String url) { + returnedUrl = url; + }, + )); + + final WebViewPlatformCallbacksHandler handler = captureBuildArgs( + mockWebViewPlatform, + webViewPlatformCallbacksHandler: true, + ).single as WebViewPlatformCallbacksHandler; + handler.onPageFinished('https://youtube.com'); + + expect(returnedUrl, 'https://youtube.com'); + }); + + testWidgets('onPageFinished is null', (WidgetTester tester) async { + await tester.pumpWidget(const WebView( + initialUrl: 'https://youtube.com', + )); + + final WebViewPlatformCallbacksHandler handler = captureBuildArgs( + mockWebViewPlatform, + webViewPlatformCallbacksHandler: true, + ).single as WebViewPlatformCallbacksHandler; + // The platform side will always invoke a call for onPageFinished. This is + // to test that it does not crash on a null callback. + handler.onPageFinished('https://youtube.com'); + }); + + testWidgets('onPageFinished changed', (WidgetTester tester) async { + String? returnedUrl; + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + onPageFinished: (String url) {}, + )); + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + onPageFinished: (String url) { + returnedUrl = url; + }, + )); + + final WebViewPlatformCallbacksHandler handler = captureBuildArgs( + mockWebViewPlatform, + webViewPlatformCallbacksHandler: true, + ).last as WebViewPlatformCallbacksHandler; + handler.onPageFinished('https://youtube.com'); + + expect(returnedUrl, 'https://youtube.com'); + }); + }); + + group('$PageLoadingCallback', () { + testWidgets('onLoadingProgress is not null', (WidgetTester tester) async { + int? loadingProgress; + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + onProgress: (int progress) { + loadingProgress = progress; + }, + )); + + final WebViewPlatformCallbacksHandler handler = captureBuildArgs( + mockWebViewPlatform, + webViewPlatformCallbacksHandler: true, + ).single as WebViewPlatformCallbacksHandler; + handler.onProgress(50); + + expect(loadingProgress, 50); + }); + + testWidgets('onLoadingProgress is null', (WidgetTester tester) async { + await tester.pumpWidget(const WebView( + initialUrl: 'https://youtube.com', + )); + + final WebViewPlatformCallbacksHandler handler = captureBuildArgs( + mockWebViewPlatform, + webViewPlatformCallbacksHandler: true, + ).single as WebViewPlatformCallbacksHandler; + + // This is to test that it does not crash on a null callback. + handler.onProgress(50); + }); + + testWidgets('onLoadingProgress changed', (WidgetTester tester) async { + int? loadingProgress; + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + onProgress: (int progress) {}, + )); + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + onProgress: (int progress) { + loadingProgress = progress; + }, + )); + + final WebViewPlatformCallbacksHandler handler = captureBuildArgs( + mockWebViewPlatform, + webViewPlatformCallbacksHandler: true, + ).last as WebViewPlatformCallbacksHandler; + handler.onProgress(50); + + expect(loadingProgress, 50); + }); + }); + + group('navigationDelegate', () { + testWidgets('hasNavigationDelegate', (WidgetTester tester) async { + await tester.pumpWidget(const WebView( + initialUrl: 'https://youtube.com', + )); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.webSettings!.hasNavigationDelegate, false); + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + navigationDelegate: (NavigationRequest r) => + NavigationDecision.navigate, + )); + + final WebSettings updateSettings = + verify(mockWebViewPlatformController.updateSettings(captureAny)) + .captured + .single as WebSettings; + + expect(updateSettings.hasNavigationDelegate, true); + }); + + testWidgets('Block navigation', (WidgetTester tester) async { + final List navigationRequests = []; + + await tester.pumpWidget(WebView( + initialUrl: 'https://youtube.com', + navigationDelegate: (NavigationRequest request) { + navigationRequests.add(request); + // Only allow navigating to https://flutter.dev + return request.url == 'https://flutter.dev' + ? NavigationDecision.navigate + : NavigationDecision.prevent; + })); + + final List args = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + webViewPlatformCallbacksHandler: true, + ); + + final CreationParams params = args[0] as CreationParams; + expect(params.webSettings!.hasNavigationDelegate, true); + + final WebViewPlatformCallbacksHandler handler = + args[1] as WebViewPlatformCallbacksHandler; + + // The navigation delegate only allows navigation to https://flutter.dev + // so we should still be in https://youtube.com. + expect( + handler.onNavigationRequest( + url: 'https://www.google.com', + isForMainFrame: true, + ), + completion(false), + ); + + expect(navigationRequests.length, 1); + expect(navigationRequests[0].url, 'https://www.google.com'); + expect(navigationRequests[0].isForMainFrame, true); + + expect( + handler.onNavigationRequest( + url: 'https://flutter.dev', + isForMainFrame: true, + ), + completion(true), + ); + }); + }); + + group('debuggingEnabled', () { + testWidgets('enable debugging', (WidgetTester tester) async { + await tester.pumpWidget(const WebView( + debuggingEnabled: true, + )); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.webSettings!.debuggingEnabled, true); + }); + + testWidgets('defaults to false', (WidgetTester tester) async { + await tester.pumpWidget(const WebView()); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.webSettings!.debuggingEnabled, false); + }); + + testWidgets('can be changed', (WidgetTester tester) async { + final GlobalKey key = GlobalKey(); + await tester.pumpWidget(WebView(key: key)); + + await tester.pumpWidget(WebView( + key: key, + debuggingEnabled: true, + )); + + final WebSettings enabledSettings = + verify(mockWebViewPlatformController.updateSettings(captureAny)) + .captured + .last as WebSettings; + expect(enabledSettings.debuggingEnabled, true); + + await tester.pumpWidget(WebView( + key: key, + )); + + final WebSettings disabledSettings = + verify(mockWebViewPlatformController.updateSettings(captureAny)) + .captured + .last as WebSettings; + expect(disabledSettings.debuggingEnabled, false); + }); + }); + + group('zoomEnabled', () { + testWidgets('Enable zoom', (WidgetTester tester) async { + await tester.pumpWidget(const WebView()); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.webSettings!.zoomEnabled, isTrue); + }); + + testWidgets('defaults to true', (WidgetTester tester) async { + await tester.pumpWidget(const WebView()); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.webSettings!.zoomEnabled, isTrue); + }); + + testWidgets('can be changed', (WidgetTester tester) async { + final GlobalKey key = GlobalKey(); + await tester.pumpWidget(WebView(key: key)); + + await tester.pumpWidget(WebView( + key: key, + )); + + final WebSettings enabledSettings = + verify(mockWebViewPlatformController.updateSettings(captureAny)) + .captured + .last as WebSettings; + // Zoom defaults to true, so no changes are made to settings. + expect(enabledSettings.zoomEnabled, isNull); + + await tester.pumpWidget(WebView( + key: key, + zoomEnabled: false, + )); + + final WebSettings disabledSettings = + verify(mockWebViewPlatformController.updateSettings(captureAny)) + .captured + .last as WebSettings; + expect(disabledSettings.zoomEnabled, isFalse); + }); + }); + + group('Background color', () { + testWidgets('Defaults to null', (WidgetTester tester) async { + await tester.pumpWidget(const WebView()); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.backgroundColor, null); + }); + + testWidgets('Can be transparent', (WidgetTester tester) async { + const Color transparentColor = Color(0x00000000); + + await tester.pumpWidget(const WebView( + backgroundColor: transparentColor, + )); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.backgroundColor, transparentColor); + }); + }); + + group('Custom platform implementation', () { + setUp(() { + WebView.platform = MyWebViewPlatform(); + }); + tearDownAll(() { + WebView.platform = null; + }); + + testWidgets('creation', (WidgetTester tester) async { + await tester.pumpWidget( + const WebView( + initialUrl: 'https://youtube.com', + gestureNavigationEnabled: true, + ), + ); + + final MyWebViewPlatform builder = WebView.platform as MyWebViewPlatform; + final MyWebViewPlatformController platform = builder.lastPlatformBuilt!; + + expect( + platform.creationParams, + MatchesCreationParams(CreationParams( + initialUrl: 'https://youtube.com', + webSettings: WebSettings( + javascriptMode: JavascriptMode.disabled, + hasNavigationDelegate: false, + debuggingEnabled: false, + userAgent: const WebSetting.of(null), + gestureNavigationEnabled: true, + zoomEnabled: true, + ), + ))); + }); + + testWidgets('loadUrl', (WidgetTester tester) async { + late WebViewController controller; + await tester.pumpWidget( + WebView( + initialUrl: 'https://youtube.com', + onWebViewCreated: (WebViewController webViewController) { + controller = webViewController; + }, + ), + ); + + final MyWebViewPlatform builder = WebView.platform as MyWebViewPlatform; + final MyWebViewPlatformController platform = builder.lastPlatformBuilt!; + + final Map headers = { + 'header': 'value', + }; + + await controller.loadUrl('https://google.com', headers: headers); + + expect(platform.lastUrlLoaded, 'https://google.com'); + expect(platform.lastRequestHeaders, headers); + }); + }); + + testWidgets('Set UserAgent', (WidgetTester tester) async { + await tester.pumpWidget(const WebView( + initialUrl: 'https://youtube.com', + javascriptMode: JavascriptMode.unrestricted, + )); + + final CreationParams params = captureBuildArgs( + mockWebViewPlatform, + creationParams: true, + ).single as CreationParams; + + expect(params.webSettings!.userAgent.value, isNull); + + await tester.pumpWidget(const WebView( + initialUrl: 'https://youtube.com', + javascriptMode: JavascriptMode.unrestricted, + userAgent: 'UA', + )); + + final WebSettings settings = + verify(mockWebViewPlatformController.updateSettings(captureAny)) + .captured + .last as WebSettings; + expect(settings.userAgent.value, 'UA'); + }); +} + +List captureBuildArgs( + MockWebViewPlatform mockWebViewPlatform, { + bool context = false, + bool creationParams = false, + bool webViewPlatformCallbacksHandler = false, + bool javascriptChannelRegistry = false, + bool onWebViewPlatformCreated = false, + bool gestureRecognizers = false, +}) { + return verify(mockWebViewPlatform.build( + context: context ? captureAnyNamed('context') : anyNamed('context'), + creationParams: creationParams + ? captureAnyNamed('creationParams') + : anyNamed('creationParams'), + webViewPlatformCallbacksHandler: webViewPlatformCallbacksHandler + ? captureAnyNamed('webViewPlatformCallbacksHandler') + : anyNamed('webViewPlatformCallbacksHandler'), + javascriptChannelRegistry: javascriptChannelRegistry + ? captureAnyNamed('javascriptChannelRegistry') + : anyNamed('javascriptChannelRegistry'), + onWebViewPlatformCreated: onWebViewPlatformCreated + ? captureAnyNamed('onWebViewPlatformCreated') + : anyNamed('onWebViewPlatformCreated'), + gestureRecognizers: gestureRecognizers + ? captureAnyNamed('gestureRecognizers') + : anyNamed('gestureRecognizers'), + )).captured; +} + +// This Widget ensures that onWebViewPlatformCreated is only called once when +// making multiple calls to `WidgetTester.pumpWidget` with different parameters +// for the WebView. +class TestPlatformWebView extends StatefulWidget { + const TestPlatformWebView({ + super.key, + required this.mockWebViewPlatformController, + this.onWebViewPlatformCreated, + }); + + final MockWebViewPlatformController mockWebViewPlatformController; + final WebViewPlatformCreatedCallback? onWebViewPlatformCreated; + + @override + State createState() => TestPlatformWebViewState(); +} + +class TestPlatformWebViewState extends State { + @override + void initState() { + super.initState(); + final WebViewPlatformCreatedCallback? onWebViewPlatformCreated = + widget.onWebViewPlatformCreated; + if (onWebViewPlatformCreated != null) { + onWebViewPlatformCreated(widget.mockWebViewPlatformController); + } + } + + @override + Widget build(BuildContext context) { + return Container(); + } +} + +class MyWebViewPlatform implements WebViewPlatform { + MyWebViewPlatformController? lastPlatformBuilt; + + @override + Widget build({ + BuildContext? context, + CreationParams? creationParams, + required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, + required JavascriptChannelRegistry javascriptChannelRegistry, + WebViewPlatformCreatedCallback? onWebViewPlatformCreated, + Set>? gestureRecognizers, + }) { + assert(onWebViewPlatformCreated != null); + lastPlatformBuilt = MyWebViewPlatformController( + creationParams, webViewPlatformCallbacksHandler); + onWebViewPlatformCreated!(lastPlatformBuilt); + return Container(); + } + + @override + Future clearCookies() { + return Future.sync(() => true); + } +} + +class MyWebViewPlatformController extends WebViewPlatformController { + MyWebViewPlatformController( + this.creationParams, WebViewPlatformCallbacksHandler platformHandler) + : super(platformHandler); + + CreationParams? creationParams; + + String? lastUrlLoaded; + Map? lastRequestHeaders; + + @override + Future loadUrl(String url, Map? headers) async { + equals(1, 1); + lastUrlLoaded = url; + lastRequestHeaders = headers; + } +} + +class MatchesWebSettings extends Matcher { + MatchesWebSettings(this._webSettings); + + final WebSettings? _webSettings; + + @override + Description describe(Description description) => + description.add('$_webSettings'); + + @override + bool matches( + covariant WebSettings webSettings, Map matchState) { + return _webSettings!.javascriptMode == webSettings.javascriptMode && + _webSettings.hasNavigationDelegate == + webSettings.hasNavigationDelegate && + _webSettings.debuggingEnabled == webSettings.debuggingEnabled && + _webSettings.gestureNavigationEnabled == + webSettings.gestureNavigationEnabled && + _webSettings.userAgent == webSettings.userAgent && + _webSettings.zoomEnabled == webSettings.zoomEnabled; + } +} + +class MatchesCreationParams extends Matcher { + MatchesCreationParams(this._creationParams); + + final CreationParams _creationParams; + + @override + Description describe(Description description) => + description.add('$_creationParams'); + + @override + bool matches(covariant CreationParams creationParams, + Map matchState) { + return _creationParams.initialUrl == creationParams.initialUrl && + MatchesWebSettings(_creationParams.webSettings) + .matches(creationParams.webSettings!, matchState) && + orderedEquals(_creationParams.javascriptChannelNames) + .matches(creationParams.javascriptChannelNames, matchState); + } +} + +class MockWebViewCookieManagerPlatform extends WebViewCookieManagerPlatform { + List setCookieCalls = []; + + @override + Future clearCookies() async => true; + + @override + Future setCookie(WebViewCookie cookie) async { + setCookieCalls.add(cookie); + } + + void reset() { + setCookieCalls = []; + } +} diff --git a/local_packages/webview_flutter-4.13.0/test/legacy/webview_flutter_test.mocks.dart b/local_packages/webview_flutter-4.13.0/test/legacy/webview_flutter_test.mocks.dart new file mode 100644 index 0000000..7a3c4d5 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/test/legacy/webview_flutter_test.mocks.dart @@ -0,0 +1,277 @@ +// Mocks generated by Mockito 5.4.5 from annotations +// in webview_flutter/test/legacy/webview_flutter_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i9; + +import 'package:flutter/foundation.dart' as _i3; +import 'package:flutter/gestures.dart' as _i8; +import 'package:flutter/widgets.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i11; +import 'package:webview_flutter_platform_interface/src/legacy/platform_interface/javascript_channel_registry.dart' + as _i7; +import 'package:webview_flutter_platform_interface/src/legacy/platform_interface/webview_platform.dart' + as _i4; +import 'package:webview_flutter_platform_interface/src/legacy/platform_interface/webview_platform_callbacks_handler.dart' + as _i6; +import 'package:webview_flutter_platform_interface/src/legacy/platform_interface/webview_platform_controller.dart' + as _i10; +import 'package:webview_flutter_platform_interface/src/legacy/types/types.dart' + as _i5; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeWidget_0 extends _i1.SmartFake implements _i2.Widget { + _FakeWidget_0(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); + + @override + String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => + super.toString(); +} + +/// A class which mocks [WebViewPlatform]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewPlatform extends _i1.Mock implements _i4.WebViewPlatform { + MockWebViewPlatform() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.Widget build({ + required _i2.BuildContext? context, + required _i5.CreationParams? creationParams, + required _i6.WebViewPlatformCallbacksHandler? + webViewPlatformCallbacksHandler, + required _i7.JavascriptChannelRegistry? javascriptChannelRegistry, + _i4.WebViewPlatformCreatedCallback? onWebViewPlatformCreated, + Set<_i3.Factory<_i8.OneSequenceGestureRecognizer>>? gestureRecognizers, + }) => + (super.noSuchMethod( + Invocation.method(#build, [], { + #context: context, + #creationParams: creationParams, + #webViewPlatformCallbacksHandler: webViewPlatformCallbacksHandler, + #javascriptChannelRegistry: javascriptChannelRegistry, + #onWebViewPlatformCreated: onWebViewPlatformCreated, + #gestureRecognizers: gestureRecognizers, + }), + returnValue: _FakeWidget_0( + this, + Invocation.method(#build, [], { + #context: context, + #creationParams: creationParams, + #webViewPlatformCallbacksHandler: webViewPlatformCallbacksHandler, + #javascriptChannelRegistry: javascriptChannelRegistry, + #onWebViewPlatformCreated: onWebViewPlatformCreated, + #gestureRecognizers: gestureRecognizers, + }), + ), + ) as _i2.Widget); + + @override + _i9.Future clearCookies() => (super.noSuchMethod( + Invocation.method(#clearCookies, []), + returnValue: _i9.Future.value(false), + ) as _i9.Future); +} + +/// A class which mocks [WebViewPlatformController]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewPlatformController extends _i1.Mock + implements _i10.WebViewPlatformController { + MockWebViewPlatformController() { + _i1.throwOnMissingStub(this); + } + + @override + _i9.Future loadFile(String? absoluteFilePath) => (super.noSuchMethod( + Invocation.method(#loadFile, [absoluteFilePath]), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future loadFlutterAsset(String? key) => (super.noSuchMethod( + Invocation.method(#loadFlutterAsset, [key]), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future loadHtmlString(String? html, {String? baseUrl}) => + (super.noSuchMethod( + Invocation.method(#loadHtmlString, [html], {#baseUrl: baseUrl}), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future loadUrl(String? url, Map? headers) => + (super.noSuchMethod( + Invocation.method(#loadUrl, [url, headers]), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future loadRequest(_i5.WebViewRequest? request) => + (super.noSuchMethod( + Invocation.method(#loadRequest, [request]), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future updateSettings(_i5.WebSettings? setting) => + (super.noSuchMethod( + Invocation.method(#updateSettings, [setting]), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future currentUrl() => (super.noSuchMethod( + Invocation.method(#currentUrl, []), + returnValue: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future canGoBack() => (super.noSuchMethod( + Invocation.method(#canGoBack, []), + returnValue: _i9.Future.value(false), + ) as _i9.Future); + + @override + _i9.Future canGoForward() => (super.noSuchMethod( + Invocation.method(#canGoForward, []), + returnValue: _i9.Future.value(false), + ) as _i9.Future); + + @override + _i9.Future goBack() => (super.noSuchMethod( + Invocation.method(#goBack, []), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future goForward() => (super.noSuchMethod( + Invocation.method(#goForward, []), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future reload() => (super.noSuchMethod( + Invocation.method(#reload, []), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future clearCache() => (super.noSuchMethod( + Invocation.method(#clearCache, []), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future evaluateJavascript(String? javascript) => + (super.noSuchMethod( + Invocation.method(#evaluateJavascript, [javascript]), + returnValue: _i9.Future.value( + _i11.dummyValue( + this, + Invocation.method(#evaluateJavascript, [javascript]), + ), + ), + ) as _i9.Future); + + @override + _i9.Future runJavascript(String? javascript) => (super.noSuchMethod( + Invocation.method(#runJavascript, [javascript]), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future runJavascriptReturningResult(String? javascript) => + (super.noSuchMethod( + Invocation.method(#runJavascriptReturningResult, [javascript]), + returnValue: _i9.Future.value( + _i11.dummyValue( + this, + Invocation.method(#runJavascriptReturningResult, [javascript]), + ), + ), + ) as _i9.Future); + + @override + _i9.Future addJavascriptChannels(Set? javascriptChannelNames) => + (super.noSuchMethod( + Invocation.method(#addJavascriptChannels, [javascriptChannelNames]), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future removeJavascriptChannels( + Set? javascriptChannelNames, + ) => + (super.noSuchMethod( + Invocation.method(#removeJavascriptChannels, [ + javascriptChannelNames, + ]), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future getTitle() => (super.noSuchMethod( + Invocation.method(#getTitle, []), + returnValue: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future scrollTo(int? x, int? y) => (super.noSuchMethod( + Invocation.method(#scrollTo, [x, y]), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future scrollBy(int? x, int? y) => (super.noSuchMethod( + Invocation.method(#scrollBy, [x, y]), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future getScrollX() => (super.noSuchMethod( + Invocation.method(#getScrollX, []), + returnValue: _i9.Future.value(0), + ) as _i9.Future); + + @override + _i9.Future getScrollY() => (super.noSuchMethod( + Invocation.method(#getScrollY, []), + returnValue: _i9.Future.value(0), + ) as _i9.Future); +} diff --git a/local_packages/webview_flutter-4.13.0/test/navigation_delegate_test.dart b/local_packages/webview_flutter-4.13.0/test/navigation_delegate_test.dart new file mode 100644 index 0000000..0389b1c --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/test/navigation_delegate_test.dart @@ -0,0 +1,153 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'navigation_delegate_test.mocks.dart'; + +@GenerateMocks([ + WebViewPlatform, + PlatformNavigationDelegate, + PlatformSslAuthError, +]) +void main() { + group('NavigationDelegate', () { + test('onNavigationRequest', () async { + WebViewPlatform.instance = TestWebViewPlatform(); + + NavigationDecision onNavigationRequest(NavigationRequest request) { + return NavigationDecision.navigate; + } + + final NavigationDelegate delegate = NavigationDelegate( + onNavigationRequest: onNavigationRequest, + ); + + verify(delegate.platform.setOnNavigationRequest(onNavigationRequest)); + }); + + test('onPageStarted', () async { + WebViewPlatform.instance = TestWebViewPlatform(); + + void onPageStarted(String url) {} + + final NavigationDelegate delegate = NavigationDelegate( + onPageStarted: onPageStarted, + ); + + verify(delegate.platform.setOnPageStarted(onPageStarted)); + }); + + test('onPageFinished', () async { + WebViewPlatform.instance = TestWebViewPlatform(); + + void onPageFinished(String url) {} + + final NavigationDelegate delegate = NavigationDelegate( + onPageFinished: onPageFinished, + ); + + verify(delegate.platform.setOnPageFinished(onPageFinished)); + }); + + test('onProgress', () async { + WebViewPlatform.instance = TestWebViewPlatform(); + + void onProgress(int progress) {} + + final NavigationDelegate delegate = NavigationDelegate( + onProgress: onProgress, + ); + + verify(delegate.platform.setOnProgress(onProgress)); + }); + + test('onWebResourceError', () async { + WebViewPlatform.instance = TestWebViewPlatform(); + + void onWebResourceError(WebResourceError error) {} + + final NavigationDelegate delegate = NavigationDelegate( + onWebResourceError: onWebResourceError, + ); + + verify(delegate.platform.setOnWebResourceError(onWebResourceError)); + }); + + test('onUrlChange', () async { + WebViewPlatform.instance = TestWebViewPlatform(); + + void onUrlChange(UrlChange change) {} + + final NavigationDelegate delegate = NavigationDelegate( + onUrlChange: onUrlChange, + ); + + verify(delegate.platform.setOnUrlChange(onUrlChange)); + }); + + test('onHttpAuthRequest', () { + WebViewPlatform.instance = TestWebViewPlatform(); + + void onHttpAuthRequest(HttpAuthRequest request) {} + + final NavigationDelegate delegate = NavigationDelegate( + onHttpAuthRequest: onHttpAuthRequest, + ); + + verify(delegate.platform.setOnHttpAuthRequest(onHttpAuthRequest)); + }); + + test('onHttpError', () async { + WebViewPlatform.instance = TestWebViewPlatform(); + + void onHttpError(HttpResponseError error) {} + + final NavigationDelegate delegate = NavigationDelegate( + onHttpError: onHttpError, + ); + + verify(delegate.platform.setOnHttpError(onHttpError)); + }); + + test('onSslAuthError', () async { + WebViewPlatform.instance = TestWebViewPlatform(); + + final NavigationDelegate delegate = NavigationDelegate( + onSslAuthError: expectAsync1((SslAuthError error) { + error.proceed(); + }), + ); + + final void Function(PlatformSslAuthError) callback = verify( + (delegate.platform as MockPlatformNavigationDelegate) + .setOnSSlAuthError(captureAny)) + .captured + .single as void Function(PlatformSslAuthError); + + final MockPlatformSslAuthError mockPlatformError = + MockPlatformSslAuthError(); + callback(mockPlatformError); + + verify(mockPlatformError.proceed()); + }); + }); +} + +class TestWebViewPlatform extends WebViewPlatform { + @override + PlatformNavigationDelegate createPlatformNavigationDelegate( + PlatformNavigationDelegateCreationParams params, + ) { + return TestMockPlatformNavigationDelegate(); + } +} + +class TestMockPlatformNavigationDelegate extends MockPlatformNavigationDelegate + with MockPlatformInterfaceMixin {} diff --git a/local_packages/webview_flutter-4.13.0/test/navigation_delegate_test.mocks.dart b/local_packages/webview_flutter-4.13.0/test/navigation_delegate_test.mocks.dart new file mode 100644 index 0000000..7c93bf2 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/test/navigation_delegate_test.mocks.dart @@ -0,0 +1,260 @@ +// Mocks generated by Mockito 5.4.5 from annotations +// in webview_flutter/test/navigation_delegate_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i8; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i10; +import 'package:webview_flutter_platform_interface/src/platform_navigation_delegate.dart' + as _i3; +import 'package:webview_flutter_platform_interface/src/platform_ssl_auth_error.dart' + as _i9; +import 'package:webview_flutter_platform_interface/src/platform_webview_controller.dart' + as _i4; +import 'package:webview_flutter_platform_interface/src/platform_webview_cookie_manager.dart' + as _i2; +import 'package:webview_flutter_platform_interface/src/platform_webview_widget.dart' + as _i5; +import 'package:webview_flutter_platform_interface/src/types/types.dart' as _i6; +import 'package:webview_flutter_platform_interface/src/webview_platform.dart' + as _i7; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakePlatformWebViewCookieManager_0 extends _i1.SmartFake + implements _i2.PlatformWebViewCookieManager { + _FakePlatformWebViewCookieManager_0( + Object parent, + Invocation parentInvocation, + ) : super(parent, parentInvocation); +} + +class _FakePlatformNavigationDelegate_1 extends _i1.SmartFake + implements _i3.PlatformNavigationDelegate { + _FakePlatformNavigationDelegate_1(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakePlatformWebViewController_2 extends _i1.SmartFake + implements _i4.PlatformWebViewController { + _FakePlatformWebViewController_2(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakePlatformWebViewWidget_3 extends _i1.SmartFake + implements _i5.PlatformWebViewWidget { + _FakePlatformWebViewWidget_3(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakePlatformNavigationDelegateCreationParams_4 extends _i1.SmartFake + implements _i6.PlatformNavigationDelegateCreationParams { + _FakePlatformNavigationDelegateCreationParams_4( + Object parent, + Invocation parentInvocation, + ) : super(parent, parentInvocation); +} + +/// A class which mocks [WebViewPlatform]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewPlatform extends _i1.Mock implements _i7.WebViewPlatform { + MockWebViewPlatform() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PlatformWebViewCookieManager createPlatformCookieManager( + _i6.PlatformWebViewCookieManagerCreationParams? params, + ) => + (super.noSuchMethod( + Invocation.method(#createPlatformCookieManager, [params]), + returnValue: _FakePlatformWebViewCookieManager_0( + this, + Invocation.method(#createPlatformCookieManager, [params]), + ), + ) as _i2.PlatformWebViewCookieManager); + + @override + _i3.PlatformNavigationDelegate createPlatformNavigationDelegate( + _i6.PlatformNavigationDelegateCreationParams? params, + ) => + (super.noSuchMethod( + Invocation.method(#createPlatformNavigationDelegate, [params]), + returnValue: _FakePlatformNavigationDelegate_1( + this, + Invocation.method(#createPlatformNavigationDelegate, [params]), + ), + ) as _i3.PlatformNavigationDelegate); + + @override + _i4.PlatformWebViewController createPlatformWebViewController( + _i6.PlatformWebViewControllerCreationParams? params, + ) => + (super.noSuchMethod( + Invocation.method(#createPlatformWebViewController, [params]), + returnValue: _FakePlatformWebViewController_2( + this, + Invocation.method(#createPlatformWebViewController, [params]), + ), + ) as _i4.PlatformWebViewController); + + @override + _i5.PlatformWebViewWidget createPlatformWebViewWidget( + _i6.PlatformWebViewWidgetCreationParams? params, + ) => + (super.noSuchMethod( + Invocation.method(#createPlatformWebViewWidget, [params]), + returnValue: _FakePlatformWebViewWidget_3( + this, + Invocation.method(#createPlatformWebViewWidget, [params]), + ), + ) as _i5.PlatformWebViewWidget); +} + +/// A class which mocks [PlatformNavigationDelegate]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPlatformNavigationDelegate extends _i1.Mock + implements _i3.PlatformNavigationDelegate { + MockPlatformNavigationDelegate() { + _i1.throwOnMissingStub(this); + } + + @override + _i6.PlatformNavigationDelegateCreationParams get params => + (super.noSuchMethod( + Invocation.getter(#params), + returnValue: _FakePlatformNavigationDelegateCreationParams_4( + this, + Invocation.getter(#params), + ), + ) as _i6.PlatformNavigationDelegateCreationParams); + + @override + _i8.Future setOnNavigationRequest( + _i3.NavigationRequestCallback? onNavigationRequest, + ) => + (super.noSuchMethod( + Invocation.method(#setOnNavigationRequest, [onNavigationRequest]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + + @override + _i8.Future setOnPageStarted(_i3.PageEventCallback? onPageStarted) => + (super.noSuchMethod( + Invocation.method(#setOnPageStarted, [onPageStarted]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + + @override + _i8.Future setOnPageFinished(_i3.PageEventCallback? onPageFinished) => + (super.noSuchMethod( + Invocation.method(#setOnPageFinished, [onPageFinished]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + + @override + _i8.Future setOnHttpError(_i3.HttpResponseErrorCallback? onHttpError) => + (super.noSuchMethod( + Invocation.method(#setOnHttpError, [onHttpError]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + + @override + _i8.Future setOnProgress(_i3.ProgressCallback? onProgress) => + (super.noSuchMethod( + Invocation.method(#setOnProgress, [onProgress]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + + @override + _i8.Future setOnWebResourceError( + _i3.WebResourceErrorCallback? onWebResourceError, + ) => + (super.noSuchMethod( + Invocation.method(#setOnWebResourceError, [onWebResourceError]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + + @override + _i8.Future setOnUrlChange(_i3.UrlChangeCallback? onUrlChange) => + (super.noSuchMethod( + Invocation.method(#setOnUrlChange, [onUrlChange]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + + @override + _i8.Future setOnHttpAuthRequest( + _i3.HttpAuthRequestCallback? onHttpAuthRequest, + ) => + (super.noSuchMethod( + Invocation.method(#setOnHttpAuthRequest, [onHttpAuthRequest]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + + @override + _i8.Future setOnSSlAuthError( + _i3.SslAuthErrorCallback? onSslAuthError, + ) => + (super.noSuchMethod( + Invocation.method(#setOnSSlAuthError, [onSslAuthError]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); +} + +/// A class which mocks [PlatformSslAuthError]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPlatformSslAuthError extends _i1.Mock + implements _i9.PlatformSslAuthError { + MockPlatformSslAuthError() { + _i1.throwOnMissingStub(this); + } + + @override + String get description => (super.noSuchMethod( + Invocation.getter(#description), + returnValue: _i10.dummyValue( + this, + Invocation.getter(#description), + ), + ) as String); + + @override + _i8.Future proceed() => (super.noSuchMethod( + Invocation.method(#proceed, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + + @override + _i8.Future cancel() => (super.noSuchMethod( + Invocation.method(#cancel, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); +} diff --git a/local_packages/webview_flutter-4.13.0/test/webview_controller_test.dart b/local_packages/webview_flutter-4.13.0/test/webview_controller_test.dart new file mode 100644 index 0000000..02acaa1 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/test/webview_controller_test.dart @@ -0,0 +1,561 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'webview_controller_test.mocks.dart'; + +@GenerateMocks([PlatformWebViewController, PlatformNavigationDelegate]) +void main() { + test('loadFile', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await webViewController.loadFile('file/path'); + verify(mockPlatformWebViewController.loadFile('file/path')); + }); + + test('loadFlutterAsset', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await webViewController.loadFlutterAsset('file/path'); + verify(mockPlatformWebViewController.loadFlutterAsset('file/path')); + }); + + test('loadHtmlString', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await webViewController.loadHtmlString('html', baseUrl: 'baseUrl'); + verify(mockPlatformWebViewController.loadHtmlString( + 'html', + baseUrl: 'baseUrl', + )); + }); + + test('loadRequest', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await webViewController.loadRequest( + Uri(scheme: 'https', host: 'dart.dev'), + method: LoadRequestMethod.post, + headers: {'a': 'header'}, + body: Uint8List(0), + ); + + final LoadRequestParams params = + verify(mockPlatformWebViewController.loadRequest(captureAny)) + .captured[0] as LoadRequestParams; + expect(params.uri, Uri(scheme: 'https', host: 'dart.dev')); + expect(params.method, LoadRequestMethod.post); + expect(params.headers, {'a': 'header'}); + expect(params.body, Uint8List(0)); + }); + + test('currentUrl', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + when(mockPlatformWebViewController.currentUrl()).thenAnswer( + (_) => Future.value('https://dart.dev'), + ); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await expectLater( + webViewController.currentUrl(), + completion('https://dart.dev'), + ); + }); + + test('canGoBack', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + when(mockPlatformWebViewController.canGoBack()).thenAnswer( + (_) => Future.value(false), + ); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await expectLater(webViewController.canGoBack(), completion(false)); + }); + + test('canGoForward', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + when(mockPlatformWebViewController.canGoForward()).thenAnswer( + (_) => Future.value(true), + ); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await expectLater(webViewController.canGoForward(), completion(true)); + }); + + test('goBack', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await webViewController.goBack(); + verify(mockPlatformWebViewController.goBack()); + }); + + test('goForward', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await webViewController.goForward(); + verify(mockPlatformWebViewController.goForward()); + }); + + test('reload', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await webViewController.reload(); + verify(mockPlatformWebViewController.reload()); + }); + + test('clearCache', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await webViewController.clearCache(); + verify(mockPlatformWebViewController.clearCache()); + }); + + test('clearLocalStorage', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await webViewController.clearLocalStorage(); + verify(mockPlatformWebViewController.clearLocalStorage()); + }); + + test('runJavaScript', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await webViewController.runJavaScript('1 + 1'); + verify(mockPlatformWebViewController.runJavaScript('1 + 1')); + }); + + test('runJavaScriptReturningResult', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + when(mockPlatformWebViewController.runJavaScriptReturningResult('1 + 1')) + .thenAnswer((_) => Future.value('2')); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await expectLater( + webViewController.runJavaScriptReturningResult('1 + 1'), + completion('2'), + ); + }); + + test('addJavaScriptChannel', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + void onMessageReceived(JavaScriptMessage message) {} + await webViewController.addJavaScriptChannel( + 'name', + onMessageReceived: onMessageReceived, + ); + + final JavaScriptChannelParams params = + verify(mockPlatformWebViewController.addJavaScriptChannel(captureAny)) + .captured[0] as JavaScriptChannelParams; + expect(params.name, 'name'); + expect(params.onMessageReceived, onMessageReceived); + }); + + test('removeJavaScriptChannel', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await webViewController.removeJavaScriptChannel('channel'); + verify(mockPlatformWebViewController.removeJavaScriptChannel('channel')); + }); + + test('getTitle', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + when(mockPlatformWebViewController.getTitle()) + .thenAnswer((_) => Future.value('myTitle')); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await expectLater(webViewController.getTitle(), completion('myTitle')); + }); + + test('scrollTo', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await webViewController.scrollTo(2, 3); + verify(mockPlatformWebViewController.scrollTo(2, 3)); + }); + + test('scrollBy', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await webViewController.scrollBy(2, 3); + verify(mockPlatformWebViewController.scrollBy(2, 3)); + }); + + test('setVerticalScrollBarEnabled', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await webViewController.setVerticalScrollBarEnabled(true); + verify(mockPlatformWebViewController.setVerticalScrollBarEnabled(true)); + }); + + test('setHorizontalScrollBarEnabled', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await webViewController.setHorizontalScrollBarEnabled(false); + verify(mockPlatformWebViewController.setHorizontalScrollBarEnabled(false)); + }); + + test('supportsSetScrollBarsEnabled', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + when(mockPlatformWebViewController.supportsSetScrollBarsEnabled()) + .thenReturn(true); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + expect(await webViewController.supportsSetScrollBarsEnabled(), true); + verify(mockPlatformWebViewController.supportsSetScrollBarsEnabled()); + }); + + test('getScrollPosition', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + when(mockPlatformWebViewController.getScrollPosition()).thenAnswer( + (_) => Future.value(const Offset(2, 3)), + ); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await expectLater( + webViewController.getScrollPosition(), + completion(const Offset(2.0, 3.0)), + ); + }); + + test('enableZoom', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await webViewController.enableZoom(false); + verify(mockPlatformWebViewController.enableZoom(false)); + }); + + test('setBackgroundColor', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await webViewController.setBackgroundColor(Colors.green); + verify(mockPlatformWebViewController.setBackgroundColor(Colors.green)); + }); + + test('setJavaScriptMode', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await webViewController.setJavaScriptMode(JavaScriptMode.disabled); + verify( + mockPlatformWebViewController.setJavaScriptMode(JavaScriptMode.disabled), + ); + }); + + test('setUserAgent', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await webViewController.setUserAgent('userAgent'); + verify(mockPlatformWebViewController.setUserAgent('userAgent')); + }); + + test('setNavigationDelegate', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + final MockPlatformNavigationDelegate mockPlatformNavigationDelegate = + MockPlatformNavigationDelegate(); + final NavigationDelegate navigationDelegate = + NavigationDelegate.fromPlatform(mockPlatformNavigationDelegate); + + await webViewController.setNavigationDelegate(navigationDelegate); + verify(mockPlatformWebViewController.setPlatformNavigationDelegate( + mockPlatformNavigationDelegate, + )); + }); + + test('onPermissionRequest', () async { + bool permissionRequestCallbackCalled = false; + + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + WebViewController.fromPlatform( + mockPlatformWebViewController, + onPermissionRequest: (WebViewPermissionRequest request) { + permissionRequestCallbackCalled = true; + }, + ); + + final void Function(PlatformWebViewPermissionRequest request) + requestCallback = verify(mockPlatformWebViewController + .setOnPlatformPermissionRequest(captureAny)) + .captured + .single as void Function(PlatformWebViewPermissionRequest request); + + requestCallback(const TestPlatformWebViewPermissionRequest()); + expect(permissionRequestCallbackCalled, isTrue); + }); + + test('setConsoleLogCallback', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + void onConsoleMessage(JavaScriptConsoleMessage message) {} + + await webViewController.setOnConsoleMessage(onConsoleMessage); + + verify(mockPlatformWebViewController.setOnConsoleMessage(onConsoleMessage)); + }); + + test('setOnJavaScriptAlertDialog', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + Future onJavaScriptAlertDialog( + JavaScriptAlertDialogRequest request) async { + return; + } + + await webViewController.setOnJavaScriptAlertDialog(onJavaScriptAlertDialog); + verify(mockPlatformWebViewController + .setOnJavaScriptAlertDialog(onJavaScriptAlertDialog)); + }); + + test('setOnJavaScriptConfirmDialog', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + Future onJavaScriptConfirmDialog( + JavaScriptConfirmDialogRequest request) async { + return true; + } + + await webViewController + .setOnJavaScriptConfirmDialog(onJavaScriptConfirmDialog); + verify(mockPlatformWebViewController + .setOnJavaScriptConfirmDialog(onJavaScriptConfirmDialog)); + }); + + test('setOnJavaScriptTextInputDialog', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + Future onJavaScriptTextInputDialog( + JavaScriptTextInputDialogRequest request) async { + return 'text'; + } + + await webViewController + .setOnJavaScriptTextInputDialog(onJavaScriptTextInputDialog); + verify(mockPlatformWebViewController + .setOnJavaScriptTextInputDialog(onJavaScriptTextInputDialog)); + }); + + test('getUserAgent', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + const String userAgent = 'str'; + + when(mockPlatformWebViewController.getUserAgent()).thenAnswer( + (_) => Future.value(userAgent), + ); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + await expectLater(webViewController.getUserAgent(), completion(userAgent)); + }); + + test('setOnScrollPositionChange', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + void onScrollPositionChange(ScrollPositionChange change) {} + + await webViewController.setOnScrollPositionChange(onScrollPositionChange); + + verify( + mockPlatformWebViewController + .setOnScrollPositionChange(onScrollPositionChange), + ); + }); + + test('setOverScrollMode', () async { + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + + final WebViewController webViewController = WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + await webViewController.setOverScrollMode(WebViewOverScrollMode.never); + verify( + mockPlatformWebViewController.setOverScrollMode( + WebViewOverScrollMode.never, + ), + ); + }); +} + +class TestPlatformWebViewPermissionRequest + extends PlatformWebViewPermissionRequest { + const TestPlatformWebViewPermissionRequest() + : super(types: const {}); + + @override + Future grant() async {} + + @override + Future deny() async {} +} diff --git a/local_packages/webview_flutter-4.13.0/test/webview_controller_test.mocks.dart b/local_packages/webview_flutter-4.13.0/test/webview_controller_test.mocks.dart new file mode 100644 index 0000000..ef5d467 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/test/webview_controller_test.mocks.dart @@ -0,0 +1,471 @@ +// Mocks generated by Mockito 5.4.5 from annotations +// in webview_flutter/test/webview_controller_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i5; +import 'dart:ui' as _i3; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:webview_flutter_platform_interface/src/platform_navigation_delegate.dart' + as _i6; +import 'package:webview_flutter_platform_interface/src/platform_webview_controller.dart' + as _i4; +import 'package:webview_flutter_platform_interface/src/types/types.dart' as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakePlatformWebViewControllerCreationParams_0 extends _i1.SmartFake + implements _i2.PlatformWebViewControllerCreationParams { + _FakePlatformWebViewControllerCreationParams_0( + Object parent, + Invocation parentInvocation, + ) : super(parent, parentInvocation); +} + +class _FakeObject_1 extends _i1.SmartFake implements Object { + _FakeObject_1(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeOffset_2 extends _i1.SmartFake implements _i3.Offset { + _FakeOffset_2(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakePlatformNavigationDelegateCreationParams_3 extends _i1.SmartFake + implements _i2.PlatformNavigationDelegateCreationParams { + _FakePlatformNavigationDelegateCreationParams_3( + Object parent, + Invocation parentInvocation, + ) : super(parent, parentInvocation); +} + +/// A class which mocks [PlatformWebViewController]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPlatformWebViewController extends _i1.Mock + implements _i4.PlatformWebViewController { + MockPlatformWebViewController() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PlatformWebViewControllerCreationParams get params => (super.noSuchMethod( + Invocation.getter(#params), + returnValue: _FakePlatformWebViewControllerCreationParams_0( + this, + Invocation.getter(#params), + ), + ) as _i2.PlatformWebViewControllerCreationParams); + + @override + _i5.Future loadFile(String? absoluteFilePath) => (super.noSuchMethod( + Invocation.method(#loadFile, [absoluteFilePath]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future loadFlutterAsset(String? key) => (super.noSuchMethod( + Invocation.method(#loadFlutterAsset, [key]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future loadHtmlString(String? html, {String? baseUrl}) => + (super.noSuchMethod( + Invocation.method(#loadHtmlString, [html], {#baseUrl: baseUrl}), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future loadRequest(_i2.LoadRequestParams? params) => + (super.noSuchMethod( + Invocation.method(#loadRequest, [params]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future currentUrl() => (super.noSuchMethod( + Invocation.method(#currentUrl, []), + returnValue: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future canGoBack() => (super.noSuchMethod( + Invocation.method(#canGoBack, []), + returnValue: _i5.Future.value(false), + ) as _i5.Future); + + @override + _i5.Future canGoForward() => (super.noSuchMethod( + Invocation.method(#canGoForward, []), + returnValue: _i5.Future.value(false), + ) as _i5.Future); + + @override + _i5.Future goBack() => (super.noSuchMethod( + Invocation.method(#goBack, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future goForward() => (super.noSuchMethod( + Invocation.method(#goForward, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future reload() => (super.noSuchMethod( + Invocation.method(#reload, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future clearCache() => (super.noSuchMethod( + Invocation.method(#clearCache, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future clearLocalStorage() => (super.noSuchMethod( + Invocation.method(#clearLocalStorage, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setPlatformNavigationDelegate( + _i6.PlatformNavigationDelegate? handler, + ) => + (super.noSuchMethod( + Invocation.method(#setPlatformNavigationDelegate, [handler]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future runJavaScript(String? javaScript) => (super.noSuchMethod( + Invocation.method(#runJavaScript, [javaScript]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future runJavaScriptReturningResult(String? javaScript) => + (super.noSuchMethod( + Invocation.method(#runJavaScriptReturningResult, [javaScript]), + returnValue: _i5.Future.value( + _FakeObject_1( + this, + Invocation.method(#runJavaScriptReturningResult, [javaScript]), + ), + ), + ) as _i5.Future); + + @override + _i5.Future addJavaScriptChannel( + _i4.JavaScriptChannelParams? javaScriptChannelParams, + ) => + (super.noSuchMethod( + Invocation.method(#addJavaScriptChannel, [javaScriptChannelParams]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future removeJavaScriptChannel(String? javaScriptChannelName) => + (super.noSuchMethod( + Invocation.method(#removeJavaScriptChannel, [ + javaScriptChannelName, + ]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future getTitle() => (super.noSuchMethod( + Invocation.method(#getTitle, []), + returnValue: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future scrollTo(int? x, int? y) => (super.noSuchMethod( + Invocation.method(#scrollTo, [x, y]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future scrollBy(int? x, int? y) => (super.noSuchMethod( + Invocation.method(#scrollBy, [x, y]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setVerticalScrollBarEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setVerticalScrollBarEnabled, [enabled]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setHorizontalScrollBarEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setHorizontalScrollBarEnabled, [enabled]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + bool supportsSetScrollBarsEnabled() => (super.noSuchMethod( + Invocation.method(#supportsSetScrollBarsEnabled, []), + returnValue: false, + ) as bool); + + @override + _i5.Future<_i3.Offset> getScrollPosition() => (super.noSuchMethod( + Invocation.method(#getScrollPosition, []), + returnValue: _i5.Future<_i3.Offset>.value( + _FakeOffset_2(this, Invocation.method(#getScrollPosition, [])), + ), + ) as _i5.Future<_i3.Offset>); + + @override + _i5.Future enableZoom(bool? enabled) => (super.noSuchMethod( + Invocation.method(#enableZoom, [enabled]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setBackgroundColor(_i3.Color? color) => (super.noSuchMethod( + Invocation.method(#setBackgroundColor, [color]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setJavaScriptMode(_i2.JavaScriptMode? javaScriptMode) => + (super.noSuchMethod( + Invocation.method(#setJavaScriptMode, [javaScriptMode]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setUserAgent(String? userAgent) => (super.noSuchMethod( + Invocation.method(#setUserAgent, [userAgent]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setOnPlatformPermissionRequest( + void Function(_i2.PlatformWebViewPermissionRequest)? onPermissionRequest, + ) => + (super.noSuchMethod( + Invocation.method(#setOnPlatformPermissionRequest, [ + onPermissionRequest, + ]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future getUserAgent() => (super.noSuchMethod( + Invocation.method(#getUserAgent, []), + returnValue: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setOnConsoleMessage( + void Function(_i2.JavaScriptConsoleMessage)? onConsoleMessage, + ) => + (super.noSuchMethod( + Invocation.method(#setOnConsoleMessage, [onConsoleMessage]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setOnScrollPositionChange( + void Function(_i2.ScrollPositionChange)? onScrollPositionChange, + ) => + (super.noSuchMethod( + Invocation.method(#setOnScrollPositionChange, [ + onScrollPositionChange, + ]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setOnJavaScriptAlertDialog( + _i5.Future Function(_i2.JavaScriptAlertDialogRequest)? + onJavaScriptAlertDialog, + ) => + (super.noSuchMethod( + Invocation.method(#setOnJavaScriptAlertDialog, [ + onJavaScriptAlertDialog, + ]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setOnJavaScriptConfirmDialog( + _i5.Future Function(_i2.JavaScriptConfirmDialogRequest)? + onJavaScriptConfirmDialog, + ) => + (super.noSuchMethod( + Invocation.method(#setOnJavaScriptConfirmDialog, [ + onJavaScriptConfirmDialog, + ]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setOnJavaScriptTextInputDialog( + _i5.Future Function(_i2.JavaScriptTextInputDialogRequest)? + onJavaScriptTextInputDialog, + ) => + (super.noSuchMethod( + Invocation.method(#setOnJavaScriptTextInputDialog, [ + onJavaScriptTextInputDialog, + ]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setOverScrollMode(_i2.WebViewOverScrollMode? mode) => + (super.noSuchMethod( + Invocation.method(#setOverScrollMode, [mode]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); +} + +/// A class which mocks [PlatformNavigationDelegate]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPlatformNavigationDelegate extends _i1.Mock + implements _i6.PlatformNavigationDelegate { + MockPlatformNavigationDelegate() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PlatformNavigationDelegateCreationParams get params => + (super.noSuchMethod( + Invocation.getter(#params), + returnValue: _FakePlatformNavigationDelegateCreationParams_3( + this, + Invocation.getter(#params), + ), + ) as _i2.PlatformNavigationDelegateCreationParams); + + @override + _i5.Future setOnNavigationRequest( + _i6.NavigationRequestCallback? onNavigationRequest, + ) => + (super.noSuchMethod( + Invocation.method(#setOnNavigationRequest, [onNavigationRequest]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setOnPageStarted(_i6.PageEventCallback? onPageStarted) => + (super.noSuchMethod( + Invocation.method(#setOnPageStarted, [onPageStarted]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setOnPageFinished(_i6.PageEventCallback? onPageFinished) => + (super.noSuchMethod( + Invocation.method(#setOnPageFinished, [onPageFinished]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setOnHttpError(_i6.HttpResponseErrorCallback? onHttpError) => + (super.noSuchMethod( + Invocation.method(#setOnHttpError, [onHttpError]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setOnProgress(_i6.ProgressCallback? onProgress) => + (super.noSuchMethod( + Invocation.method(#setOnProgress, [onProgress]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setOnWebResourceError( + _i6.WebResourceErrorCallback? onWebResourceError, + ) => + (super.noSuchMethod( + Invocation.method(#setOnWebResourceError, [onWebResourceError]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setOnUrlChange(_i6.UrlChangeCallback? onUrlChange) => + (super.noSuchMethod( + Invocation.method(#setOnUrlChange, [onUrlChange]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setOnHttpAuthRequest( + _i6.HttpAuthRequestCallback? onHttpAuthRequest, + ) => + (super.noSuchMethod( + Invocation.method(#setOnHttpAuthRequest, [onHttpAuthRequest]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + + @override + _i5.Future setOnSSlAuthError( + _i6.SslAuthErrorCallback? onSslAuthError, + ) => + (super.noSuchMethod( + Invocation.method(#setOnSSlAuthError, [onSslAuthError]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); +} diff --git a/local_packages/webview_flutter-4.13.0/test/webview_cookie_manager_test.dart b/local_packages/webview_flutter-4.13.0/test/webview_cookie_manager_test.dart new file mode 100644 index 0000000..babf74b --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/test/webview_cookie_manager_test.dart @@ -0,0 +1,54 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'webview_cookie_manager_test.mocks.dart'; + +@GenerateMocks([PlatformWebViewCookieManager]) +void main() { + group('WebViewCookieManager', () { + test('clearCookies', () async { + final MockPlatformWebViewCookieManager mockPlatformWebViewCookieManager = + MockPlatformWebViewCookieManager(); + when(mockPlatformWebViewCookieManager.clearCookies()).thenAnswer( + (_) => Future.value(false), + ); + + final WebViewCookieManager cookieManager = + WebViewCookieManager.fromPlatform( + mockPlatformWebViewCookieManager, + ); + + await expectLater(cookieManager.clearCookies(), completion(false)); + }); + + test('setCookie', () async { + final MockPlatformWebViewCookieManager mockPlatformWebViewCookieManager = + MockPlatformWebViewCookieManager(); + + final WebViewCookieManager cookieManager = + WebViewCookieManager.fromPlatform( + mockPlatformWebViewCookieManager, + ); + + const WebViewCookie cookie = WebViewCookie( + name: 'name', + value: 'value', + domain: 'domain', + ); + + await cookieManager.setCookie(cookie); + + final WebViewCookie capturedCookie = verify( + mockPlatformWebViewCookieManager.setCookie(captureAny), + ).captured.single as WebViewCookie; + expect(capturedCookie, cookie); + }); + }); +} diff --git a/local_packages/webview_flutter-4.13.0/test/webview_cookie_manager_test.mocks.dart b/local_packages/webview_flutter-4.13.0/test/webview_cookie_manager_test.mocks.dart new file mode 100644 index 0000000..31b1188 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/test/webview_cookie_manager_test.mocks.dart @@ -0,0 +1,66 @@ +// Mocks generated by Mockito 5.4.5 from annotations +// in webview_flutter/test/webview_cookie_manager_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i4; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:webview_flutter_platform_interface/src/platform_webview_cookie_manager.dart' + as _i3; +import 'package:webview_flutter_platform_interface/src/types/types.dart' as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakePlatformWebViewCookieManagerCreationParams_0 extends _i1.SmartFake + implements _i2.PlatformWebViewCookieManagerCreationParams { + _FakePlatformWebViewCookieManagerCreationParams_0( + Object parent, + Invocation parentInvocation, + ) : super(parent, parentInvocation); +} + +/// A class which mocks [PlatformWebViewCookieManager]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPlatformWebViewCookieManager extends _i1.Mock + implements _i3.PlatformWebViewCookieManager { + MockPlatformWebViewCookieManager() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PlatformWebViewCookieManagerCreationParams get params => + (super.noSuchMethod( + Invocation.getter(#params), + returnValue: _FakePlatformWebViewCookieManagerCreationParams_0( + this, + Invocation.getter(#params), + ), + ) as _i2.PlatformWebViewCookieManagerCreationParams); + + @override + _i4.Future clearCookies() => (super.noSuchMethod( + Invocation.method(#clearCookies, []), + returnValue: _i4.Future.value(false), + ) as _i4.Future); + + @override + _i4.Future setCookie(_i2.WebViewCookie? cookie) => (super.noSuchMethod( + Invocation.method(#setCookie, [cookie]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); +} diff --git a/local_packages/webview_flutter-4.13.0/test/webview_flutter_export_test.dart b/local_packages/webview_flutter-4.13.0/test/webview_flutter_export_test.dart new file mode 100644 index 0000000..55cebdc --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/test/webview_flutter_export_test.dart @@ -0,0 +1,47 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: unnecessary_statements + +import 'package:flutter_test/flutter_test.dart'; +import 'package:webview_flutter/webview_flutter.dart' as main_file; + +void main() { + group('webview_flutter', () { + test( + 'ensure webview_flutter.dart exports classes from platform interface', + () { + main_file.HttpAuthRequest; + main_file.HttpResponseError; + main_file.HttpResponseErrorCallback; + main_file.JavaScriptConsoleMessage; + main_file.JavaScriptLogLevel; + main_file.JavaScriptMessage; + main_file.JavaScriptMode; + main_file.LoadRequestMethod; + main_file.NavigationDecision; + main_file.NavigationRequest; + main_file.NavigationRequestCallback; + main_file.PageEventCallback; + main_file.PlatformNavigationDelegateCreationParams; + main_file.PlatformWebViewControllerCreationParams; + main_file.PlatformWebViewCookieManagerCreationParams; + main_file.PlatformWebViewPermissionRequest; + main_file.PlatformWebViewWidgetCreationParams; + main_file.ProgressCallback; + main_file.UrlChange; + main_file.WebViewOverScrollMode; + main_file.WebViewPermissionResourceType; + main_file.WebResourceError; + main_file.WebResourceErrorCallback; + main_file.WebViewCookie; + main_file.WebViewCredential; + main_file.WebResourceErrorType; + main_file.WebResourceRequest; + main_file.WebResourceResponse; + main_file.X509Certificate; + }, + ); + }); +} diff --git a/local_packages/webview_flutter-4.13.0/test/webview_widget_test.dart b/local_packages/webview_flutter-4.13.0/test/webview_widget_test.dart new file mode 100644 index 0000000..21e9f53 --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/test/webview_widget_test.dart @@ -0,0 +1,86 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'webview_widget_test.mocks.dart'; + +@GenerateMocks([PlatformWebViewController, PlatformWebViewWidget]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('WebViewWidget', () { + testWidgets('build', (WidgetTester tester) async { + final MockPlatformWebViewWidget mockPlatformWebViewWidget = + MockPlatformWebViewWidget(); + when(mockPlatformWebViewWidget.build(any)).thenReturn(Container()); + + await tester.pumpWidget(WebViewWidget.fromPlatform( + platform: mockPlatformWebViewWidget, + )); + + expect(find.byType(Container), findsOneWidget); + }); + + testWidgets( + 'constructor parameters are correctly passed to creation params', + (WidgetTester tester) async { + WebViewPlatform.instance = TestWebViewPlatform(); + + final MockPlatformWebViewController mockPlatformWebViewController = + MockPlatformWebViewController(); + final WebViewController webViewController = + WebViewController.fromPlatform( + mockPlatformWebViewController, + ); + + final WebViewWidget webViewWidget = WebViewWidget( + key: GlobalKey(), + controller: webViewController, + layoutDirection: TextDirection.rtl, + gestureRecognizers: >{ + Factory(() => EagerGestureRecognizer()), + }, + ); + + // The key passed to the default constructor is used by the super class + // and not passed to the platform implementation. + expect(webViewWidget.platform.params.key, isNull); + expect( + webViewWidget.platform.params.controller, + webViewController.platform, + ); + expect(webViewWidget.platform.params.layoutDirection, TextDirection.rtl); + expect( + webViewWidget.platform.params.gestureRecognizers.isNotEmpty, + isTrue, + ); + }); + }); +} + +class TestWebViewPlatform extends WebViewPlatform { + @override + PlatformWebViewWidget createPlatformWebViewWidget( + PlatformWebViewWidgetCreationParams params, + ) { + return TestPlatformWebViewWidget(params); + } +} + +class TestPlatformWebViewWidget extends PlatformWebViewWidget { + TestPlatformWebViewWidget(super.params) : super.implementation(); + + @override + Widget build(BuildContext context) { + throw UnimplementedError(); + } +} diff --git a/local_packages/webview_flutter-4.13.0/test/webview_widget_test.mocks.dart b/local_packages/webview_flutter-4.13.0/test/webview_widget_test.mocks.dart new file mode 100644 index 0000000..691f0fd --- /dev/null +++ b/local_packages/webview_flutter-4.13.0/test/webview_widget_test.mocks.dart @@ -0,0 +1,412 @@ +// Mocks generated by Mockito 5.4.5 from annotations +// in webview_flutter/test/webview_widget_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i7; +import 'dart:ui' as _i3; + +import 'package:flutter/foundation.dart' as _i5; +import 'package:flutter/widgets.dart' as _i4; +import 'package:mockito/mockito.dart' as _i1; +import 'package:webview_flutter_platform_interface/src/platform_navigation_delegate.dart' + as _i8; +import 'package:webview_flutter_platform_interface/src/platform_webview_controller.dart' + as _i6; +import 'package:webview_flutter_platform_interface/src/platform_webview_widget.dart' + as _i9; +import 'package:webview_flutter_platform_interface/src/types/types.dart' as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakePlatformWebViewControllerCreationParams_0 extends _i1.SmartFake + implements _i2.PlatformWebViewControllerCreationParams { + _FakePlatformWebViewControllerCreationParams_0( + Object parent, + Invocation parentInvocation, + ) : super(parent, parentInvocation); +} + +class _FakeObject_1 extends _i1.SmartFake implements Object { + _FakeObject_1(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeOffset_2 extends _i1.SmartFake implements _i3.Offset { + _FakeOffset_2(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakePlatformWebViewWidgetCreationParams_3 extends _i1.SmartFake + implements _i2.PlatformWebViewWidgetCreationParams { + _FakePlatformWebViewWidgetCreationParams_3( + Object parent, + Invocation parentInvocation, + ) : super(parent, parentInvocation); +} + +class _FakeWidget_4 extends _i1.SmartFake implements _i4.Widget { + _FakeWidget_4(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); + + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +/// A class which mocks [PlatformWebViewController]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPlatformWebViewController extends _i1.Mock + implements _i6.PlatformWebViewController { + MockPlatformWebViewController() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PlatformWebViewControllerCreationParams get params => (super.noSuchMethod( + Invocation.getter(#params), + returnValue: _FakePlatformWebViewControllerCreationParams_0( + this, + Invocation.getter(#params), + ), + ) as _i2.PlatformWebViewControllerCreationParams); + + @override + _i7.Future loadFile(String? absoluteFilePath) => (super.noSuchMethod( + Invocation.method(#loadFile, [absoluteFilePath]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future loadFlutterAsset(String? key) => (super.noSuchMethod( + Invocation.method(#loadFlutterAsset, [key]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future loadHtmlString(String? html, {String? baseUrl}) => + (super.noSuchMethod( + Invocation.method(#loadHtmlString, [html], {#baseUrl: baseUrl}), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future loadRequest(_i2.LoadRequestParams? params) => + (super.noSuchMethod( + Invocation.method(#loadRequest, [params]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future currentUrl() => (super.noSuchMethod( + Invocation.method(#currentUrl, []), + returnValue: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future canGoBack() => (super.noSuchMethod( + Invocation.method(#canGoBack, []), + returnValue: _i7.Future.value(false), + ) as _i7.Future); + + @override + _i7.Future canGoForward() => (super.noSuchMethod( + Invocation.method(#canGoForward, []), + returnValue: _i7.Future.value(false), + ) as _i7.Future); + + @override + _i7.Future goBack() => (super.noSuchMethod( + Invocation.method(#goBack, []), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future goForward() => (super.noSuchMethod( + Invocation.method(#goForward, []), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future reload() => (super.noSuchMethod( + Invocation.method(#reload, []), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future clearCache() => (super.noSuchMethod( + Invocation.method(#clearCache, []), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future clearLocalStorage() => (super.noSuchMethod( + Invocation.method(#clearLocalStorage, []), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future setPlatformNavigationDelegate( + _i8.PlatformNavigationDelegate? handler, + ) => + (super.noSuchMethod( + Invocation.method(#setPlatformNavigationDelegate, [handler]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future runJavaScript(String? javaScript) => (super.noSuchMethod( + Invocation.method(#runJavaScript, [javaScript]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future runJavaScriptReturningResult(String? javaScript) => + (super.noSuchMethod( + Invocation.method(#runJavaScriptReturningResult, [javaScript]), + returnValue: _i7.Future.value( + _FakeObject_1( + this, + Invocation.method(#runJavaScriptReturningResult, [javaScript]), + ), + ), + ) as _i7.Future); + + @override + _i7.Future addJavaScriptChannel( + _i6.JavaScriptChannelParams? javaScriptChannelParams, + ) => + (super.noSuchMethod( + Invocation.method(#addJavaScriptChannel, [javaScriptChannelParams]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future removeJavaScriptChannel(String? javaScriptChannelName) => + (super.noSuchMethod( + Invocation.method(#removeJavaScriptChannel, [ + javaScriptChannelName, + ]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future getTitle() => (super.noSuchMethod( + Invocation.method(#getTitle, []), + returnValue: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future scrollTo(int? x, int? y) => (super.noSuchMethod( + Invocation.method(#scrollTo, [x, y]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future scrollBy(int? x, int? y) => (super.noSuchMethod( + Invocation.method(#scrollBy, [x, y]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future setVerticalScrollBarEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setVerticalScrollBarEnabled, [enabled]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future setHorizontalScrollBarEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setHorizontalScrollBarEnabled, [enabled]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + bool supportsSetScrollBarsEnabled() => (super.noSuchMethod( + Invocation.method(#supportsSetScrollBarsEnabled, []), + returnValue: false, + ) as bool); + + @override + _i7.Future<_i3.Offset> getScrollPosition() => (super.noSuchMethod( + Invocation.method(#getScrollPosition, []), + returnValue: _i7.Future<_i3.Offset>.value( + _FakeOffset_2(this, Invocation.method(#getScrollPosition, [])), + ), + ) as _i7.Future<_i3.Offset>); + + @override + _i7.Future enableZoom(bool? enabled) => (super.noSuchMethod( + Invocation.method(#enableZoom, [enabled]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future setBackgroundColor(_i3.Color? color) => (super.noSuchMethod( + Invocation.method(#setBackgroundColor, [color]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future setJavaScriptMode(_i2.JavaScriptMode? javaScriptMode) => + (super.noSuchMethod( + Invocation.method(#setJavaScriptMode, [javaScriptMode]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future setUserAgent(String? userAgent) => (super.noSuchMethod( + Invocation.method(#setUserAgent, [userAgent]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future setOnPlatformPermissionRequest( + void Function(_i2.PlatformWebViewPermissionRequest)? onPermissionRequest, + ) => + (super.noSuchMethod( + Invocation.method(#setOnPlatformPermissionRequest, [ + onPermissionRequest, + ]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future getUserAgent() => (super.noSuchMethod( + Invocation.method(#getUserAgent, []), + returnValue: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future setOnConsoleMessage( + void Function(_i2.JavaScriptConsoleMessage)? onConsoleMessage, + ) => + (super.noSuchMethod( + Invocation.method(#setOnConsoleMessage, [onConsoleMessage]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future setOnScrollPositionChange( + void Function(_i2.ScrollPositionChange)? onScrollPositionChange, + ) => + (super.noSuchMethod( + Invocation.method(#setOnScrollPositionChange, [ + onScrollPositionChange, + ]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future setOnJavaScriptAlertDialog( + _i7.Future Function(_i2.JavaScriptAlertDialogRequest)? + onJavaScriptAlertDialog, + ) => + (super.noSuchMethod( + Invocation.method(#setOnJavaScriptAlertDialog, [ + onJavaScriptAlertDialog, + ]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future setOnJavaScriptConfirmDialog( + _i7.Future Function(_i2.JavaScriptConfirmDialogRequest)? + onJavaScriptConfirmDialog, + ) => + (super.noSuchMethod( + Invocation.method(#setOnJavaScriptConfirmDialog, [ + onJavaScriptConfirmDialog, + ]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future setOnJavaScriptTextInputDialog( + _i7.Future Function(_i2.JavaScriptTextInputDialogRequest)? + onJavaScriptTextInputDialog, + ) => + (super.noSuchMethod( + Invocation.method(#setOnJavaScriptTextInputDialog, [ + onJavaScriptTextInputDialog, + ]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future setOverScrollMode(_i2.WebViewOverScrollMode? mode) => + (super.noSuchMethod( + Invocation.method(#setOverScrollMode, [mode]), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); +} + +/// A class which mocks [PlatformWebViewWidget]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPlatformWebViewWidget extends _i1.Mock + implements _i9.PlatformWebViewWidget { + MockPlatformWebViewWidget() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PlatformWebViewWidgetCreationParams get params => (super.noSuchMethod( + Invocation.getter(#params), + returnValue: _FakePlatformWebViewWidgetCreationParams_3( + this, + Invocation.getter(#params), + ), + ) as _i2.PlatformWebViewWidgetCreationParams); + + @override + _i4.Widget build(_i4.BuildContext? context) => (super.noSuchMethod( + Invocation.method(#build, [context]), + returnValue: _FakeWidget_4( + this, + Invocation.method(#build, [context]), + ), + ) as _i4.Widget); +} diff --git a/local_packages/webview_flutter_android-4.10.5/AUTHORS b/local_packages/webview_flutter_android-4.10.5/AUTHORS new file mode 100644 index 0000000..0979d96 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/AUTHORS @@ -0,0 +1,71 @@ +# Below is a list of people and organizations that have contributed +# to the Flutter project. Names should be added to the list like so: +# +# Name/Organization + +Google Inc. +The Chromium Authors +German Saprykin +Benjamin Sauer +larsenthomasj@gmail.com +Ali Bitek +Pol Batlló +Anatoly Pulyaevskiy +Hayden Flinner +Stefano Rodriguez +Salvatore Giordano +Brian Armstrong +Paul DeMarco +Fabricio Nogueira +Simon Lightfoot +Ashton Thomas +Thomas Danner +Diego Velásquez +Hajime Nakamura +Tuyển Vũ Xuân +Miguel Ruivo +Sarthak Verma +Mike Diarmid +Invertase +Elliot Hesp +Vince Varga +Aawaz Gyawali +EUI Limited +Katarina Sheremet +Thomas Stockx +Sarbagya Dhaubanjar +Ozkan Eksi +Rishab Nayak +ko2ic +Jonathan Younger +Jose Sanchez +Debkanchan Samadder +Audrius Karosevicius +Lukasz Piliszczuk +SoundReply Solutions GmbH +Rafal Wachol +Pau Picas +Christian Weder +Alexandru Tuca +Christian Weder +Rhodes Davis Jr. +Luigi Agosti +Quentin Le Guennec +Koushik Ravikumar +Nissim Dsilva +Giancarlo Rocha +Ryo Miyake +Théo Champion +Kazuki Yamaguchi +Eitan Schwartz +Chris Rutkowski +Juan Alvarez +Aleksandr Yurkovskiy +Anton Borries +Alex Li +Rahul Raj <64.rahulraj@gmail.com> +Maurits van Beusekom +Nick Bradshaw +Kai Yu +The Vinh Luong + diff --git a/local_packages/webview_flutter_android-4.10.5/CHANGELOG.md b/local_packages/webview_flutter_android-4.10.5/CHANGELOG.md new file mode 100644 index 0000000..655226a --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/CHANGELOG.md @@ -0,0 +1,559 @@ +## 4.10.5 + +* Resolves Gradle 9 deprecations. + +## 4.10.4 + +* Adds a README section about enabling geolocation. + +## 4.10.3 + +* Updates Java compatibility version to 17. + +## 4.10.2 + +* Updates minimum supported SDK version to Flutter 3.35. +* Removes obsolete code related to supporting SDK <24. + +## 4.10.1 + +* Bumps com.android.tools.build:gradle to 8.12.1 and kotlin_version to 2.2.10. +* Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. + +## 4.10.0 + +* Adds support for the Payment Request API with `AndroidWebViewController.isWebViewFeatureSupported` and `AndroidWebViewController.setPaymentRequestEnabled`. + +## 4.9.1 + +* Updates kotlin version to 2.2.0 to enable gradle 8.11 support. + +## 4.9.0 + +* Adds support for `PlatformWebViewController.loadFileWithParams`. +* Introduces `AndroidLoadFileParams`, a platform-specific extension of `LoadFileParams` for Android that adds support for `headers`. + +## 4.8.2 + +* Bumps gradle from 8.9.0 to 8.11.1. + +## 4.8.1 + +* Updates `androidx.webkit` to 1.14.0. + +## 4.8.0 + +* Adds `AndroidWebViewController.setMixedContentMode` to control how + mixed-content pages load. + +## 4.7.0 + +* Adds support to respond to recoverable SSL certificate errors. See `AndroidNavigationDelegate.setOnSSlAuthError`. + +## 4.6.0 + +* Adds support to set using wide view port. See `AndroidWebViewController.setUseWideViewPort`. +* Changes default of `WebSettings.setUseWideViewPort` to `false` to align with native WebViews. + +## 4.5.0 + +* Adds support to set whether to draw the scrollbar. See + `AndroidWebViewController.setVerticalScrollBarEnabled`, + `AndroidWebViewController.setHorizontalScrollBarEnabled`, + `AndroidWebViewController.supportsSetScrollBarsEnabled`. + +## 4.4.2 + +* Updates pigeon generated code to fix `ImplicitSamInstance` and `SyntheticAccessor` Kotlin lint + warnings. + +## 4.4.1 + +* Removes obsolete code related to supporting SDK <21. + +## 4.4.0 + +* Adds support to set the over-scroll mode for the WebView. See `AndroidWebViewController.setOverScrollMode`. + +## 4.3.5 + +* Adds internal wrapper methods for native `WebViewClient`. + +## 4.3.4 + +* Bumps gradle from 8.0.0 to 8.9.0. + +## 4.3.3 + +* Updates compileSdk 34 to flutter.compileSdkVersion. + +## 4.3.2 + +* Bumps gradle-plugin to 2.1.10. + +## 4.3.1 + +* Bumps gradle-plugin to 2.1.0. + +## 4.3.0 + +* Adds support for disabling content URL access within WebView and disabling the Geolocation API. + See `AndroidWebViewController.setAllowContentAccess` and + `AndroidWebViewController.setGeolocationEnabled`. + +## 4.2.0 + +* Adds support for configuring file access permissions. See `AndroidWebViewController.setAllowFileAccess`. + +## 4.1.0 + +* Updates internal API wrapper to use `ProxyApi`s. + +## 4.0.3 + +* Bumps androidx.annotation:annotation from 1.8.2 to 1.9.1. + +## 4.0.2 + +* Updates README to remove contributor-focused documentation. + +## 4.0.1 + +* Adds `missing_code_block_language_in_doc_comment` lint. + +## 4.0.0 + +* Bumps androidx.webkit:webkit from 1.12.0 to 1.12.1. +* **Breaking Change** Bumps Android `minSdkVersion` from 19 to 21. + +## 3.16.9 + +* Updates Java compatibility version to 11. +* Updates minimum supported SDK version to Flutter 3.24/Dart 3.5. + +## 3.16.8 + +* Bumps androidx.webkit:webkit from 1.11.0 to 1.12.0. + +## 3.16.7 + +* Bumps androidx.annotation:annotation from 1.8.1 to 1.8.2. + +## 3.16.6 + +* Bumps androidx.annotation:annotation from 1.7.1 to 1.8.1. + +## 3.16.5 + +* Updates lint checks to ignore NewerVersionAvailable. + +## 3.16.4 + +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Removes support for apps using the v1 Android embedding. + +## 3.16.3 + +* Bumps androidx.webkit:webkit from 1.10.0 to 1.11.0. + +## 3.16.2 + +* Bumps androidx.webkit:webkit from 1.7.0 to 1.10.0. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + +## 3.16.1 + +* Fixes iframe navigation being handled in the main frame when `NavigationDelegate.onNavigationRequest` is present. + +## 3.16.0 + +* Adds onReceivedHttpError WebViewClient callback to support + `PlatformNavigationDelegate.onHttpError`. +* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. +* Updates compileSdk to 34. + +## 3.15.0 + +* Adds support for `setOnScrollPositionChange` method to the `AndroidWebViewController`. + +## 3.14.0 + +* Adds support to show JavaScript dialog. See `AndroidWebViewController.setOnJavaScriptAlertDialog`, `AndroidWebViewController.setOnJavaScriptConfirmDialog` and `AndroidWebViewController.setOnJavaScriptTextInputDialog`. + +## 3.13.2 + +* Fixes new lint warnings. + +## 3.13.1 + +* Bumps androidx.annotation:annotation from 1.7.0 to 1.7.1. + +## 3.13.0 + +* Adds support for `PlatformNavigationDelegate.setOnHttpAuthRequest`. +* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0. + +## 3.12.1 + +* Fixes `use_build_context_synchronously` lint violations in the example app. + +## 3.12.0 + +* Adds support for `PlatformWebViewController.getUserAgent`. + +## 3.11.0 + +* Adds support to register a callback to receive JavaScript console messages. See `AndroidWebViewController.onConsoleMessage`. + +## 3.10.1 + +* Bumps androidx.annotation:annotation from 1.5.0 to 1.7.0. + +## 3.10.0 + +* Adds support for playing video in fullscreen. See + `AndroidWebViewController.setCustomWidgetCallbacks`. + +## 3.9.5 + +* Updates pigeon to 11 and removes unneeded enum wrappers. + +## 3.9.4 + +* Adds pub topics to package metadata. +* Updates minimum supported SDK version to Flutter 3.7/Dart 2.19. + +## 3.9.3 + +* Fixes bug where the `PlatformWebViewWidget` was rebuilt unnecessarily. + +## 3.9.2 + +* Fixes bug where `PlatformWebViewWidget` doesn't rebuild when the controller or PlatformView + implementation flag changes. + +## 3.9.1 + +* Adjusts SDK checks for better testability. + +## 3.9.0 + +* Adds support for `WebResouceError.url`. + +## 3.8.2 + +* Fixes unawaited_futures violations. + +## 3.8.1 + +* Bumps androidx.webkit:webkit from 1.6.0 to 1.7.0. + +## 3.8.0 + +* Adds support for handling geolocation permissions. See + `AndroidWebViewController.setGeolocationPermissionsPromptCallbacks`. + +## 3.7.1 + +* Removes obsolete null checks on non-nullable values. + +## 3.7.0 + +* Adds support to accept third party cookies. See + `AndroidWebViewCookieManager.setAcceptThirdPartyCookies`. + +## 3.6.3 + +* Updates gradle, AGP and fixes some lint errors. + +## 3.6.2 + +* Fixes compatibility with AGP versions older than 4.2. + +## 3.6.1 + +* Adds a namespace for compatibility with AGP 8.0. + +## 3.6.0 + +* Adds support for `PlatformWebViewController.setOnPlatformPermissionRequest`. + +## 3.5.3 + +* Bumps gradle from 7.2.2 to 8.0.0. + +## 3.5.2 + +* Updates internal Java InstanceManager to only stop finalization callbacks when stopped. + +## 3.5.1 + +* Updates pigeon dev dependency to `9.2.4`. +* Fixes Android lint warnings. + +## 3.5.0 + +* Adds support for `PlatformNavigationDelegate.onUrlChange`. +* Bumps androidx.webkit:webkit from 1.6.0 to 1.6.1. +* Fixes common typos in tests and documentation. + +## 3.4.5 + +* Removes unused internal `WebView` field and Java class. + +## 3.4.4 + +* Fixes a bug where the native `WebView` wouldn't be traversed for autofill automatically. +* Updates minimum Flutter version to 3.3. + +## 3.4.3 + +* Updates internal Java InstanceManager to be cleared on hot restart. + +## 3.4.2 + +* Clarifies explanation of endorsement in README. + +## 3.4.1 + +* Fixes a potential bug where a `WebView` that was not added to the `InstanceManager` could be + returned by a `WebViewClient` or `WebChromeClient`. + +## 3.4.0 + +* Adds support to set text zoom of a page. See `AndroidWebViewController.setTextZoom`. +* Aligns Dart and Flutter SDK constraints. + +## 3.3.2 + +* Resolves compilations warnings. +* Updates compileSdkVersion to 33. +* Bumps androidx.webkit:webkit from 1.5.0 to 1.6.0. + +## 3.3.1 + +* Updates links for the merge of flutter/plugins into flutter/packages. + +## 3.3.0 + +* Adds support to access native `WebView`. + +## 3.2.4 + +* Renames Pigeon output files. + +## 3.2.3 + +* Fixes bug that prevented the web view from being garbage collected. +* Fixes bug causing a `LateInitializationError` when a `PlatformNavigationDelegate` is not provided. + +## 3.2.2 + +* Updates example code for `use_build_context_synchronously` lint. + +## 3.2.1 + +* Updates code for stricter lint checks. + +## 3.2.0 + +* Adds support for handling file selection. See `AndroidWebViewController.setOnShowFileSelector`. +* Updates pigeon dev dependency to `4.2.14`. + +## 3.1.3 + +* Fixes crash when the Java `InstanceManager` was used after plugin was removed from the engine. + +## 3.1.2 + +* Fixes bug where an `AndroidWebViewController` couldn't be reused with a new `WebViewWidget`. + +## 3.1.1 + +* Fixes bug where a `AndroidNavigationDelegate` was required to load a request. + +## 3.1.0 + +* Adds support for selecting Hybrid Composition on versions 23+. Please use + `AndroidWebViewControllerCreationParams.displayWithHybridComposition`. + +## 3.0.0 + +* **BREAKING CHANGE** Updates platform implementation to `2.0.0` release of + `webview_flutter_platform_interface`. See + [webview_flutter](https://pub.dev/packages/webview_flutter/versions/4.0.0) for updated usage. + +## 2.10.4 + +* Updates code for `no_leading_underscores_for_local_identifiers` lint. +* Bumps androidx.annotation from 1.4.0 to 1.5.0. + +## 2.10.3 + +* Updates imports for `prefer_relative_imports`. + +## 2.10.2 + +* Adds a getter to expose the Java InstanceManager. + +## 2.10.1 + +* Adds a method to the `WebView` wrapper to retrieve the X and Y positions simultaneously. +* Removes reference to https://github.com/flutter/flutter/issues/97744 from `README`. + +## 2.10.0 + +* Bumps webkit from 1.0.0 to 1.5.0. +* Raises minimum `compileSdkVersion` to 32. + +## 2.9.5 + +* Adds dispose methods for HostApi and FlutterApi of JavaObject. + +## 2.9.4 + +* Fixes avoid_redundant_argument_values lint warnings and minor typos. +* Bumps gradle from 7.2.1 to 7.2.2. + +## 2.9.3 + +* Updates the Dart InstanceManager to take a listener for when an object is garbage collected. + See https://github.com/flutter/flutter/issues/107199. + +## 2.9.2 + +* Updates the Java InstanceManager to take a listener for when an object is garbage collected. + See https://github.com/flutter/flutter/issues/107199. + +## 2.9.1 + +* Updates Android WebView classes as Copyable. This is a part of moving the api to handle garbage + collection automatically. See https://github.com/flutter/flutter/issues/107199. + +## 2.9.0 + +* Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/106316). +* Fixes bug where `Directionality` from context didn't affect `SurfaceAndroidWebView`. +* Fixes bug where default text direction was different for `SurfaceAndroidWebView` and `AndroidWebView`. + Default is now `TextDirection.ltr` for both. +* Fixes bug where setting WebView to a transparent background could cause visual errors when using + `SurfaceAndroidWebView`. Hybrid composition is now used when the background color is not 100% + opaque. +* Raises minimum Flutter version to 3.0.0. + +## 2.8.14 + +* Bumps androidx.annotation from 1.0.0 to 1.4.0. + +## 2.8.13 + +* Fixes a bug which causes an exception when the `onNavigationRequestCallback` return `false`. + +## 2.8.12 + +* Bumps mockito-inline from 3.11.1 to 4.6.1. + +## 2.8.11 + +* Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/104231). + +## 2.8.10 + +* Updates references to the obsolete master branch. + +## 2.8.9 + +* Updates Gradle to 7.2.1. + +## 2.8.8 + +* Minor fixes for new analysis options. + +## 2.8.7 + +* Removes unnecessary imports. +* Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors + lint warnings. + +## 2.8.6 + +* Updates pigeon developer dependency to the latest version which adds support for null safety. + +## 2.8.5 + +* Migrates deprecated `Scaffold.showSnackBar` to `ScaffoldMessenger` in example app. + +## 2.8.4 + +* Fixes bug preventing `mockito` code generation for tests. +* Fixes regression where local storage wasn't cleared when `WebViewController.clearCache` was + called. + +## 2.8.3 + +* Fixes a bug causing `debuggingEnabled` to always be set to true. +* Fixes an integration test race condition. + +## 2.8.2 + +* Adds the `WebSettings.setAllowFileAccess()` method and ensure that file access is allowed when the `WebViewAndroidWidget.loadFile()` method is executed. + +## 2.8.1 + +* Fixes bug where the default user agent string was being set for every rebuild. See + https://github.com/flutter/flutter/issues/94847. + +## 2.8.0 + +* Implements new cookie manager for setting cookies and providing initial cookies. + +## 2.7.0 + +* Adds support for the `loadRequest` method from the platform interface. + +## 2.6.0 + +* Adds implementation of the `loadFlutterAsset` method from the platform interface. + +## 2.5.0 + +* Adds an option to set the background color of the webview. + +## 2.4.0 + +* Adds support for Android's `WebView.loadData` and `WebView.loadDataWithBaseUrl` methods and implements the `loadFile` and `loadHtmlString` methods from the platform interface. +* Updates to webview_flutter_platform_interface version 1.5.2. + +## 2.3.1 + +* Adds explanation on how to generate the pigeon communication layer and mockito mock objects. +* Updates compileSdkVersion to 31. + +## 2.3.0 + +* Replaces platform implementation with API built with pigeon. + +## 2.2.1 + +* Fix `NullPointerException` from a race condition when changing focus. This only affects `WebView` +when it is created without Hybrid Composition. + +## 2.2.0 + +* Implemented new `runJavascript` and `runJavascriptReturningResult` methods in platform interface. + +## 2.1.0 + +* Add `zoomEnabled` functionality. + +## 2.0.15 + +* Added Overrides in FlutterWebView.java + +## 2.0.14 + +* Update example App so navigation menu loads immediatly but only becomes available when `WebViewController` is available (same behavior as example App in webview_flutter package). + +## 2.0.13 + +* Extract Android implementation from `webview_flutter`. diff --git a/local_packages/webview_flutter_android-4.10.5/CONTRIBUTING.md b/local_packages/webview_flutter_android-4.10.5/CONTRIBUTING.md new file mode 100644 index 0000000..0ecb255 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/CONTRIBUTING.md @@ -0,0 +1,74 @@ +# Contributing to `webview_flutter_android` + +Please start by taking a look at the general guide to contributing to the `flutter/packages` repo: +https://github.com/flutter/packages/blob/main/CONTRIBUTING.md + +## Package Structure + +This plugin serves as a platform implementation plugin as outlined in [federated plugins](https://docs.flutter.dev/packages-and-plugins/developing-packages#federated-plugins). +The sections below will provide an overview of how this plugin implements this portion with Android. + +For making changes to this package, please take a look at [changing federated plugins](https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins). + +### Quick Overview + +This plugin implements the platform interface provided by `webview_flutter_platform_interface` using +the native WebKit APIs for Android. + +#### SDK Wrappers + +To access native APIS, this plugins uses Dart wrappers of the native library. The native library is +wrapped using using the `ProxyApi` feature from the `pigeon` package. + +The wrappers for the native library can be updated and modified by changing `pigeons/android_webkit.dart`. + +The generated files are located: +* `lib/src/android_webkit.g.dart` +* `android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt` + +To update the wrapper, follow the steps below: + +##### 1. Ensure the project has been built at least once + +Run `flutter build apk --debug` in `example/`. + +##### 2. Make changes to the respective pigeon file that matches the native SDK + +* Android Dependency: https://developer.android.com/reference/android/webkit/package-summary +* Pigeon file to update: `pigeons/android_webkit.dart` + +##### 3. Run the code generator from the terminal + +Run: `dart run pigeon --input pigeons/android_webkit.dart` + +##### 4. Update the generated APIs in native code + +Running the `flutter build` command from step 1 again should provide build errors and indicate what +needs to be done. Alternatively, it can be easier to update native code with the platform's specific +IDE: + +Open `example/android/` in a separate Android Studio project. + +##### 5. Write API tests + +Assuming a non-static method or constructor was added to the native wrapper, a native test will need +to be added. + +Tests location: `android/src/test/java/io/flutter/plugins/webviewflutter/` + +## Important Notes + +### Calling `WebView.destroy()` after Dart Instance is Garbage Collected + +To avoid a potentially breaking change, the code in `android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt` +needs to be updated after the pigeon generator is run. Please add + +```kotlin +val instance: Any? = getInstance(identifier) +if (instance is WebViewProxyApi.WebViewPlatformView) { + instance.destroy() +} +``` + +to `AndroidWebkitLibraryPigeonInstanceManager.remove`. The current implementation of the native +code doesn't currently support handling when the Dart instance is garbage collected. diff --git a/local_packages/webview_flutter_android-4.10.5/LICENSE b/local_packages/webview_flutter_android-4.10.5/LICENSE new file mode 100644 index 0000000..16b4bbb --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/LICENSE @@ -0,0 +1,26 @@ +Copyright 2013 The Flutter Authors + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/local_packages/webview_flutter_android-4.10.5/README.md b/local_packages/webview_flutter_android-4.10.5/README.md new file mode 100644 index 0000000..ef73e52 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/README.md @@ -0,0 +1,137 @@ +# webview\_flutter\_android + +The Android implementation of [`webview_flutter`][1]. + +## Usage + +This package is [endorsed][2], which means you can simply use `webview_flutter` +normally. This package will be automatically included in your app when you do, +so you do not need to add it to your `pubspec.yaml`. + +However, if you `import` this package to use any of its APIs directly, you +should add it to your `pubspec.yaml` as usual. + +## Display Mode + +This plugin supports two different platform view display modes. The default display mode is subject +to change in the future, and will not be considered a breaking change, so if you want to ensure a +specific mode, you can set it explicitly. + +### Texture Layer Hybrid Composition + +This is the current default mode, and is the display mode used by most +plugins starting with Flutter 3.0. This is more performant than Hybrid Composition, but has some +limitations from using an Android [SurfaceTexture](https://developer.android.com/reference/android/graphics/SurfaceTexture). +See: +* https://github.com/flutter/flutter/issues/104889 +* https://github.com/flutter/flutter/issues/116954 + +### Hybrid Composition + +This ensures that the WebView will display and work as expected in the edge cases noted above, at +the cost of some performance. See: +* https://docs.flutter.dev/platform-integration/android/platform-views#performance + +This can be configured with +`AndroidWebViewWidgetCreationParams.displayWithHybridComposition`. See https://pub.dev/packages/webview_flutter#platform-specific-features +for more details on setting platform-specific features in the main plugin. + +## External Native API + +The plugin also provides a native API accessible by the native code of Android applications or +packages. This API follows the convention of breaking changes of the Dart API, which means that any +changes to the class that are not backwards compatible will only be made with a major version change +of the plugin. Native code other than this external API does not follow breaking change conventions, +so app or plugin clients should not use any other native APIs. + +The API can be accessed by importing the native class `WebViewFlutterAndroidExternalApi`: + +Java: + +```java +import io.flutter.plugins.webviewflutter.WebViewFlutterAndroidExternalApi; +``` + +## Enable Payment Request in WebView + +The Payment Request API can be enabled by calling `AndroidWebViewController.setPaymentRequestEnabled` after +checking `AndroidWebViewController.isWebViewFeatureSupported`. + + +```dart +final bool paymentRequestEnabled = await androidController + .isWebViewFeatureSupported(WebViewFeatureType.paymentRequest); + +if (paymentRequestEnabled) { + await androidController.setPaymentRequestEnabled(true); +} +``` + +Add intent filters to your AndroidManifest.xml to discover and invoke Android payment apps using system intents: + +```xml + + + + + + + + + + + +``` + +## Fullscreen Video + +To display a video as fullscreen, an app must manually handle the notification that the current page +has entered fullscreen mode. This can be done by calling +`AndroidWebViewController.setCustomWidgetCallbacks`. Below is an example implementation. + + +```dart +androidController.setCustomWidgetCallbacks( + onShowCustomWidget: (Widget widget, OnHideCustomWidgetCallback callback) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => widget, + fullscreenDialog: true, + ), + ); + }, + onHideCustomWidget: () { + Navigator.of(context).pop(); + }, +); +``` + +## Geolocation + +By default, WebView does not allow geolocation requests. To allow them, call +`setGeolocationPermissionsPromptCallbacks` on the `AndroidWebViewController` to +configure a prompt handler. For example, to unconditionally allow all requests: + + +```dart +await androidController.setGeolocationPermissionsPromptCallbacks( + onShowPrompt: (GeolocationPermissionsRequestParams request) async { + return const GeolocationPermissionsResponse(allow: true, retain: true); + }, +); +``` + +**Important:** Geolocation requests should only be allowed unconditionally if +the web view content is restricted to domains you control or trust. If you are +showing untrusted content, the `onShowPrompt` implementation should request +permission from the user before responding. + +Your application must have geolocation permissions granted in order for the +WebView to have access to geolocation. + +## Contributing + +For information on contributing to this plugin, see [`CONTRIBUTING.md`](CONTRIBUTING.md). + +[1]: https://pub.dev/packages/webview_flutter +[2]: https://flutter.dev/to/endorsed-federated-plugin diff --git a/local_packages/webview_flutter_android-4.10.5/android/build.gradle b/local_packages/webview_flutter_android-4.10.5/android/build.gradle new file mode 100644 index 0000000..9ecf26b --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/build.gradle @@ -0,0 +1,72 @@ +group = 'io.flutter.plugins.webviewflutter' +version = '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '2.2.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:8.12.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + namespace = "io.flutter.plugins.webviewflutter" + compileSdk = flutter.compileSdkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + minSdkVersion 21 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + lint { + checkAllWarnings = true + warningsAsErrors = true + disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency', 'NewerVersionAvailable' + baseline = file("lint-baseline.xml") + } + + dependencies { + implementation("androidx.annotation:annotation:1.9.1") + implementation("androidx.webkit:webkit:1.14.0") + testImplementation("junit:junit:4.13.2") + testImplementation("org.mockito:mockito-core:5.19.0") + testImplementation("org.mockito:mockito-inline:5.2.0") + testImplementation("androidx.test:core:1.7.0") + } + + testOptions { + unitTests.includeAndroidResources = true + unitTests.returnDefaultValues = true + unitTests.all { + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen {false} + showStandardStreams = true + } + } + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/lint-baseline.xml b/local_packages/webview_flutter_android-4.10.5/android/lint-baseline.xml new file mode 100644 index 0000000..f443fcf --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/lint-baseline.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/webview_flutter_android-4.10.5/android/settings.gradle b/local_packages/webview_flutter_android-4.10.5/android/settings.gradle new file mode 100644 index 0000000..5be7a4b --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'webview_flutter' diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/AndroidManifest.xml b/local_packages/webview_flutter_android-4.10.5/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a087f2c --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt new file mode 100644 index 0000000..be39e1a --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt @@ -0,0 +1,6449 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v25.5.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package io.flutter.plugins.webviewflutter + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer + +private object AndroidWebkitLibraryPigeonUtils { + + fun createConnectionError(channelName: String): AndroidWebKitError { + return AndroidWebKitError( + "channel-error", "Unable to establish connection on channel: '$channelName'.", "") + } + + fun wrapResult(result: Any?): List { + return listOf(result) + } + + fun wrapError(exception: Throwable): List { + return if (exception is AndroidWebKitError) { + listOf(exception.code, exception.message, exception.details) + } else { + listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)) + } + } +} + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class AndroidWebKitError( + val code: String, + override val message: String? = null, + val details: Any? = null +) : Throwable() +/** + * Maintains instances used to communicate with the corresponding objects in Dart. + * + * Objects stored in this container are represented by an object in Dart that is also stored in an + * InstanceManager with the same identifier. + * + * When an instance is added with an identifier, either can be used to retrieve the other. + * + * Added instances are added as a weak reference and a strong reference. When the strong reference + * is removed with [remove] and the weak reference is deallocated, the + * `finalizationListener.onFinalize` is called with the instance's identifier. However, if the + * strong reference is removed and then the identifier is retrieved with the intention to pass the + * identifier to Dart (e.g. calling [getIdentifierForStrongReference]), the strong reference to the + * instance is recreated. The strong reference will then need to be removed manually again. + */ +@Suppress("UNCHECKED_CAST", "MemberVisibilityCanBePrivate") +class AndroidWebkitLibraryPigeonInstanceManager( + private val finalizationListener: PigeonFinalizationListener +) { + /** Interface for listening when a weak reference of an instance is removed from the manager. */ + interface PigeonFinalizationListener { + fun onFinalize(identifier: Long) + } + + private val identifiers = java.util.WeakHashMap() + private val weakInstances = HashMap>() + private val strongInstances = HashMap() + private val referenceQueue = java.lang.ref.ReferenceQueue() + private val weakReferencesToIdentifiers = HashMap, Long>() + private val handler = android.os.Handler(android.os.Looper.getMainLooper()) + private val releaseAllFinalizedInstancesRunnable = Runnable { + this.releaseAllFinalizedInstances() + } + private var nextIdentifier: Long = minHostCreatedIdentifier + private var hasFinalizationListenerStopped = false + + /** + * Modifies the time interval used to define how often this instance removes garbage collected + * weak references to native Android objects that this instance was managing. + */ + var clearFinalizedWeakReferencesInterval: Long = 3000 + set(value) { + handler.removeCallbacks(releaseAllFinalizedInstancesRunnable) + field = value + releaseAllFinalizedInstances() + } + + init { + handler.postDelayed(releaseAllFinalizedInstancesRunnable, clearFinalizedWeakReferencesInterval) + } + + companion object { + // Identifiers are locked to a specific range to avoid collisions with objects + // created simultaneously from Dart. + // Host uses identifiers >= 2^16 and Dart is expected to use values n where, + // 0 <= n < 2^16. + private const val minHostCreatedIdentifier: Long = 65536 + private const val tag = "PigeonInstanceManager" + + /** + * Instantiate a new manager with a listener for garbage collected weak references. + * + * When the manager is no longer needed, [stopFinalizationListener] must be called. + */ + fun create( + finalizationListener: PigeonFinalizationListener + ): AndroidWebkitLibraryPigeonInstanceManager { + return AndroidWebkitLibraryPigeonInstanceManager(finalizationListener) + } + } + + /** + * Removes `identifier` and return its associated strongly referenced instance, if present, from + * the manager. + */ + fun remove(identifier: Long): T? { + logWarningIfFinalizationListenerHasStopped() + val instance: Any? = getInstance(identifier) + if (instance is WebViewProxyApi.WebViewPlatformView) { + instance.destroy() + } + return strongInstances.remove(identifier) as T? + } + + /** + * Retrieves the identifier paired with an instance, if present, otherwise `null`. + * + * If the manager contains a strong reference to `instance`, it will return the identifier + * associated with `instance`. If the manager contains only a weak reference to `instance`, a new + * strong reference to `instance` will be added and will need to be removed again with [remove]. + * + * If this method returns a nonnull identifier, this method also expects the Dart + * `AndroidWebkitLibraryPigeonInstanceManager` to have, or recreate, a weak reference to the Dart + * instance the identifier is associated with. + */ + fun getIdentifierForStrongReference(instance: Any?): Long? { + logWarningIfFinalizationListenerHasStopped() + val identifier = identifiers[instance] + if (identifier != null) { + strongInstances[identifier] = instance!! + } + return identifier + } + + /** + * Adds a new instance that was instantiated from Dart. + * + * The same instance can be added multiple times, but each identifier must be unique. This allows + * two objects that are equivalent (e.g. the `equals` method returns true and their hashcodes are + * equal) to both be added. + * + * [identifier] must be >= 0 and unique. + */ + fun addDartCreatedInstance(instance: Any, identifier: Long) { + logWarningIfFinalizationListenerHasStopped() + addInstance(instance, identifier) + } + + /** + * Adds a new unique instance that was instantiated from the host platform. + * + * If the manager contains [instance], this returns the corresponding identifier. If the manager + * does not contain [instance], this adds the instance and returns a unique identifier for that + * [instance]. + */ + fun addHostCreatedInstance(instance: Any): Long { + logWarningIfFinalizationListenerHasStopped() + require(!containsInstance(instance)) { + "Instance of ${instance.javaClass} has already been added." + } + val identifier = nextIdentifier++ + addInstance(instance, identifier) + return identifier + } + + /** Retrieves the instance associated with identifier, if present, otherwise `null`. */ + fun getInstance(identifier: Long): T? { + logWarningIfFinalizationListenerHasStopped() + val instance = weakInstances[identifier] as java.lang.ref.WeakReference? + return instance?.get() + } + + /** Returns whether this manager contains the given `instance`. */ + fun containsInstance(instance: Any?): Boolean { + logWarningIfFinalizationListenerHasStopped() + return identifiers.containsKey(instance) + } + + /** + * Stops the periodic run of the [PigeonFinalizationListener] for instances that have been garbage + * collected. + * + * The InstanceManager can continue to be used, but the [PigeonFinalizationListener] will no + * longer be called and methods will log a warning. + */ + fun stopFinalizationListener() { + handler.removeCallbacks(releaseAllFinalizedInstancesRunnable) + hasFinalizationListenerStopped = true + } + + /** + * Removes all of the instances from this manager. + * + * The manager will be empty after this call returns. + */ + fun clear() { + identifiers.clear() + weakInstances.clear() + strongInstances.clear() + weakReferencesToIdentifiers.clear() + } + + /** + * Whether the [PigeonFinalizationListener] is still being called for instances that are garbage + * collected. + * + * See [stopFinalizationListener]. + */ + fun hasFinalizationListenerStopped(): Boolean { + return hasFinalizationListenerStopped + } + + private fun releaseAllFinalizedInstances() { + if (hasFinalizationListenerStopped()) { + return + } + var reference: java.lang.ref.WeakReference? + while ((referenceQueue.poll() as java.lang.ref.WeakReference?).also { reference = it } != + null) { + val identifier = weakReferencesToIdentifiers.remove(reference) + if (identifier != null) { + weakInstances.remove(identifier) + strongInstances.remove(identifier) + finalizationListener.onFinalize(identifier) + } + } + handler.postDelayed(releaseAllFinalizedInstancesRunnable, clearFinalizedWeakReferencesInterval) + } + + private fun addInstance(instance: Any, identifier: Long) { + require(identifier >= 0) { "Identifier must be >= 0: $identifier" } + require(!weakInstances.containsKey(identifier)) { + "Identifier has already been added: $identifier" + } + val weakReference = java.lang.ref.WeakReference(instance, referenceQueue) + identifiers[instance] = identifier + weakInstances[identifier] = weakReference + weakReferencesToIdentifiers[weakReference] = identifier + strongInstances[identifier] = instance + } + + private fun logWarningIfFinalizationListenerHasStopped() { + if (hasFinalizationListenerStopped()) { + Log.w( + tag, + "The manager was used after calls to the PigeonFinalizationListener has been stopped.") + } + } +} + +/** Generated API for managing the Dart and native `InstanceManager`s. */ +private class AndroidWebkitLibraryPigeonInstanceManagerApi(val binaryMessenger: BinaryMessenger) { + companion object { + /** The codec used by AndroidWebkitLibraryPigeonInstanceManagerApi. */ + val codec: MessageCodec by lazy { AndroidWebkitLibraryPigeonCodec() } + + /** + * Sets up an instance of `AndroidWebkitLibraryPigeonInstanceManagerApi` to handle messages from + * the `binaryMessenger`. + */ + fun setUpMessageHandlers( + binaryMessenger: BinaryMessenger, + instanceManager: AndroidWebkitLibraryPigeonInstanceManager? + ) { + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.PigeonInternalInstanceManager.removeStrongReference", + codec) + if (instanceManager != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val identifierArg = args[0] as Long + val wrapped: List = + try { + instanceManager.remove(identifierArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.PigeonInternalInstanceManager.clear", + codec) + if (instanceManager != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = + try { + instanceManager.clear() + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + fun removeStrongReference(identifierArg: Long, callback: (Result) -> Unit) { + val channelName = + "dev.flutter.pigeon.webview_flutter_android.PigeonInternalInstanceManager.removeStrongReference" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } +} +/** + * Provides implementations for each ProxyApi implementation and provides access to resources needed + * by any implementation. + */ +abstract class AndroidWebkitLibraryPigeonProxyApiRegistrar(val binaryMessenger: BinaryMessenger) { + /** Whether APIs should ignore calling to Dart. */ + public var ignoreCallsToDart = false + val instanceManager: AndroidWebkitLibraryPigeonInstanceManager + private var _codec: MessageCodec? = null + val codec: MessageCodec + get() { + if (_codec == null) { + _codec = AndroidWebkitLibraryPigeonProxyApiBaseCodec(this) + } + return _codec!! + } + + init { + val api = AndroidWebkitLibraryPigeonInstanceManagerApi(binaryMessenger) + instanceManager = + AndroidWebkitLibraryPigeonInstanceManager.create( + object : AndroidWebkitLibraryPigeonInstanceManager.PigeonFinalizationListener { + override fun onFinalize(identifier: Long) { + api.removeStrongReference(identifier) { + if (it.isFailure) { + Log.e( + "PigeonProxyApiRegistrar", + "Failed to remove Dart strong reference with identifier: $identifier") + } + } + } + }) + } + /** + * An implementation of [PigeonApiWebResourceRequest] used to add a new Dart instance of + * `WebResourceRequest` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiWebResourceRequest(): PigeonApiWebResourceRequest + + /** + * An implementation of [PigeonApiWebResourceResponse] used to add a new Dart instance of + * `WebResourceResponse` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiWebResourceResponse(): PigeonApiWebResourceResponse + + /** + * An implementation of [PigeonApiWebResourceError] used to add a new Dart instance of + * `WebResourceError` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiWebResourceError(): PigeonApiWebResourceError + + /** + * An implementation of [PigeonApiWebResourceErrorCompat] used to add a new Dart instance of + * `WebResourceErrorCompat` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiWebResourceErrorCompat(): PigeonApiWebResourceErrorCompat + + /** + * An implementation of [PigeonApiWebViewPoint] used to add a new Dart instance of `WebViewPoint` + * to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiWebViewPoint(): PigeonApiWebViewPoint + + /** + * An implementation of [PigeonApiConsoleMessage] used to add a new Dart instance of + * `ConsoleMessage` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiConsoleMessage(): PigeonApiConsoleMessage + + /** + * An implementation of [PigeonApiCookieManager] used to add a new Dart instance of + * `CookieManager` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiCookieManager(): PigeonApiCookieManager + + /** + * An implementation of [PigeonApiWebView] used to add a new Dart instance of `WebView` to the + * Dart `InstanceManager`. + */ + abstract fun getPigeonApiWebView(): PigeonApiWebView + + /** + * An implementation of [PigeonApiWebSettings] used to add a new Dart instance of `WebSettings` to + * the Dart `InstanceManager`. + */ + abstract fun getPigeonApiWebSettings(): PigeonApiWebSettings + + /** + * An implementation of [PigeonApiJavaScriptChannel] used to add a new Dart instance of + * `JavaScriptChannel` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiJavaScriptChannel(): PigeonApiJavaScriptChannel + + /** + * An implementation of [PigeonApiWebViewClient] used to add a new Dart instance of + * `WebViewClient` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiWebViewClient(): PigeonApiWebViewClient + + /** + * An implementation of [PigeonApiDownloadListener] used to add a new Dart instance of + * `DownloadListener` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiDownloadListener(): PigeonApiDownloadListener + + /** + * An implementation of [PigeonApiWebChromeClient] used to add a new Dart instance of + * `WebChromeClient` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiWebChromeClient(): PigeonApiWebChromeClient + + /** + * An implementation of [PigeonApiFlutterAssetManager] used to add a new Dart instance of + * `FlutterAssetManager` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiFlutterAssetManager(): PigeonApiFlutterAssetManager + + /** + * An implementation of [PigeonApiWebStorage] used to add a new Dart instance of `WebStorage` to + * the Dart `InstanceManager`. + */ + abstract fun getPigeonApiWebStorage(): PigeonApiWebStorage + + /** + * An implementation of [PigeonApiFileChooserParams] used to add a new Dart instance of + * `FileChooserParams` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiFileChooserParams(): PigeonApiFileChooserParams + + /** + * An implementation of [PigeonApiPermissionRequest] used to add a new Dart instance of + * `PermissionRequest` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiPermissionRequest(): PigeonApiPermissionRequest + + /** + * An implementation of [PigeonApiCustomViewCallback] used to add a new Dart instance of + * `CustomViewCallback` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiCustomViewCallback(): PigeonApiCustomViewCallback + + /** + * An implementation of [PigeonApiView] used to add a new Dart instance of `View` to the Dart + * `InstanceManager`. + */ + abstract fun getPigeonApiView(): PigeonApiView + + /** + * An implementation of [PigeonApiGeolocationPermissionsCallback] used to add a new Dart instance + * of `GeolocationPermissionsCallback` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiGeolocationPermissionsCallback(): PigeonApiGeolocationPermissionsCallback + + /** + * An implementation of [PigeonApiHttpAuthHandler] used to add a new Dart instance of + * `HttpAuthHandler` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiHttpAuthHandler(): PigeonApiHttpAuthHandler + + /** + * An implementation of [PigeonApiAndroidMessage] used to add a new Dart instance of + * `AndroidMessage` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiAndroidMessage(): PigeonApiAndroidMessage + + /** + * An implementation of [PigeonApiClientCertRequest] used to add a new Dart instance of + * `ClientCertRequest` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiClientCertRequest(): PigeonApiClientCertRequest + + /** + * An implementation of [PigeonApiPrivateKey] used to add a new Dart instance of `PrivateKey` to + * the Dart `InstanceManager`. + */ + open fun getPigeonApiPrivateKey(): PigeonApiPrivateKey { + return PigeonApiPrivateKey(this) + } + + /** + * An implementation of [PigeonApiX509Certificate] used to add a new Dart instance of + * `X509Certificate` to the Dart `InstanceManager`. + */ + open fun getPigeonApiX509Certificate(): PigeonApiX509Certificate { + return PigeonApiX509Certificate(this) + } + + /** + * An implementation of [PigeonApiSslErrorHandler] used to add a new Dart instance of + * `SslErrorHandler` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiSslErrorHandler(): PigeonApiSslErrorHandler + + /** + * An implementation of [PigeonApiSslError] used to add a new Dart instance of `SslError` to the + * Dart `InstanceManager`. + */ + abstract fun getPigeonApiSslError(): PigeonApiSslError + + /** + * An implementation of [PigeonApiSslCertificateDName] used to add a new Dart instance of + * `SslCertificateDName` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiSslCertificateDName(): PigeonApiSslCertificateDName + + /** + * An implementation of [PigeonApiSslCertificate] used to add a new Dart instance of + * `SslCertificate` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiSslCertificate(): PigeonApiSslCertificate + + /** + * An implementation of [PigeonApiCertificate] used to add a new Dart instance of `Certificate` to + * the Dart `InstanceManager`. + */ + abstract fun getPigeonApiCertificate(): PigeonApiCertificate + + /** + * An implementation of [PigeonApiWebSettingsCompat] used to add a new Dart instance of + * `WebSettingsCompat` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiWebSettingsCompat(): PigeonApiWebSettingsCompat + + /** + * An implementation of [PigeonApiWebViewFeature] used to add a new Dart instance of + * `WebViewFeature` to the Dart `InstanceManager`. + */ + abstract fun getPigeonApiWebViewFeature(): PigeonApiWebViewFeature + + fun setUp() { + AndroidWebkitLibraryPigeonInstanceManagerApi.setUpMessageHandlers( + binaryMessenger, instanceManager) + PigeonApiCookieManager.setUpMessageHandlers(binaryMessenger, getPigeonApiCookieManager()) + PigeonApiWebView.setUpMessageHandlers(binaryMessenger, getPigeonApiWebView()) + PigeonApiWebSettings.setUpMessageHandlers(binaryMessenger, getPigeonApiWebSettings()) + PigeonApiJavaScriptChannel.setUpMessageHandlers( + binaryMessenger, getPigeonApiJavaScriptChannel()) + PigeonApiWebViewClient.setUpMessageHandlers(binaryMessenger, getPigeonApiWebViewClient()) + PigeonApiDownloadListener.setUpMessageHandlers(binaryMessenger, getPigeonApiDownloadListener()) + PigeonApiWebChromeClient.setUpMessageHandlers(binaryMessenger, getPigeonApiWebChromeClient()) + PigeonApiFlutterAssetManager.setUpMessageHandlers( + binaryMessenger, getPigeonApiFlutterAssetManager()) + PigeonApiWebStorage.setUpMessageHandlers(binaryMessenger, getPigeonApiWebStorage()) + PigeonApiPermissionRequest.setUpMessageHandlers( + binaryMessenger, getPigeonApiPermissionRequest()) + PigeonApiCustomViewCallback.setUpMessageHandlers( + binaryMessenger, getPigeonApiCustomViewCallback()) + PigeonApiView.setUpMessageHandlers(binaryMessenger, getPigeonApiView()) + PigeonApiGeolocationPermissionsCallback.setUpMessageHandlers( + binaryMessenger, getPigeonApiGeolocationPermissionsCallback()) + PigeonApiHttpAuthHandler.setUpMessageHandlers(binaryMessenger, getPigeonApiHttpAuthHandler()) + PigeonApiAndroidMessage.setUpMessageHandlers(binaryMessenger, getPigeonApiAndroidMessage()) + PigeonApiClientCertRequest.setUpMessageHandlers( + binaryMessenger, getPigeonApiClientCertRequest()) + PigeonApiSslErrorHandler.setUpMessageHandlers(binaryMessenger, getPigeonApiSslErrorHandler()) + PigeonApiSslError.setUpMessageHandlers(binaryMessenger, getPigeonApiSslError()) + PigeonApiSslCertificateDName.setUpMessageHandlers( + binaryMessenger, getPigeonApiSslCertificateDName()) + PigeonApiSslCertificate.setUpMessageHandlers(binaryMessenger, getPigeonApiSslCertificate()) + PigeonApiCertificate.setUpMessageHandlers(binaryMessenger, getPigeonApiCertificate()) + PigeonApiWebSettingsCompat.setUpMessageHandlers( + binaryMessenger, getPigeonApiWebSettingsCompat()) + PigeonApiWebViewFeature.setUpMessageHandlers(binaryMessenger, getPigeonApiWebViewFeature()) + } + + fun tearDown() { + AndroidWebkitLibraryPigeonInstanceManagerApi.setUpMessageHandlers(binaryMessenger, null) + PigeonApiCookieManager.setUpMessageHandlers(binaryMessenger, null) + PigeonApiWebView.setUpMessageHandlers(binaryMessenger, null) + PigeonApiWebSettings.setUpMessageHandlers(binaryMessenger, null) + PigeonApiJavaScriptChannel.setUpMessageHandlers(binaryMessenger, null) + PigeonApiWebViewClient.setUpMessageHandlers(binaryMessenger, null) + PigeonApiDownloadListener.setUpMessageHandlers(binaryMessenger, null) + PigeonApiWebChromeClient.setUpMessageHandlers(binaryMessenger, null) + PigeonApiFlutterAssetManager.setUpMessageHandlers(binaryMessenger, null) + PigeonApiWebStorage.setUpMessageHandlers(binaryMessenger, null) + PigeonApiPermissionRequest.setUpMessageHandlers(binaryMessenger, null) + PigeonApiCustomViewCallback.setUpMessageHandlers(binaryMessenger, null) + PigeonApiView.setUpMessageHandlers(binaryMessenger, null) + PigeonApiGeolocationPermissionsCallback.setUpMessageHandlers(binaryMessenger, null) + PigeonApiHttpAuthHandler.setUpMessageHandlers(binaryMessenger, null) + PigeonApiAndroidMessage.setUpMessageHandlers(binaryMessenger, null) + PigeonApiClientCertRequest.setUpMessageHandlers(binaryMessenger, null) + PigeonApiSslErrorHandler.setUpMessageHandlers(binaryMessenger, null) + PigeonApiSslError.setUpMessageHandlers(binaryMessenger, null) + PigeonApiSslCertificateDName.setUpMessageHandlers(binaryMessenger, null) + PigeonApiSslCertificate.setUpMessageHandlers(binaryMessenger, null) + PigeonApiCertificate.setUpMessageHandlers(binaryMessenger, null) + PigeonApiWebSettingsCompat.setUpMessageHandlers(binaryMessenger, null) + PigeonApiWebViewFeature.setUpMessageHandlers(binaryMessenger, null) + } +} + +private class AndroidWebkitLibraryPigeonProxyApiBaseCodec( + val registrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) : AndroidWebkitLibraryPigeonCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 128.toByte() -> { + val identifier: Long = readValue(buffer) as Long + val instance: Any? = registrar.instanceManager.getInstance(identifier) + if (instance == null) { + Log.e("PigeonProxyApiBaseCodec", "Failed to find instance with identifier: $identifier") + } + return instance + } + else -> super.readValueOfType(type, buffer) + } + } + + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + if (value is Boolean || + value is ByteArray || + value is Double || + value is DoubleArray || + value is FloatArray || + value is Int || + value is IntArray || + value is List<*> || + value is Long || + value is LongArray || + value is Map<*, *> || + value is String || + value is FileChooserMode || + value is ConsoleMessageLevel || + value is OverScrollMode || + value is SslErrorType || + value is MixedContentMode || + value == null) { + super.writeValue(stream, value) + return + } + + if (value is android.webkit.WebResourceRequest) { + registrar.getPigeonApiWebResourceRequest().pigeon_newInstance(value) {} + } else if (value is android.webkit.WebResourceResponse) { + registrar.getPigeonApiWebResourceResponse().pigeon_newInstance(value) {} + } else if (value is android.webkit.WebResourceError) { + registrar.getPigeonApiWebResourceError().pigeon_newInstance(value) {} + } else if (value is androidx.webkit.WebResourceErrorCompat) { + registrar.getPigeonApiWebResourceErrorCompat().pigeon_newInstance(value) {} + } else if (value is WebViewPoint) { + registrar.getPigeonApiWebViewPoint().pigeon_newInstance(value) {} + } else if (value is android.webkit.ConsoleMessage) { + registrar.getPigeonApiConsoleMessage().pigeon_newInstance(value) {} + } else if (value is android.webkit.CookieManager) { + registrar.getPigeonApiCookieManager().pigeon_newInstance(value) {} + } else if (value is android.webkit.WebView) { + registrar.getPigeonApiWebView().pigeon_newInstance(value) {} + } else if (value is android.webkit.WebSettings) { + registrar.getPigeonApiWebSettings().pigeon_newInstance(value) {} + } else if (value is JavaScriptChannel) { + registrar.getPigeonApiJavaScriptChannel().pigeon_newInstance(value) {} + } else if (value is android.webkit.WebViewClient) { + registrar.getPigeonApiWebViewClient().pigeon_newInstance(value) {} + } else if (value is android.webkit.DownloadListener) { + registrar.getPigeonApiDownloadListener().pigeon_newInstance(value) {} + } else if (value + is io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl) { + registrar.getPigeonApiWebChromeClient().pigeon_newInstance(value) {} + } else if (value is io.flutter.plugins.webviewflutter.FlutterAssetManager) { + registrar.getPigeonApiFlutterAssetManager().pigeon_newInstance(value) {} + } else if (value is android.webkit.WebStorage) { + registrar.getPigeonApiWebStorage().pigeon_newInstance(value) {} + } else if (value is android.webkit.WebChromeClient.FileChooserParams) { + registrar.getPigeonApiFileChooserParams().pigeon_newInstance(value) {} + } else if (value is android.webkit.PermissionRequest) { + registrar.getPigeonApiPermissionRequest().pigeon_newInstance(value) {} + } else if (value is android.webkit.WebChromeClient.CustomViewCallback) { + registrar.getPigeonApiCustomViewCallback().pigeon_newInstance(value) {} + } else if (value is android.view.View) { + registrar.getPigeonApiView().pigeon_newInstance(value) {} + } else if (value is android.webkit.GeolocationPermissions.Callback) { + registrar.getPigeonApiGeolocationPermissionsCallback().pigeon_newInstance(value) {} + } else if (value is android.webkit.HttpAuthHandler) { + registrar.getPigeonApiHttpAuthHandler().pigeon_newInstance(value) {} + } else if (value is android.os.Message) { + registrar.getPigeonApiAndroidMessage().pigeon_newInstance(value) {} + } else if (value is android.webkit.ClientCertRequest) { + registrar.getPigeonApiClientCertRequest().pigeon_newInstance(value) {} + } else if (value is java.security.PrivateKey) { + registrar.getPigeonApiPrivateKey().pigeon_newInstance(value) {} + } else if (value is java.security.cert.X509Certificate) { + registrar.getPigeonApiX509Certificate().pigeon_newInstance(value) {} + } else if (value is android.webkit.SslErrorHandler) { + registrar.getPigeonApiSslErrorHandler().pigeon_newInstance(value) {} + } else if (value is android.net.http.SslError) { + registrar.getPigeonApiSslError().pigeon_newInstance(value) {} + } else if (value is android.net.http.SslCertificate.DName) { + registrar.getPigeonApiSslCertificateDName().pigeon_newInstance(value) {} + } else if (value is android.net.http.SslCertificate) { + registrar.getPigeonApiSslCertificate().pigeon_newInstance(value) {} + } else if (value is java.security.cert.Certificate) { + registrar.getPigeonApiCertificate().pigeon_newInstance(value) {} + } else if (value is androidx.webkit.WebSettingsCompat) { + registrar.getPigeonApiWebSettingsCompat().pigeon_newInstance(value) {} + } else if (value is androidx.webkit.WebViewFeature) { + registrar.getPigeonApiWebViewFeature().pigeon_newInstance(value) {} + } + + when { + registrar.instanceManager.containsInstance(value) -> { + stream.write(128) + writeValue(stream, registrar.instanceManager.getIdentifierForStrongReference(value)) + } + else -> + throw IllegalArgumentException( + "Unsupported value: '$value' of type '${value.javaClass.name}'") + } + } +} + +/** + * Mode of how to select files for a file chooser. + * + * See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams. + */ +enum class FileChooserMode(val raw: Int) { + /** + * Open single file and requires that the file exists before allowing the user to pick it. + * + * See + * https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_OPEN. + */ + OPEN(0), + /** + * Similar to [open] but allows multiple files to be selected. + * + * See + * https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_OPEN_MULTIPLE. + */ + OPEN_MULTIPLE(1), + /** + * Allows picking a nonexistent file and saving it. + * + * See + * https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_SAVE. + */ + SAVE(2), + /** + * Indicates a `FileChooserMode` with an unknown mode. + * + * This does not represent an actual value provided by the platform and only indicates a value was + * provided that isn't currently supported. + */ + UNKNOWN(3); + + companion object { + fun ofRaw(raw: Int): FileChooserMode? { + return values().firstOrNull { it.raw == raw } + } + } +} + +/** + * Indicates the type of message logged to the console. + * + * See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel. + */ +enum class ConsoleMessageLevel(val raw: Int) { + /** + * Indicates a message is logged for debugging. + * + * See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#DEBUG. + */ + DEBUG(0), + /** + * Indicates a message is provided as an error. + * + * See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#ERROR. + */ + ERROR(1), + /** + * Indicates a message is provided as a basic log message. + * + * See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#LOG. + */ + LOG(2), + /** + * Indicates a message is provided as a tip. + * + * See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#TIP. + */ + TIP(3), + /** + * Indicates a message is provided as a warning. + * + * See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#WARNING. + */ + WARNING(4), + /** + * Indicates a message with an unknown level. + * + * This does not represent an actual value provided by the platform and only indicates a value was + * provided that isn't currently supported. + */ + UNKNOWN(5); + + companion object { + fun ofRaw(raw: Int): ConsoleMessageLevel? { + return values().firstOrNull { it.raw == raw } + } + } +} + +/** + * The over-scroll mode for a view. + * + * See https://developer.android.com/reference/android/view/View#OVER_SCROLL_ALWAYS. + */ +enum class OverScrollMode(val raw: Int) { + /** Always allow a user to over-scroll this view, provided it is a view that can scroll. */ + ALWAYS(0), + /** + * Allow a user to over-scroll this view only if the content is large enough to meaningfully + * scroll, provided it is a view that can scroll. + */ + IF_CONTENT_SCROLLS(1), + /** Never allow a user to over-scroll this view. */ + NEVER(2), + /** The type is not recognized by this wrapper. */ + UNKNOWN(3); + + companion object { + fun ofRaw(raw: Int): OverScrollMode? { + return values().firstOrNull { it.raw == raw } + } + } +} + +/** + * Type of error for a SslCertificate. + * + * See https://developer.android.com/reference/android/net/http/SslError#SSL_DATE_INVALID. + */ +enum class SslErrorType(val raw: Int) { + /** The date of the certificate is invalid. */ + DATE_INVALID(0), + /** The certificate has expired. */ + EXPIRED(1), + /** Hostname mismatch. */ + ID_MISMATCH(2), + /** A generic error occurred. */ + INVALID(3), + /** The certificate is not yet valid. */ + NOT_YET_VALID(4), + /** The certificate authority is not trusted. */ + UNTRUSTED(5), + /** The type is not recognized by this wrapper. */ + UNKNOWN(6); + + companion object { + fun ofRaw(raw: Int): SslErrorType? { + return values().firstOrNull { it.raw == raw } + } + } +} + +/** + * Options for mixed content mode support. + * + * See https://developer.android.com/reference/android/webkit/WebSettings#MIXED_CONTENT_ALWAYS_ALLOW + */ +enum class MixedContentMode(val raw: Int) { + /** + * The WebView will allow a secure origin to load content from any other origin, even if that + * origin is insecure. + */ + ALWAYS_ALLOW(0), + /** + * The WebView will attempt to be compatible with the approach of a modern web browser with regard + * to mixed content. + */ + COMPATIBILITY_MODE(1), + /** The WebView will not allow a secure origin to load content from an insecure origin. */ + NEVER_ALLOW(2); + + companion object { + fun ofRaw(raw: Int): MixedContentMode? { + return values().firstOrNull { it.raw == raw } + } + } +} + +private open class AndroidWebkitLibraryPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 129.toByte() -> { + return (readValue(buffer) as Long?)?.let { FileChooserMode.ofRaw(it.toInt()) } + } + 130.toByte() -> { + return (readValue(buffer) as Long?)?.let { ConsoleMessageLevel.ofRaw(it.toInt()) } + } + 131.toByte() -> { + return (readValue(buffer) as Long?)?.let { OverScrollMode.ofRaw(it.toInt()) } + } + 132.toByte() -> { + return (readValue(buffer) as Long?)?.let { SslErrorType.ofRaw(it.toInt()) } + } + 133.toByte() -> { + return (readValue(buffer) as Long?)?.let { MixedContentMode.ofRaw(it.toInt()) } + } + else -> super.readValueOfType(type, buffer) + } + } + + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is FileChooserMode -> { + stream.write(129) + writeValue(stream, value.raw) + } + is ConsoleMessageLevel -> { + stream.write(130) + writeValue(stream, value.raw) + } + is OverScrollMode -> { + stream.write(131) + writeValue(stream, value.raw) + } + is SslErrorType -> { + stream.write(132) + writeValue(stream, value.raw) + } + is MixedContentMode -> { + stream.write(133) + writeValue(stream, value.raw) + } + else -> super.writeValue(stream, value) + } + } +} + +/** + * Encompasses parameters to the `WebViewClient.shouldInterceptRequest` method. + * + * See https://developer.android.com/reference/android/webkit/WebResourceRequest. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiWebResourceRequest( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + /** The URL for which the resource request was made. */ + abstract fun url(pigeon_instance: android.webkit.WebResourceRequest): String + + /** Whether the request was made in order to fetch the main frame's document. */ + abstract fun isForMainFrame(pigeon_instance: android.webkit.WebResourceRequest): Boolean + + /** Whether the request was a result of a server-side redirect. */ + abstract fun isRedirect(pigeon_instance: android.webkit.WebResourceRequest): Boolean + + /** Whether a gesture (such as a click) was associated with the request. */ + abstract fun hasGesture(pigeon_instance: android.webkit.WebResourceRequest): Boolean + + /** The method associated with the request, for example "GET". */ + abstract fun method(pigeon_instance: android.webkit.WebResourceRequest): String + + /** The headers associated with the request. */ + abstract fun requestHeaders( + pigeon_instance: android.webkit.WebResourceRequest + ): Map? + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of WebResourceRequest and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.webkit.WebResourceRequest, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val urlArg = url(pigeon_instanceArg) + val isForMainFrameArg = isForMainFrame(pigeon_instanceArg) + val isRedirectArg = isRedirect(pigeon_instanceArg) + val hasGestureArg = hasGesture(pigeon_instanceArg) + val methodArg = method(pigeon_instanceArg) + val requestHeadersArg = requestHeaders(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.WebResourceRequest.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send( + listOf( + pigeon_identifierArg, + urlArg, + isForMainFrameArg, + isRedirectArg, + hasGestureArg, + methodArg, + requestHeadersArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure( + AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * Encapsulates a resource response. + * + * See https://developer.android.com/reference/android/webkit/WebResourceResponse. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiWebResourceResponse( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + /** The resource response's status code. */ + abstract fun statusCode(pigeon_instance: android.webkit.WebResourceResponse): Long + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of WebResourceResponse and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.webkit.WebResourceResponse, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val statusCodeArg = statusCode(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.WebResourceResponse.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg, statusCodeArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * Encapsulates information about errors that occurred during loading of web resources. + * + * See https://developer.android.com/reference/android/webkit/WebResourceError. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiWebResourceError( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + /** The error code of the error. */ + abstract fun errorCode(pigeon_instance: android.webkit.WebResourceError): Long + + /** The string describing the error. */ + abstract fun description(pigeon_instance: android.webkit.WebResourceError): String + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of WebResourceError and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.webkit.WebResourceError, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val errorCodeArg = errorCode(pigeon_instanceArg) + val descriptionArg = description(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.WebResourceError.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg, errorCodeArg, descriptionArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * Encapsulates information about errors that occurred during loading of web resources. + * + * See https://developer.android.com/reference/androidx/webkit/WebResourceErrorCompat. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiWebResourceErrorCompat( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + /** The error code of the error. */ + abstract fun errorCode(pigeon_instance: androidx.webkit.WebResourceErrorCompat): Long + + /** The string describing the error. */ + abstract fun description(pigeon_instance: androidx.webkit.WebResourceErrorCompat): String + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of WebResourceErrorCompat and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: androidx.webkit.WebResourceErrorCompat, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val errorCodeArg = errorCode(pigeon_instanceArg) + val descriptionArg = description(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.WebResourceErrorCompat.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg, errorCodeArg, descriptionArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * Represents a position on a web page. + * + * This is a custom class created for convenience of the wrapper. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiWebViewPoint( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + abstract fun x(pigeon_instance: WebViewPoint): Long + + abstract fun y(pigeon_instance: WebViewPoint): Long + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of WebViewPoint and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance(pigeon_instanceArg: WebViewPoint, callback: (Result) -> Unit) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val xArg = x(pigeon_instanceArg) + val yArg = y(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebViewPoint.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg, xArg, yArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * Represents a JavaScript console message from WebCore. + * + * See https://developer.android.com/reference/android/webkit/ConsoleMessage + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiConsoleMessage( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + abstract fun lineNumber(pigeon_instance: android.webkit.ConsoleMessage): Long + + abstract fun message(pigeon_instance: android.webkit.ConsoleMessage): String + + abstract fun level(pigeon_instance: android.webkit.ConsoleMessage): ConsoleMessageLevel + + abstract fun sourceId(pigeon_instance: android.webkit.ConsoleMessage): String + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of ConsoleMessage and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.webkit.ConsoleMessage, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val lineNumberArg = lineNumber(pigeon_instanceArg) + val messageArg = message(pigeon_instanceArg) + val levelArg = level(pigeon_instanceArg) + val sourceIdArg = sourceId(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.ConsoleMessage.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg, lineNumberArg, messageArg, levelArg, sourceIdArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * Manages the cookies used by an application's `WebView` instances. + * + * See https://developer.android.com/reference/android/webkit/CookieManager. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiCookieManager( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + abstract fun instance(): android.webkit.CookieManager + + /** Sets a single cookie (key-value pair) for the given URL. */ + abstract fun setCookie(pigeon_instance: android.webkit.CookieManager, url: String, value: String) + + /** Removes all cookies. */ + abstract fun removeAllCookies( + pigeon_instance: android.webkit.CookieManager, + callback: (Result) -> Unit + ) + + /** Sets whether the `WebView` should allow third party cookies to be set. */ + abstract fun setAcceptThirdPartyCookies( + pigeon_instance: android.webkit.CookieManager, + webView: android.webkit.WebView, + accept: Boolean + ) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiCookieManager?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.CookieManager.instance", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_identifierArg = args[0] as Long + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.instance(), pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.CookieManager.setCookie", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.CookieManager + val urlArg = args[1] as String + val valueArg = args[2] as String + val wrapped: List = + try { + api.setCookie(pigeon_instanceArg, urlArg, valueArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.CookieManager.removeAllCookies", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.CookieManager + api.removeAllCookies(pigeon_instanceArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(AndroidWebkitLibraryPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(AndroidWebkitLibraryPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.CookieManager.setAcceptThirdPartyCookies", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.CookieManager + val webViewArg = args[1] as android.webkit.WebView + val acceptArg = args[2] as Boolean + val wrapped: List = + try { + api.setAcceptThirdPartyCookies(pigeon_instanceArg, webViewArg, acceptArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of CookieManager and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.webkit.CookieManager, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.CookieManager.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * A View that displays web pages. + * + * See https://developer.android.com/reference/android/webkit/WebView. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiWebView( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + abstract fun pigeon_defaultConstructor(): android.webkit.WebView + + /** The WebSettings object used to control the settings for this WebView. */ + abstract fun settings(pigeon_instance: android.webkit.WebView): android.webkit.WebSettings + + /** Loads the given data into this WebView using a 'data' scheme URL. */ + abstract fun loadData( + pigeon_instance: android.webkit.WebView, + data: String, + mimeType: String?, + encoding: String? + ) + + /** Loads the given data into this WebView, using baseUrl as the base URL for the content. */ + abstract fun loadDataWithBaseUrl( + pigeon_instance: android.webkit.WebView, + baseUrl: String?, + data: String, + mimeType: String?, + encoding: String?, + historyUrl: String? + ) + + /** Loads the given URL. */ + abstract fun loadUrl( + pigeon_instance: android.webkit.WebView, + url: String, + headers: Map + ) + + /** Loads the URL with postData using "POST" method into this WebView. */ + abstract fun postUrl(pigeon_instance: android.webkit.WebView, url: String, data: ByteArray) + + /** Gets the URL for the current page. */ + abstract fun getUrl(pigeon_instance: android.webkit.WebView): String? + + /** Gets whether this WebView has a back history item. */ + abstract fun canGoBack(pigeon_instance: android.webkit.WebView): Boolean + + /** Gets whether this WebView has a forward history item. */ + abstract fun canGoForward(pigeon_instance: android.webkit.WebView): Boolean + + /** Goes back in the history of this WebView. */ + abstract fun goBack(pigeon_instance: android.webkit.WebView) + + /** Goes forward in the history of this WebView. */ + abstract fun goForward(pigeon_instance: android.webkit.WebView) + + /** Reloads the current URL. */ + abstract fun reload(pigeon_instance: android.webkit.WebView) + + /** Clears the resource cache. */ + abstract fun clearCache(pigeon_instance: android.webkit.WebView, includeDiskFiles: Boolean) + + /** Asynchronously evaluates JavaScript in the context of the currently displayed page. */ + abstract fun evaluateJavascript( + pigeon_instance: android.webkit.WebView, + javascriptString: String, + callback: (Result) -> Unit + ) + + /** Gets the title for the current page. */ + abstract fun getTitle(pigeon_instance: android.webkit.WebView): String? + + /** + * Enables debugging of web contents (HTML / CSS / JavaScript) loaded into any WebViews of this + * application. + */ + abstract fun setWebContentsDebuggingEnabled(enabled: Boolean) + + /** Sets the WebViewClient that will receive various notifications and requests. */ + abstract fun setWebViewClient( + pigeon_instance: android.webkit.WebView, + client: android.webkit.WebViewClient? + ) + + /** Injects the supplied Java object into this WebView. */ + abstract fun addJavaScriptChannel( + pigeon_instance: android.webkit.WebView, + channel: JavaScriptChannel + ) + + /** Removes a previously injected Java object from this WebView. */ + abstract fun removeJavaScriptChannel(pigeon_instance: android.webkit.WebView, name: String) + + /** + * Registers the interface to be used when content can not be handled by the rendering engine, and + * should be downloaded instead. + */ + abstract fun setDownloadListener( + pigeon_instance: android.webkit.WebView, + listener: android.webkit.DownloadListener? + ) + + /** Sets the chrome handler. */ + abstract fun setWebChromeClient( + pigeon_instance: android.webkit.WebView, + client: io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl? + ) + + /** Sets the background color for this view. */ + abstract fun setBackgroundColor(pigeon_instance: android.webkit.WebView, color: Long) + + /** Destroys the internal state of this WebView. */ + abstract fun destroy(pigeon_instance: android.webkit.WebView) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiWebView?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebView.pigeon_defaultConstructor", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_identifierArg = args[0] as Long + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.pigeon_defaultConstructor(), pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebView.settings", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val pigeon_identifierArg = args[1] as Long + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.settings(pigeon_instanceArg), pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebView.loadData", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val dataArg = args[1] as String + val mimeTypeArg = args[2] as String? + val encodingArg = args[3] as String? + val wrapped: List = + try { + api.loadData(pigeon_instanceArg, dataArg, mimeTypeArg, encodingArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebView.loadDataWithBaseUrl", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val baseUrlArg = args[1] as String? + val dataArg = args[2] as String + val mimeTypeArg = args[3] as String? + val encodingArg = args[4] as String? + val historyUrlArg = args[5] as String? + val wrapped: List = + try { + api.loadDataWithBaseUrl( + pigeon_instanceArg, + baseUrlArg, + dataArg, + mimeTypeArg, + encodingArg, + historyUrlArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebView.loadUrl", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val urlArg = args[1] as String + val headersArg = args[2] as Map + val wrapped: List = + try { + api.loadUrl(pigeon_instanceArg, urlArg, headersArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebView.postUrl", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val urlArg = args[1] as String + val dataArg = args[2] as ByteArray + val wrapped: List = + try { + api.postUrl(pigeon_instanceArg, urlArg, dataArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, "dev.flutter.pigeon.webview_flutter_android.WebView.getUrl", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val wrapped: List = + try { + listOf(api.getUrl(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebView.canGoBack", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val wrapped: List = + try { + listOf(api.canGoBack(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebView.canGoForward", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val wrapped: List = + try { + listOf(api.canGoForward(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, "dev.flutter.pigeon.webview_flutter_android.WebView.goBack", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val wrapped: List = + try { + api.goBack(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebView.goForward", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val wrapped: List = + try { + api.goForward(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, "dev.flutter.pigeon.webview_flutter_android.WebView.reload", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val wrapped: List = + try { + api.reload(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebView.clearCache", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val includeDiskFilesArg = args[1] as Boolean + val wrapped: List = + try { + api.clearCache(pigeon_instanceArg, includeDiskFilesArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebView.evaluateJavascript", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val javascriptStringArg = args[1] as String + api.evaluateJavascript(pigeon_instanceArg, javascriptStringArg) { + result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(AndroidWebkitLibraryPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(AndroidWebkitLibraryPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebView.getTitle", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val wrapped: List = + try { + listOf(api.getTitle(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebView.setWebContentsDebuggingEnabled", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val enabledArg = args[0] as Boolean + val wrapped: List = + try { + api.setWebContentsDebuggingEnabled(enabledArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebView.setWebViewClient", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val clientArg = args[1] as android.webkit.WebViewClient? + val wrapped: List = + try { + api.setWebViewClient(pigeon_instanceArg, clientArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebView.addJavaScriptChannel", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val channelArg = args[1] as JavaScriptChannel + val wrapped: List = + try { + api.addJavaScriptChannel(pigeon_instanceArg, channelArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebView.removeJavaScriptChannel", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val nameArg = args[1] as String + val wrapped: List = + try { + api.removeJavaScriptChannel(pigeon_instanceArg, nameArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebView.setDownloadListener", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val listenerArg = args[1] as android.webkit.DownloadListener? + val wrapped: List = + try { + api.setDownloadListener(pigeon_instanceArg, listenerArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebView.setWebChromeClient", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val clientArg = + args[1] + as + io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl? + val wrapped: List = + try { + api.setWebChromeClient(pigeon_instanceArg, clientArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebView.setBackgroundColor", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val colorArg = args[1] as Long + val wrapped: List = + try { + api.setBackgroundColor(pigeon_instanceArg, colorArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebView.destroy", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebView + val wrapped: List = + try { + api.destroy(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of WebView and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.webkit.WebView, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebView.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } + + /** + * This is called in response to an internal scroll in this view (i.e., the view scrolled its own + * contents). + */ + fun onScrollChanged( + pigeon_instanceArg: android.webkit.WebView, + leftArg: Long, + topArg: Long, + oldLeftArg: Long, + oldTopArg: Long, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebView.onScrollChanged" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, leftArg, topArg, oldLeftArg, oldTopArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + @Suppress("FunctionName") + /** An implementation of [PigeonApiView] used to access callback methods */ + fun pigeon_getPigeonApiView(): PigeonApiView { + return pigeonRegistrar.getPigeonApiView() + } +} +/** + * Manages settings state for a `WebView`. + * + * See https://developer.android.com/reference/android/webkit/WebSettings. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiWebSettings( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + /** Sets whether the DOM storage API is enabled. */ + abstract fun setDomStorageEnabled(pigeon_instance: android.webkit.WebSettings, flag: Boolean) + + /** Tells JavaScript to open windows automatically. */ + abstract fun setJavaScriptCanOpenWindowsAutomatically( + pigeon_instance: android.webkit.WebSettings, + flag: Boolean + ) + + /** Sets whether the WebView whether supports multiple windows. */ + abstract fun setSupportMultipleWindows( + pigeon_instance: android.webkit.WebSettings, + support: Boolean + ) + + /** Tells the WebView to enable JavaScript execution. */ + abstract fun setJavaScriptEnabled(pigeon_instance: android.webkit.WebSettings, flag: Boolean) + + /** Sets the WebView's user-agent string. */ + abstract fun setUserAgentString( + pigeon_instance: android.webkit.WebSettings, + userAgentString: String? + ) + + /** Sets whether the WebView requires a user gesture to play media. */ + abstract fun setMediaPlaybackRequiresUserGesture( + pigeon_instance: android.webkit.WebSettings, + require: Boolean + ) + + /** + * Sets whether the WebView should support zooming using its on-screen zoom controls and gestures. + */ + abstract fun setSupportZoom(pigeon_instance: android.webkit.WebSettings, support: Boolean) + + /** + * Sets whether the WebView loads pages in overview mode, that is, zooms out the content to fit on + * screen by width. + */ + abstract fun setLoadWithOverviewMode( + pigeon_instance: android.webkit.WebSettings, + overview: Boolean + ) + + /** + * Sets whether the WebView should enable support for the "viewport" HTML meta tag or should use a + * wide viewport. + */ + abstract fun setUseWideViewPort(pigeon_instance: android.webkit.WebSettings, use: Boolean) + + /** + * Sets whether the WebView should display on-screen zoom controls when using the built-in zoom + * mechanisms. + */ + abstract fun setDisplayZoomControls(pigeon_instance: android.webkit.WebSettings, enabled: Boolean) + + /** + * Sets whether the WebView should display on-screen zoom controls when using the built-in zoom + * mechanisms. + */ + abstract fun setBuiltInZoomControls(pigeon_instance: android.webkit.WebSettings, enabled: Boolean) + + /** Enables or disables file access within WebView. */ + abstract fun setAllowFileAccess(pigeon_instance: android.webkit.WebSettings, enabled: Boolean) + + /** Enables or disables content URL access within WebView. */ + abstract fun setAllowContentAccess(pigeon_instance: android.webkit.WebSettings, enabled: Boolean) + + /** Sets whether Geolocation is enabled within WebView. */ + abstract fun setGeolocationEnabled(pigeon_instance: android.webkit.WebSettings, enabled: Boolean) + + /** Sets the text zoom of the page in percent. */ + abstract fun setTextZoom(pigeon_instance: android.webkit.WebSettings, textZoom: Long) + + /** Gets the WebView's user-agent string. */ + abstract fun getUserAgentString(pigeon_instance: android.webkit.WebSettings): String + + /** Configures the WebView's behavior when handling mixed content. */ + abstract fun setMixedContentMode( + pigeon_instance: android.webkit.WebSettings, + mode: MixedContentMode + ) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiWebSettings?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebSettings.setDomStorageEnabled", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebSettings + val flagArg = args[1] as Boolean + val wrapped: List = + try { + api.setDomStorageEnabled(pigeon_instanceArg, flagArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebSettings.setJavaScriptCanOpenWindowsAutomatically", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebSettings + val flagArg = args[1] as Boolean + val wrapped: List = + try { + api.setJavaScriptCanOpenWindowsAutomatically(pigeon_instanceArg, flagArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebSettings.setSupportMultipleWindows", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebSettings + val supportArg = args[1] as Boolean + val wrapped: List = + try { + api.setSupportMultipleWindows(pigeon_instanceArg, supportArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebSettings.setJavaScriptEnabled", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebSettings + val flagArg = args[1] as Boolean + val wrapped: List = + try { + api.setJavaScriptEnabled(pigeon_instanceArg, flagArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebSettings.setUserAgentString", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebSettings + val userAgentStringArg = args[1] as String? + val wrapped: List = + try { + api.setUserAgentString(pigeon_instanceArg, userAgentStringArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebSettings.setMediaPlaybackRequiresUserGesture", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebSettings + val requireArg = args[1] as Boolean + val wrapped: List = + try { + api.setMediaPlaybackRequiresUserGesture(pigeon_instanceArg, requireArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebSettings.setSupportZoom", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebSettings + val supportArg = args[1] as Boolean + val wrapped: List = + try { + api.setSupportZoom(pigeon_instanceArg, supportArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebSettings.setLoadWithOverviewMode", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebSettings + val overviewArg = args[1] as Boolean + val wrapped: List = + try { + api.setLoadWithOverviewMode(pigeon_instanceArg, overviewArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebSettings.setUseWideViewPort", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebSettings + val useArg = args[1] as Boolean + val wrapped: List = + try { + api.setUseWideViewPort(pigeon_instanceArg, useArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebSettings.setDisplayZoomControls", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebSettings + val enabledArg = args[1] as Boolean + val wrapped: List = + try { + api.setDisplayZoomControls(pigeon_instanceArg, enabledArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebSettings.setBuiltInZoomControls", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebSettings + val enabledArg = args[1] as Boolean + val wrapped: List = + try { + api.setBuiltInZoomControls(pigeon_instanceArg, enabledArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebSettings.setAllowFileAccess", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebSettings + val enabledArg = args[1] as Boolean + val wrapped: List = + try { + api.setAllowFileAccess(pigeon_instanceArg, enabledArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebSettings.setAllowContentAccess", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebSettings + val enabledArg = args[1] as Boolean + val wrapped: List = + try { + api.setAllowContentAccess(pigeon_instanceArg, enabledArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebSettings.setGeolocationEnabled", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebSettings + val enabledArg = args[1] as Boolean + val wrapped: List = + try { + api.setGeolocationEnabled(pigeon_instanceArg, enabledArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebSettings.setTextZoom", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebSettings + val textZoomArg = args[1] as Long + val wrapped: List = + try { + api.setTextZoom(pigeon_instanceArg, textZoomArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebSettings.getUserAgentString", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebSettings + val wrapped: List = + try { + listOf(api.getUserAgentString(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebSettings.setMixedContentMode", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebSettings + val modeArg = args[1] as MixedContentMode + val wrapped: List = + try { + api.setMixedContentMode(pigeon_instanceArg, modeArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of WebSettings and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.webkit.WebSettings, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebSettings.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * A JavaScript interface for exposing Javascript callbacks to Dart. + * + * This is a custom class for the wrapper that is annotated with + * [JavascriptInterface](https://developer.android.com/reference/android/webkit/JavascriptInterface). + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiJavaScriptChannel( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + abstract fun pigeon_defaultConstructor(channelName: String): JavaScriptChannel + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiJavaScriptChannel?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.JavaScriptChannel.pigeon_defaultConstructor", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_identifierArg = args[0] as Long + val channelNameArg = args[1] as String + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.pigeon_defaultConstructor(channelNameArg), pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of JavaScriptChannel and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance(pigeon_instanceArg: JavaScriptChannel, callback: (Result) -> Unit) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + callback( + Result.failure( + AndroidWebKitError( + "new-instance-error", + "Attempting to create a new Dart instance of JavaScriptChannel, but the class has a nonnull callback method.", + ""))) + } + } + + /** Handles callbacks messages from JavaScript. */ + fun postMessage( + pigeon_instanceArg: JavaScriptChannel, + messageArg: String, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.JavaScriptChannel.postMessage" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, messageArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } +} +/** + * Receives various notifications and requests from a `WebView`. + * + * See https://developer.android.com/reference/android/webkit/WebViewClient. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiWebViewClient( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + abstract fun pigeon_defaultConstructor(): android.webkit.WebViewClient + + /** + * Sets the required synchronous return value for the Java method, + * `WebViewClient.shouldOverrideUrlLoading(...)`. + * + * The Java method, `WebViewClient.shouldOverrideUrlLoading(...)`, requires a boolean to be + * returned and this method sets the returned value for all calls to the Java method. + * + * Setting this to true causes the current [WebView] to abort loading any URL received by + * [requestLoading] or [urlLoading], while setting this to false causes the [WebView] to continue + * loading a URL as usual. + * + * Defaults to false. + */ + abstract fun setSynchronousReturnValueForShouldOverrideUrlLoading( + pigeon_instance: android.webkit.WebViewClient, + value: Boolean + ) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiWebViewClient?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebViewClient.pigeon_defaultConstructor", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_identifierArg = args[0] as Long + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.pigeon_defaultConstructor(), pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebViewClient.setSynchronousReturnValueForShouldOverrideUrlLoading", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebViewClient + val valueArg = args[1] as Boolean + val wrapped: List = + try { + api.setSynchronousReturnValueForShouldOverrideUrlLoading( + pigeon_instanceArg, valueArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of WebViewClient and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.webkit.WebViewClient, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.WebViewClient.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } + + /** Notify the host application that a page has started loading. */ + fun onPageStarted( + pigeon_instanceArg: android.webkit.WebViewClient, + webViewArg: android.webkit.WebView, + urlArg: String, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebViewClient.onPageStarted" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, webViewArg, urlArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** Notify the host application that a page has finished loading. */ + fun onPageFinished( + pigeon_instanceArg: android.webkit.WebViewClient, + webViewArg: android.webkit.WebView, + urlArg: String, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebViewClient.onPageFinished" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, webViewArg, urlArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** + * Notify the host application that an HTTP error has been received from the server while loading + * a resource. + */ + fun onReceivedHttpError( + pigeon_instanceArg: android.webkit.WebViewClient, + webViewArg: android.webkit.WebView, + requestArg: android.webkit.WebResourceRequest, + responseArg: android.webkit.WebResourceResponse, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedHttpError" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, webViewArg, requestArg, responseArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** Report web resource loading error to the host application. */ + fun onReceivedRequestError( + pigeon_instanceArg: android.webkit.WebViewClient, + webViewArg: android.webkit.WebView, + requestArg: android.webkit.WebResourceRequest, + errorArg: android.webkit.WebResourceError, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedRequestError" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, webViewArg, requestArg, errorArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** Report web resource loading error to the host application. */ + fun onReceivedRequestErrorCompat( + pigeon_instanceArg: android.webkit.WebViewClient, + webViewArg: android.webkit.WebView, + requestArg: android.webkit.WebResourceRequest, + errorArg: androidx.webkit.WebResourceErrorCompat, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedRequestErrorCompat" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, webViewArg, requestArg, errorArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** + * Give the host application a chance to take control when a URL is about to be loaded in the + * current WebView. + */ + fun requestLoading( + pigeon_instanceArg: android.webkit.WebViewClient, + webViewArg: android.webkit.WebView, + requestArg: android.webkit.WebResourceRequest, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebViewClient.requestLoading" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, webViewArg, requestArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** + * Give the host application a chance to take control when a URL is about to be loaded in the + * current WebView. + */ + fun urlLoading( + pigeon_instanceArg: android.webkit.WebViewClient, + webViewArg: android.webkit.WebView, + urlArg: String, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebViewClient.urlLoading" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, webViewArg, urlArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** Notify the host application to update its visited links database. */ + fun doUpdateVisitedHistory( + pigeon_instanceArg: android.webkit.WebViewClient, + webViewArg: android.webkit.WebView, + urlArg: String, + isReloadArg: Boolean, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.WebViewClient.doUpdateVisitedHistory" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, webViewArg, urlArg, isReloadArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** Notifies the host application that the WebView received an HTTP authentication request. */ + fun onReceivedHttpAuthRequest( + pigeon_instanceArg: android.webkit.WebViewClient, + webViewArg: android.webkit.WebView, + handlerArg: android.webkit.HttpAuthHandler, + hostArg: String, + realmArg: String, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedHttpAuthRequest" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, webViewArg, handlerArg, hostArg, realmArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** + * Ask the host application if the browser should resend data as the requested page was a result + * of a POST. + */ + fun onFormResubmission( + pigeon_instanceArg: android.webkit.WebViewClient, + viewArg: android.webkit.WebView, + dontResendArg: android.os.Message, + resendArg: android.os.Message, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebViewClient.onFormResubmission" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, viewArg, dontResendArg, resendArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** + * Notify the host application that the WebView will load the resource specified by the given url. + */ + fun onLoadResource( + pigeon_instanceArg: android.webkit.WebViewClient, + viewArg: android.webkit.WebView, + urlArg: String, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebViewClient.onLoadResource" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, viewArg, urlArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** + * Notify the host application that WebView content left over from previous page navigations will + * no longer be drawn. + */ + fun onPageCommitVisible( + pigeon_instanceArg: android.webkit.WebViewClient, + viewArg: android.webkit.WebView, + urlArg: String, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebViewClient.onPageCommitVisible" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, viewArg, urlArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** Notify the host application to handle a SSL client certificate request. */ + fun onReceivedClientCertRequest( + pigeon_instanceArg: android.webkit.WebViewClient, + viewArg: android.webkit.WebView, + requestArg: android.webkit.ClientCertRequest, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedClientCertRequest" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, viewArg, requestArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** + * Notify the host application that a request to automatically log in the user has been processed. + */ + fun onReceivedLoginRequest( + pigeon_instanceArg: android.webkit.WebViewClient, + viewArg: android.webkit.WebView, + realmArg: String, + accountArg: String?, + argsArg: String, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedLoginRequest" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, viewArg, realmArg, accountArg, argsArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** Notifies the host application that an SSL error occurred while loading a resource. */ + fun onReceivedSslError( + pigeon_instanceArg: android.webkit.WebViewClient, + viewArg: android.webkit.WebView, + handlerArg: android.webkit.SslErrorHandler, + errorArg: android.net.http.SslError, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedSslError" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, viewArg, handlerArg, errorArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** Notify the host application that the scale applied to the WebView has changed. */ + fun onScaleChanged( + pigeon_instanceArg: android.webkit.WebViewClient, + viewArg: android.webkit.WebView, + oldScaleArg: Double, + newScaleArg: Double, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebViewClient.onScaleChanged" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, viewArg, oldScaleArg, newScaleArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } +} +/** + * Handles notifications that a file should be downloaded. + * + * See https://developer.android.com/reference/android/webkit/DownloadListener. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiDownloadListener( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + abstract fun pigeon_defaultConstructor(): android.webkit.DownloadListener + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiDownloadListener?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.DownloadListener.pigeon_defaultConstructor", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_identifierArg = args[0] as Long + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.pigeon_defaultConstructor(), pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of DownloadListener and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.webkit.DownloadListener, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + callback( + Result.failure( + AndroidWebKitError( + "new-instance-error", + "Attempting to create a new Dart instance of DownloadListener, but the class has a nonnull callback method.", + ""))) + } + } + + /** Notify the host application that a file should be downloaded. */ + fun onDownloadStart( + pigeon_instanceArg: android.webkit.DownloadListener, + urlArg: String, + userAgentArg: String, + contentDispositionArg: String, + mimetypeArg: String, + contentLengthArg: Long, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.DownloadListener.onDownloadStart" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send( + listOf( + pigeon_instanceArg, + urlArg, + userAgentArg, + contentDispositionArg, + mimetypeArg, + contentLengthArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } +} +/** + * Handles notification of JavaScript dialogs, favicons, titles, and the progress. + * + * See https://developer.android.com/reference/android/webkit/WebChromeClient. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiWebChromeClient( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + abstract fun pigeon_defaultConstructor(): + io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl + + /** + * Sets the required synchronous return value for the Java method, + * `WebChromeClient.onShowFileChooser(...)`. + * + * The Java method, `WebChromeClient.onShowFileChooser(...)`, requires a boolean to be returned + * and this method sets the returned value for all calls to the Java method. + * + * Setting this to true indicates that all file chooser requests should be handled by + * `onShowFileChooser` and the returned list of Strings will be returned to the WebView. + * Otherwise, the client will use the default handling and the returned value in + * `onShowFileChooser` will be ignored. + * + * Requires `onShowFileChooser` to be nonnull. + * + * Defaults to false. + */ + abstract fun setSynchronousReturnValueForOnShowFileChooser( + pigeon_instance: + io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl, + value: Boolean + ) + + /** + * Sets the required synchronous return value for the Java method, + * `WebChromeClient.onConsoleMessage(...)`. + * + * The Java method, `WebChromeClient.onConsoleMessage(...)`, requires a boolean to be returned and + * this method sets the returned value for all calls to the Java method. + * + * Setting this to true indicates that the client is handling all console messages. + * + * Requires `onConsoleMessage` to be nonnull. + * + * Defaults to false. + */ + abstract fun setSynchronousReturnValueForOnConsoleMessage( + pigeon_instance: + io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl, + value: Boolean + ) + + /** + * Sets the required synchronous return value for the Java method, + * `WebChromeClient.onJsAlert(...)`. + * + * The Java method, `WebChromeClient.onJsAlert(...)`, requires a boolean to be returned and this + * method sets the returned value for all calls to the Java method. + * + * Setting this to true indicates that the client is handling all console messages. + * + * Requires `onJsAlert` to be nonnull. + * + * Defaults to false. + */ + abstract fun setSynchronousReturnValueForOnJsAlert( + pigeon_instance: + io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl, + value: Boolean + ) + + /** + * Sets the required synchronous return value for the Java method, + * `WebChromeClient.onJsConfirm(...)`. + * + * The Java method, `WebChromeClient.onJsConfirm(...)`, requires a boolean to be returned and this + * method sets the returned value for all calls to the Java method. + * + * Setting this to true indicates that the client is handling all console messages. + * + * Requires `onJsConfirm` to be nonnull. + * + * Defaults to false. + */ + abstract fun setSynchronousReturnValueForOnJsConfirm( + pigeon_instance: + io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl, + value: Boolean + ) + + /** + * Sets the required synchronous return value for the Java method, + * `WebChromeClient.onJsPrompt(...)`. + * + * The Java method, `WebChromeClient.onJsPrompt(...)`, requires a boolean to be returned and this + * method sets the returned value for all calls to the Java method. + * + * Setting this to true indicates that the client is handling all console messages. + * + * Requires `onJsPrompt` to be nonnull. + * + * Defaults to false. + */ + abstract fun setSynchronousReturnValueForOnJsPrompt( + pigeon_instance: + io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl, + value: Boolean + ) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiWebChromeClient?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebChromeClient.pigeon_defaultConstructor", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_identifierArg = args[0] as Long + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.pigeon_defaultConstructor(), pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebChromeClient.setSynchronousReturnValueForOnShowFileChooser", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = + args[0] + as io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl + val valueArg = args[1] as Boolean + val wrapped: List = + try { + api.setSynchronousReturnValueForOnShowFileChooser(pigeon_instanceArg, valueArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebChromeClient.setSynchronousReturnValueForOnConsoleMessage", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = + args[0] + as io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl + val valueArg = args[1] as Boolean + val wrapped: List = + try { + api.setSynchronousReturnValueForOnConsoleMessage(pigeon_instanceArg, valueArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebChromeClient.setSynchronousReturnValueForOnJsAlert", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = + args[0] + as io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl + val valueArg = args[1] as Boolean + val wrapped: List = + try { + api.setSynchronousReturnValueForOnJsAlert(pigeon_instanceArg, valueArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebChromeClient.setSynchronousReturnValueForOnJsConfirm", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = + args[0] + as io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl + val valueArg = args[1] as Boolean + val wrapped: List = + try { + api.setSynchronousReturnValueForOnJsConfirm(pigeon_instanceArg, valueArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebChromeClient.setSynchronousReturnValueForOnJsPrompt", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = + args[0] + as io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl + val valueArg = args[1] as Boolean + val wrapped: List = + try { + api.setSynchronousReturnValueForOnJsPrompt(pigeon_instanceArg, valueArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of WebChromeClient and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: + io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + callback( + Result.failure( + AndroidWebKitError( + "new-instance-error", + "Attempting to create a new Dart instance of WebChromeClient, but the class has a nonnull callback method.", + ""))) + } + } + + /** Tell the host application the current progress of loading a page. */ + fun onProgressChanged( + pigeon_instanceArg: + io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl, + webViewArg: android.webkit.WebView, + progressArg: Long, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onProgressChanged" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, webViewArg, progressArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** Tell the client to show a file chooser. */ + fun onShowFileChooser( + pigeon_instanceArg: + io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl, + webViewArg: android.webkit.WebView, + paramsArg: android.webkit.WebChromeClient.FileChooserParams, + callback: (Result>) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onShowFileChooser" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, webViewArg, paramsArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else if (it[0] == null) { + callback( + Result.failure( + AndroidWebKitError( + "null-error", + "Flutter api returned null value for non-null return value.", + ""))) + } else { + val output = it[0] as List + callback(Result.success(output)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** + * Notify the host application that web content is requesting permission to access the specified + * resources and the permission currently isn't granted or denied. + */ + fun onPermissionRequest( + pigeon_instanceArg: + io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl, + requestArg: android.webkit.PermissionRequest, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onPermissionRequest" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, requestArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** Callback to Dart function `WebChromeClient.onShowCustomView`. */ + fun onShowCustomView( + pigeon_instanceArg: + io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl, + viewArg: android.view.View, + callbackArg: android.webkit.WebChromeClient.CustomViewCallback, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onShowCustomView" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, viewArg, callbackArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** Notify the host application that the current page has entered full screen mode. */ + fun onHideCustomView( + pigeon_instanceArg: + io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onHideCustomView" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** + * Notify the host application that web content from the specified origin is attempting to use the + * Geolocation API, but no permission state is currently set for that origin. + */ + fun onGeolocationPermissionsShowPrompt( + pigeon_instanceArg: + io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl, + originArg: String, + callbackArg: android.webkit.GeolocationPermissions.Callback, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onGeolocationPermissionsShowPrompt" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, originArg, callbackArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** + * Notify the host application that a request for Geolocation permissions, made with a previous + * call to `onGeolocationPermissionsShowPrompt` has been canceled. + */ + fun onGeolocationPermissionsHidePrompt( + pigeon_instanceArg: + io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onGeolocationPermissionsHidePrompt" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** Report a JavaScript console message to the host application. */ + fun onConsoleMessage( + pigeon_instanceArg: + io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl, + messageArg: android.webkit.ConsoleMessage, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onConsoleMessage" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, messageArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** + * Notify the host application that the web page wants to display a JavaScript `alert()` dialog. + */ + fun onJsAlert( + pigeon_instanceArg: + io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl, + webViewArg: android.webkit.WebView, + urlArg: String, + messageArg: String, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsAlert" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, webViewArg, urlArg, messageArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** + * Notify the host application that the web page wants to display a JavaScript `confirm()` dialog. + */ + fun onJsConfirm( + pigeon_instanceArg: + io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl, + webViewArg: android.webkit.WebView, + urlArg: String, + messageArg: String, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsConfirm" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, webViewArg, urlArg, messageArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else if (it[0] == null) { + callback( + Result.failure( + AndroidWebKitError( + "null-error", + "Flutter api returned null value for non-null return value.", + ""))) + } else { + val output = it[0] as Boolean + callback(Result.success(output)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + + /** + * Notify the host application that the web page wants to display a JavaScript `prompt()` dialog. + */ + fun onJsPrompt( + pigeon_instanceArg: + io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl, + webViewArg: android.webkit.WebView, + urlArg: String, + messageArg: String, + defaultValueArg: String, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + return + } + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsPrompt" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_instanceArg, webViewArg, urlArg, messageArg, defaultValueArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + val output = it[0] as String? + callback(Result.success(output)) + } + } else { + callback(Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } +} +/** + * Provides access to the assets registered as part of the App bundle. + * + * Convenience class for accessing Flutter asset resources. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiFlutterAssetManager( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + /** The global instance of the `FlutterAssetManager`. */ + abstract fun instance(): io.flutter.plugins.webviewflutter.FlutterAssetManager + + /** + * Returns a String array of all the assets at the given path. + * + * Throws an IOException in case I/O operations were interrupted. + */ + abstract fun list( + pigeon_instance: io.flutter.plugins.webviewflutter.FlutterAssetManager, + path: String + ): List + + /** + * Gets the relative file path to the Flutter asset with the given name, including the file's + * extension, e.g., "myImage.jpg". + * + * The returned file path is relative to the Android app's standard asset's directory. Therefore, + * the returned path is appropriate to pass to Android's AssetManager, but the path is not + * appropriate to load as an absolute path. + */ + abstract fun getAssetFilePathByName( + pigeon_instance: io.flutter.plugins.webviewflutter.FlutterAssetManager, + name: String + ): String + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiFlutterAssetManager?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.FlutterAssetManager.instance", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_identifierArg = args[0] as Long + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.instance(), pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.FlutterAssetManager.list", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = + args[0] as io.flutter.plugins.webviewflutter.FlutterAssetManager + val pathArg = args[1] as String + val wrapped: List = + try { + listOf(api.list(pigeon_instanceArg, pathArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.FlutterAssetManager.getAssetFilePathByName", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = + args[0] as io.flutter.plugins.webviewflutter.FlutterAssetManager + val nameArg = args[1] as String + val wrapped: List = + try { + listOf(api.getAssetFilePathByName(pigeon_instanceArg, nameArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of FlutterAssetManager and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: io.flutter.plugins.webviewflutter.FlutterAssetManager, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.FlutterAssetManager.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * This class is used to manage the JavaScript storage APIs provided by the WebView. + * + * See https://developer.android.com/reference/android/webkit/WebStorage. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiWebStorage( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + abstract fun instance(): android.webkit.WebStorage + + /** Clears all storage currently being used by the JavaScript storage APIs. */ + abstract fun deleteAllData(pigeon_instance: android.webkit.WebStorage) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiWebStorage?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebStorage.instance", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_identifierArg = args[0] as Long + val wrapped: List = + try { + api.pigeonRegistrar.instanceManager.addDartCreatedInstance( + api.instance(), pigeon_identifierArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebStorage.deleteAllData", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebStorage + val wrapped: List = + try { + api.deleteAllData(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of WebStorage and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.webkit.WebStorage, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.WebStorage.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * Parameters used in the `WebChromeClient.onShowFileChooser` method. + * + * See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiFileChooserParams( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + /** Preference for a live media captured value (e.g. Camera, Microphone). */ + abstract fun isCaptureEnabled( + pigeon_instance: android.webkit.WebChromeClient.FileChooserParams + ): Boolean + + /** An array of acceptable MIME types. */ + abstract fun acceptTypes( + pigeon_instance: android.webkit.WebChromeClient.FileChooserParams + ): List + + /** File chooser mode. */ + abstract fun mode( + pigeon_instance: android.webkit.WebChromeClient.FileChooserParams + ): FileChooserMode + + /** File name of a default selection if specified, or null. */ + abstract fun filenameHint( + pigeon_instance: android.webkit.WebChromeClient.FileChooserParams + ): String? + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of FileChooserParams and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.webkit.WebChromeClient.FileChooserParams, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val isCaptureEnabledArg = isCaptureEnabled(pigeon_instanceArg) + val acceptTypesArg = acceptTypes(pigeon_instanceArg) + val modeArg = mode(pigeon_instanceArg) + val filenameHintArg = filenameHint(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.FileChooserParams.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send( + listOf( + pigeon_identifierArg, + isCaptureEnabledArg, + acceptTypesArg, + modeArg, + filenameHintArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure( + AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * This class defines a permission request and is used when web content requests access to protected + * resources. + * + * See https://developer.android.com/reference/android/webkit/PermissionRequest. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiPermissionRequest( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + abstract fun resources(pigeon_instance: android.webkit.PermissionRequest): List + + /** Call this method to grant origin the permission to access the given resources. */ + abstract fun grant(pigeon_instance: android.webkit.PermissionRequest, resources: List) + + /** Call this method to deny the request. */ + abstract fun deny(pigeon_instance: android.webkit.PermissionRequest) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiPermissionRequest?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.PermissionRequest.grant", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.PermissionRequest + val resourcesArg = args[1] as List + val wrapped: List = + try { + api.grant(pigeon_instanceArg, resourcesArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.PermissionRequest.deny", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.PermissionRequest + val wrapped: List = + try { + api.deny(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of PermissionRequest and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.webkit.PermissionRequest, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val resourcesArg = resources(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.PermissionRequest.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg, resourcesArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * A callback interface used by the host application to notify the current page that its custom view + * has been dismissed. + * + * See https://developer.android.com/reference/android/webkit/WebChromeClient.CustomViewCallback. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiCustomViewCallback( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + /** Invoked when the host application dismisses the custom view. */ + abstract fun onCustomViewHidden( + pigeon_instance: android.webkit.WebChromeClient.CustomViewCallback + ) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiCustomViewCallback?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.CustomViewCallback.onCustomViewHidden", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebChromeClient.CustomViewCallback + val wrapped: List = + try { + api.onCustomViewHidden(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of CustomViewCallback and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.webkit.WebChromeClient.CustomViewCallback, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.CustomViewCallback.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * This class represents the basic building block for user interface components. + * + * See https://developer.android.com/reference/android/view/View. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiView( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + /** Set the scrolled position of your view. */ + abstract fun scrollTo(pigeon_instance: android.view.View, x: Long, y: Long) + + /** Move the scrolled position of your view. */ + abstract fun scrollBy(pigeon_instance: android.view.View, x: Long, y: Long) + + /** Return the scrolled position of this view. */ + abstract fun getScrollPosition(pigeon_instance: android.view.View): WebViewPoint + + /** + * Define whether the vertical scrollbar should be drawn or not. + * + * The scrollbar is not drawn by default. + */ + abstract fun setVerticalScrollBarEnabled(pigeon_instance: android.view.View, enabled: Boolean) + + /** + * Define whether the horizontal scrollbar should be drawn or not. + * + * The scrollbar is not drawn by default. + */ + abstract fun setHorizontalScrollBarEnabled(pigeon_instance: android.view.View, enabled: Boolean) + + /** Set the over-scroll mode for this view. */ + abstract fun setOverScrollMode(pigeon_instance: android.view.View, mode: OverScrollMode) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiView?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, "dev.flutter.pigeon.webview_flutter_android.View.scrollTo", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.view.View + val xArg = args[1] as Long + val yArg = args[2] as Long + val wrapped: List = + try { + api.scrollTo(pigeon_instanceArg, xArg, yArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, "dev.flutter.pigeon.webview_flutter_android.View.scrollBy", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.view.View + val xArg = args[1] as Long + val yArg = args[2] as Long + val wrapped: List = + try { + api.scrollBy(pigeon_instanceArg, xArg, yArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.View.getScrollPosition", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.view.View + val wrapped: List = + try { + listOf(api.getScrollPosition(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.View.setVerticalScrollBarEnabled", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.view.View + val enabledArg = args[1] as Boolean + val wrapped: List = + try { + api.setVerticalScrollBarEnabled(pigeon_instanceArg, enabledArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.View.setHorizontalScrollBarEnabled", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.view.View + val enabledArg = args[1] as Boolean + val wrapped: List = + try { + api.setHorizontalScrollBarEnabled(pigeon_instanceArg, enabledArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.View.setOverScrollMode", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.view.View + val modeArg = args[1] as OverScrollMode + val wrapped: List = + try { + api.setOverScrollMode(pigeon_instanceArg, modeArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of View and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance(pigeon_instanceArg: android.view.View, callback: (Result) -> Unit) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.View.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * A callback interface used by the host application to set the Geolocation permission state for an + * origin. + * + * See https://developer.android.com/reference/android/webkit/GeolocationPermissions.Callback. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiGeolocationPermissionsCallback( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + /** Sets the Geolocation permission state for the supplied origin. */ + abstract fun invoke( + pigeon_instance: android.webkit.GeolocationPermissions.Callback, + origin: String, + allow: Boolean, + retain: Boolean + ) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers( + binaryMessenger: BinaryMessenger, + api: PigeonApiGeolocationPermissionsCallback? + ) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.GeolocationPermissionsCallback.invoke", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.GeolocationPermissions.Callback + val originArg = args[1] as String + val allowArg = args[2] as Boolean + val retainArg = args[3] as Boolean + val wrapped: List = + try { + api.invoke(pigeon_instanceArg, originArg, allowArg, retainArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** + * Creates a Dart instance of GeolocationPermissionsCallback and attaches it to + * [pigeon_instanceArg]. + */ + fun pigeon_newInstance( + pigeon_instanceArg: android.webkit.GeolocationPermissions.Callback, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.GeolocationPermissionsCallback.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * Represents a request for HTTP authentication. + * + * See https://developer.android.com/reference/android/webkit/HttpAuthHandler. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiHttpAuthHandler( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + /** + * Gets whether the credentials stored for the current host (i.e. the host for which + * `WebViewClient.onReceivedHttpAuthRequest` was called) are suitable for use. + */ + abstract fun useHttpAuthUsernamePassword(pigeon_instance: android.webkit.HttpAuthHandler): Boolean + + /** Instructs the WebView to cancel the authentication request.. */ + abstract fun cancel(pigeon_instance: android.webkit.HttpAuthHandler) + + /** Instructs the WebView to proceed with the authentication with the given credentials. */ + abstract fun proceed( + pigeon_instance: android.webkit.HttpAuthHandler, + username: String, + password: String + ) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiHttpAuthHandler?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.HttpAuthHandler.useHttpAuthUsernamePassword", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.HttpAuthHandler + val wrapped: List = + try { + listOf(api.useHttpAuthUsernamePassword(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.HttpAuthHandler.cancel", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.HttpAuthHandler + val wrapped: List = + try { + api.cancel(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.HttpAuthHandler.proceed", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.HttpAuthHandler + val usernameArg = args[1] as String + val passwordArg = args[2] as String + val wrapped: List = + try { + api.proceed(pigeon_instanceArg, usernameArg, passwordArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of HttpAuthHandler and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.webkit.HttpAuthHandler, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.HttpAuthHandler.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * Defines a message containing a description and arbitrary data object that can be sent to a + * `Handler`. + * + * See https://developer.android.com/reference/android/os/Message. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiAndroidMessage( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + /** + * Sends this message to the Android native `Handler` specified by getTarget(). + * + * Throws a null pointer exception if this field has not been set. + */ + abstract fun sendToTarget(pigeon_instance: android.os.Message) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiAndroidMessage?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.AndroidMessage.sendToTarget", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.os.Message + val wrapped: List = + try { + api.sendToTarget(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of AndroidMessage and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance(pigeon_instanceArg: android.os.Message, callback: (Result) -> Unit) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.AndroidMessage.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * Defines a message containing a description and arbitrary data object that can be sent to a + * `Handler`. + * + * See https://developer.android.com/reference/android/webkit/ClientCertRequest. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiClientCertRequest( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + /** Cancel this request. */ + abstract fun cancel(pigeon_instance: android.webkit.ClientCertRequest) + + /** Ignore the request for now. */ + abstract fun ignore(pigeon_instance: android.webkit.ClientCertRequest) + + /** Proceed with the specified private key and client certificate chain. */ + abstract fun proceed( + pigeon_instance: android.webkit.ClientCertRequest, + privateKey: java.security.PrivateKey, + chain: List + ) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiClientCertRequest?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.ClientCertRequest.cancel", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.ClientCertRequest + val wrapped: List = + try { + api.cancel(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.ClientCertRequest.ignore", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.ClientCertRequest + val wrapped: List = + try { + api.ignore(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.ClientCertRequest.proceed", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.ClientCertRequest + val privateKeyArg = args[1] as java.security.PrivateKey + val chainArg = args[2] as List + val wrapped: List = + try { + api.proceed(pigeon_instanceArg, privateKeyArg, chainArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of ClientCertRequest and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.webkit.ClientCertRequest, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.ClientCertRequest.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * A private key. + * + * The purpose of this interface is to group (and provide type safety for) all private key + * interfaces. + * + * See https://developer.android.com/reference/java/security/PrivateKey. + */ +@Suppress("UNCHECKED_CAST") +open class PigeonApiPrivateKey( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of PrivateKey and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: java.security.PrivateKey, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.PrivateKey.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * Abstract class for X.509 certificates. + * + * This provides a standard way to access all the attributes of an X.509 certificate. + * + * See https://developer.android.com/reference/java/security/cert/X509Certificate. + */ +@Suppress("UNCHECKED_CAST") +open class PigeonApiX509Certificate( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of X509Certificate and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: java.security.cert.X509Certificate, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.X509Certificate.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } + + @Suppress("FunctionName") + /** An implementation of [PigeonApiCertificate] used to access callback methods */ + fun pigeon_getPigeonApiCertificate(): PigeonApiCertificate { + return pigeonRegistrar.getPigeonApiCertificate() + } +} +/** + * Represents a request for handling an SSL error. + * + * See https://developer.android.com/reference/android/webkit/SslErrorHandler. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiSslErrorHandler( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + /** + * Instructs the WebView that encountered the SSL certificate error to terminate communication + * with the server. + */ + abstract fun cancel(pigeon_instance: android.webkit.SslErrorHandler) + + /** + * Instructs the WebView that encountered the SSL certificate error to ignore the error and + * continue communicating with the server. + */ + abstract fun proceed(pigeon_instance: android.webkit.SslErrorHandler) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiSslErrorHandler?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.SslErrorHandler.cancel", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.SslErrorHandler + val wrapped: List = + try { + api.cancel(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.SslErrorHandler.proceed", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.SslErrorHandler + val wrapped: List = + try { + api.proceed(pigeon_instanceArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of SslErrorHandler and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.webkit.SslErrorHandler, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.SslErrorHandler.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * This class represents a set of one or more SSL errors and the associated SSL certificate. + * + * See https://developer.android.com/reference/android/net/http/SslError. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiSslError( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + /** Gets the SSL certificate associated with this object. */ + abstract fun certificate( + pigeon_instance: android.net.http.SslError + ): android.net.http.SslCertificate + + /** Gets the URL associated with this object. */ + abstract fun url(pigeon_instance: android.net.http.SslError): String + + /** Gets the most severe SSL error in this object's set of errors. */ + abstract fun getPrimaryError(pigeon_instance: android.net.http.SslError): SslErrorType + + /** Determines whether this object includes the supplied error. */ + abstract fun hasError(pigeon_instance: android.net.http.SslError, error: SslErrorType): Boolean + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiSslError?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.SslError.getPrimaryError", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.net.http.SslError + val wrapped: List = + try { + listOf(api.getPrimaryError(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.SslError.hasError", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.net.http.SslError + val errorArg = args[1] as SslErrorType + val wrapped: List = + try { + listOf(api.hasError(pigeon_instanceArg, errorArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of SslError and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.net.http.SslError, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val certificateArg = certificate(pigeon_instanceArg) + val urlArg = url(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.SslError.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg, certificateArg, urlArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * A distinguished name helper class. + * + * A 3-tuple of: the most specific common name (CN) the most specific organization (O) the most + * specific organizational unit (OU) + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiSslCertificateDName( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + /** The most specific Common-name (CN) component of this name. */ + abstract fun getCName(pigeon_instance: android.net.http.SslCertificate.DName): String + + /** The distinguished name (normally includes CN, O, and OU names). */ + abstract fun getDName(pigeon_instance: android.net.http.SslCertificate.DName): String + + /** The most specific Organization (O) component of this name. */ + abstract fun getOName(pigeon_instance: android.net.http.SslCertificate.DName): String + + /** The most specific Organizational Unit (OU) component of this name. */ + abstract fun getUName(pigeon_instance: android.net.http.SslCertificate.DName): String + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiSslCertificateDName?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.SslCertificateDName.getCName", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.net.http.SslCertificate.DName + val wrapped: List = + try { + listOf(api.getCName(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.SslCertificateDName.getDName", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.net.http.SslCertificate.DName + val wrapped: List = + try { + listOf(api.getDName(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.SslCertificateDName.getOName", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.net.http.SslCertificate.DName + val wrapped: List = + try { + listOf(api.getOName(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.SslCertificateDName.getUName", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.net.http.SslCertificate.DName + val wrapped: List = + try { + listOf(api.getUName(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of SslCertificateDName and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.net.http.SslCertificate.DName, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.SslCertificateDName.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * SSL certificate info (certificate details) class. + * + * See https://developer.android.com/reference/android/net/http/SslCertificate. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiSslCertificate( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + /** Issued-by distinguished name or null if none has been set. */ + abstract fun getIssuedBy( + pigeon_instance: android.net.http.SslCertificate + ): android.net.http.SslCertificate.DName? + + /** Issued-to distinguished name or null if none has been set. */ + abstract fun getIssuedTo( + pigeon_instance: android.net.http.SslCertificate + ): android.net.http.SslCertificate.DName? + + /** Not-after date from the certificate validity period or null if none has been set. */ + abstract fun getValidNotAfterMsSinceEpoch(pigeon_instance: android.net.http.SslCertificate): Long? + + /** Not-before date from the certificate validity period or null if none has been set. */ + abstract fun getValidNotBeforeMsSinceEpoch( + pigeon_instance: android.net.http.SslCertificate + ): Long? + + /** + * The X509Certificate used to create this SslCertificate or null if no certificate was provided. + * + * Always returns null on Android versions below Q. + */ + abstract fun getX509Certificate( + pigeon_instance: android.net.http.SslCertificate + ): java.security.cert.X509Certificate? + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiSslCertificate?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.SslCertificate.getIssuedBy", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.net.http.SslCertificate + val wrapped: List = + try { + listOf(api.getIssuedBy(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.SslCertificate.getIssuedTo", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.net.http.SslCertificate + val wrapped: List = + try { + listOf(api.getIssuedTo(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.SslCertificate.getValidNotAfterMsSinceEpoch", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.net.http.SslCertificate + val wrapped: List = + try { + listOf(api.getValidNotAfterMsSinceEpoch(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.SslCertificate.getValidNotBeforeMsSinceEpoch", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.net.http.SslCertificate + val wrapped: List = + try { + listOf(api.getValidNotBeforeMsSinceEpoch(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.SslCertificate.getX509Certificate", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.net.http.SslCertificate + val wrapped: List = + try { + listOf(api.getX509Certificate(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of SslCertificate and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: android.net.http.SslCertificate, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.SslCertificate.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * Abstract class for managing a variety of identity certificates. + * + * See https://developer.android.com/reference/java/security/cert/Certificate. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiCertificate( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + /** The encoded form of this certificate. */ + abstract fun getEncoded(pigeon_instance: java.security.cert.Certificate): ByteArray + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiCertificate?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.Certificate.getEncoded", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as java.security.cert.Certificate + val wrapped: List = + try { + listOf(api.getEncoded(pigeon_instanceArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of Certificate and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: java.security.cert.Certificate, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = "dev.flutter.pigeon.webview_flutter_android.Certificate.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * Compatibility version of `WebSettings`. + * + * See https://developer.android.com/reference/kotlin/androidx/webkit/WebSettingsCompat. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiWebSettingsCompat( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + abstract fun setPaymentRequestEnabled(webSettings: android.webkit.WebSettings, enabled: Boolean) + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiWebSettingsCompat?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebSettingsCompat.setPaymentRequestEnabled", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val webSettingsArg = args[0] as android.webkit.WebSettings + val enabledArg = args[1] as Boolean + val wrapped: List = + try { + api.setPaymentRequestEnabled(webSettingsArg, enabledArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of WebSettingsCompat and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: androidx.webkit.WebSettingsCompat, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.WebSettingsCompat.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} +/** + * Utility class for checking which WebView Support Library features are supported on the device. + * + * See https://developer.android.com/reference/kotlin/androidx/webkit/WebViewFeature. + */ +@Suppress("UNCHECKED_CAST") +abstract class PigeonApiWebViewFeature( + open val pigeonRegistrar: AndroidWebkitLibraryPigeonProxyApiRegistrar +) { + abstract fun isFeatureSupported(feature: String): Boolean + + companion object { + @Suppress("LocalVariableName") + fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiWebViewFeature?) { + val codec = api?.pigeonRegistrar?.codec ?: AndroidWebkitLibraryPigeonCodec() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebViewFeature.isFeatureSupported", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val featureArg = args[0] as String + val wrapped: List = + try { + listOf(api.isFeatureSupported(featureArg)) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } + + @Suppress("LocalVariableName", "FunctionName") + /** Creates a Dart instance of WebViewFeature and attaches it to [pigeon_instanceArg]. */ + fun pigeon_newInstance( + pigeon_instanceArg: androidx.webkit.WebViewFeature, + callback: (Result) -> Unit + ) { + if (pigeonRegistrar.ignoreCallsToDart) { + callback( + Result.failure( + AndroidWebKitError("ignore-calls-error", "Calls to Dart are being ignored.", ""))) + } else if (pigeonRegistrar.instanceManager.containsInstance(pigeon_instanceArg)) { + callback(Result.success(Unit)) + } else { + val pigeon_identifierArg = + pigeonRegistrar.instanceManager.addHostCreatedInstance(pigeon_instanceArg) + val binaryMessenger = pigeonRegistrar.binaryMessenger + val codec = pigeonRegistrar.codec + val channelName = + "dev.flutter.pigeon.webview_flutter_android.WebViewFeature.pigeon_newInstance" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(pigeon_identifierArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback( + Result.failure( + AndroidWebKitError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback( + Result.failure(AndroidWebkitLibraryPigeonUtils.createConnectionError(channelName))) + } + } + } + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/BSEventChannel.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/BSEventChannel.java new file mode 100644 index 0000000..10fb961 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/BSEventChannel.java @@ -0,0 +1,44 @@ +package io.flutter.plugins.webviewflutter; + +import android.util.Log; + +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.plugin.common.EventChannel; +import io.flutter.plugin.common.PluginRegistry; + +public class BSEventChannel implements EventChannel.StreamHandler { + + public static final String CHANNEL = "baishunChannel"; + public static EventChannel.EventSink bsSink; + // private Activity activity; + public static EventChannel channel; + + public static BSEventChannel registerWith(FlutterPlugin.FlutterPluginBinding binding) { + channel = new EventChannel(binding.getBinaryMessenger(), CHANNEL); +// channel = new EventChannel(registrar.messenger(), CHANNEL); + + BSEventChannel instance = new BSEventChannel(); + + channel.setStreamHandler(instance); + return instance; + } + + @Override + public void onListen(Object arguments, EventChannel.EventSink events) { + // The Flutter app has opened the Event Channel + bsSink = events; + } + + @Override + public void onCancel(Object arguments) { + // The Flutter app has closed the Event Channel + bsSink = null; + } + + + public void sendEvent(Object o) { + if (bsSink != null) { + bsSink.success(o); + } + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/CertificateProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/CertificateProxyApi.java new file mode 100644 index 0000000..c736d96 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/CertificateProxyApi.java @@ -0,0 +1,30 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import androidx.annotation.NonNull; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; + +/** + * ProxyApi implementation for {@link Certificate}. This class may handle instantiating native + * object instances that are attached to a Dart instance or handle method calls on the associated + * native class or an instance of that class. + */ +class CertificateProxyApi extends PigeonApiCertificate { + CertificateProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @NonNull + @Override + public byte[] getEncoded(@NonNull Certificate pigeon_instance) { + try { + return pigeon_instance.getEncoded(); + } catch (CertificateEncodingException exception) { + throw new RuntimeException(exception); + } + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/ClientCertRequestProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/ClientCertRequestProxyApi.java new file mode 100644 index 0000000..7771871 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/ClientCertRequestProxyApi.java @@ -0,0 +1,40 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.webkit.ClientCertRequest; +import androidx.annotation.NonNull; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.List; + +/** + * ProxyApi implementation for {@link ClientCertRequest}. This class may handle instantiating native + * object instances that are attached to a Dart instance or handle method calls on the associated + * native class or an instance of that class. + */ +class ClientCertRequestProxyApi extends PigeonApiClientCertRequest { + ClientCertRequestProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @Override + public void cancel(@NonNull ClientCertRequest pigeon_instance) { + pigeon_instance.cancel(); + } + + @Override + public void ignore(@NonNull ClientCertRequest pigeon_instance) { + pigeon_instance.ignore(); + } + + @Override + public void proceed( + @NonNull ClientCertRequest pigeon_instance, + @NonNull PrivateKey privateKey, + @NonNull List chain) { + pigeon_instance.proceed(privateKey, chain.toArray(new X509Certificate[0])); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/ConsoleMessageProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/ConsoleMessageProxyApi.java new file mode 100644 index 0000000..82f9697 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/ConsoleMessageProxyApi.java @@ -0,0 +1,50 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.webkit.ConsoleMessage; +import androidx.annotation.NonNull; + +public class ConsoleMessageProxyApi extends PigeonApiConsoleMessage { + public ConsoleMessageProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @Override + public long lineNumber(@NonNull ConsoleMessage pigeon_instance) { + return pigeon_instance.lineNumber(); + } + + @NonNull + @Override + public String message(@NonNull ConsoleMessage pigeon_instance) { + return pigeon_instance.message(); + } + + @NonNull + @Override + public ConsoleMessageLevel level(@NonNull ConsoleMessage pigeon_instance) { + switch (pigeon_instance.messageLevel()) { + case TIP: + return ConsoleMessageLevel.TIP; + case LOG: + return ConsoleMessageLevel.LOG; + case WARNING: + return ConsoleMessageLevel.WARNING; + case ERROR: + return ConsoleMessageLevel.ERROR; + case DEBUG: + return ConsoleMessageLevel.DEBUG; + default: + return ConsoleMessageLevel.UNKNOWN; + } + } + + @NonNull + @Override + public String sourceId(@NonNull ConsoleMessage pigeon_instance) { + return pigeon_instance.sourceId(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerProxyApi.java new file mode 100644 index 0000000..9c2988e --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerProxyApi.java @@ -0,0 +1,55 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.webkit.CookieManager; +import android.webkit.WebView; +import androidx.annotation.NonNull; +import kotlin.Result; +import kotlin.Unit; +import kotlin.jvm.functions.Function1; + +/** + * Host API implementation for `CookieManager`. + * + *

This class may handle instantiating and adding native object instances that are attached to a + * Dart instance or handle method calls on the associated native class or an instance of the class. + */ +public class CookieManagerProxyApi extends PigeonApiCookieManager { + public CookieManagerProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @NonNull + @Override + public ProxyApiRegistrar getPigeonRegistrar() { + return (ProxyApiRegistrar) super.getPigeonRegistrar(); + } + + @NonNull + @Override + public CookieManager instance() { + return CookieManager.getInstance(); + } + + @Override + public void setCookie( + @NonNull CookieManager pigeon_instance, @NonNull String url, @NonNull String value) { + pigeon_instance.setCookie(url, value); + } + + @Override + public void removeAllCookies( + @NonNull CookieManager pigeon_instance, + @NonNull Function1, Unit> callback) { + pigeon_instance.removeAllCookies(aBoolean -> ResultCompat.success(aBoolean, callback)); + } + + @Override + public void setAcceptThirdPartyCookies( + @NonNull CookieManager pigeon_instance, @NonNull WebView webView, boolean accept) { + pigeon_instance.setAcceptThirdPartyCookies(webView, accept); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/CustomViewCallbackProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/CustomViewCallbackProxyApi.java new file mode 100644 index 0000000..07581b4 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/CustomViewCallbackProxyApi.java @@ -0,0 +1,26 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.webkit.WebChromeClient.CustomViewCallback; +import androidx.annotation.NonNull; + +/** + * Host API implementation for `CustomViewCallback`. + * + *

This class may handle instantiating and adding native object instances that are attached to a + * Dart instance or handle method calls on the associated native class or an instance of the class. + */ +public class CustomViewCallbackProxyApi extends PigeonApiCustomViewCallback { + /** Constructs a {@link CustomViewCallbackProxyApi}. */ + public CustomViewCallbackProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @Override + public void onCustomViewHidden(@NonNull CustomViewCallback pigeon_instance) { + pigeon_instance.onCustomViewHidden(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/DisplayListenerProxy.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/DisplayListenerProxy.java new file mode 100644 index 0000000..41e3aad --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/DisplayListenerProxy.java @@ -0,0 +1,145 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static android.hardware.display.DisplayManager.DisplayListener; + +import android.hardware.display.DisplayManager; +import android.os.Build; +import android.util.Log; +import java.lang.reflect.Field; +import java.util.ArrayList; + +/** + * Works around an Android WebView bug by filtering some DisplayListener invocations. + * + *

Older Android WebView versions had assumed that when {@link DisplayListener#onDisplayChanged} + * is invoked, the display ID it is provided is of a valid display. However it turns out that when a + * display is removed Android may call onDisplayChanged with the ID of the removed display, in this + * case the Android WebView code tries to fetch and use the display with this ID and crashes with an + * NPE. + * + *

This issue was fixed in the Android WebView code in + * https://chromium-review.googlesource.com/517913 which is available starting WebView version + * 58.0.3029.125 however older webviews in the wild still have this issue. + * + *

Since Flutter removes virtual displays whenever a platform view is resized the webview crash + * is more likely to happen than other apps. And users were reporting this issue see: + * https://github.com/flutter/flutter/issues/30420 + * + *

This class works around the webview bug by unregistering the WebView's DisplayListener, and + * instead registering its own DisplayListener which delegates the callbacks to the WebView's + * listener unless it's a onDisplayChanged for an invalid display. + * + *

I did not find a clean way to get a handle of the WebView's DisplayListener so I'm using + * reflection to fetch all registered listeners before and after initializing a webview. In the + * first initialization of a webview within the process the difference between the lists is the + * webview's display listener. + */ +class DisplayListenerProxy { + private static final String TAG = "DisplayListenerProxy"; + + private ArrayList listenersBeforeWebView; + + /** Should be called prior to the webview's initialization. */ + void onPreWebViewInitialization(DisplayManager displayManager) { + listenersBeforeWebView = yoinkDisplayListeners(displayManager); + } + + /** Should be called after the webview's initialization. */ + void onPostWebViewInitialization(final DisplayManager displayManager) { + final ArrayList webViewListeners = yoinkDisplayListeners(displayManager); + // We recorded the list of listeners prior to initializing webview, any new listeners we see + // after initializing the webview are listeners added by the webview. + webViewListeners.removeAll(listenersBeforeWebView); + + if (webViewListeners.isEmpty()) { + // The Android WebView registers a single display listener per process (even if there + // are multiple WebView instances) so this list is expected to be non-empty only the + // first time a webview is initialized. + // Note that in an add2app scenario if the application had instantiated a non Flutter + // WebView prior to instantiating the Flutter WebView we are not able to get a reference + // to the WebView's display listener and can't work around the bug. + // + // This means that webview resizes in add2app Flutter apps with a non Flutter WebView + // running on a system with a webview prior to 58.0.3029.125 may crash (the Android's + // behavior seems to be racy so it doesn't always happen). + return; + } + + for (DisplayListener webViewListener : webViewListeners) { + // Note that while DisplayManager.unregisterDisplayListener throws when given an + // unregistered listener, this isn't an issue as the WebView code never calls + // unregisterDisplayListener. + displayManager.unregisterDisplayListener(webViewListener); + + // We never explicitly unregister this listener as the webview's listener is never + // unregistered (it's released when the process is terminated). + displayManager.registerDisplayListener( + new DisplayListener() { + @Override + public void onDisplayAdded(int displayId) { + for (DisplayListener webViewListener : webViewListeners) { + webViewListener.onDisplayAdded(displayId); + } + } + + @Override + public void onDisplayRemoved(int displayId) { + for (DisplayListener webViewListener : webViewListeners) { + webViewListener.onDisplayRemoved(displayId); + } + } + + @Override + public void onDisplayChanged(int displayId) { + if (displayManager.getDisplay(displayId) == null) { + return; + } + for (DisplayListener webViewListener : webViewListeners) { + webViewListener.onDisplayChanged(displayId); + } + } + }, + null); + } + } + + @SuppressWarnings({"unchecked", "PrivateApi"}) + private static ArrayList yoinkDisplayListeners(DisplayManager displayManager) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + // We cannot use reflection on Android P, but it shouldn't matter as it shipped + // with WebView 66.0.3359.158 and the WebView version the bug this code is working around was + // fixed in 61.0.3116.0. + return new ArrayList<>(); + } + try { + Field displayManagerGlobalField = DisplayManager.class.getDeclaredField("mGlobal"); + displayManagerGlobalField.setAccessible(true); + Object displayManagerGlobal = displayManagerGlobalField.get(displayManager); + Field displayListenersField = + displayManagerGlobal.getClass().getDeclaredField("mDisplayListeners"); + displayListenersField.setAccessible(true); + ArrayList delegates = + (ArrayList) displayListenersField.get(displayManagerGlobal); + + Field listenerField = null; + ArrayList listeners = new ArrayList<>(); + for (Object delegate : delegates) { + if (listenerField == null) { + listenerField = delegate.getClass().getField("mListener"); + listenerField.setAccessible(true); + } + DisplayManager.DisplayListener listener = + (DisplayManager.DisplayListener) listenerField.get(delegate); + listeners.add(listener); + } + return listeners; + } catch (NoSuchFieldException | IllegalAccessException e) { + Log.w(TAG, "Could not extract WebView's display listeners. " + e); + return new ArrayList<>(); + } + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerProxyApi.java new file mode 100644 index 0000000..a0ec039 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerProxyApi.java @@ -0,0 +1,66 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.webkit.DownloadListener; +import androidx.annotation.NonNull; + +/** + * Host api implementation for {@link DownloadListener}. + * + *

Handles creating {@link DownloadListener}s that intercommunicate with a paired Dart object. + */ +public class DownloadListenerProxyApi extends PigeonApiDownloadListener { + /** + * Implementation of {@link DownloadListener} that passes arguments of callback methods to Dart. + */ + public static class DownloadListenerImpl implements DownloadListener { + private final DownloadListenerProxyApi api; + + /** + * Creates a {@link DownloadListenerImpl} that passes arguments of callbacks methods to Dart. + */ + public DownloadListenerImpl(@NonNull DownloadListenerProxyApi api) { + this.api = api; + } + + @Override + public void onDownloadStart( + @NonNull String url, + @NonNull String userAgent, + @NonNull String contentDisposition, + @NonNull String mimetype, + long contentLength) { + api.getPigeonRegistrar() + .runOnMainThread( + () -> + api.onDownloadStart( + this, + url, + userAgent, + contentDisposition, + mimetype, + contentLength, + reply -> null)); + } + } + + /** Creates a host API that handles creating {@link DownloadListener}s. */ + public DownloadListenerProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @NonNull + @Override + public DownloadListener pigeon_defaultConstructor() { + return new DownloadListenerImpl(this); + } + + @NonNull + @Override + public ProxyApiRegistrar getPigeonRegistrar() { + return (ProxyApiRegistrar) super.getPigeonRegistrar(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/FileChooserParamsProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/FileChooserParamsProxyApi.java new file mode 100644 index 0000000..4308c8d --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/FileChooserParamsProxyApi.java @@ -0,0 +1,56 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.webkit.WebChromeClient; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import java.util.Arrays; +import java.util.List; + +/** + * Flutter Api implementation for {@link android.webkit.WebChromeClient.FileChooserParams}. + * + *

Passes arguments of callbacks methods from a {@link + * android.webkit.WebChromeClient.FileChooserParams} to Dart. + */ +public class FileChooserParamsProxyApi extends PigeonApiFileChooserParams { + /** Creates a Flutter api that sends messages to Dart. */ + public FileChooserParamsProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @Override + public boolean isCaptureEnabled(@NonNull WebChromeClient.FileChooserParams pigeon_instance) { + return pigeon_instance.isCaptureEnabled(); + } + + @NonNull + @Override + public List acceptTypes(@NonNull WebChromeClient.FileChooserParams pigeon_instance) { + return Arrays.asList(pigeon_instance.getAcceptTypes()); + } + + @NonNull + @Override + public FileChooserMode mode(@NonNull WebChromeClient.FileChooserParams pigeon_instance) { + switch (pigeon_instance.getMode()) { + case WebChromeClient.FileChooserParams.MODE_OPEN: + return FileChooserMode.OPEN; + case WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE: + return FileChooserMode.OPEN_MULTIPLE; + case WebChromeClient.FileChooserParams.MODE_SAVE: + return FileChooserMode.SAVE; + default: + return FileChooserMode.UNKNOWN; + } + } + + @Nullable + @Override + public String filenameHint(@NonNull WebChromeClient.FileChooserParams pigeon_instance) { + return pigeon_instance.getFilenameHint(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManager.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManager.java new file mode 100644 index 0000000..6a04fe5 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManager.java @@ -0,0 +1,79 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.content.res.AssetManager; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import java.io.IOException; + +/** Provides access to the assets registered as part of the App bundle. */ +public abstract class FlutterAssetManager { + @NonNull final AssetManager assetManager; + + /** + * Constructs a new instance of the {@link FlutterAssetManager}. + * + * @param assetManager Instance of Android's {@link AssetManager} used to access assets within the + * App bundle. + */ + public FlutterAssetManager(@NonNull AssetManager assetManager) { + this.assetManager = assetManager; + } + + /** + * Gets the relative file path to the Flutter asset with the given name, including the file's + * extension, e.g., "myImage.jpg". + * + *

The returned file path is relative to the Android app's standard asset's directory. + * Therefore, the returned path is appropriate to pass to Android's AssetManager, but the path is + * not appropriate to load as an absolute path. + */ + @Nullable + abstract String getAssetFilePathByName(@NonNull String name); + + /** + * Returns a String array of all the assets at the given path. + * + * @param path A relative path within the assets, i.e., "docs/home.html". This value cannot be + * null. + * @return String[] Array of strings, one for each asset. These file names are relative to 'path'. + * This value may be null. + * @throws IOException Throws an IOException in case I/O operations were interrupted. + */ + @NonNull + public String[] list(@NonNull String path) throws IOException { + return assetManager.list(path); + } + + /** + * Provides access to assets using the {@link FlutterPlugin.FlutterAssets} for looking up file + * paths to Flutter assets. + */ + static class PluginBindingFlutterAssetManager extends FlutterAssetManager { + final FlutterPlugin.FlutterAssets flutterAssets; + + /** + * Constructs a new instance of the {@link PluginBindingFlutterAssetManager}. + * + * @param assetManager Instance of Android's {@link AssetManager} used to access assets within + * the App bundle. + * @param flutterAssets Instance of {@link + * io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterAssets} used to look up file + * paths to assets registered by Flutter. + */ + PluginBindingFlutterAssetManager( + @NonNull AssetManager assetManager, @NonNull FlutterPlugin.FlutterAssets flutterAssets) { + super(assetManager); + this.flutterAssets = flutterAssets; + } + + @Override + public String getAssetFilePathByName(@NonNull String name) { + return flutterAssets.getAssetFilePathByName(name); + } + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerProxyApi.java new file mode 100644 index 0000000..3da7f7d --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerProxyApi.java @@ -0,0 +1,59 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.webkit.WebView; +import androidx.annotation.NonNull; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Host api implementation for {@link WebView}. + * + *

Handles creating {@link WebView}s that intercommunicate with a paired Dart object. + */ +public class FlutterAssetManagerProxyApi extends PigeonApiFlutterAssetManager { + /** Constructs a new instance of {@link FlutterAssetManagerProxyApi}. */ + public FlutterAssetManagerProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @NonNull + @Override + public FlutterAssetManager instance() { + return getPigeonRegistrar().getFlutterAssetManager(); + } + + @NonNull + @Override + public List list(@NonNull FlutterAssetManager pigeon_instance, @NonNull String path) { + try { + String[] paths = pigeon_instance.list(path); + + if (paths == null) { + return new ArrayList<>(); + } + + return Arrays.asList(paths); + } catch (IOException ex) { + throw new RuntimeException(ex.getMessage()); + } + } + + @NonNull + @Override + public String getAssetFilePathByName( + @NonNull FlutterAssetManager pigeon_instance, @NonNull String name) { + return pigeon_instance.getAssetFilePathByName(name); + } + + @NonNull + @Override + public ProxyApiRegistrar getPigeonRegistrar() { + return (ProxyApiRegistrar) super.getPigeonRegistrar(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterViewFactory.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterViewFactory.java new file mode 100644 index 0000000..f6d5979 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterViewFactory.java @@ -0,0 +1,50 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.content.Context; +import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.plugin.common.StandardMessageCodec; +import io.flutter.plugin.platform.PlatformView; +import io.flutter.plugin.platform.PlatformViewFactory; + +class FlutterViewFactory extends PlatformViewFactory { + private final AndroidWebkitLibraryPigeonInstanceManager instanceManager; + + FlutterViewFactory(AndroidWebkitLibraryPigeonInstanceManager instanceManager) { + super(StandardMessageCodec.INSTANCE); + this.instanceManager = instanceManager; + } + + @NonNull + @Override + public PlatformView create(Context context, int viewId, @Nullable Object args) { + final Integer identifier = (Integer) args; + if (identifier == null) { + throw new IllegalStateException("An identifier is required to retrieve a View instance."); + } + + final Object instance = instanceManager.getInstance(identifier); + + if (instance instanceof PlatformView) { + return (PlatformView) instance; + } else if (instance instanceof View) { + return new PlatformView() { + @Override + public View getView() { + return (View) instance; + } + + @Override + public void dispose() {} + }; + } + + throw new IllegalStateException( + "Unable to find a PlatformView or View instance: " + args + ", " + instance); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/GeolocationPermissionsCallbackProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/GeolocationPermissionsCallbackProxyApi.java new file mode 100644 index 0000000..d538fb0 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/GeolocationPermissionsCallbackProxyApi.java @@ -0,0 +1,31 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.webkit.GeolocationPermissions; +import androidx.annotation.NonNull; + +/** + * Host API implementation for `GeolocationPermissionsCallback`. + * + *

This class may handle instantiating and adding native object instances that are attached to a + * Dart instance or handle method calls on the associated native class or an instance of the class. + */ +public class GeolocationPermissionsCallbackProxyApi + extends PigeonApiGeolocationPermissionsCallback { + /** Constructs a {@link GeolocationPermissionsCallbackProxyApi}. */ + public GeolocationPermissionsCallbackProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @Override + public void invoke( + @NonNull GeolocationPermissions.Callback pigeon_instance, + @NonNull String origin, + boolean allow, + boolean retain) { + pigeon_instance.invoke(origin, allow, retain); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/HttpAuthHandlerProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/HttpAuthHandlerProxyApi.java new file mode 100644 index 0000000..fcde7e3 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/HttpAuthHandlerProxyApi.java @@ -0,0 +1,38 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.webkit.HttpAuthHandler; +import androidx.annotation.NonNull; + +/** + * Host api implementation for {@link HttpAuthHandler}. + * + *

Handles creating {@link HttpAuthHandler}s that intercommunicate with a paired Dart object. + */ +public class HttpAuthHandlerProxyApi extends PigeonApiHttpAuthHandler { + /** Constructs a {@link HttpAuthHandlerProxyApi}. */ + public HttpAuthHandlerProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @Override + public boolean useHttpAuthUsernamePassword(@NonNull HttpAuthHandler pigeon_instance) { + return pigeon_instance.useHttpAuthUsernamePassword(); + } + + @Override + public void cancel(@NonNull HttpAuthHandler pigeon_instance) { + pigeon_instance.cancel(); + } + + @Override + public void proceed( + @NonNull HttpAuthHandler pigeon_instance, + @NonNull String username, + @NonNull String password) { + pigeon_instance.proceed(username, password); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java new file mode 100644 index 0000000..51fb3dc --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java @@ -0,0 +1,37 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.webkit.JavascriptInterface; +import androidx.annotation.NonNull; + +/** + * Added as a JavaScript interface to the WebView for any JavaScript channel that the Dart code sets + * up. + * + *

Exposes a single method named `postMessage` to JavaScript, which sends a message to the Dart + * code. + */ +public class JavaScriptChannel { + final String javaScriptChannelName; + private final JavaScriptChannelProxyApi api; + + /** Creates a {@link JavaScriptChannel} that passes arguments of callback methods to Dart. */ + public JavaScriptChannel(@NonNull String channelName, @NonNull JavaScriptChannelProxyApi api) { + this.javaScriptChannelName = channelName; + this.api = api; + } + + // Suppressing unused warning as this is invoked from JavaScript. + @SuppressWarnings("unused") + @JavascriptInterface + public void postMessage(@NonNull final String message) { + api.getPigeonRegistrar() + .runOnMainThread( + () -> { + api.postMessage(JavaScriptChannel.this, message, reply -> null); + }); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelProxyApi.java new file mode 100644 index 0000000..10f9382 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelProxyApi.java @@ -0,0 +1,32 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import androidx.annotation.NonNull; + +/** + * Host api implementation for {@link JavaScriptChannel}. + * + *

Handles creating {@link JavaScriptChannel}s that intercommunicate with a paired Dart object. + */ +public class JavaScriptChannelProxyApi extends PigeonApiJavaScriptChannel { + + /** Creates a host API that handles creating {@link JavaScriptChannel}s. */ + public JavaScriptChannelProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @NonNull + @Override + public JavaScriptChannel pigeon_defaultConstructor(@NonNull String channelName) { + return new JavaScriptChannel(channelName, this); + } + + @NonNull + @Override + public ProxyApiRegistrar getPigeonRegistrar() { + return (ProxyApiRegistrar) super.getPigeonRegistrar(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/MessageProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/MessageProxyApi.java new file mode 100644 index 0000000..8c42267 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/MessageProxyApi.java @@ -0,0 +1,25 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.os.Message; +import androidx.annotation.NonNull; + +/** + * Proxy API implementation for `Message`. + * + *

This class may handle instantiating and adding native object instances that are attached to a + * Dart instance or handle method calls on the associated native class or an instance of the class. + */ +public class MessageProxyApi extends PigeonApiAndroidMessage { + public MessageProxyApi(@NonNull AndroidWebkitLibraryPigeonProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @Override + public void sendToTarget(@NonNull Message pigeon_instance) { + pigeon_instance.sendToTarget(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/PermissionRequestProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/PermissionRequestProxyApi.java new file mode 100644 index 0000000..ab2cab6 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/PermissionRequestProxyApi.java @@ -0,0 +1,39 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.webkit.PermissionRequest; +import androidx.annotation.NonNull; +import java.util.Arrays; +import java.util.List; + +/** + * Host API implementation for `PermissionRequest`. + * + *

This class may handle instantiating and adding native object instances that are attached to a + * Dart instance or handle method calls on the associated native class or an instance of the class. + */ +public class PermissionRequestProxyApi extends PigeonApiPermissionRequest { + /** Constructs a {@link PermissionRequestProxyApi}. */ + public PermissionRequestProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @NonNull + @Override + public List resources(@NonNull PermissionRequest pigeon_instance) { + return Arrays.asList(pigeon_instance.getResources()); + } + + @Override + public void grant(@NonNull PermissionRequest pigeon_instance, @NonNull List resources) { + pigeon_instance.grant(resources.toArray(new String[0])); + } + + @Override + public void deny(@NonNull PermissionRequest pigeon_instance) { + pigeon_instance.deny(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/ProxyApiRegistrar.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/ProxyApiRegistrar.java new file mode 100644 index 0000000..73848e0 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/ProxyApiRegistrar.java @@ -0,0 +1,264 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.app.Activity; +import android.content.Context; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import androidx.annotation.ChecksSdkIntAtLeast; +import androidx.annotation.NonNull; +import io.flutter.plugin.common.BinaryMessenger; + +public class ProxyApiRegistrar extends AndroidWebkitLibraryPigeonProxyApiRegistrar { + @NonNull private Context context; + + @NonNull private final FlutterAssetManager flutterAssetManager; + + public ProxyApiRegistrar( + @NonNull BinaryMessenger binaryMessenger, + @NonNull Context context, + @NonNull FlutterAssetManager flutterAssetManager) { + super(binaryMessenger); + this.context = context; + this.flutterAssetManager = flutterAssetManager; + } + + // Interface for an injectable SDK version checker. + @ChecksSdkIntAtLeast(parameter = 0) + boolean sdkIsAtLeast(int version) { + return Build.VERSION.SDK_INT >= version; + } + + // Added to be overridden for tests. The test implementation calls `callback` immediately, instead + // of waiting for the main thread to run it. + void runOnMainThread(Runnable runnable) { + if (context instanceof Activity) { + ((Activity) context).runOnUiThread(runnable); + } else { + new Handler(Looper.getMainLooper()).post(runnable); + } + } + + // For logging exception received from Host -> Dart message calls. + void logError(String tag, Throwable exception) { + Log.e( + tag, + exception.getClass().getSimpleName() + + ", Message: " + + exception.getMessage() + + ", Stacktrace: " + + Log.getStackTraceString(exception)); + } + + /** Creates an exception when the `unknown` enum value is passed to a host method. */ + @NonNull + IllegalArgumentException createUnknownEnumException(@NonNull Object enumValue) { + return new IllegalArgumentException(enumValue + " doesn't represent a native value."); + } + + /** Creates the error message when a method is called on an unsupported version. */ + @NonNull + String createUnsupportedVersionMessage( + @NonNull String method, @NonNull String versionRequirements) { + return method + " requires " + versionRequirements + "."; + } + + @NonNull + @Override + public PigeonApiWebResourceRequest getPigeonApiWebResourceRequest() { + return new WebResourceRequestProxyApi(this); + } + + @NonNull + @Override + public PigeonApiWebResourceError getPigeonApiWebResourceError() { + return new WebResourceErrorProxyApi(this); + } + + @NonNull + @Override + public PigeonApiWebResourceErrorCompat getPigeonApiWebResourceErrorCompat() { + return new WebResourceErrorCompatProxyApi(this); + } + + @NonNull + @Override + public PigeonApiWebViewPoint getPigeonApiWebViewPoint() { + return new WebViewPointProxyApi(this); + } + + @NonNull + @Override + public PigeonApiConsoleMessage getPigeonApiConsoleMessage() { + return new ConsoleMessageProxyApi(this); + } + + @NonNull + @Override + public PigeonApiCookieManager getPigeonApiCookieManager() { + return new CookieManagerProxyApi(this); + } + + @NonNull + @Override + public PigeonApiWebResourceResponse getPigeonApiWebResourceResponse() { + return new WebResourceResponseProxyApi(this); + } + + @NonNull + @Override + public PigeonApiWebView getPigeonApiWebView() { + return new WebViewProxyApi(this); + } + + @NonNull + @Override + public PigeonApiWebSettings getPigeonApiWebSettings() { + return new WebSettingsProxyApi(this); + } + + @NonNull + @Override + public PigeonApiJavaScriptChannel getPigeonApiJavaScriptChannel() { + return new JavaScriptChannelProxyApi(this); + } + + @NonNull + @Override + public PigeonApiWebViewClient getPigeonApiWebViewClient() { + return new WebViewClientProxyApi(this); + } + + @NonNull + @Override + public PigeonApiDownloadListener getPigeonApiDownloadListener() { + return new DownloadListenerProxyApi(this); + } + + @NonNull + @Override + public PigeonApiWebChromeClient getPigeonApiWebChromeClient() { + return new WebChromeClientProxyApi(this); + } + + @NonNull + @Override + public PigeonApiFlutterAssetManager getPigeonApiFlutterAssetManager() { + return new FlutterAssetManagerProxyApi(this); + } + + @NonNull + @Override + public PigeonApiWebStorage getPigeonApiWebStorage() { + return new WebStorageProxyApi(this); + } + + @NonNull + @Override + public PigeonApiFileChooserParams getPigeonApiFileChooserParams() { + return new FileChooserParamsProxyApi(this); + } + + @NonNull + @Override + public PigeonApiPermissionRequest getPigeonApiPermissionRequest() { + return new PermissionRequestProxyApi(this); + } + + @NonNull + @Override + public PigeonApiCustomViewCallback getPigeonApiCustomViewCallback() { + return new CustomViewCallbackProxyApi(this); + } + + @NonNull + @Override + public PigeonApiView getPigeonApiView() { + return new ViewProxyApi(this); + } + + @NonNull + @Override + public PigeonApiGeolocationPermissionsCallback getPigeonApiGeolocationPermissionsCallback() { + return new GeolocationPermissionsCallbackProxyApi(this); + } + + @NonNull + @Override + public PigeonApiHttpAuthHandler getPigeonApiHttpAuthHandler() { + return new HttpAuthHandlerProxyApi(this); + } + + @NonNull + @Override + public PigeonApiClientCertRequest getPigeonApiClientCertRequest() { + return new ClientCertRequestProxyApi(this); + } + + @NonNull + @Override + public PigeonApiSslErrorHandler getPigeonApiSslErrorHandler() { + return new SslErrorHandlerProxyApi(this); + } + + @NonNull + @Override + public PigeonApiSslError getPigeonApiSslError() { + return new SslErrorProxyApi(this); + } + + @NonNull + @Override + public PigeonApiSslCertificateDName getPigeonApiSslCertificateDName() { + return new SslCertificateDNameProxyApi(this); + } + + @NonNull + @Override + public PigeonApiSslCertificate getPigeonApiSslCertificate() { + return new SslCertificateProxyApi(this); + } + + @NonNull + @Override + public PigeonApiAndroidMessage getPigeonApiAndroidMessage() { + return new MessageProxyApi(this); + } + + @NonNull + @Override + public PigeonApiCertificate getPigeonApiCertificate() { + return new CertificateProxyApi(this); + } + + @NonNull + public Context getContext() { + return context; + } + + public void setContext(@NonNull Context context) { + this.context = context; + } + + @NonNull + public FlutterAssetManager getFlutterAssetManager() { + return flutterAssetManager; + } + + @NonNull + @Override + public PigeonApiWebViewFeature getPigeonApiWebViewFeature() { + return new WebViewFeatureProxyApi(this); + } + + @NonNull + @Override + public PigeonApiWebSettingsCompat getPigeonApiWebSettingsCompat() { + return new WebSettingsCompatProxyApi(this); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/ResultCompat.kt b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/ResultCompat.kt new file mode 100644 index 0000000..4b8e613 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/ResultCompat.kt @@ -0,0 +1,41 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter + +/** + * ResultCompat. + * + * It is intended to solve the problem of being unable to obtain [kotlin.Result] in Java. + * + * [kotlin.Result] has a weird quirk when it is passed to Java where it seems to wrap itself. + */ +@Suppress("UNCHECKED_CAST") +class ResultCompat(val result: Result) { + private val value: T? = result.getOrNull() + private val exception = result.exceptionOrNull() + val isSuccess = result.isSuccess + val isFailure = result.isFailure + + companion object { + @JvmStatic + fun success(value: T, callback: Any) { + val castedCallback: (Result) -> Unit = callback as (Result) -> Unit + castedCallback(Result.success(value)) + } + + @JvmStatic + fun asCompatCallback(result: (ResultCompat) -> Unit): (Result) -> Unit { + return { result(ResultCompat(it)) } + } + } + + fun getOrNull(): T? { + return value + } + + fun exceptionOrNull(): Throwable? { + return exception + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/SslCertificateDNameProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/SslCertificateDNameProxyApi.java new file mode 100644 index 0000000..a3a3e15 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/SslCertificateDNameProxyApi.java @@ -0,0 +1,43 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.net.http.SslCertificate; +import androidx.annotation.NonNull; + +/** + * ProxyApi implementation for {@link SslCertificate.DName}. This class may handle instantiating + * native object instances that are attached to a Dart instance or handle method calls on the + * associated native class or an instance of that class. + */ +class SslCertificateDNameProxyApi extends PigeonApiSslCertificateDName { + SslCertificateDNameProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @NonNull + @Override + public String getCName(@NonNull SslCertificate.DName pigeon_instance) { + return pigeon_instance.getCName(); + } + + @NonNull + @Override + public String getDName(@NonNull SslCertificate.DName pigeon_instance) { + return pigeon_instance.getDName(); + } + + @NonNull + @Override + public String getOName(@NonNull SslCertificate.DName pigeon_instance) { + return pigeon_instance.getOName(); + } + + @NonNull + @Override + public String getUName(@NonNull SslCertificate.DName pigeon_instance) { + return pigeon_instance.getUName(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/SslCertificateProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/SslCertificateProxyApi.java new file mode 100644 index 0000000..2cbd9ad --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/SslCertificateProxyApi.java @@ -0,0 +1,79 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.net.http.SslCertificate; +import android.os.Build; +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import java.util.Date; + +/** + * ProxyApi implementation for {@link SslCertificate}. This class may handle instantiating native + * object instances that are attached to a Dart instance or handle method calls on the associated + * native class or an instance of that class. + */ +class SslCertificateProxyApi extends PigeonApiSslCertificate { + SslCertificateProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @NonNull + @Override + public ProxyApiRegistrar getPigeonRegistrar() { + return (ProxyApiRegistrar) super.getPigeonRegistrar(); + } + + @Nullable + @Override + public android.net.http.SslCertificate.DName getIssuedBy( + @NonNull SslCertificate pigeon_instance) { + return pigeon_instance.getIssuedBy(); + } + + @Nullable + @Override + public android.net.http.SslCertificate.DName getIssuedTo( + @NonNull SslCertificate pigeon_instance) { + return pigeon_instance.getIssuedTo(); + } + + @Nullable + @Override + public Long getValidNotAfterMsSinceEpoch(@NonNull SslCertificate pigeon_instance) { + final Date date = pigeon_instance.getValidNotAfterDate(); + if (date != null) { + return date.getTime(); + } + return null; + } + + @Nullable + @Override + public Long getValidNotBeforeMsSinceEpoch(@NonNull SslCertificate pigeon_instance) { + final Date date = pigeon_instance.getValidNotBeforeDate(); + if (date != null) { + return date.getTime(); + } + return null; + } + + @Nullable + @Override + public java.security.cert.X509Certificate getX509Certificate( + @NonNull SslCertificate pigeon_instance) { + if (getPigeonRegistrar().sdkIsAtLeast(Build.VERSION_CODES.Q)) { + return pigeon_instance.getX509Certificate(); + } else { + Log.d( + "SslCertificateProxyApi", + getPigeonRegistrar() + .createUnsupportedVersionMessage( + "SslCertificate.getX509Certificate", "Build.VERSION_CODES.Q")); + return null; + } + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/SslErrorHandlerProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/SslErrorHandlerProxyApi.java new file mode 100644 index 0000000..558529a --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/SslErrorHandlerProxyApi.java @@ -0,0 +1,29 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.webkit.SslErrorHandler; +import androidx.annotation.NonNull; + +/** + * ProxyApi implementation for {@link SslErrorHandler}. This class may handle instantiating native + * object instances that are attached to a Dart instance or handle method calls on the associated + * native class or an instance of that class. + */ +class SslErrorHandlerProxyApi extends PigeonApiSslErrorHandler { + SslErrorHandlerProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @Override + public void cancel(@NonNull SslErrorHandler pigeon_instance) { + pigeon_instance.cancel(); + } + + @Override + public void proceed(@NonNull SslErrorHandler pigeon_instance) { + pigeon_instance.proceed(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/SslErrorProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/SslErrorProxyApi.java new file mode 100644 index 0000000..591033a --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/SslErrorProxyApi.java @@ -0,0 +1,86 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.net.http.SslError; +import androidx.annotation.NonNull; + +/** + * ProxyApi implementation for {@link SslError}. This class may handle instantiating native object + * instances that are attached to a Dart instance or handle method calls on the associated native + * class or an instance of that class. + */ +class SslErrorProxyApi extends PigeonApiSslError { + SslErrorProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @NonNull + @Override + public ProxyApiRegistrar getPigeonRegistrar() { + return (ProxyApiRegistrar) super.getPigeonRegistrar(); + } + + @NonNull + @Override + public android.net.http.SslCertificate certificate(@NonNull SslError pigeon_instance) { + return pigeon_instance.getCertificate(); + } + + @NonNull + @Override + public String url(@NonNull SslError pigeon_instance) { + return pigeon_instance.getUrl(); + } + + @NonNull + @Override + public SslErrorType getPrimaryError(@NonNull SslError pigeon_instance) { + switch (pigeon_instance.getPrimaryError()) { + case SslError.SSL_DATE_INVALID: + return SslErrorType.DATE_INVALID; + case SslError.SSL_EXPIRED: + return SslErrorType.EXPIRED; + case SslError.SSL_IDMISMATCH: + return SslErrorType.ID_MISMATCH; + case SslError.SSL_INVALID: + return SslErrorType.INVALID; + case SslError.SSL_NOTYETVALID: + return SslErrorType.NOT_YET_VALID; + case SslError.SSL_UNTRUSTED: + return SslErrorType.UNTRUSTED; + default: + return SslErrorType.UNKNOWN; + } + } + + @Override + public boolean hasError(@NonNull SslError pigeon_instance, @NonNull SslErrorType error) { + int nativeError = -1; + switch (error) { + case DATE_INVALID: + nativeError = SslError.SSL_DATE_INVALID; + break; + case EXPIRED: + nativeError = SslError.SSL_EXPIRED; + break; + case ID_MISMATCH: + nativeError = SslError.SSL_IDMISMATCH; + break; + case INVALID: + nativeError = SslError.SSL_INVALID; + break; + case NOT_YET_VALID: + nativeError = SslError.SSL_NOTYETVALID; + break; + case UNTRUSTED: + nativeError = SslError.SSL_UNTRUSTED; + break; + case UNKNOWN: + throw getPigeonRegistrar().createUnknownEnumException(error); + } + return pigeon_instance.hasError(nativeError); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/ViewProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/ViewProxyApi.java new file mode 100644 index 0000000..291767e --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/ViewProxyApi.java @@ -0,0 +1,70 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.view.View; +import androidx.annotation.NonNull; + +/** + * Flutter API implementation for `View`. + * + *

This class may handle adding native instances that are attached to a Dart instance or passing + * arguments of callbacks methods to a Dart instance. + */ +public class ViewProxyApi extends PigeonApiView { + /** Constructs a {@link ViewProxyApi}. */ + public ViewProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @NonNull + @Override + public ProxyApiRegistrar getPigeonRegistrar() { + return (ProxyApiRegistrar) super.getPigeonRegistrar(); + } + + @Override + public void scrollTo(@NonNull View pigeon_instance, long x, long y) { + pigeon_instance.scrollTo((int) x, (int) y); + } + + @Override + public void scrollBy(@NonNull View pigeon_instance, long x, long y) { + pigeon_instance.scrollBy((int) x, (int) y); + } + + @NonNull + @Override + public WebViewPoint getScrollPosition(@NonNull View pigeon_instance) { + return new WebViewPoint(pigeon_instance.getScrollX(), pigeon_instance.getScrollY()); + } + + @Override + public void setVerticalScrollBarEnabled(@NonNull View pigeon_instance, boolean enabled) { + pigeon_instance.setVerticalScrollBarEnabled(enabled); + } + + @Override + public void setHorizontalScrollBarEnabled(@NonNull View pigeon_instance, boolean enabled) { + pigeon_instance.setHorizontalScrollBarEnabled(enabled); + } + + @Override + public void setOverScrollMode(@NonNull View pigeon_instance, @NonNull OverScrollMode mode) { + switch (mode) { + case ALWAYS: + pigeon_instance.setOverScrollMode(View.OVER_SCROLL_ALWAYS); + break; + case IF_CONTENT_SCROLLS: + pigeon_instance.setOverScrollMode(View.OVER_SCROLL_IF_CONTENT_SCROLLS); + break; + case NEVER: + pigeon_instance.setOverScrollMode(View.OVER_SCROLL_NEVER); + break; + case UNKNOWN: + throw getPigeonRegistrar().createUnknownEnumException(OverScrollMode.UNKNOWN); + } + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientProxyApi.java new file mode 100644 index 0000000..184661e --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientProxyApi.java @@ -0,0 +1,358 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.net.Uri; +import android.os.Message; +import android.view.View; +import android.webkit.ConsoleMessage; +import android.webkit.GeolocationPermissions; +import android.webkit.JsPromptResult; +import android.webkit.JsResult; +import android.webkit.PermissionRequest; +import android.webkit.ValueCallback; +import android.webkit.WebChromeClient; +import android.webkit.WebResourceRequest; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import java.util.List; +import java.util.Objects; + +/** + * Host api implementation for {@link WebChromeClient}. + * + *

Handles creating {@link WebChromeClient}s that intercommunicate with a paired Dart object. + */ +public class WebChromeClientProxyApi extends PigeonApiWebChromeClient { + /** + * Implementation of {@link WebChromeClient} that passes arguments of callback methods to Dart. + */ + public static class WebChromeClientImpl extends SecureWebChromeClient { + private static final String TAG = "WebChromeClientImpl"; + + private final WebChromeClientProxyApi api; + private boolean returnValueForOnShowFileChooser = false; + private boolean returnValueForOnConsoleMessage = false; + + private boolean returnValueForOnJsAlert = false; + private boolean returnValueForOnJsConfirm = false; + private boolean returnValueForOnJsPrompt = false; + + /** Creates a {@link WebChromeClient} that passes arguments of callbacks methods to Dart. */ + public WebChromeClientImpl(@NonNull WebChromeClientProxyApi api) { + this.api = api; + } + + @Override + public void onProgressChanged(@NonNull WebView view, int progress) { + api.onProgressChanged(this, view, (long) progress, reply -> null); + } + + @Override + public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) { + api.onShowCustomView(this, view, callback, reply -> null); + } + + @Override + public void onHideCustomView() { + api.onHideCustomView(this, reply -> null); + } + + public void onGeolocationPermissionsShowPrompt( + @NonNull String origin, @NonNull GeolocationPermissions.Callback callback) { + api.onGeolocationPermissionsShowPrompt(this, origin, callback, reply -> null); + } + + @Override + public void onGeolocationPermissionsHidePrompt() { + api.onGeolocationPermissionsHidePrompt(this, reply -> null); + } + + @SuppressWarnings("LambdaLast") + @Override + public boolean onShowFileChooser( + @NonNull WebView webView, + @NonNull ValueCallback filePathCallback, + @NonNull FileChooserParams fileChooserParams) { + final boolean currentReturnValueForOnShowFileChooser = returnValueForOnShowFileChooser; + api.onShowFileChooser( + this, + webView, + fileChooserParams, + ResultCompat.asCompatCallback( + reply -> { + if (reply.isFailure()) { + api.getPigeonRegistrar() + .logError(TAG, Objects.requireNonNull(reply.exceptionOrNull())); + return null; + } + + final List value = Objects.requireNonNull(reply.getOrNull()); + + // The returned list of file paths can only be passed to `filePathCallback` if the + // `onShowFileChooser` method returned true. + if (currentReturnValueForOnShowFileChooser) { + final Uri[] filePaths = new Uri[value.size()]; + for (int i = 0; i < value.size(); i++) { + filePaths[i] = Uri.parse(value.get(i)); + } + filePathCallback.onReceiveValue(filePaths); + } + + return null; + })); + return currentReturnValueForOnShowFileChooser; + } + + @Override + public void onPermissionRequest(@NonNull PermissionRequest request) { + api.onPermissionRequest(this, request, reply -> null); + } + + @Override + public boolean onConsoleMessage(ConsoleMessage consoleMessage) { + api.onConsoleMessage(this, consoleMessage, reply -> null); + return returnValueForOnConsoleMessage; + } + + /** Sets return value for {@link #onShowFileChooser}. */ + public void setReturnValueForOnShowFileChooser(boolean value) { + returnValueForOnShowFileChooser = value; + } + + /** Sets return value for {@link #onConsoleMessage}. */ + public void setReturnValueForOnConsoleMessage(boolean value) { + returnValueForOnConsoleMessage = value; + } + + public void setReturnValueForOnJsAlert(boolean value) { + returnValueForOnJsAlert = value; + } + + public void setReturnValueForOnJsConfirm(boolean value) { + returnValueForOnJsConfirm = value; + } + + public void setReturnValueForOnJsPrompt(boolean value) { + returnValueForOnJsPrompt = value; + } + + @Override + public boolean onJsAlert(WebView view, String url, String message, JsResult result) { + if (returnValueForOnJsAlert) { + api.onJsAlert( + this, + view, + url, + message, + ResultCompat.asCompatCallback( + reply -> { + if (reply.isFailure()) { + api.getPigeonRegistrar() + .logError(TAG, Objects.requireNonNull(reply.exceptionOrNull())); + return null; + } + + result.confirm(); + return null; + })); + return true; + } else { + return false; + } + } + + @Override + public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { + if (returnValueForOnJsConfirm) { + api.onJsConfirm( + this, + view, + url, + message, + ResultCompat.asCompatCallback( + reply -> { + if (reply.isFailure()) { + api.getPigeonRegistrar() + .logError(TAG, Objects.requireNonNull(reply.exceptionOrNull())); + return null; + } + + if (Boolean.TRUE.equals(reply.getOrNull())) { + result.confirm(); + } else { + result.cancel(); + } + + return null; + })); + return true; + } else { + return false; + } + } + + @Override + public boolean onJsPrompt( + WebView view, String url, String message, String defaultValue, JsPromptResult result) { + if (returnValueForOnJsPrompt) { + api.onJsPrompt( + this, + view, + url, + message, + defaultValue, + ResultCompat.asCompatCallback( + reply -> { + if (reply.isFailure()) { + api.getPigeonRegistrar() + .logError(TAG, Objects.requireNonNull(reply.exceptionOrNull())); + return null; + } + + @Nullable final String inputMessage = reply.getOrNull(); + + if (inputMessage != null) { + result.confirm(inputMessage); + } else { + result.cancel(); + } + + return null; + })); + return true; + } else { + return false; + } + } + } + + /** + * Implementation of {@link WebChromeClient} that only allows secure urls when opening a new + * window. + */ + public static class SecureWebChromeClient extends WebChromeClient { + @Nullable WebViewClient webViewClient; + + @Override + public boolean onCreateWindow( + @NonNull final WebView view, + boolean isDialog, + boolean isUserGesture, + @NonNull Message resultMsg) { + return onCreateWindow(view, resultMsg, new WebView(view.getContext())); + } + + /** + * Verifies that a url opened by `Window.open` has a secure url. + * + * @param view the WebView from which the request for a new window originated. + * @param resultMsg the message to send when once a new WebView has been created. resultMsg.obj + * is a {@link WebView.WebViewTransport} object. This should be used to transport the new + * WebView, by calling WebView.WebViewTransport.setWebView(WebView) + * @param onCreateWindowWebView the temporary WebView used to verify the url is secure + * @return this method should return true if the host application will create a new window, in + * which case resultMsg should be sent to its target. Otherwise, this method should return + * false. Returning false from this method but also sending resultMsg will result in + * undefined behavior + */ + @VisibleForTesting + boolean onCreateWindow( + @NonNull final WebView view, + @NonNull Message resultMsg, + @Nullable WebView onCreateWindowWebView) { + // WebChromeClient requires a WebViewClient because of a bug fix that makes + // calls to WebViewClient.requestLoading/WebViewClient.urlLoading when a new + // window is opened. This is to make sure a url opened by `Window.open` has + // a secure url. + if (webViewClient == null) { + return false; + } + + final WebViewClient windowWebViewClient = + new WebViewClient() { + @Override + public boolean shouldOverrideUrlLoading( + @NonNull WebView windowWebView, @NonNull WebResourceRequest request) { + if (!webViewClient.shouldOverrideUrlLoading(view, request)) { + view.loadUrl(request.getUrl().toString()); + } + return true; + } + }; + + if (onCreateWindowWebView == null) { + onCreateWindowWebView = new WebView(view.getContext()); + } + onCreateWindowWebView.setWebViewClient(windowWebViewClient); + + final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; + transport.setWebView(onCreateWindowWebView); + resultMsg.sendToTarget(); + + return true; + } + + /** + * Set the {@link WebViewClient} that calls to {@link WebChromeClient#onCreateWindow} are passed + * to. + * + * @param webViewClient the forwarding {@link WebViewClient} + */ + public void setWebViewClient(@NonNull WebViewClient webViewClient) { + this.webViewClient = webViewClient; + } + } + + /** Creates a host API that handles creating {@link WebChromeClient}s. */ + public WebChromeClientProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @NonNull + @Override + public WebChromeClientImpl pigeon_defaultConstructor() { + return new WebChromeClientImpl(this); + } + + @Override + public void setSynchronousReturnValueForOnShowFileChooser( + @NonNull WebChromeClientImpl pigeon_instance, boolean value) { + pigeon_instance.setReturnValueForOnShowFileChooser(value); + } + + @Override + public void setSynchronousReturnValueForOnConsoleMessage( + @NonNull WebChromeClientImpl pigeon_instance, boolean value) { + pigeon_instance.setReturnValueForOnConsoleMessage(value); + } + + @Override + public void setSynchronousReturnValueForOnJsAlert( + @NonNull WebChromeClientImpl pigeon_instance, boolean value) { + pigeon_instance.setReturnValueForOnJsAlert(value); + } + + @Override + public void setSynchronousReturnValueForOnJsConfirm( + @NonNull WebChromeClientImpl pigeon_instance, boolean value) { + pigeon_instance.setReturnValueForOnJsConfirm(value); + } + + @Override + public void setSynchronousReturnValueForOnJsPrompt( + @NonNull WebChromeClientImpl pigeon_instance, boolean value) { + pigeon_instance.setReturnValueForOnJsPrompt(value); + } + + @NonNull + @Override + public ProxyApiRegistrar getPigeonRegistrar() { + return (ProxyApiRegistrar) super.getPigeonRegistrar(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebResourceErrorCompatProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebResourceErrorCompatProxyApi.java new file mode 100644 index 0000000..a426301 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebResourceErrorCompatProxyApi.java @@ -0,0 +1,28 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.annotation.SuppressLint; +import androidx.annotation.NonNull; +import androidx.webkit.WebResourceErrorCompat; + +public class WebResourceErrorCompatProxyApi extends PigeonApiWebResourceErrorCompat { + public WebResourceErrorCompatProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @SuppressLint("RequiresFeature") + @Override + public long errorCode(@NonNull WebResourceErrorCompat pigeon_instance) { + return pigeon_instance.getErrorCode(); + } + + @SuppressLint("RequiresFeature") + @NonNull + @Override + public String description(@NonNull WebResourceErrorCompat pigeon_instance) { + return pigeon_instance.getDescription().toString(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebResourceErrorProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebResourceErrorProxyApi.java new file mode 100644 index 0000000..907e620 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebResourceErrorProxyApi.java @@ -0,0 +1,25 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.webkit.WebResourceError; +import androidx.annotation.NonNull; + +public class WebResourceErrorProxyApi extends PigeonApiWebResourceError { + public WebResourceErrorProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @Override + public long errorCode(@NonNull WebResourceError pigeon_instance) { + return pigeon_instance.getErrorCode(); + } + + @NonNull + @Override + public String description(@NonNull WebResourceError pigeon_instance) { + return pigeon_instance.getDescription().toString(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebResourceRequestProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebResourceRequestProxyApi.java new file mode 100644 index 0000000..c47750e --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebResourceRequestProxyApi.java @@ -0,0 +1,63 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.webkit.WebResourceRequest; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import java.util.Collections; +import java.util.Map; + +public class WebResourceRequestProxyApi extends PigeonApiWebResourceRequest { + public WebResourceRequestProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @NonNull + @Override + public String url(@NonNull WebResourceRequest pigeon_instance) { + return pigeon_instance.getUrl().toString(); + } + + @NonNull + @Override + public boolean isForMainFrame(@NonNull WebResourceRequest pigeon_instance) { + return pigeon_instance.isForMainFrame(); + } + + @NonNull + @Override + public boolean isRedirect(@NonNull WebResourceRequest pigeon_instance) { + return pigeon_instance.isRedirect(); + } + + @NonNull + @Override + public boolean hasGesture(@NonNull WebResourceRequest pigeon_instance) { + return pigeon_instance.hasGesture(); + } + + @NonNull + @Override + public String method(@NonNull WebResourceRequest pigeon_instance) { + return pigeon_instance.getMethod(); + } + + @Nullable + @Override + public Map requestHeaders(@NonNull WebResourceRequest pigeon_instance) { + if (pigeon_instance.getRequestHeaders() == null) { + return Collections.emptyMap(); + } else { + return pigeon_instance.getRequestHeaders(); + } + } + + @NonNull + @Override + public ProxyApiRegistrar getPigeonRegistrar() { + return (ProxyApiRegistrar) super.getPigeonRegistrar(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebResourceResponseProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebResourceResponseProxyApi.java new file mode 100644 index 0000000..4a4c985 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebResourceResponseProxyApi.java @@ -0,0 +1,19 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.webkit.WebResourceResponse; +import androidx.annotation.NonNull; + +public class WebResourceResponseProxyApi extends PigeonApiWebResourceResponse { + public WebResourceResponseProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @Override + public long statusCode(@NonNull WebResourceResponse pigeon_instance) { + return pigeon_instance.getStatusCode(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java new file mode 100644 index 0000000..295b61f --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsCompatProxyApi.java @@ -0,0 +1,32 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.annotation.SuppressLint; +import android.webkit.WebSettings; +import androidx.annotation.NonNull; +import androidx.webkit.WebSettingsCompat; + +/** + * Proxy API implementation for {@link WebSettingsCompat}. + * + *

This class may handle instantiating and adding native object instances that are attached to a + * Dart instance or handle method calls on the associated native class or an instance of the class. + */ +public class WebSettingsCompatProxyApi extends PigeonApiWebSettingsCompat { + public WebSettingsCompatProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + /** + * This method should only be called if {@link WebViewFeatureProxyApi#isFeatureSupported(String)} + * with PAYMENT_REQUEST returns true. + */ + @SuppressLint("RequiresFeature") + @Override + public void setPaymentRequestEnabled(@NonNull WebSettings webSettings, boolean enabled) { + WebSettingsCompat.setPaymentRequestEnabled(webSettings, enabled); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsProxyApi.java new file mode 100644 index 0000000..fb1a363 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsProxyApi.java @@ -0,0 +1,120 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.webkit.WebSettings; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * Host api implementation for {@link WebSettings}. + * + *

Handles creating {@link WebSettings}s that intercommunicate with a paired Dart object. + */ +public class WebSettingsProxyApi extends PigeonApiWebSettings { + public WebSettingsProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @Override + public void setDomStorageEnabled(@NonNull WebSettings pigeon_instance, boolean flag) { + pigeon_instance.setDomStorageEnabled(flag); + } + + @Override + public void setJavaScriptCanOpenWindowsAutomatically( + @NonNull WebSettings pigeon_instance, boolean flag) { + pigeon_instance.setJavaScriptCanOpenWindowsAutomatically(flag); + } + + @Override + public void setSupportMultipleWindows(@NonNull WebSettings pigeon_instance, boolean support) { + pigeon_instance.setSupportMultipleWindows(support); + } + + @Override + public void setJavaScriptEnabled(@NonNull WebSettings pigeon_instance, boolean flag) { + pigeon_instance.setJavaScriptEnabled(flag); + } + + @Override + public void setUserAgentString( + @NonNull WebSettings pigeon_instance, @Nullable String userAgentString) { + pigeon_instance.setUserAgentString(userAgentString); + } + + @Override + public void setMediaPlaybackRequiresUserGesture( + @NonNull WebSettings pigeon_instance, boolean require) { + pigeon_instance.setMediaPlaybackRequiresUserGesture(require); + } + + @Override + public void setSupportZoom(@NonNull WebSettings pigeon_instance, boolean support) { + pigeon_instance.setSupportZoom(support); + } + + @Override + public void setLoadWithOverviewMode(@NonNull WebSettings pigeon_instance, boolean overview) { + pigeon_instance.setLoadWithOverviewMode(overview); + } + + @Override + public void setUseWideViewPort(@NonNull WebSettings pigeon_instance, boolean use) { + pigeon_instance.setUseWideViewPort(use); + } + + @Override + public void setDisplayZoomControls(@NonNull WebSettings pigeon_instance, boolean enabled) { + pigeon_instance.setDisplayZoomControls(enabled); + } + + @Override + public void setBuiltInZoomControls(@NonNull WebSettings pigeon_instance, boolean enabled) { + pigeon_instance.setBuiltInZoomControls(enabled); + } + + @Override + public void setAllowFileAccess(@NonNull WebSettings pigeon_instance, boolean enabled) { + pigeon_instance.setAllowFileAccess(enabled); + } + + @Override + public void setAllowContentAccess(@NonNull WebSettings pigeon_instance, boolean enabled) { + pigeon_instance.setAllowContentAccess(enabled); + } + + @Override + public void setGeolocationEnabled(@NonNull WebSettings pigeon_instance, boolean enabled) { + pigeon_instance.setGeolocationEnabled(enabled); + } + + @Override + public void setTextZoom(@NonNull WebSettings pigeon_instance, long textZoom) { + pigeon_instance.setTextZoom((int) textZoom); + } + + @NonNull + @Override + public String getUserAgentString(@NonNull WebSettings pigeon_instance) { + return pigeon_instance.getUserAgentString(); + } + + @Override + public void setMixedContentMode( + @NonNull WebSettings pigeon_instance, @NonNull MixedContentMode mode) { + switch (mode) { + case ALWAYS_ALLOW: + pigeon_instance.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); + break; + case COMPATIBILITY_MODE: + pigeon_instance.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); + break; + case NEVER_ALLOW: + pigeon_instance.setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW); + break; + } + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebStorageProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebStorageProxyApi.java new file mode 100644 index 0000000..860ed87 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebStorageProxyApi.java @@ -0,0 +1,31 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.webkit.WebStorage; +import androidx.annotation.NonNull; + +/** + * Host api implementation for {@link WebStorage}. + * + *

Handles creating {@link WebStorage}s that intercommunicate with a paired Dart object. + */ +public class WebStorageProxyApi extends PigeonApiWebStorage { + /** Creates a host API that handles creating {@link WebStorage} and invoke its methods. */ + public WebStorageProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @NonNull + @Override + public WebStorage instance() { + return WebStorage.getInstance(); + } + + @Override + public void deleteAllData(@NonNull WebStorage pigeon_instance) { + pigeon_instance.deleteAllData(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientProxyApi.java new file mode 100644 index 0000000..5690b96 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientProxyApi.java @@ -0,0 +1,197 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.graphics.Bitmap; +import android.view.KeyEvent; +import android.webkit.HttpAuthHandler; +import android.webkit.WebResourceError; +import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * Host api implementation for {@link WebViewClient}. + * + *

Handles creating {@link WebViewClient}s that intercommunicate with a paired Dart object. + */ +public class WebViewClientProxyApi extends PigeonApiWebViewClient { + /** Implementation of {@link WebViewClient} that passes arguments of callback methods to Dart. */ + public static class WebViewClientImpl extends WebViewClient { + private final WebViewClientProxyApi api; + private boolean returnValueForShouldOverrideUrlLoading = false; + + /** + * Creates a {@link WebViewClient} that passes arguments of callbacks methods to Dart. + * + * @param api handles sending messages to Dart. + */ + public WebViewClientImpl(@NonNull WebViewClientProxyApi api) { + this.api = api; + } + + @Override + public void onPageStarted(@NonNull WebView view, @NonNull String url, @NonNull Bitmap favicon) { + api.getPigeonRegistrar() + .runOnMainThread(() -> api.onPageStarted(this, view, url, reply -> null)); + } + + @Override + public void onPageFinished(@NonNull WebView view, @NonNull String url) { + api.getPigeonRegistrar() + .runOnMainThread(() -> api.onPageFinished(this, view, url, reply -> null)); + } + + @Override + public void onReceivedHttpError( + @NonNull WebView view, + @NonNull WebResourceRequest request, + @NonNull WebResourceResponse response) { + api.getPigeonRegistrar() + .runOnMainThread( + () -> api.onReceivedHttpError(this, view, request, response, reply -> null)); + } + + @Override + public void onReceivedError( + @NonNull WebView view, + @NonNull WebResourceRequest request, + @NonNull WebResourceError error) { + api.getPigeonRegistrar() + .runOnMainThread( + () -> api.onReceivedRequestError(this, view, request, error, reply -> null)); + } + + @Override + public boolean shouldOverrideUrlLoading( + @NonNull WebView view, @NonNull WebResourceRequest request) { + api.getPigeonRegistrar() + .runOnMainThread(() -> api.requestLoading(this, view, request, reply -> null)); + + // The client is only allowed to stop navigations that target the main frame because + // overridden URLs are passed to `loadUrl` and `loadUrl` cannot load a subframe. + return request.isForMainFrame() && returnValueForShouldOverrideUrlLoading; + } + + @Override + public void doUpdateVisitedHistory( + @NonNull WebView view, @NonNull String url, boolean isReload) { + api.getPigeonRegistrar() + .runOnMainThread( + () -> api.doUpdateVisitedHistory(this, view, url, isReload, reply -> null)); + } + + @Override + public void onReceivedHttpAuthRequest( + @NonNull WebView view, + @NonNull HttpAuthHandler handler, + @NonNull String host, + @NonNull String realm) { + api.getPigeonRegistrar() + .runOnMainThread( + () -> api.onReceivedHttpAuthRequest(this, view, handler, host, realm, reply -> null)); + } + + @Override + public void onFormResubmission( + @NonNull android.webkit.WebView view, + @NonNull android.os.Message dontResend, + @NonNull android.os.Message resend) { + api.getPigeonRegistrar() + .runOnMainThread( + () -> api.onFormResubmission(this, view, dontResend, resend, reply -> null)); + } + + @Override + public void onLoadResource(@NonNull android.webkit.WebView view, @NonNull String url) { + api.getPigeonRegistrar() + .runOnMainThread(() -> api.onLoadResource(this, view, url, reply -> null)); + } + + @Override + public void onPageCommitVisible(@NonNull android.webkit.WebView view, @NonNull String url) { + api.getPigeonRegistrar() + .runOnMainThread(() -> api.onPageCommitVisible(this, view, url, reply -> null)); + } + + @Override + public void onReceivedClientCertRequest( + @NonNull android.webkit.WebView view, @NonNull android.webkit.ClientCertRequest request) { + api.getPigeonRegistrar() + .runOnMainThread( + () -> api.onReceivedClientCertRequest(this, view, request, reply -> null)); + } + + @Override + public void onReceivedLoginRequest( + @NonNull android.webkit.WebView view, + @NonNull String realm, + @Nullable String account, + @NonNull String args) { + api.getPigeonRegistrar() + .runOnMainThread( + () -> api.onReceivedLoginRequest(this, view, realm, account, args, reply -> null)); + } + + @Override + public void onReceivedSslError( + @NonNull android.webkit.WebView view, + @NonNull android.webkit.SslErrorHandler handler, + @NonNull android.net.http.SslError error) { + api.getPigeonRegistrar() + .runOnMainThread(() -> api.onReceivedSslError(this, view, handler, error, reply -> null)); + } + + @Override + public void onScaleChanged( + @NonNull android.webkit.WebView view, float oldScale, float newScale) { + api.getPigeonRegistrar() + .runOnMainThread(() -> api.onScaleChanged(this, view, oldScale, newScale, reply -> null)); + } + + @Override + public void onUnhandledKeyEvent(@NonNull WebView view, @NonNull KeyEvent event) { + // Deliberately empty. Occasionally the webview will mark events as having failed to be + // handled even though they were handled. We don't want to propagate those as they're not + // truly lost. + } + + /** Sets return value for {@link #shouldOverrideUrlLoading}. */ + public void setReturnValueForShouldOverrideUrlLoading(boolean value) { + returnValueForShouldOverrideUrlLoading = value; + } + } + + /** Creates a host API that handles creating {@link WebViewClient}s. */ + public WebViewClientProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @NonNull + @Override + public WebViewClient pigeon_defaultConstructor() { + return new WebViewClientImpl(this); + } + + @Override + public void setSynchronousReturnValueForShouldOverrideUrlLoading( + @NonNull WebViewClient pigeon_instance, boolean value) { + if (pigeon_instance instanceof WebViewClientImpl) { + ((WebViewClientImpl) pigeon_instance).setReturnValueForShouldOverrideUrlLoading(value); + } else { + throw new IllegalStateException( + "This WebViewClient doesn't support setting the returnValueForShouldOverrideUrlLoading."); + } + } + + @NonNull + @Override + public ProxyApiRegistrar getPigeonRegistrar() { + return (ProxyApiRegistrar) super.getPigeonRegistrar(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFeatureProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFeatureProxyApi.java new file mode 100644 index 0000000..09c22b6 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFeatureProxyApi.java @@ -0,0 +1,25 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import androidx.annotation.NonNull; +import androidx.webkit.WebViewFeature; + +/** + * Proxy API implementation for {@link WebViewFeature}. + * + *

This class may handle instantiating and adding native object instances that are attached to a + * Dart instance or handle method calls on the associated native class or an instance of the class. + */ +public class WebViewFeatureProxyApi extends PigeonApiWebViewFeature { + public WebViewFeatureProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @Override + public boolean isFeatureSupported(@NonNull String feature) { + return WebViewFeature.isFeatureSupported(feature); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApi.java new file mode 100644 index 0000000..58d2da2 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApi.java @@ -0,0 +1,51 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.webkit.WebView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.embedding.engine.FlutterEngine; + +/** + * App and package facing native API provided by the `webview_flutter_android` plugin. + * + *

This class follows the convention of breaking changes of the Dart API, which means that any + * changes to the class that are not backwards compatible will only be made with a major version + * change of the plugin. + * + *

Native code other than this external API does not follow breaking change conventions, so app + * or plugin clients should not use any other native APIs. + */ +@SuppressWarnings("unused") +public interface WebViewFlutterAndroidExternalApi { + /** + * Retrieves the {@link WebView} that is associated with `identifier`. + * + *

See the Dart method `AndroidWebViewController.webViewIdentifier` to get the identifier of an + * underlying `WebView`. + * + * @param engine the execution environment the {@link WebViewFlutterPlugin} should belong to. If + * the engine doesn't contain an attached instance of {@link WebViewFlutterPlugin}, this + * method returns null. + * @param identifier the associated identifier of the `WebView`. + * @return the `WebView` associated with `identifier` or null if a `WebView` instance associated + * with `identifier` could not be found. + */ + @Nullable + static WebView getWebView(@NonNull FlutterEngine engine, long identifier) { + final WebViewFlutterPlugin webViewPlugin = + (WebViewFlutterPlugin) engine.getPlugins().get(WebViewFlutterPlugin.class); + + if (webViewPlugin != null && webViewPlugin.getInstanceManager() != null) { + final Object instance = webViewPlugin.getInstanceManager().getInstance(identifier); + if (instance instanceof WebView) { + return (WebView) instance; + } + } + + return null; + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java new file mode 100644 index 0000000..5fe57ef --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java @@ -0,0 +1,93 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; + +/** + * Java platform implementation of the webview_flutter plugin. + * + *

Register this in an add to app scenario to gracefully handle activity and context changes. + */ +public class WebViewFlutterPlugin implements FlutterPlugin, ActivityAware { + private FlutterPluginBinding pluginBinding; + private ProxyApiRegistrar proxyApiRegistrar; + + public static BSEventChannel bsEventChannel; + + /** + * Add an instance of this to {@link io.flutter.embedding.engine.plugins.PluginRegistry} to + * register it. + * + *

Registration should eventually be handled automatically by v2 of the + * GeneratedPluginRegistrant. https://github.com/flutter/flutter/issues/42694 + */ + public WebViewFlutterPlugin() {} + + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { + pluginBinding = binding; + + proxyApiRegistrar = + new ProxyApiRegistrar( + binding.getBinaryMessenger(), + binding.getApplicationContext(), + new FlutterAssetManager.PluginBindingFlutterAssetManager( + binding.getApplicationContext().getAssets(), binding.getFlutterAssets())); + + binding + .getPlatformViewRegistry() + .registerViewFactory( + "plugins.flutter.io/webview", + new FlutterViewFactory(proxyApiRegistrar.getInstanceManager())); + + proxyApiRegistrar.setUp(); + + bsEventChannel = BSEventChannel.registerWith(binding); + } + + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + if (proxyApiRegistrar != null) { + proxyApiRegistrar.tearDown(); + proxyApiRegistrar.getInstanceManager().stopFinalizationListener(); + proxyApiRegistrar = null; + BSEventChannel.channel.setStreamHandler(null); + } + } + + @Override + public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) { + if (proxyApiRegistrar != null) { + proxyApiRegistrar.setContext(activityPluginBinding.getActivity()); + } + } + + @Override + public void onDetachedFromActivityForConfigChanges() { + proxyApiRegistrar.setContext(pluginBinding.getApplicationContext()); + } + + @Override + public void onReattachedToActivityForConfigChanges( + @NonNull ActivityPluginBinding activityPluginBinding) { + proxyApiRegistrar.setContext(activityPluginBinding.getActivity()); + } + + @Override + public void onDetachedFromActivity() { + proxyApiRegistrar.setContext(pluginBinding.getApplicationContext()); + } + + /** Maintains instances used to communicate with the corresponding objects in Dart. */ + @Nullable + public AndroidWebkitLibraryPigeonInstanceManager getInstanceManager() { + return proxyApiRegistrar.getInstanceManager(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewPoint.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewPoint.java new file mode 100644 index 0000000..541c910 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewPoint.java @@ -0,0 +1,28 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +/** + * Represents a position on a web page. + * + *

This is a custom class created for convenience of the wrapper. + */ +public class WebViewPoint { + private final long x; + private final long y; + + public WebViewPoint(long x, long y) { + this.x = x; + this.y = y; + } + + public long getX() { + return x; + } + + public long getY() { + return y; + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewPointProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewPointProxyApi.java new file mode 100644 index 0000000..75b5b91 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewPointProxyApi.java @@ -0,0 +1,23 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import androidx.annotation.NonNull; + +public class WebViewPointProxyApi extends PigeonApiWebViewPoint { + public WebViewPointProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @Override + public long x(@NonNull WebViewPoint pigeon_instance) { + return pigeon_instance.getX(); + } + + @Override + public long y(@NonNull WebViewPoint pigeon_instance) { + return pigeon_instance.getY(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewProxyApi.java b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewProxyApi.java new file mode 100644 index 0000000..97eae20 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewProxyApi.java @@ -0,0 +1,356 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.os.Build; +import android.view.View; +import android.view.ViewParent; +import android.webkit.DownloadListener; +import android.webkit.WebChromeClient; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.embedding.android.FlutterView; +import io.flutter.plugin.platform.PlatformView; +import java.util.Map; +import kotlin.Result; +import kotlin.Unit; +import kotlin.jvm.functions.Function1; +import android.webkit.JavascriptInterface; +import android.os.Handler; +import android.os.Looper; + +/** + * Host api implementation for {@link WebView}. + * + *

Handles creating {@link WebView}s that intercommunicate with a paired Dart object. + */ +public class WebViewProxyApi extends PigeonApiWebView { + /** Implementation of {@link WebView} that can be used as a Flutter {@link PlatformView}s. */ + @SuppressLint("ViewConstructor") + public static class WebViewPlatformView extends WebView implements PlatformView { + private final WebViewProxyApi api; + + private WebViewClient currentWebViewClient; + + private WebChromeClientProxyApi.SecureWebChromeClient currentWebChromeClient; + + WebViewPlatformView(@NonNull WebViewProxyApi api) { + super(api.getPigeonRegistrar().getContext()); + this.api = api; + currentWebViewClient = new WebViewClient(); + currentWebChromeClient = new WebChromeClientProxyApi.SecureWebChromeClient(); + + setWebViewClient(currentWebViewClient); + setWebChromeClient(currentWebChromeClient); + } + + @Nullable + @Override + public View getView() { + return this; + } + + @Override + public void dispose() {} + + // TODO(bparrishMines): This should be removed once https://github.com/flutter/engine/pull/40771 makes it to stable. + // Temporary fix for https://github.com/flutter/flutter/issues/92165. The FlutterView is setting + // setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS) which prevents this + // view from automatically being traversed for autofill. + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (api.getPigeonRegistrar().sdkIsAtLeast(Build.VERSION_CODES.O)) { + final FlutterView flutterView = tryFindFlutterView(); + if (flutterView != null) { + flutterView.setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES); + } + } + } + + // Attempt to traverse the parents of this view until a FlutterView is found. + private FlutterView tryFindFlutterView() { + ViewParent currentView = this; + + while (currentView.getParent() != null) { + currentView = currentView.getParent(); + if (currentView instanceof FlutterView) { + return (FlutterView) currentView; + } + } + + return null; + } + + @Override + public void setWebViewClient(@NonNull WebViewClient webViewClient) { + super.setWebViewClient(webViewClient); + currentWebViewClient = webViewClient; + currentWebChromeClient.setWebViewClient(webViewClient); + } + + @Override + public void setWebChromeClient(@Nullable WebChromeClient client) { + super.setWebChromeClient(client); + if (!(client instanceof WebChromeClientProxyApi.SecureWebChromeClient)) { + throw new AssertionError("Client must be a SecureWebChromeClient."); + } + currentWebChromeClient = (WebChromeClientProxyApi.SecureWebChromeClient) client; + currentWebChromeClient.setWebViewClient(currentWebViewClient); + } + + // When running unit tests, the parent `WebView` class is replaced by a stub that returns null + // for every method. This is overridden so that this returns the current WebChromeClient during + // unit tests. This should only remain overridden as long as `setWebChromeClient` is overridden. + @Nullable + @Override + public WebChromeClient getWebChromeClient() { + return currentWebChromeClient; + } + + @Override + protected void onScrollChanged(int left, int top, int oldLeft, int oldTop) { + super.onScrollChanged(left, top, oldLeft, oldTop); + api.getPigeonRegistrar() + .runOnMainThread( + () -> + api.onScrollChanged( + this, (long) left, (long) top, (long) oldLeft, (long) oldTop, reply -> null)); + } + } + + public WebViewProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) { + super(pigeonRegistrar); + } + + @NonNull + @Override + public WebView pigeon_defaultConstructor() { + DisplayListenerProxy displayListenerProxy = new DisplayListenerProxy(); + DisplayManager displayManager = + (DisplayManager) + getPigeonRegistrar().getContext().getSystemService(Context.DISPLAY_SERVICE); + displayListenerProxy.onPreWebViewInitialization(displayManager); + + final WebView webView = new WebViewPlatformView(this); + displayListenerProxy.onPostWebViewInitialization(displayManager); + + return webView; + } + + @NonNull + @Override + public WebSettings settings(@NonNull WebView pigeon_instance) { + return pigeon_instance.getSettings(); + } + + @Override + public void loadData( + @NonNull WebView pigeon_instance, + @NonNull String data, + @Nullable String mimeType, + @Nullable String encoding) { + pigeon_instance.loadData(data, mimeType, encoding); + } + + @Override + public void loadDataWithBaseUrl( + @NonNull WebView pigeon_instance, + @Nullable String baseUrl, + @NonNull String data, + @Nullable String mimeType, + @Nullable String encoding, + @Nullable String historyUrl) { + pigeon_instance.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); + } + + @Override + public void loadUrl( + @NonNull WebView pigeon_instance, @NonNull String url, @NonNull Map headers) { + pigeon_instance.loadUrl(url, headers); + //baishun + setNativeBridge(pigeon_instance); + } + @SuppressLint("JavascriptInterface") + public void setNativeBridge(WebView pigeon_instance){ + pigeon_instance.addJavascriptInterface(new NativeBridge(), "NativeBridge"); + } + + @Override + public void postUrl(@NonNull WebView pigeon_instance, @NonNull String url, @NonNull byte[] data) { + pigeon_instance.postUrl(url, data); + } + + @Nullable + @Override + public String getUrl(@NonNull WebView pigeon_instance) { + return pigeon_instance.getUrl(); + } + + @Override + public boolean canGoBack(@NonNull WebView pigeon_instance) { + return pigeon_instance.canGoBack(); + } + + @Override + public boolean canGoForward(@NonNull WebView pigeon_instance) { + return pigeon_instance.canGoForward(); + } + + @Override + public void goBack(@NonNull WebView pigeon_instance) { + pigeon_instance.goBack(); + } + + @Override + public void goForward(@NonNull WebView pigeon_instance) { + pigeon_instance.goForward(); + } + + @Override + public void reload(@NonNull WebView pigeon_instance) { + pigeon_instance.reload(); + } + + @Override + public void clearCache(@NonNull WebView pigeon_instance, boolean includeDiskFiles) { + pigeon_instance.clearCache(includeDiskFiles); + } + + @Override + public void evaluateJavascript( + @NonNull WebView pigeon_instance, + @NonNull String javascriptString, + @NonNull Function1, Unit> callback) { + pigeon_instance.evaluateJavascript( + javascriptString, result -> ResultCompat.success(result, callback)); + } + + @Nullable + @Override + public String getTitle(@NonNull WebView pigeon_instance) { + return pigeon_instance.getTitle(); + } + + @Override + public void setWebContentsDebuggingEnabled(boolean enabled) { + WebView.setWebContentsDebuggingEnabled(enabled); + } + + @Override + public void setWebViewClient(@NonNull WebView pigeon_instance, @Nullable WebViewClient client) { + pigeon_instance.setWebViewClient(client); + } + + @SuppressLint("JavascriptInterface") + @Override + public void addJavaScriptChannel( + @NonNull WebView pigeon_instance, @NonNull JavaScriptChannel channel) { + pigeon_instance.addJavascriptInterface(channel, channel.javaScriptChannelName); + } + + @Override + public void removeJavaScriptChannel(@NonNull WebView pigeon_instance, @NonNull String channel) { + pigeon_instance.removeJavascriptInterface(channel); + } + + @Override + public void setDownloadListener( + @NonNull WebView pigeon_instance, @Nullable DownloadListener listener) { + pigeon_instance.setDownloadListener(listener); + } + + @Override + public void setWebChromeClient( + @NonNull WebView pigeon_instance, + @Nullable WebChromeClientProxyApi.WebChromeClientImpl client) { + pigeon_instance.setWebChromeClient(client); + } + + @Override + public void setBackgroundColor(@NonNull WebView pigeon_instance, long color) { + pigeon_instance.setBackgroundColor((int) color); + } + + @NonNull + @Override + public ProxyApiRegistrar getPigeonRegistrar() { + return (ProxyApiRegistrar) super.getPigeonRegistrar(); + } + + @Override + public void destroy(@NonNull WebView pigeon_instance) { + pigeon_instance.destroy(); + } +} + + +class NativeBridge { + NativeBridge() { + + } + // 增加JS调用接口 + @JavascriptInterface + public void getConfig(String msg) { + Handler mainHandler = new Handler(Looper.getMainLooper()); + mainHandler.post(new Runnable() { + @Override + public void run() { + WebViewFlutterPlugin.bsEventChannel.sendEvent(msg); + } + }); + } + + @JavascriptInterface + public void destroy(String msg) { + Handler mainHandler = new Handler(Looper.getMainLooper()); + mainHandler.post(new Runnable() { + @Override + public void run() { + WebViewFlutterPlugin.bsEventChannel.sendEvent(msg); + } + }); + } + + @JavascriptInterface + public void gameRecharge(String msg) { + Handler mainHandler = new Handler(Looper.getMainLooper()); + mainHandler.post(new Runnable() { + @Override + public void run() { + WebViewFlutterPlugin.bsEventChannel.sendEvent(msg); + } + }); + } + + @JavascriptInterface + public void gameLoaded(String msg) { + Handler mainHandler = new Handler(Looper.getMainLooper()); + mainHandler.post(new Runnable() { + @Override + public void run() { + WebViewFlutterPlugin.bsEventChannel.sendEvent(msg); + } + }); + } + + @JavascriptInterface + public void sendGameAction(String msg) { + Handler mainHandler = new Handler(Looper.getMainLooper()); + mainHandler.post(new Runnable() { + @Override + public void run() { + WebViewFlutterPlugin.bsEventChannel.sendEvent(msg); + } + }); + } + +} \ No newline at end of file diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/android/net/Uri.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/android/net/Uri.java new file mode 100644 index 0000000..02c8f9f --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/android/net/Uri.java @@ -0,0 +1,27 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package android.net; + +import androidx.annotation.NonNull; + +// Creates an implementation of Uri that can be used with unittests and the JVM. Typically +// android.net.Uri does nothing when not used with an Android environment. +public class Uri { + private final String url; + + public static Uri parse(String url) { + return new Uri(url); + } + + private Uri(String url) { + this.url = url; + } + + @NonNull + @Override + public String toString() { + return url; + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/android/util/LongSparseArray.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/android/util/LongSparseArray.java new file mode 100644 index 0000000..542d82f --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/android/util/LongSparseArray.java @@ -0,0 +1,29 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package android.util; + +import java.util.HashMap; + +// Creates an implementation of LongSparseArray that can be used with unittests and the JVM. +// Typically android.util.LongSparseArray does nothing when not used with an Android environment. +public class LongSparseArray { + private final HashMap mHashMap; + + public LongSparseArray() { + mHashMap = new HashMap<>(); + } + + public void append(long key, E value) { + mHashMap.put(key, value); + } + + public E get(long key) { + return mHashMap.get(key); + } + + public void remove(long key) { + mHashMap.remove(key); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/CertificateTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/CertificateTest.java new file mode 100644 index 0000000..95b8d10 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/CertificateTest.java @@ -0,0 +1,26 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import org.junit.Test; + +public class CertificateTest { + @Test + public void getEncoded() throws CertificateEncodingException { + final PigeonApiCertificate api = new TestProxyApiRegistrar().getPigeonApiCertificate(); + + final Certificate instance = mock(Certificate.class); + final byte[] value = new byte[] {(byte) 0xA1}; + when(instance.getEncoded()).thenReturn(value); + + assertEquals(value, api.getEncoded(instance)); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/ClientCertRequestTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/ClientCertRequestTest.java new file mode 100644 index 0000000..da6fdf1 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/ClientCertRequestTest.java @@ -0,0 +1,53 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.webkit.ClientCertRequest; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.List; +import org.junit.Test; + +public class ClientCertRequestTest { + @Test + public void cancel() { + final PigeonApiClientCertRequest api = + new TestProxyApiRegistrar().getPigeonApiClientCertRequest(); + + final ClientCertRequest instance = mock(ClientCertRequest.class); + api.cancel(instance); + + verify(instance).cancel(); + } + + @Test + public void ignore() { + final PigeonApiClientCertRequest api = + new TestProxyApiRegistrar().getPigeonApiClientCertRequest(); + + final ClientCertRequest instance = mock(ClientCertRequest.class); + api.ignore(instance); + + verify(instance).ignore(); + } + + @Test + public void proceed() { + final PigeonApiClientCertRequest api = + new TestProxyApiRegistrar().getPigeonApiClientCertRequest(); + + final ClientCertRequest instance = mock(ClientCertRequest.class); + final java.security.PrivateKey privateKey = mock(PrivateKey.class); + final X509Certificate cert = mock(X509Certificate.class); + final List chain = Collections.singletonList(cert); + api.proceed(instance, privateKey, chain); + + verify(instance).proceed(privateKey, new X509Certificate[] {cert}); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/ConsoleMessageTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/ConsoleMessageTest.java new file mode 100644 index 0000000..4c2fb0d --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/ConsoleMessageTest.java @@ -0,0 +1,58 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.webkit.ConsoleMessage; +import org.junit.Test; + +public class ConsoleMessageTest { + @Test + public void lineNumber() { + final PigeonApiConsoleMessage api = new TestProxyApiRegistrar().getPigeonApiConsoleMessage(); + + final ConsoleMessage instance = mock(ConsoleMessage.class); + final Long value = 0L; + when(instance.lineNumber()).thenReturn(value.intValue()); + + assertEquals(value, (Long) api.lineNumber(instance)); + } + + @Test + public void message() { + final PigeonApiConsoleMessage api = new TestProxyApiRegistrar().getPigeonApiConsoleMessage(); + + final ConsoleMessage instance = mock(ConsoleMessage.class); + final String value = "myString"; + when(instance.message()).thenReturn(value); + + assertEquals(value, api.message(instance)); + } + + @Test + public void level() { + final PigeonApiConsoleMessage api = new TestProxyApiRegistrar().getPigeonApiConsoleMessage(); + + final ConsoleMessage instance = mock(ConsoleMessage.class); + final ConsoleMessageLevel value = io.flutter.plugins.webviewflutter.ConsoleMessageLevel.DEBUG; + when(instance.messageLevel()).thenReturn(ConsoleMessage.MessageLevel.DEBUG); + + assertEquals(value, api.level(instance)); + } + + @Test + public void sourceId() { + final PigeonApiConsoleMessage api = new TestProxyApiRegistrar().getPigeonApiConsoleMessage(); + + final ConsoleMessage instance = mock(ConsoleMessage.class); + final String value = "myString"; + when(instance.sourceId()).thenReturn(value); + + assertEquals(value, api.sourceId(instance)); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerTest.java new file mode 100644 index 0000000..3a9415b --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerTest.java @@ -0,0 +1,67 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.webkit.CookieManager; +import android.webkit.ValueCallback; +import android.webkit.WebView; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +public class CookieManagerTest { + @Test + public void setCookie() { + final PigeonApiCookieManager api = new TestProxyApiRegistrar().getPigeonApiCookieManager(); + + final CookieManager instance = mock(CookieManager.class); + final String url = "myString"; + final String value = "myString2"; + api.setCookie(instance, url, value); + + verify(instance).setCookie(url, value); + } + + @Test + public void removeAllCookies() { + final PigeonApiCookieManager api = new TestProxyApiRegistrar().getPigeonApiCookieManager(); + + final CookieManager instance = mock(CookieManager.class); + + final Boolean[] successResult = new Boolean[1]; + api.removeAllCookies( + instance, + ResultCompat.asCompatCallback( + reply -> { + successResult[0] = reply.getOrNull(); + return null; + })); + + @SuppressWarnings("unchecked") + final ArgumentCaptor> valueCallbackArgumentCaptor = + ArgumentCaptor.forClass(ValueCallback.class); + verify(instance).removeAllCookies(valueCallbackArgumentCaptor.capture()); + + final Boolean returnValue = true; + valueCallbackArgumentCaptor.getValue().onReceiveValue(returnValue); + + assertEquals(successResult[0], returnValue); + } + + @Test + public void setAcceptThirdPartyCookies() { + final PigeonApiCookieManager api = new TestProxyApiRegistrar().getPigeonApiCookieManager(); + + final CookieManager instance = mock(CookieManager.class); + final android.webkit.WebView webView = mock(WebView.class); + final boolean accept = true; + api.setAcceptThirdPartyCookies(instance, webView, accept); + + verify(instance).setAcceptThirdPartyCookies(webView, accept); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/CustomViewCallbackTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/CustomViewCallbackTest.java new file mode 100644 index 0000000..de6c465 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/CustomViewCallbackTest.java @@ -0,0 +1,24 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.webkit.WebChromeClient.CustomViewCallback; +import org.junit.Test; + +public class CustomViewCallbackTest { + @Test + public void onCustomViewHidden() { + final PigeonApiCustomViewCallback api = + new TestProxyApiRegistrar().getPigeonApiCustomViewCallback(); + + final CustomViewCallback instance = mock(CustomViewCallback.class); + api.onCustomViewHidden(instance); + + verify(instance).onCustomViewHidden(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/DownloadListenerTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/DownloadListenerTest.java new file mode 100644 index 0000000..f923755 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/DownloadListenerTest.java @@ -0,0 +1,50 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.flutter.plugins.webviewflutter.DownloadListenerProxyApi.DownloadListenerImpl; +import org.junit.Test; + +public class DownloadListenerTest { + @Test + public void pigeon_defaultConstructor() { + final PigeonApiDownloadListener api = + new TestProxyApiRegistrar().getPigeonApiDownloadListener(); + + assertTrue( + api.pigeon_defaultConstructor() instanceof DownloadListenerProxyApi.DownloadListenerImpl); + } + + @Test + public void onDownloadStart() { + final DownloadListenerProxyApi mockApi = mock(DownloadListenerProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final DownloadListenerImpl instance = new DownloadListenerImpl(mockApi); + final String url = "myString"; + final String userAgent = "myString1"; + final String contentDisposition = "myString2"; + final String mimetype = "myString3"; + final Long contentLength = 0L; + instance.onDownloadStart(url, userAgent, contentDisposition, mimetype, contentLength); + + verify(mockApi) + .onDownloadStart( + eq(instance), + eq(url), + eq(userAgent), + eq(contentDisposition), + eq(mimetype), + eq(contentLength), + any()); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/FileChooserParamsTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/FileChooserParamsTest.java new file mode 100644 index 0000000..8946ee9 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/FileChooserParamsTest.java @@ -0,0 +1,64 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.webkit.WebChromeClient.FileChooserParams; +import java.util.Collections; +import java.util.List; +import org.junit.Test; + +public class FileChooserParamsTest { + @Test + public void isCaptureEnabled() { + final PigeonApiFileChooserParams api = + new TestProxyApiRegistrar().getPigeonApiFileChooserParams(); + + final FileChooserParams instance = mock(FileChooserParams.class); + final Boolean value = true; + when(instance.isCaptureEnabled()).thenReturn(value); + + assertEquals(value, api.isCaptureEnabled(instance)); + } + + @Test + public void acceptTypes() { + final PigeonApiFileChooserParams api = + new TestProxyApiRegistrar().getPigeonApiFileChooserParams(); + + final FileChooserParams instance = mock(FileChooserParams.class); + final List value = Collections.singletonList("myString"); + when(instance.getAcceptTypes()).thenReturn(value.toArray(new String[0])); + + assertEquals(value, api.acceptTypes(instance)); + } + + @Test + public void mode() { + final PigeonApiFileChooserParams api = + new TestProxyApiRegistrar().getPigeonApiFileChooserParams(); + + final FileChooserParams instance = mock(FileChooserParams.class); + final FileChooserMode value = io.flutter.plugins.webviewflutter.FileChooserMode.OPEN; + when(instance.getMode()).thenReturn(FileChooserParams.MODE_OPEN); + + assertEquals(value, api.mode(instance)); + } + + @Test + public void filenameHint() { + final PigeonApiFileChooserParams api = + new TestProxyApiRegistrar().getPigeonApiFileChooserParams(); + + final FileChooserParams instance = mock(FileChooserParams.class); + final String value = "myString"; + when(instance.getFilenameHint()).thenReturn(value); + + assertEquals(value, api.filenameHint(instance)); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerTest.java new file mode 100644 index 0000000..407530d --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerTest.java @@ -0,0 +1,71 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import org.junit.Test; + +public class FlutterAssetManagerTest { + @Test + public void list() throws IOException { + final PigeonApiFlutterAssetManager api = + new TestProxyApiRegistrar().getPigeonApiFlutterAssetManager(); + + final FlutterAssetManager instance = mock(FlutterAssetManager.class); + final String path = "myString"; + final List value = Collections.singletonList("myString"); + when(instance.list(path)).thenReturn(value.toArray(new String[0])); + + assertEquals(value, api.list(instance, path)); + } + + @Test + public void list_returns_empty_list_when_no_results() throws IOException { + final PigeonApiFlutterAssetManager api = + new TestProxyApiRegistrar().getPigeonApiFlutterAssetManager(); + + final FlutterAssetManager instance = mock(FlutterAssetManager.class); + final String path = "myString"; + when(instance.list(path)).thenReturn(null); + + assertEquals(Collections.emptyList(), api.list(instance, path)); + } + + @Test(expected = RuntimeException.class) + public void list_should_convert_io_exception_to_runtime_exception() { + final PigeonApiFlutterAssetManager api = + new TestProxyApiRegistrar().getPigeonApiFlutterAssetManager(); + + final FlutterAssetManager instance = mock(FlutterAssetManager.class); + final String path = "test/path"; + + try { + when(instance.list(path)).thenThrow(new IOException()); + api.list(instance, "test/path"); + } catch (IOException e) { + fail(); + } + } + + @Test + public void getAssetFilePathByName() { + final PigeonApiFlutterAssetManager api = + new TestProxyApiRegistrar().getPigeonApiFlutterAssetManager(); + + final FlutterAssetManager instance = mock(FlutterAssetManager.class); + final String name = "myString"; + final String value = "myString1"; + when(instance.getAssetFilePathByName(name)).thenReturn(value); + + assertEquals(value, api.getAssetFilePathByName(instance, name)); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/GeolocationPermissionsCallbackTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/GeolocationPermissionsCallbackTest.java new file mode 100644 index 0000000..9915e1d --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/GeolocationPermissionsCallbackTest.java @@ -0,0 +1,27 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.webkit.GeolocationPermissions; +import org.junit.Test; + +public class GeolocationPermissionsCallbackTest { + @Test + public void invoke() { + final PigeonApiGeolocationPermissionsCallback api = + new TestProxyApiRegistrar().getPigeonApiGeolocationPermissionsCallback(); + + final GeolocationPermissions.Callback instance = mock(GeolocationPermissions.Callback.class); + final String origin = "myString"; + final boolean allow = true; + final boolean retain = false; + api.invoke(instance, origin, allow, retain); + + verify(instance).invoke(origin, allow, retain); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/HttpAuthHandlerTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/HttpAuthHandlerTest.java new file mode 100644 index 0000000..6a8c277 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/HttpAuthHandlerTest.java @@ -0,0 +1,48 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.webkit.HttpAuthHandler; +import org.junit.Test; + +public class HttpAuthHandlerTest { + @Test + public void proceed() { + final PigeonApiHttpAuthHandler api = new TestProxyApiRegistrar().getPigeonApiHttpAuthHandler(); + + final HttpAuthHandler instance = mock(HttpAuthHandler.class); + final String username = "myString"; + final String password = "myString1"; + api.proceed(instance, username, password); + + verify(instance).proceed(username, password); + } + + @Test + public void cancel() { + final PigeonApiHttpAuthHandler api = new TestProxyApiRegistrar().getPigeonApiHttpAuthHandler(); + + final HttpAuthHandler instance = mock(HttpAuthHandler.class); + api.cancel(instance); + + verify(instance).cancel(); + } + + @Test + public void useHttpAuthUsernamePassword() { + final PigeonApiHttpAuthHandler api = new TestProxyApiRegistrar().getPigeonApiHttpAuthHandler(); + + final HttpAuthHandler instance = mock(HttpAuthHandler.class); + final Boolean value = true; + when(instance.useHttpAuthUsernamePassword()).thenReturn(value); + + assertEquals(value, api.useHttpAuthUsernamePassword(instance)); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/JavaScriptChannelTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/JavaScriptChannelTest.java new file mode 100644 index 0000000..3260188 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/JavaScriptChannelTest.java @@ -0,0 +1,27 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Test; + +public class JavaScriptChannelTest { + @Test + public void postMessage() { + final JavaScriptChannelProxyApi mockApi = mock(JavaScriptChannelProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final JavaScriptChannel instance = new JavaScriptChannel("channel", mockApi); + final String message = "myString"; + instance.postMessage(message); + + verify(mockApi).postMessage(eq(instance), eq(message), any()); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/MessageTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/MessageTest.java new file mode 100644 index 0000000..dc0ca6c --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/MessageTest.java @@ -0,0 +1,23 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.os.Message; +import org.junit.Test; + +public class MessageTest { + @Test + public void sendToTarget() { + final PigeonApiAndroidMessage api = new TestProxyApiRegistrar().getPigeonApiAndroidMessage(); + + final Message instance = mock(Message.class); + api.sendToTarget(instance); + + verify(instance).sendToTarget(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/PermissionRequestTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/PermissionRequestTest.java new file mode 100644 index 0000000..86a2be9 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/PermissionRequestTest.java @@ -0,0 +1,64 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.webkit.PermissionRequest; +import java.util.Collections; +import java.util.List; +import org.junit.Test; + +public class PermissionRequestTest { + // These values MUST equal the constants for the Dart PermissionRequestConstants class. + @Test + public void enums() { + assertEquals(PermissionRequest.RESOURCE_AUDIO_CAPTURE, "android.webkit.resource.AUDIO_CAPTURE"); + assertEquals(PermissionRequest.RESOURCE_VIDEO_CAPTURE, "android.webkit.resource.VIDEO_CAPTURE"); + assertEquals(PermissionRequest.RESOURCE_MIDI_SYSEX, "android.webkit.resource.MIDI_SYSEX"); + assertEquals( + PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID, + "android.webkit.resource.PROTECTED_MEDIA_ID"); + } + + @Test + public void grant() { + final PigeonApiPermissionRequest api = + new TestProxyApiRegistrar().getPigeonApiPermissionRequest(); + + final PermissionRequest instance = mock(PermissionRequest.class); + final List resources = + Collections.singletonList(PermissionRequest.RESOURCE_AUDIO_CAPTURE); + api.grant(instance, resources); + + verify(instance).grant(new String[] {PermissionRequest.RESOURCE_AUDIO_CAPTURE}); + } + + @Test + public void deny() { + final PigeonApiPermissionRequest api = + new TestProxyApiRegistrar().getPigeonApiPermissionRequest(); + + final PermissionRequest instance = mock(PermissionRequest.class); + api.deny(instance); + + verify(instance).deny(); + } + + @Test + public void resources() { + final PigeonApiPermissionRequest api = + new TestProxyApiRegistrar().getPigeonApiPermissionRequest(); + + final PermissionRequest instance = mock(PermissionRequest.class); + final List value = Collections.singletonList(PermissionRequest.RESOURCE_AUDIO_CAPTURE); + when(instance.getResources()).thenReturn(value.toArray(new String[0])); + + assertEquals(value, api.resources(instance)); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/PluginBindingFlutterAssetManagerTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/PluginBindingFlutterAssetManagerTest.java new file mode 100644 index 0000000..fbeb41a --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/PluginBindingFlutterAssetManagerTest.java @@ -0,0 +1,54 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.res.AssetManager; +import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterAssets; +import io.flutter.plugins.webviewflutter.FlutterAssetManager.PluginBindingFlutterAssetManager; +import java.io.IOException; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +public class PluginBindingFlutterAssetManagerTest { + @Mock AssetManager mockAssetManager; + @Mock FlutterAssets mockFlutterAssets; + + PluginBindingFlutterAssetManager tesPluginBindingFlutterAssetManager; + + @Before + public void setUp() { + mockAssetManager = mock(AssetManager.class); + mockFlutterAssets = mock(FlutterAssets.class); + + tesPluginBindingFlutterAssetManager = + new PluginBindingFlutterAssetManager(mockAssetManager, mockFlutterAssets); + } + + @Test + public void list() { + try { + when(mockAssetManager.list("test/path")) + .thenReturn(new String[] {"index.html", "styles.css"}); + String[] actualFilePaths = tesPluginBindingFlutterAssetManager.list("test/path"); + verify(mockAssetManager).list("test/path"); + assertArrayEquals(new String[] {"index.html", "styles.css"}, actualFilePaths); + } catch (IOException ex) { + fail(); + } + } + + @Test + public void registrar_getAssetFilePathByName() { + tesPluginBindingFlutterAssetManager.getAssetFilePathByName("sample_movie.mp4"); + verify(mockFlutterAssets).getAssetFilePathByName("sample_movie.mp4"); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/SslCertificateDNameTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/SslCertificateDNameTest.java new file mode 100644 index 0000000..be612d7 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/SslCertificateDNameTest.java @@ -0,0 +1,62 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.net.http.SslCertificate; +import org.junit.Test; + +public class SslCertificateDNameTest { + @Test + public void getCName() { + final PigeonApiSslCertificateDName api = + new TestProxyApiRegistrar().getPigeonApiSslCertificateDName(); + + final SslCertificate.DName instance = mock(SslCertificate.DName.class); + final String value = "myString"; + when(instance.getCName()).thenReturn(value); + + assertEquals(value, api.getCName(instance)); + } + + @Test + public void getDName() { + final PigeonApiSslCertificateDName api = + new TestProxyApiRegistrar().getPigeonApiSslCertificateDName(); + + final SslCertificate.DName instance = mock(SslCertificate.DName.class); + final String value = "myString"; + when(instance.getDName()).thenReturn(value); + + assertEquals(value, api.getDName(instance)); + } + + @Test + public void getOName() { + final PigeonApiSslCertificateDName api = + new TestProxyApiRegistrar().getPigeonApiSslCertificateDName(); + + final SslCertificate.DName instance = mock(SslCertificate.DName.class); + final String value = "myString"; + when(instance.getOName()).thenReturn(value); + + assertEquals(value, api.getOName(instance)); + } + + @Test + public void getUName() { + final PigeonApiSslCertificateDName api = + new TestProxyApiRegistrar().getPigeonApiSslCertificateDName(); + + final SslCertificate.DName instance = mock(SslCertificate.DName.class); + final String value = "myString"; + when(instance.getUName()).thenReturn(value); + + assertEquals(value, api.getUName(instance)); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/SslCertificateTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/SslCertificateTest.java new file mode 100644 index 0000000..8d2123e --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/SslCertificateTest.java @@ -0,0 +1,71 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.net.http.SslCertificate; +import java.security.cert.X509Certificate; +import java.util.Date; +import org.junit.Test; + +public class SslCertificateTest { + @Test + public void getIssuedBy() { + final PigeonApiSslCertificate api = new TestProxyApiRegistrar().getPigeonApiSslCertificate(); + + final SslCertificate instance = mock(SslCertificate.class); + final android.net.http.SslCertificate.DName value = mock(SslCertificate.DName.class); + when(instance.getIssuedBy()).thenReturn(value); + + assertEquals(value, api.getIssuedBy(instance)); + } + + @Test + public void getIssuedTo() { + final PigeonApiSslCertificate api = new TestProxyApiRegistrar().getPigeonApiSslCertificate(); + + final SslCertificate instance = mock(SslCertificate.class); + final android.net.http.SslCertificate.DName value = mock(SslCertificate.DName.class); + when(instance.getIssuedTo()).thenReturn(value); + + assertEquals(value, api.getIssuedTo(instance)); + } + + @Test + public void getValidNotAfterMsSinceEpoch() { + final PigeonApiSslCertificate api = new TestProxyApiRegistrar().getPigeonApiSslCertificate(); + + final SslCertificate instance = mock(SslCertificate.class); + final Date value = new Date(1000); + when(instance.getValidNotAfterDate()).thenReturn(value); + + assertEquals(value.getTime(), (long) api.getValidNotAfterMsSinceEpoch(instance)); + } + + @Test + public void getValidNotBeforeMsSinceEpoch() { + final PigeonApiSslCertificate api = new TestProxyApiRegistrar().getPigeonApiSslCertificate(); + + final SslCertificate instance = mock(SslCertificate.class); + final Date value = new Date(1000); + when(instance.getValidNotBeforeDate()).thenReturn(value); + + assertEquals(value.getTime(), (long) api.getValidNotBeforeMsSinceEpoch(instance)); + } + + @Test + public void getX509Certificate() { + final PigeonApiSslCertificate api = new TestProxyApiRegistrar().getPigeonApiSslCertificate(); + + final SslCertificate instance = mock(SslCertificate.class); + final java.security.cert.X509Certificate value = mock(X509Certificate.class); + when(instance.getX509Certificate()).thenReturn(value); + + assertEquals(value, api.getX509Certificate(instance)); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/SslErrorHandlerTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/SslErrorHandlerTest.java new file mode 100644 index 0000000..7b0f579 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/SslErrorHandlerTest.java @@ -0,0 +1,33 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.webkit.SslErrorHandler; +import org.junit.Test; + +public class SslErrorHandlerTest { + @Test + public void cancel() { + final PigeonApiSslErrorHandler api = new TestProxyApiRegistrar().getPigeonApiSslErrorHandler(); + + final SslErrorHandler instance = mock(SslErrorHandler.class); + api.cancel(instance); + + verify(instance).cancel(); + } + + @Test + public void proceed() { + final PigeonApiSslErrorHandler api = new TestProxyApiRegistrar().getPigeonApiSslErrorHandler(); + + final SslErrorHandler instance = mock(SslErrorHandler.class); + api.proceed(instance); + + verify(instance).proceed(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/SslErrorTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/SslErrorTest.java new file mode 100644 index 0000000..1275de3 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/SslErrorTest.java @@ -0,0 +1,60 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.net.http.SslCertificate; +import android.net.http.SslError; +import org.junit.Test; + +public class SslErrorTest { + @Test + public void certificate() { + final PigeonApiSslError api = new TestProxyApiRegistrar().getPigeonApiSslError(); + + final SslError instance = mock(SslError.class); + final android.net.http.SslCertificate value = mock(SslCertificate.class); + when(instance.getCertificate()).thenReturn(value); + + assertEquals(value, api.certificate(instance)); + } + + @Test + public void url() { + final PigeonApiSslError api = new TestProxyApiRegistrar().getPigeonApiSslError(); + + final SslError instance = mock(SslError.class); + final String value = "myString"; + when(instance.getUrl()).thenReturn(value); + + assertEquals(value, api.url(instance)); + } + + @Test + public void getPrimaryError() { + final PigeonApiSslError api = new TestProxyApiRegistrar().getPigeonApiSslError(); + + final SslError instance = mock(SslError.class); + final SslErrorType value = io.flutter.plugins.webviewflutter.SslErrorType.DATE_INVALID; + when(instance.getPrimaryError()).thenReturn(SslError.SSL_DATE_INVALID); + + assertEquals(value, api.getPrimaryError(instance)); + } + + @Test + public void hasError() { + final PigeonApiSslError api = new TestProxyApiRegistrar().getPigeonApiSslError(); + + final SslError instance = mock(SslError.class); + final SslErrorType error = io.flutter.plugins.webviewflutter.SslErrorType.DATE_INVALID; + final Boolean value = true; + when(instance.hasError(SslError.SSL_DATE_INVALID)).thenReturn(value); + + assertEquals(value, api.hasError(instance, error)); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/TestProxyApiRegistrar.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/TestProxyApiRegistrar.java new file mode 100644 index 0000000..2beeeb4 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/TestProxyApiRegistrar.java @@ -0,0 +1,30 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.mockito.Mockito.mock; + +import android.content.Context; +import io.flutter.plugin.common.BinaryMessenger; + +/** + * Test implementation of `ProxyApiRegistrar` that provides mocks, instantly runs callbacks instead + * of posting them, and makes all SDK checks pass by default. + */ +public class TestProxyApiRegistrar extends ProxyApiRegistrar { + public TestProxyApiRegistrar() { + super(mock(BinaryMessenger.class), mock(Context.class), mock(FlutterAssetManager.class)); + } + + @Override + void runOnMainThread(Runnable runnable) { + runnable.run(); + } + + @Override + boolean sdkIsAtLeast(int version) { + return true; + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/ViewTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/ViewTest.java new file mode 100644 index 0000000..aef6c6f --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/ViewTest.java @@ -0,0 +1,85 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.view.View; +import org.junit.Test; + +public class ViewTest { + @Test + public void scrollTo() { + final PigeonApiView api = new TestProxyApiRegistrar().getPigeonApiView(); + + final View instance = mock(View.class); + final long x = 0L; + final long y = 1L; + api.scrollTo(instance, x, y); + + verify(instance).scrollTo((int) x, (int) y); + } + + @Test + public void scrollBy() { + final PigeonApiView api = new TestProxyApiRegistrar().getPigeonApiView(); + + final View instance = mock(View.class); + final long x = 0L; + final long y = 1L; + api.scrollBy(instance, x, y); + + verify(instance).scrollBy((int) x, (int) y); + } + + @Test + public void getScrollPosition() { + final PigeonApiView api = new TestProxyApiRegistrar().getPigeonApiView(); + + final View instance = mock(View.class); + final WebViewPoint value = new WebViewPoint(0L, 1L); + when(instance.getScrollX()).thenReturn((int) value.getX()); + when(instance.getScrollY()).thenReturn((int) value.getY()); + + assertEquals(value.getX(), api.getScrollPosition(instance).getX()); + assertEquals(value.getY(), api.getScrollPosition(instance).getY()); + } + + @Test + public void setVerticalScrollBarEnabled() { + final PigeonApiView api = new TestProxyApiRegistrar().getPigeonApiView(); + + final View instance = mock(View.class); + final boolean enabled = true; + api.setVerticalScrollBarEnabled(instance, enabled); + + verify(instance).setVerticalScrollBarEnabled(enabled); + } + + @Test + public void setHorizontalScrollBarEnabled() { + final PigeonApiView api = new TestProxyApiRegistrar().getPigeonApiView(); + + final View instance = mock(View.class); + final boolean enabled = false; + api.setHorizontalScrollBarEnabled(instance, enabled); + + verify(instance).setHorizontalScrollBarEnabled(enabled); + } + + @Test + public void setOverScrollMode() { + final PigeonApiView api = new TestProxyApiRegistrar().getPigeonApiView(); + + final View instance = mock(View.class); + final OverScrollMode mode = io.flutter.plugins.webviewflutter.OverScrollMode.ALWAYS; + api.setOverScrollMode(instance, mode); + + verify(instance).setOverScrollMode(View.OVER_SCROLL_ALWAYS); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java new file mode 100644 index 0000000..2fe1209 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java @@ -0,0 +1,217 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.net.Uri; +import android.os.Message; +import android.view.View; +import android.webkit.ConsoleMessage; +import android.webkit.GeolocationPermissions; +import android.webkit.JsPromptResult; +import android.webkit.JsResult; +import android.webkit.PermissionRequest; +import android.webkit.WebChromeClient; +import android.webkit.WebResourceRequest; +import android.webkit.WebView; +import android.webkit.WebView.WebViewTransport; +import android.webkit.WebViewClient; +import io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +public class WebChromeClientTest { + @Test + public void onProgressChanged() { + final WebChromeClientProxyApi mockApi = mock(WebChromeClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebChromeClientImpl instance = new WebChromeClientImpl(mockApi); + final android.webkit.WebView webView = mock(WebView.class); + final Long progress = 0L; + instance.onProgressChanged(webView, progress.intValue()); + + verify(mockApi).onProgressChanged(eq(instance), eq(webView), eq(progress), any()); + } + + @Test + public void onCreateWindow() { + final WebView mockOnCreateWindowWebView = mock(WebView.class); + + // Create a fake message to transport requests to onCreateWindowWebView. + final Message message = new Message(); + message.obj = mock(WebViewTransport.class); + + final WebChromeClientProxyApi mockApi = mock(WebChromeClientProxyApi.class); + final WebChromeClientImpl instance = new WebChromeClientImpl(mockApi); + + final WebViewClient mockWebViewClient = mock(WebViewClient.class); + final WebView mockWebView = mock(WebView.class); + instance.setWebViewClient(mockWebViewClient); + assertTrue(instance.onCreateWindow(mockWebView, message, mockOnCreateWindowWebView)); + + /// Capture the WebViewClient used with onCreateWindow WebView. + final ArgumentCaptor webViewClientCaptor = + ArgumentCaptor.forClass(WebViewClient.class); + verify(mockOnCreateWindowWebView).setWebViewClient(webViewClientCaptor.capture()); + final WebViewClient onCreateWindowWebViewClient = webViewClientCaptor.getValue(); + assertNotNull(onCreateWindowWebViewClient); + + /// Create a WebResourceRequest with a Uri. + final WebResourceRequest mockRequest = mock(WebResourceRequest.class); + when(mockRequest.getUrl()).thenReturn(mock(Uri.class)); + when(mockRequest.getUrl().toString()).thenReturn("https://www.google.com"); + + // Test when the forwarding WebViewClient is overriding all url loading. + when(mockWebViewClient.shouldOverrideUrlLoading(any(), any(WebResourceRequest.class))) + .thenReturn(true); + assertTrue( + onCreateWindowWebViewClient.shouldOverrideUrlLoading( + mockOnCreateWindowWebView, mockRequest)); + verify(mockWebView, never()).loadUrl(any()); + + // Test when the forwarding WebViewClient is NOT overriding all url loading. + when(mockWebViewClient.shouldOverrideUrlLoading(any(), any(WebResourceRequest.class))) + .thenReturn(false); + assertTrue( + onCreateWindowWebViewClient.shouldOverrideUrlLoading( + mockOnCreateWindowWebView, mockRequest)); + verify(mockWebView).loadUrl("https://www.google.com"); + } + + @Test + public void onPermissionRequest() { + final WebChromeClientProxyApi mockApi = mock(WebChromeClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebChromeClientImpl instance = new WebChromeClientImpl(mockApi); + final android.webkit.PermissionRequest request = mock(PermissionRequest.class); + instance.onPermissionRequest(request); + + verify(mockApi).onPermissionRequest(eq(instance), eq(request), any()); + } + + @Test + public void onShowCustomView() { + final WebChromeClientProxyApi mockApi = mock(WebChromeClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebChromeClientImpl instance = new WebChromeClientImpl(mockApi); + final android.view.View view = mock(View.class); + final android.webkit.WebChromeClient.CustomViewCallback callback = + mock(WebChromeClient.CustomViewCallback.class); + instance.onShowCustomView(view, callback); + + verify(mockApi).onShowCustomView(eq(instance), eq(view), eq(callback), any()); + } + + @Test + public void onHideCustomView() { + final WebChromeClientProxyApi mockApi = mock(WebChromeClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebChromeClientImpl instance = new WebChromeClientImpl(mockApi); + instance.onHideCustomView(); + + verify(mockApi).onHideCustomView(eq(instance), any()); + } + + @Test + public void onGeolocationPermissionsShowPrompt() { + final WebChromeClientProxyApi mockApi = mock(WebChromeClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebChromeClientImpl instance = new WebChromeClientImpl(mockApi); + final String origin = "myString"; + final android.webkit.GeolocationPermissions.Callback callback = + mock(GeolocationPermissions.Callback.class); + instance.onGeolocationPermissionsShowPrompt(origin, callback); + + verify(mockApi) + .onGeolocationPermissionsShowPrompt(eq(instance), eq(origin), eq(callback), any()); + } + + @Test + public void onGeolocationPermissionsHidePrompt() { + final WebChromeClientProxyApi mockApi = mock(WebChromeClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebChromeClientImpl instance = new WebChromeClientImpl(mockApi); + instance.onGeolocationPermissionsHidePrompt(); + + verify(mockApi).onGeolocationPermissionsHidePrompt(eq(instance), any()); + } + + @Test + public void onConsoleMessage() { + final WebChromeClientProxyApi mockApi = mock(WebChromeClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebChromeClientImpl instance = new WebChromeClientImpl(mockApi); + instance.setReturnValueForOnConsoleMessage(true); + final android.webkit.ConsoleMessage message = mock(ConsoleMessage.class); + instance.onConsoleMessage(message); + + verify(mockApi).onConsoleMessage(eq(instance), eq(message), any()); + } + + @Test + public void onJsAlert() { + final WebChromeClientProxyApi mockApi = mock(WebChromeClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebChromeClientImpl instance = new WebChromeClientImpl(mockApi); + instance.setReturnValueForOnJsAlert(true); + final android.webkit.WebView webView = mock(WebView.class); + final String url = "myString"; + final String message = "myString"; + final JsResult mockJsResult = mock(JsResult.class); + instance.onJsAlert(webView, url, message, mockJsResult); + + verify(mockApi).onJsAlert(eq(instance), eq(webView), eq(url), eq(message), any()); + } + + @Test + public void onJsConfirm() { + final WebChromeClientProxyApi mockApi = mock(WebChromeClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebChromeClientImpl instance = new WebChromeClientImpl(mockApi); + instance.setReturnValueForOnJsConfirm(true); + final android.webkit.WebView webView = mock(WebView.class); + final String url = "myString"; + final String message = "myString"; + final JsResult mockJsResult = mock(JsResult.class); + instance.onJsConfirm(webView, url, message, mockJsResult); + + verify(mockApi).onJsConfirm(eq(instance), eq(webView), eq(url), eq(message), any()); + } + + @Test + public void onJsPrompt() { + final WebChromeClientProxyApi mockApi = mock(WebChromeClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebChromeClientImpl instance = new WebChromeClientImpl(mockApi); + instance.setReturnValueForOnJsPrompt(true); + final android.webkit.WebView webView = mock(WebView.class); + final String url = "myString"; + final String message = "myString"; + final String defaultValue = "myString"; + final JsPromptResult mockJsPromptResult = mock(JsPromptResult.class); + instance.onJsPrompt(webView, url, message, defaultValue, mockJsPromptResult); + + verify(mockApi) + .onJsPrompt(eq(instance), eq(webView), eq(url), eq(message), eq(defaultValue), any()); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebResourceErrorCompatTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebResourceErrorCompatTest.java new file mode 100644 index 0000000..c7a8996 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebResourceErrorCompatTest.java @@ -0,0 +1,38 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import androidx.webkit.WebResourceErrorCompat; +import org.junit.Test; + +public class WebResourceErrorCompatTest { + @Test + public void errorCode() { + final PigeonApiWebResourceErrorCompat api = + new TestProxyApiRegistrar().getPigeonApiWebResourceErrorCompat(); + + final WebResourceErrorCompat instance = mock(WebResourceErrorCompat.class); + final Long value = 0L; + when(instance.getErrorCode()).thenReturn(value.intValue()); + + assertEquals(value, (Long) api.errorCode(instance)); + } + + @Test + public void description() { + final PigeonApiWebResourceErrorCompat api = + new TestProxyApiRegistrar().getPigeonApiWebResourceErrorCompat(); + + final WebResourceErrorCompat instance = mock(WebResourceErrorCompat.class); + final String value = "myString"; + when(instance.getDescription()).thenReturn(value); + + assertEquals(value, api.description(instance)); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebResourceErrorTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebResourceErrorTest.java new file mode 100644 index 0000000..b330186 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebResourceErrorTest.java @@ -0,0 +1,38 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.webkit.WebResourceError; +import org.junit.Test; + +public class WebResourceErrorTest { + @Test + public void errorCode() { + final PigeonApiWebResourceError api = + new TestProxyApiRegistrar().getPigeonApiWebResourceError(); + + final WebResourceError instance = mock(WebResourceError.class); + final Long value = 0L; + when(instance.getErrorCode()).thenReturn(value.intValue()); + + assertEquals(value, (Long) api.errorCode(instance)); + } + + @Test + public void description() { + final PigeonApiWebResourceError api = + new TestProxyApiRegistrar().getPigeonApiWebResourceError(); + + final WebResourceError instance = mock(WebResourceError.class); + final String value = "myString"; + when(instance.getDescription()).thenReturn(value); + + assertEquals(value, api.description(instance)); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebResourceRequestTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebResourceRequestTest.java new file mode 100644 index 0000000..541cccf --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebResourceRequestTest.java @@ -0,0 +1,107 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.net.Uri; +import android.webkit.WebResourceRequest; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.Test; + +public class WebResourceRequestTest { + @Test + public void url() { + final PigeonApiWebResourceRequest api = + new TestProxyApiRegistrar().getPigeonApiWebResourceRequest(); + + final WebResourceRequest instance = mock(WebResourceRequest.class); + final String value = "myString"; + when(instance.getUrl()).thenReturn(Uri.parse(value)); + + assertEquals(value, api.url(instance)); + } + + @Test + public void isForMainFrame() { + final PigeonApiWebResourceRequest api = + new TestProxyApiRegistrar().getPigeonApiWebResourceRequest(); + + final WebResourceRequest instance = mock(WebResourceRequest.class); + final Boolean value = true; + when(instance.isForMainFrame()).thenReturn(value); + + assertEquals(value, api.isForMainFrame(instance)); + } + + @Test + public void isRedirect() { + final PigeonApiWebResourceRequest api = + new TestProxyApiRegistrar().getPigeonApiWebResourceRequest(); + + final WebResourceRequest instance = mock(WebResourceRequest.class); + final Boolean value = true; + when(instance.isRedirect()).thenReturn(value); + + assertEquals(value, api.isRedirect(instance)); + } + + @Test + public void hasGesture() { + final PigeonApiWebResourceRequest api = + new TestProxyApiRegistrar().getPigeonApiWebResourceRequest(); + + final WebResourceRequest instance = mock(WebResourceRequest.class); + final Boolean value = true; + when(instance.hasGesture()).thenReturn(value); + + assertEquals(value, api.hasGesture(instance)); + } + + @Test + public void method() { + final PigeonApiWebResourceRequest api = + new TestProxyApiRegistrar().getPigeonApiWebResourceRequest(); + + final WebResourceRequest instance = mock(WebResourceRequest.class); + final String value = "myString"; + when(instance.getMethod()).thenReturn(value); + + assertEquals(value, api.method(instance)); + } + + @Test + public void requestHeaders() { + final PigeonApiWebResourceRequest api = + new TestProxyApiRegistrar().getPigeonApiWebResourceRequest(); + + final WebResourceRequest instance = mock(WebResourceRequest.class); + final Map value = + new HashMap() { + { + put("myString", "myString1"); + } + }; + when(instance.getRequestHeaders()).thenReturn(value); + + assertEquals(value, api.requestHeaders(instance)); + } + + @Test + public void requestHeadersHandlesNull() { + final PigeonApiWebResourceRequest api = + new TestProxyApiRegistrar().getPigeonApiWebResourceRequest(); + + final WebResourceRequest instance = mock(WebResourceRequest.class); + final Map value = Collections.emptyMap(); + when(instance.getRequestHeaders()).thenReturn(value); + + assertEquals(value, api.requestHeaders(instance)); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebResourceResponseTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebResourceResponseTest.java new file mode 100644 index 0000000..b172040 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebResourceResponseTest.java @@ -0,0 +1,26 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.webkit.WebResourceResponse; +import org.junit.Test; + +public class WebResourceResponseTest { + @Test + public void statusCode() { + final PigeonApiWebResourceResponse api = + new TestProxyApiRegistrar().getPigeonApiWebResourceResponse(); + + final WebResourceResponse instance = mock(WebResourceResponse.class); + final Long value = 0L; + when(instance.getStatusCode()).thenReturn(value.intValue()); + + assertEquals(value, (Long) api.statusCode(instance)); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java new file mode 100644 index 0000000..2b959c0 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsCompatTest.java @@ -0,0 +1,39 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; + +import android.webkit.WebSettings; +import androidx.webkit.WebSettingsCompat; +import androidx.webkit.WebViewFeature; +import org.junit.Test; +import org.mockito.MockedStatic; + +public class WebSettingsCompatTest { + @Test + public void setPaymentRequestEnabled() { + final PigeonApiWebSettingsCompat api = + new TestProxyApiRegistrar().getPigeonApiWebSettingsCompat(); + + final WebSettings webSettings = mock(WebSettings.class); + + try (MockedStatic mockedStatic = mockStatic(WebSettingsCompat.class)) { + try (MockedStatic mockedWebViewFeature = mockStatic(WebViewFeature.class)) { + mockedWebViewFeature + .when(() -> WebViewFeature.isFeatureSupported(WebViewFeature.PAYMENT_REQUEST)) + .thenReturn(true); + api.setPaymentRequestEnabled(webSettings, true); + mockedStatic.verify(() -> WebSettingsCompat.setPaymentRequestEnabled(webSettings, true)); + } catch (Exception e) { + fail(e.toString()); + } + } catch (Exception e) { + fail(e.toString()); + } + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsTest.java new file mode 100644 index 0000000..c9be6d4 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsTest.java @@ -0,0 +1,179 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.webkit.WebSettings; +import org.junit.Test; + +public class WebSettingsTest { + @Test + public void setDomStorageEnabled() { + final PigeonApiWebSettings api = new TestProxyApiRegistrar().getPigeonApiWebSettings(); + + final WebSettings instance = mock(WebSettings.class); + final boolean flag = true; + api.setDomStorageEnabled(instance, flag); + + verify(instance).setDomStorageEnabled(flag); + } + + @Test + public void setJavaScriptCanOpenWindowsAutomatically() { + final PigeonApiWebSettings api = new TestProxyApiRegistrar().getPigeonApiWebSettings(); + + final WebSettings instance = mock(WebSettings.class); + final boolean flag = true; + api.setJavaScriptCanOpenWindowsAutomatically(instance, flag); + + verify(instance).setJavaScriptCanOpenWindowsAutomatically(flag); + } + + @Test + public void setSupportMultipleWindows() { + final PigeonApiWebSettings api = new TestProxyApiRegistrar().getPigeonApiWebSettings(); + + final WebSettings instance = mock(WebSettings.class); + final boolean support = true; + api.setSupportMultipleWindows(instance, support); + + verify(instance).setSupportMultipleWindows(support); + } + + @Test + public void setJavaScriptEnabled() { + final PigeonApiWebSettings api = new TestProxyApiRegistrar().getPigeonApiWebSettings(); + + final WebSettings instance = mock(WebSettings.class); + final boolean flag = true; + api.setJavaScriptEnabled(instance, flag); + + verify(instance).setJavaScriptEnabled(flag); + } + + @Test + public void setUserAgentString() { + final PigeonApiWebSettings api = new TestProxyApiRegistrar().getPigeonApiWebSettings(); + + final WebSettings instance = mock(WebSettings.class); + final String userAgentString = "myString"; + api.setUserAgentString(instance, userAgentString); + + verify(instance).setUserAgentString(userAgentString); + } + + @Test + public void setMediaPlaybackRequiresUserGesture() { + final PigeonApiWebSettings api = new TestProxyApiRegistrar().getPigeonApiWebSettings(); + + final WebSettings instance = mock(WebSettings.class); + final boolean require = true; + api.setMediaPlaybackRequiresUserGesture(instance, require); + + verify(instance).setMediaPlaybackRequiresUserGesture(require); + } + + @Test + public void setSupportZoom() { + final PigeonApiWebSettings api = new TestProxyApiRegistrar().getPigeonApiWebSettings(); + + final WebSettings instance = mock(WebSettings.class); + final boolean support = true; + api.setSupportZoom(instance, support); + + verify(instance).setSupportZoom(support); + } + + @Test + public void setLoadWithOverviewMode() { + final PigeonApiWebSettings api = new TestProxyApiRegistrar().getPigeonApiWebSettings(); + + final WebSettings instance = mock(WebSettings.class); + final boolean overview = true; + api.setLoadWithOverviewMode(instance, overview); + + verify(instance).setLoadWithOverviewMode(overview); + } + + @Test + public void setUseWideViewPort() { + final PigeonApiWebSettings api = new TestProxyApiRegistrar().getPigeonApiWebSettings(); + + final WebSettings instance = mock(WebSettings.class); + final boolean use = true; + api.setUseWideViewPort(instance, use); + + verify(instance).setUseWideViewPort(use); + } + + @Test + public void setDisplayZoomControls() { + final PigeonApiWebSettings api = new TestProxyApiRegistrar().getPigeonApiWebSettings(); + + final WebSettings instance = mock(WebSettings.class); + final boolean enabled = true; + api.setDisplayZoomControls(instance, enabled); + + verify(instance).setDisplayZoomControls(enabled); + } + + @Test + public void setBuiltInZoomControls() { + final PigeonApiWebSettings api = new TestProxyApiRegistrar().getPigeonApiWebSettings(); + + final WebSettings instance = mock(WebSettings.class); + final boolean enabled = true; + api.setBuiltInZoomControls(instance, enabled); + + verify(instance).setBuiltInZoomControls(enabled); + } + + @Test + public void setAllowFileAccess() { + final PigeonApiWebSettings api = new TestProxyApiRegistrar().getPigeonApiWebSettings(); + + final WebSettings instance = mock(WebSettings.class); + final boolean enabled = true; + api.setAllowFileAccess(instance, enabled); + + verify(instance).setAllowFileAccess(enabled); + } + + @Test + public void setTextZoom() { + final PigeonApiWebSettings api = new TestProxyApiRegistrar().getPigeonApiWebSettings(); + + final WebSettings instance = mock(WebSettings.class); + final long textZoom = 0L; + api.setTextZoom(instance, textZoom); + + verify(instance).setTextZoom((int) textZoom); + } + + @Test + public void getUserAgentString() { + final PigeonApiWebSettings api = new TestProxyApiRegistrar().getPigeonApiWebSettings(); + + final WebSettings instance = mock(WebSettings.class); + final String value = "myString"; + when(instance.getUserAgentString()).thenReturn(value); + + assertEquals(value, api.getUserAgentString(instance)); + } + + @Test + public void setMixedContentMode() { + final PigeonApiWebSettings api = new TestProxyApiRegistrar().getPigeonApiWebSettings(); + + final WebSettings instance = mock(WebSettings.class); + api.setMixedContentMode(instance, MixedContentMode.COMPATIBILITY_MODE); + + verify(instance).setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebStorageTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebStorageTest.java new file mode 100644 index 0000000..ed2e041 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebStorageTest.java @@ -0,0 +1,23 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.webkit.WebStorage; +import org.junit.Test; + +public class WebStorageTest { + @Test + public void deleteAllData() { + final PigeonApiWebStorage api = new TestProxyApiRegistrar().getPigeonApiWebStorage(); + + final WebStorage instance = mock(WebStorage.class); + api.deleteAllData(instance); + + verify(instance).deleteAllData(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java new file mode 100644 index 0000000..6ad5d70 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java @@ -0,0 +1,255 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.net.http.SslError; +import android.os.Message; +import android.webkit.ClientCertRequest; +import android.webkit.HttpAuthHandler; +import android.webkit.SslErrorHandler; +import android.webkit.WebResourceError; +import android.webkit.WebResourceRequest; +import android.webkit.WebView; +import io.flutter.plugins.webviewflutter.WebViewClientProxyApi.WebViewClientImpl; +import org.junit.Test; + +public class WebViewClientTest { + @Test + public void onPageStarted() { + final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebViewClientImpl instance = new WebViewClientProxyApi.WebViewClientImpl(mockApi); + final WebView webView = mock(WebView.class); + final String url = "myString"; + instance.onPageStarted(webView, url, null); + + verify(mockApi).onPageStarted(eq(instance), eq(webView), eq(url), any()); + } + + @Test + public void onReceivedError() { + final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebViewClientImpl instance = new WebViewClientImpl(mockApi); + final android.webkit.WebView webView = mock(WebView.class); + final android.webkit.WebResourceRequest request = mock(WebResourceRequest.class); + final android.webkit.WebResourceError error = mock(WebResourceError.class); + instance.onReceivedError(webView, request, error); + + verify(mockApi) + .onReceivedRequestError(eq(instance), eq(webView), eq(request), eq(error), any()); + } + + @Test + public void urlLoading() { + final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebViewClientImpl instance = new WebViewClientImpl(mockApi); + final android.webkit.WebView webView = mock(WebView.class); + final android.webkit.WebResourceRequest request = mock(WebResourceRequest.class); + instance.shouldOverrideUrlLoading(webView, request); + + verify(mockApi).requestLoading(eq(instance), eq(webView), eq(request), any()); + } + + @Test + public void urlLoadingForMainFrame() { + final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebViewClientImpl instance = new WebViewClientImpl(mockApi); + instance.setReturnValueForShouldOverrideUrlLoading(false); + final android.webkit.WebView webView = mock(WebView.class); + final android.webkit.WebResourceRequest request = mock(WebResourceRequest.class); + when(request.isForMainFrame()).thenReturn(true); + instance.shouldOverrideUrlLoading(webView, request); + + verify(mockApi).requestLoading(eq(instance), eq(webView), eq(request), any()); + } + + @Test + public void urlLoadingForMainFrameWithOverride() { + final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebViewClientImpl instance = new WebViewClientImpl(mockApi); + instance.setReturnValueForShouldOverrideUrlLoading(true); + final android.webkit.WebView webView = mock(WebView.class); + final android.webkit.WebResourceRequest request = mock(WebResourceRequest.class); + when(request.isForMainFrame()).thenReturn(true); + + assertTrue(instance.shouldOverrideUrlLoading(webView, request)); + verify(mockApi).requestLoading(eq(instance), eq(webView), eq(request), any()); + } + + @Test + public void urlLoadingNotForMainFrame() { + final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebViewClientImpl instance = new WebViewClientImpl(mockApi); + final android.webkit.WebView webView = mock(WebView.class); + final android.webkit.WebResourceRequest request = mock(WebResourceRequest.class); + when(request.isForMainFrame()).thenReturn(false); + instance.shouldOverrideUrlLoading(webView, request); + + verify(mockApi).requestLoading(eq(instance), eq(webView), eq(request), any()); + } + + @Test + public void urlLoadingNotForMainFrameWithOverride() { + final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebViewClientImpl instance = new WebViewClientImpl(mockApi); + final android.webkit.WebView webView = mock(WebView.class); + final android.webkit.WebResourceRequest request = mock(WebResourceRequest.class); + when(request.isForMainFrame()).thenReturn(false); + + assertFalse(instance.shouldOverrideUrlLoading(webView, request)); + verify(mockApi).requestLoading(eq(instance), eq(webView), eq(request), any()); + } + + @Test + public void doUpdateVisitedHistory() { + final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebViewClientImpl instance = new WebViewClientImpl(mockApi); + final android.webkit.WebView webView = mock(WebView.class); + final String url = "myString"; + final Boolean isReload = true; + instance.doUpdateVisitedHistory(webView, url, isReload); + + verify(mockApi).doUpdateVisitedHistory(eq(instance), eq(webView), eq(url), eq(isReload), any()); + } + + @Test + public void onReceivedHttpError() { + final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebViewClientImpl instance = new WebViewClientImpl(mockApi); + final android.webkit.WebView webView = mock(WebView.class); + final HttpAuthHandler handler = mock(HttpAuthHandler.class); + final String host = "myString"; + final String realm = "myString1"; + instance.onReceivedHttpAuthRequest(webView, handler, host, realm); + + verify(mockApi) + .onReceivedHttpAuthRequest( + eq(instance), eq(webView), eq(handler), eq(host), eq(realm), any()); + } + + @Test + public void onFormResubmission() { + final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebViewClientImpl instance = new WebViewClientImpl(mockApi); + final android.webkit.WebView view = mock(WebView.class); + final android.os.Message dontResend = mock(Message.class); + final android.os.Message resend = mock(Message.class); + instance.onFormResubmission(view, dontResend, resend); + + verify(mockApi).onFormResubmission(eq(instance), eq(view), eq(dontResend), eq(resend), any()); + } + + @Test + public void onLoadResource() { + final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebViewClientImpl instance = new WebViewClientImpl(mockApi); + final android.webkit.WebView view = mock(WebView.class); + final String url = "myString"; + instance.onLoadResource(view, url); + + verify(mockApi).onLoadResource(eq(instance), eq(view), eq(url), any()); + } + + @Test + public void onPageCommitVisible() { + final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebViewClientImpl instance = new WebViewClientImpl(mockApi); + final android.webkit.WebView view = mock(WebView.class); + final String url = "myString"; + instance.onPageCommitVisible(view, url); + + verify(mockApi).onPageCommitVisible(eq(instance), eq(view), eq(url), any()); + } + + @Test + public void onReceivedClientCertRequest() { + final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebViewClientImpl instance = new WebViewClientImpl(mockApi); + final android.webkit.WebView view = mock(WebView.class); + final android.webkit.ClientCertRequest request = mock(ClientCertRequest.class); + instance.onReceivedClientCertRequest(view, request); + + verify(mockApi).onReceivedClientCertRequest(eq(instance), eq(view), eq(request), any()); + } + + @Test + public void onReceivedLoginRequest() { + final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebViewClientImpl instance = new WebViewClientImpl(mockApi); + final android.webkit.WebView view = mock(WebView.class); + final String realm = "myString"; + final String account = "myString1"; + final String args = "myString2"; + instance.onReceivedLoginRequest(view, realm, account, args); + + verify(mockApi) + .onReceivedLoginRequest(eq(instance), eq(view), eq(realm), eq(account), eq(args), any()); + } + + @Test + public void onReceivedSslError() { + final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebViewClientImpl instance = new WebViewClientImpl(mockApi); + final android.webkit.WebView view = mock(WebView.class); + final android.webkit.SslErrorHandler handler = mock(SslErrorHandler.class); + final android.net.http.SslError error = mock(SslError.class); + instance.onReceivedSslError(view, handler, error); + + verify(mockApi).onReceivedSslError(eq(instance), eq(view), eq(handler), eq(error), any()); + } + + @Test + public void onScaleChanged() { + final WebViewClientProxyApi mockApi = mock(WebViewClientProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebViewClientImpl instance = new WebViewClientImpl(mockApi); + final android.webkit.WebView view = mock(WebView.class); + final float oldScale = 1.0f; + final float newScale = 2.0f; + instance.onScaleChanged(view, oldScale, newScale); + + verify(mockApi) + .onScaleChanged( + eq(instance), eq(view), eq((double) oldScale), eq((double) newScale), any()); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFeatureTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFeatureTest.java new file mode 100644 index 0000000..9af8b37 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFeatureTest.java @@ -0,0 +1,54 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mockStatic; + +import androidx.webkit.WebViewFeature; +import org.junit.Test; +import org.mockito.MockedStatic; + +public class WebViewFeatureTest { + @Test + public void isFeatureSupported() { + final PigeonApiWebViewFeature api = new TestProxyApiRegistrar().getPigeonApiWebViewFeature(); + + try (MockedStatic mockedStatic = mockStatic(WebViewFeature.class)) { + mockedStatic + .when(() -> WebViewFeature.isFeatureSupported("PAYMENT_REQUEST")) + .thenReturn(true); + + boolean result = api.isFeatureSupported("PAYMENT_REQUEST"); + + assertTrue(result); + + mockedStatic.verify(() -> WebViewFeature.isFeatureSupported("PAYMENT_REQUEST")); + } catch (Exception e) { + fail(e.toString()); + } + } + + @Test + public void isFeatureSupportedReturnsFalse() { + final PigeonApiWebViewFeature api = new TestProxyApiRegistrar().getPigeonApiWebViewFeature(); + + try (MockedStatic mockedStatic = mockStatic(WebViewFeature.class)) { + mockedStatic + .when(() -> WebViewFeature.isFeatureSupported("UNSUPPORTED_FEATURE")) + .thenReturn(false); + + boolean result = api.isFeatureSupported("UNSUPPORTED_FEATURE"); + + assertFalse(result); + + mockedStatic.verify(() -> WebViewFeature.isFeatureSupported("UNSUPPORTED_FEATURE")); + } catch (Exception e) { + fail(e.toString()); + } + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApiTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApiTest.java new file mode 100644 index 0000000..f6d244f --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApiTest.java @@ -0,0 +1,63 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.webkit.WebView; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.PluginRegistry; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.platform.PlatformViewRegistry; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +public class WebViewFlutterAndroidExternalApiTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock Context mockContext; + + @Mock BinaryMessenger mockBinaryMessenger; + + @Mock PlatformViewRegistry mockViewRegistry; + + @Mock FlutterPlugin.FlutterPluginBinding mockPluginBinding; + + @Test + public void getWebView() { + final WebViewFlutterPlugin webViewFlutterPlugin = new WebViewFlutterPlugin(); + + when(mockPluginBinding.getApplicationContext()).thenReturn(mockContext); + when(mockPluginBinding.getPlatformViewRegistry()).thenReturn(mockViewRegistry); + when(mockPluginBinding.getBinaryMessenger()).thenReturn(mockBinaryMessenger); + + webViewFlutterPlugin.onAttachedToEngine(mockPluginBinding); + + final AndroidWebkitLibraryPigeonInstanceManager instanceManager = + webViewFlutterPlugin.getInstanceManager(); + assertNotNull(instanceManager); + + final WebView mockWebView = mock(WebView.class); + instanceManager.addDartCreatedInstance(mockWebView, 0); + + final PluginRegistry mockPluginRegistry = mock(PluginRegistry.class); + when(mockPluginRegistry.get(WebViewFlutterPlugin.class)).thenReturn(webViewFlutterPlugin); + + final FlutterEngine mockFlutterEngine = mock(FlutterEngine.class); + when(mockFlutterEngine.getPlugins()).thenReturn(mockPluginRegistry); + + assertEquals(WebViewFlutterAndroidExternalApi.getWebView(mockFlutterEngine, 0), mockWebView); + + webViewFlutterPlugin.onDetachedFromEngine(mockPluginBinding); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewPointTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewPointTest.java new file mode 100644 index 0000000..1c6029f --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewPointTest.java @@ -0,0 +1,35 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.Test; + +public class WebViewPointTest { + @Test + public void x() { + final PigeonApiWebViewPoint api = new TestProxyApiRegistrar().getPigeonApiWebViewPoint(); + + final WebViewPoint instance = mock(WebViewPoint.class); + final Long value = 0L; + when(instance.getX()).thenReturn(value); + + assertEquals(value, (Long) api.x(instance)); + } + + @Test + public void y() { + final PigeonApiWebViewPoint api = new TestProxyApiRegistrar().getPigeonApiWebViewPoint(); + + final WebViewPoint instance = mock(WebViewPoint.class); + final Long value = 0L; + when(instance.getY()).thenReturn(value); + + assertEquals(value, (Long) api.y(instance)); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java new file mode 100644 index 0000000..3ca8ce9 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java @@ -0,0 +1,371 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.view.View; +import android.webkit.DownloadListener; +import android.webkit.ValueCallback; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import io.flutter.embedding.android.FlutterView; +import java.util.HashMap; +import java.util.Map; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +public class WebViewTest { + @Test + public void pigeon_defaultConstructor() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + assertTrue(api.pigeon_defaultConstructor() instanceof WebViewProxyApi.WebViewPlatformView); + } + + @Test + public void loadData() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + final String data = "VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg=="; + final String mimeType = "text/plain"; + final String encoding = "base64"; + api.loadData(instance, data, mimeType, encoding); + + verify(instance).loadData(data, mimeType, encoding); + } + + @Test + public void loadDataWithNullValues() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + final String data = "VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg=="; + final String mimeType = null; + final String encoding = null; + api.loadData(instance, data, mimeType, encoding); + + verify(instance).loadData(data, mimeType, encoding); + } + + @Test + public void loadDataWithBaseUrl() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + final String baseUrl = "myString"; + final String data = "myString1"; + final String mimeType = "myString2"; + final String encoding = "myString3"; + final String historyUrl = "myString4"; + api.loadDataWithBaseUrl(instance, baseUrl, data, mimeType, encoding, historyUrl); + + verify(instance).loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); + } + + @Test + public void loadDataWithBaseUrlAndNullValues() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + final String baseUrl = null; + final String data = "myString1"; + final String mimeType = null; + final String encoding = null; + final String historyUrl = null; + api.loadDataWithBaseUrl(instance, baseUrl, data, mimeType, encoding, historyUrl); + + verify(instance).loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); + } + + @Test + public void loadUrl() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + final String url = "myString"; + final Map headers = + new HashMap() { + { + put("myString", "myString"); + } + }; + api.loadUrl(instance, url, headers); + + verify(instance).loadUrl(url, headers); + } + + @Test + public void postUrl() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + final String url = "myString"; + final byte[] data = {(byte) 0xA1}; + api.postUrl(instance, url, data); + + verify(instance).postUrl(url, data); + } + + @Test + public void getUrl() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + final String value = "myString"; + when(instance.getUrl()).thenReturn(value); + + assertEquals(value, api.getUrl(instance)); + } + + @Test + public void canGoBack() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + final Boolean value = true; + when(instance.canGoBack()).thenReturn(value); + + assertEquals(value, api.canGoBack(instance)); + } + + @Test + public void canGoForward() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + final Boolean value = true; + when(instance.canGoForward()).thenReturn(value); + + assertEquals(value, api.canGoForward(instance)); + } + + @Test + public void goBack() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + api.goBack(instance); + + verify(instance).goBack(); + } + + @Test + public void goForward() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + api.goForward(instance); + + verify(instance).goForward(); + } + + @Test + public void reload() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + api.reload(instance); + + verify(instance).reload(); + } + + @Test + public void clearCache() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + final boolean includeDiskFiles = true; + api.clearCache(instance, includeDiskFiles); + + verify(instance).clearCache(includeDiskFiles); + } + + @Test + public void evaluateJavaScript() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + final String script = "2 + 2"; + final String[] resultValue = new String[1]; + api.evaluateJavascript( + instance, + script, + ResultCompat.asCompatCallback( + reply -> { + resultValue[0] = reply.getOrNull(); + return null; + })); + + @SuppressWarnings("unchecked") + final ArgumentCaptor> callbackCaptor = + ArgumentCaptor.forClass(ValueCallback.class); + verify(instance).evaluateJavascript(eq(script), callbackCaptor.capture()); + + final String result = "resultValue"; + callbackCaptor.getValue().onReceiveValue(result); + assertEquals(resultValue[0], result); + } + + @Test + public void getTitle() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + final String value = "myString"; + when(instance.getTitle()).thenReturn(value); + + assertEquals(value, api.getTitle(instance)); + } + + @Test + public void setWebViewClient() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + final android.webkit.WebViewClient client = mock(WebViewClient.class); + api.setWebViewClient(instance, client); + + verify(instance).setWebViewClient(client); + } + + @Test + public void addJavaScriptChannel() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + final JavaScriptChannel channel = mock(JavaScriptChannel.class); + api.addJavaScriptChannel(instance, channel); + + verify(instance).addJavascriptInterface(channel, channel.javaScriptChannelName); + } + + @Test + public void removeJavaScriptChannel() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + final String name = "myString"; + api.removeJavaScriptChannel(instance, name); + + verify(instance).removeJavascriptInterface(name); + } + + @Test + public void setDownloadListener() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + final android.webkit.DownloadListener listener = mock(DownloadListener.class); + api.setDownloadListener(instance, listener); + + verify(instance).setDownloadListener(listener); + } + + @Test + public void setWebChromeClient() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + final io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl client = + mock(WebChromeClientProxyApi.WebChromeClientImpl.class); + api.setWebChromeClient(instance, client); + + verify(instance).setWebChromeClient(client); + } + + @Test + public void setBackgroundColor() { + final PigeonApiWebView api = new TestProxyApiRegistrar().getPigeonApiWebView(); + + final WebView instance = mock(WebView.class); + final long color = 0L; + api.setBackgroundColor(instance, color); + + verify(instance).setBackgroundColor((int) color); + } + + @Test + public void defaultWebChromeClientIsSecureWebChromeClient() { + final WebViewProxyApi mockApi = mock(WebViewProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + final WebViewProxyApi.WebViewPlatformView webView = + new WebViewProxyApi.WebViewPlatformView(mockApi); + + assertTrue( + webView.getWebChromeClient() instanceof WebChromeClientProxyApi.SecureWebChromeClient); + assertFalse( + webView.getWebChromeClient() instanceof WebChromeClientProxyApi.WebChromeClientImpl); + } + + // This test verifies that WebView.destroy() is called when the Dart instance is garbage collected. + // This requires adding + // + // ``` + // val instance: Any? = getInstance(identifier) + // if (instance is WebViewProxyApi.WebViewPlatformView) { + // instance.destroy() + // } + // ``` + // + // to `AndroidWebkitLibraryPigeonInstanceManager.remove` in the generated code. This is done as a + // temporary workaround to prevent the transition to the new pigeon ProxyApi generator from being + // a breaking change. Maintainers should consider whether continuing to call `destroy` on + // `WebView` is valuable. + @Test + public void destroyWebViewWhenRemovedFromInstanceManager() { + final WebViewProxyApi.WebViewPlatformView mockWebView = + mock(WebViewProxyApi.WebViewPlatformView.class); + + final TestProxyApiRegistrar registrar = new TestProxyApiRegistrar(); + registrar.getInstanceManager().addDartCreatedInstance(mockWebView, 0); + + registrar.getInstanceManager().remove(0); + verify(mockWebView).destroy(); + } + + @Test + public void setImportantForAutofillForParentFlutterView() { + final WebViewProxyApi mockApi = mock(WebViewProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + final WebViewProxyApi.WebViewPlatformView webView = + new WebViewProxyApi.WebViewPlatformView(mockApi); + + final WebViewProxyApi.WebViewPlatformView webViewSpy = spy(webView); + final FlutterView mockFlutterView = mock(FlutterView.class); + when(webViewSpy.getParent()).thenReturn(mockFlutterView); + + webViewSpy.onAttachedToWindow(); + + verify(mockFlutterView).setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_YES); + } + + @Test + public void onScrollChanged() { + final WebViewProxyApi mockApi = mock(WebViewProxyApi.class); + when(mockApi.getPigeonRegistrar()).thenReturn(new TestProxyApiRegistrar()); + + final WebViewProxyApi.WebViewPlatformView instance = + new WebViewProxyApi.WebViewPlatformView(mockApi); + final Long left = 0L; + final Long top = 0L; + final Long oldLeft = 0L; + final Long oldTop = 0L; + instance.onScrollChanged( + left.intValue(), top.intValue(), oldLeft.intValue(), oldTop.intValue()); + + verify(mockApi) + .onScrollChanged(eq(instance), eq(left), eq(top), eq(oldLeft), eq(oldTop), any()); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/example/README.md b/local_packages/webview_flutter_android-4.10.5/example/README.md new file mode 100644 index 0000000..96b8bb1 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/README.md @@ -0,0 +1,9 @@ +# Platform Implementation Test App + +This is a test app for manual testing and automated integration testing +of this platform implementation. It is not intended to demonstrate actual use of +this package, since the intent is that plugin clients use the app-facing +package. + +Unless you are making changes to this implementation package, this example is +very unlikely to be relevant. diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/app/build.gradle b/local_packages/webview_flutter_android-4.10.5/example/android/app/build.gradle new file mode 100644 index 0000000..d5c6de2 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/android/app/build.gradle @@ -0,0 +1,70 @@ +plugins { + id "com.android.application" + id "org.jetbrains.kotlin.android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + namespace = "io.flutter.plugins.webviewflutterexample" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "io.flutter.plugins.webviewflutterandroidexample" + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = '17' + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.debug + } + } + lint { + disable 'InvalidPackage' + } +} + +flutter { + source = '../..' +} + +dependencies { + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test:runner:1.2.0") + androidTestImplementation("androidx.test.espresso:espresso-core:3.2.0") + api("androidx.test:core:1.4.0") +} diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/app/gradle/wrapper/gradle-wrapper.properties b/local_packages/webview_flutter_android-4.10.5/example/android/app/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..0c68fbc --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/android/app/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists + diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java new file mode 100644 index 0000000..4ed2112 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java @@ -0,0 +1,21 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/* + * Annotation to aid repository tooling in determining if a test is + * a native java unit test or a java class with a dart integration. + * + * See: https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md#enabling-android-ui-tests + * for more infomation. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface DartIntegrationTest {} diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/BackgroundColorTest.java b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/BackgroundColorTest.java new file mode 100644 index 0000000..884ef0b --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/BackgroundColorTest.java @@ -0,0 +1,61 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutterexample; + +import static androidx.test.espresso.flutter.EspressoFlutter.onFlutterWidget; +import static androidx.test.espresso.flutter.action.FlutterActions.click; +import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withText; +import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withValueKey; +import static org.junit.Assert.assertEquals; + +import android.graphics.Bitmap; +import android.graphics.Color; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.screenshot.ScreenCapture; +import androidx.test.runner.screenshot.Screenshot; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class BackgroundColorTest { + @Rule + public ActivityTestRule myActivityTestRule = + new ActivityTestRule<>(DriverExtensionActivity.class, true, false); + + @Before + public void setUp() { + ActivityScenario.launch(DriverExtensionActivity.class); + } + + @Ignore("Doesn't run in Firebase Test Lab: https://github.com/flutter/flutter/issues/94748") + @Test + public void backgroundColor() { + onFlutterWidget(withValueKey("ShowPopupMenu")).perform(click()); + onFlutterWidget(withValueKey("ShowTransparentBackgroundExample")).perform(click()); + onFlutterWidget(withText("Transparent background test")); + + final ScreenCapture screenCapture = Screenshot.capture(); + final Bitmap screenBitmap = screenCapture.getBitmap(); + + final int centerLeftColor = + screenBitmap.getPixel(10, (int) Math.floor(screenBitmap.getHeight() / 2.0)); + final int centerColor = + screenBitmap.getPixel( + (int) Math.floor(screenBitmap.getWidth() / 2.0), + (int) Math.floor(screenBitmap.getHeight() / 2.0)); + + // Flutter Colors.green color : 0xFF4CAF50 + // https://github.com/flutter/flutter/blob/f4abaa0735eba4dfd8f33f73363911d63931fe03/packages/flutter/lib/src/material/colors.dart#L1208 + // The background color of the webview is : rgba(0, 0, 0, 0.5) + // The expected color is : rgba(38, 87, 40, 1) -> 0xFF265728 + assertEquals(0xFF265728, centerLeftColor); + assertEquals(Color.RED, centerColor); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/MainActivityTest.java b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/MainActivityTest.java new file mode 100644 index 0000000..4cd11ed --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/MainActivityTest.java @@ -0,0 +1,19 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutterexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.integration_test.FlutterTestRunner; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.plugins.DartIntegrationTest; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@DartIntegrationTest +@RunWith(FlutterTestRunner.class) +public class MainActivityTest { + @Rule + public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); +} diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/WebViewTest.java b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/WebViewTest.java new file mode 100644 index 0000000..ff0b3b5 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/WebViewTest.java @@ -0,0 +1,23 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutterexample; + +import static org.junit.Assert.assertTrue; + +import androidx.test.core.app.ActivityScenario; +import io.flutter.plugins.webviewflutter.WebViewFlutterPlugin; +import org.junit.Test; + +public class WebViewTest { + @Test + public void webViewPluginIsAdded() { + final ActivityScenario scenario = + ActivityScenario.launch(WebViewTestActivity.class); + scenario.onActivity( + activity -> { + assertTrue(activity.engine.getPlugins().has(WebViewFlutterPlugin.class)); + }); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/app/src/debug/AndroidManifest.xml b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..110b9ab --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/AndroidManifest.xml b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e61c403 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/DriverExtensionActivity.java b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/DriverExtensionActivity.java new file mode 100644 index 0000000..03bd4b5 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/DriverExtensionActivity.java @@ -0,0 +1,16 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutterexample; + +import androidx.annotation.NonNull; +import io.flutter.embedding.android.FlutterActivity; + +public class DriverExtensionActivity extends FlutterActivity { + @Override + @NonNull + public String getDartEntrypointFunctionName() { + return "appMain"; + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/WebViewTestActivity.java b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/WebViewTestActivity.java new file mode 100644 index 0000000..065d7da --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/WebViewTestActivity.java @@ -0,0 +1,20 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutterexample; + +import androidx.annotation.NonNull; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; + +// Extends FlutterActivity to make the FlutterEngine accessible for testing. +public class WebViewTestActivity extends FlutterActivity { + public FlutterEngine engine; + + @Override + public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { + super.configureFlutterEngine(flutterEngine); + engine = flutterEngine; + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/drawable/launch_background.xml b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/values/styles.xml b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..00fa441 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/build.gradle b/local_packages/webview_flutter_android-4.10.5/example/android/build.gradle new file mode 100644 index 0000000..a3e7dce --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/android/build.gradle @@ -0,0 +1,37 @@ +allprojects { + repositories { + // See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. + def artifactRepoKey = 'ARTIFACT_HUB_REPOSITORY' + if (System.getenv().containsKey(artifactRepoKey)) { + println "Using artifact hub" + maven { url System.getenv(artifactRepoKey) } + } + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} + +// Build the plugin project with warnings enabled. This is here rather than +// in the plugin itself to avoid breaking clients that have different +// warnings (e.g., deprecation warnings from a newer SDK than this project +// builds with). +gradle.projectsEvaluated { + project(":webview_flutter_android") { + tasks.withType(JavaCompile) { + // Ignore classfile due to https://issuetracker.google.com/issues/342067844 + options.compilerArgs << "-Xlint:all" << "-Werror" << "-Xlint:-classfile" + } + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/gradle.properties b/local_packages/webview_flutter_android-4.10.5/example/android/gradle.properties new file mode 100644 index 0000000..da87b2c --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx4G +android.useAndroidX=true diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/gradle/wrapper/gradle-wrapper.properties b/local_packages/webview_flutter_android-4.10.5/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..c24d16c --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip + diff --git a/local_packages/webview_flutter_android-4.10.5/example/android/settings.gradle b/local_packages/webview_flutter_android-4.10.5/example/android/settings.gradle new file mode 100644 index 0000000..96fce46 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/android/settings.gradle @@ -0,0 +1,27 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +// See https://github.com/flutter/flutter/blob/master/docs/ecosystem/Plugins-and-Packages-repository-structure.md#gradle-structure for more info. +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.11.0" apply false + id "org.jetbrains.kotlin.android" version "2.2.0" apply false + id "com.google.cloud.artifactregistry.gradle-plugin" version "2.2.1" +} + +include ":app" diff --git a/local_packages/webview_flutter_android-4.10.5/example/assets/sample_audio.ogg b/local_packages/webview_flutter_android-4.10.5/example/assets/sample_audio.ogg new file mode 100644 index 0000000..27e1710 Binary files /dev/null and b/local_packages/webview_flutter_android-4.10.5/example/assets/sample_audio.ogg differ diff --git a/local_packages/webview_flutter_android-4.10.5/example/assets/sample_video.mp4 b/local_packages/webview_flutter_android-4.10.5/example/assets/sample_video.mp4 new file mode 100644 index 0000000..a203d0c Binary files /dev/null and b/local_packages/webview_flutter_android-4.10.5/example/assets/sample_video.mp4 differ diff --git a/local_packages/webview_flutter_android-4.10.5/example/assets/www/index.html b/local_packages/webview_flutter_android-4.10.5/example/assets/www/index.html new file mode 100644 index 0000000..46e2087 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/assets/www/index.html @@ -0,0 +1,21 @@ + + + + + +Load file or HTML string example + + + + +

Local demo page

+

+ This is an example page used to demonstrate how to load a local file or HTML + string using the Flutter + webview plugin. +

+ + + diff --git a/local_packages/webview_flutter_android-4.10.5/example/assets/www/styles/style.css b/local_packages/webview_flutter_android-4.10.5/example/assets/www/styles/style.css new file mode 100644 index 0000000..c2140b8 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/assets/www/styles/style.css @@ -0,0 +1,3 @@ +h1 { + color: blue; +} \ No newline at end of file diff --git a/local_packages/webview_flutter_android-4.10.5/example/integration_test/webview_flutter_test.dart b/local_packages/webview_flutter_android-4.10.5/example/integration_test/webview_flutter_test.dart new file mode 100644 index 0000000..1579e6a --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/integration_test/webview_flutter_test.dart @@ -0,0 +1,1835 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This test is run using `flutter drive` by the CI (see /script/tool/README.md +// in this repository for details on driving that tooling manually), but can +// also be run using `flutter test` directly during development. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:webview_flutter_android/src/android_proxy.dart'; +import 'package:webview_flutter_android/src/android_webkit.g.dart' + as android_webkit; +import 'package:webview_flutter_android/src/weak_reference_utils.dart'; +import 'package:webview_flutter_android/webview_flutter_android.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +// `IntegrationTestWidgetsFlutterBinding.watchPerformance` is throwing an +// exception when called. See https://github.com/flutter/flutter/issues/159500 +// for more info. +const bool skipFor159500 = true; + +Future main() async { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + final HttpServer server = await HttpServer.bind(InternetAddress.anyIPv4, 0); + unawaited( + server.forEach((HttpRequest request) { + if (request.uri.path == '/hello.txt') { + request.response.writeln('Hello, world.'); + } else if (request.uri.path == '/secondary.txt') { + request.response.writeln('How are you today?'); + } else if (request.uri.path == '/headers') { + request.response.writeln('${request.headers}'); + } else if (request.uri.path == '/favicon.ico') { + request.response.statusCode = HttpStatus.notFound; + } else if (request.uri.path == '/http-basic-authentication') { + final bool isAuthenticating = request.headers['Authorization'] != null; + if (isAuthenticating) { + request.response.writeln('Authorized'); + } else { + request.response.headers.add( + 'WWW-Authenticate', + 'Basic realm="Test realm"', + ); + request.response.statusCode = HttpStatus.unauthorized; + } + } else { + fail('unexpected request: ${request.method} ${request.uri}'); + } + request.response.close(); + }), + ); + final String prefixUrl = 'http://${server.address.address}:${server.port}'; + final String primaryUrl = '$prefixUrl/hello.txt'; + final String secondaryUrl = '$prefixUrl/secondary.txt'; + final String headersUrl = '$prefixUrl/headers'; + final String basicAuthUrl = '$prefixUrl/http-basic-authentication'; + + testWidgets('loadRequest', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageFinished.complete()); + await controller.setPlatformNavigationDelegate(delegate); + await controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await pageFinished.future; + + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, primaryUrl); + }); + + testWidgets( + 'withWeakRefenceTo allows encapsulating class to be garbage collected', + (WidgetTester tester) async { + final Completer gcCompleter = Completer(); + final android_webkit.PigeonInstanceManager instanceManager = + android_webkit.PigeonInstanceManager( + onWeakReferenceRemoved: gcCompleter.complete, + ); + + ClassWithCallbackClass? instance = ClassWithCallbackClass(); + instanceManager.addHostCreatedInstance(instance.callbackClass, 0); + instance = null; + + // Force garbage collection. + await IntegrationTestWidgetsFlutterBinding.instance.watchPerformance( + () async { + await tester.pumpAndSettle(); + }, + ); + + final int gcIdentifier = await gcCompleter.future; + expect(gcIdentifier, 0); + }, + timeout: const Timeout(Duration(seconds: 10)), + skip: skipFor159500, + ); + + testWidgets( + 'WebView is released by garbage collection', + (WidgetTester tester) async { + final Completer webViewGCCompleter = Completer(); + + const int webViewToken = -1; + final Finalizer finalizer = Finalizer((int token) { + if (token == webViewToken) { + webViewGCCompleter.complete(); + } + }); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + AndroidWebViewWidgetCreationParams( + controller: PlatformWebViewController( + AndroidWebViewControllerCreationParams( + androidWebViewProxy: AndroidWebViewProxy( + newWebView: + ({ + void Function( + android_webkit.WebView, + int, + int, + int, + int, + )? + onScrollChanged, + }) { + final android_webkit.WebView webView = + android_webkit.WebView( + onScrollChanged: onScrollChanged, + ); + finalizer.attach(webView, webViewToken); + return webView; + }, + ), + ), + ), + ), + ).build(context); + }, + ), + ); + await tester.pumpAndSettle(); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + AndroidWebViewWidgetCreationParams( + controller: PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ), + ), + ).build(context); + }, + ), + ); + await tester.pumpAndSettle(); + + // Force garbage collection. + await IntegrationTestWidgetsFlutterBinding.instance.watchPerformance( + () async { + await tester.pumpAndSettle(); + }, + ); + + await tester.pumpAndSettle(); + await expectLater(webViewGCCompleter.future, completes); + }, + timeout: const Timeout(Duration(seconds: 10)), + skip: skipFor159500, + ); + + testWidgets('runJavaScriptReturningResult', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageFinished.complete()); + await controller.setPlatformNavigationDelegate(delegate); + await controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await pageFinished.future; + + await expectLater( + controller.runJavaScriptReturningResult('1 + 1'), + completion(2), + ); + }); + + testWidgets('loadRequest with headers', (WidgetTester tester) async { + final Map headers = { + 'test_header': 'flutter_test_header', + }; + + final StreamController pageLoads = StreamController(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((String url) => pageLoads.add(url)); + await controller.setPlatformNavigationDelegate(delegate); + await controller.loadRequest( + LoadRequestParams(uri: Uri.parse(headersUrl), headers: headers), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await pageLoads.stream.firstWhere((String url) => url == headersUrl); + + final String content = + await controller.runJavaScriptReturningResult( + 'document.documentElement.innerText', + ) + as String; + expect(content.contains('flutter_test_header'), isTrue); + }); + + testWidgets('JavascriptChannel', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageFinished.complete()); + await controller.setPlatformNavigationDelegate(delegate); + + final Completer channelCompleter = Completer(); + await controller.addJavaScriptChannel( + JavaScriptChannelParams( + name: 'Echo', + onMessageReceived: (JavaScriptMessage message) { + channelCompleter.complete(message.message); + }, + ), + ); + + await controller.loadHtmlString( + 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await pageFinished.future; + + await controller.runJavaScript('Echo.postMessage("hello");'); + await expectLater(channelCompleter.future, completion('hello')); + }); + + testWidgets('resize webview', (WidgetTester tester) async { + final Completer initialResizeCompleter = Completer(); + final Completer buttonTapResizeCompleter = Completer(); + final Completer onPageFinished = Completer(); + + bool resizeButtonTapped = false; + await tester.pumpWidget( + ResizableWebView( + onResize: () { + if (resizeButtonTapped) { + buttonTapResizeCompleter.complete(); + } else { + initialResizeCompleter.complete(); + } + }, + onPageFinished: () => onPageFinished.complete(), + ), + ); + + await onPageFinished.future; + // Wait for a potential call to resize after page is loaded. + await initialResizeCompleter.future.timeout( + const Duration(seconds: 3), + onTimeout: () => null, + ); + + resizeButtonTapped = true; + + await tester.tap(find.byKey(const ValueKey('resizeButton'))); + await tester.pumpAndSettle(); + + await expectLater(buttonTapResizeCompleter.future, completes); + }); + + testWidgets('set custom userAgent', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setUserAgent('Custom_User_Agent1'); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageFinished.complete()); + await controller.setPlatformNavigationDelegate(delegate); + + await controller.loadRequest( + LoadRequestParams(uri: Uri.parse('about:blank')), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await pageFinished.future; + + final String? customUserAgent = await controller.getUserAgent(); + expect(customUserAgent, 'Custom_User_Agent1'); + }); + + group('Video playback policy', () { + late String videoTestBase64; + setUpAll(() async { + final ByteData videoData = await rootBundle.load( + 'assets/sample_video.mp4', + ); + final String base64VideoData = base64Encode( + Uint8List.view(videoData.buffer), + ); + final String videoTest = + ''' + + Video auto play + + + + +
+
+ +
+ + + '''; + videoTestBase64 = base64Encode(const Utf8Encoder().convert(videoTest)); + }); + + testWidgets('Auto media playback', (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + AndroidWebViewController controller = AndroidWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setMediaPlaybackRequiresUserGesture(false); + AndroidNavigationDelegate delegate = AndroidNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); + + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$videoTestBase64', + ), + ), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await pageLoaded.future; + + bool isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + expect(isPaused, false); + + pageLoaded = Completer(); + controller = AndroidWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + delegate = AndroidNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); + + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$videoTestBase64', + ), + ), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await pageLoaded.future; + + isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + expect(isPaused, true); + }); + + testWidgets('Video plays inline', (WidgetTester tester) async { + final Completer pageLoaded = Completer(); + final Completer videoPlaying = Completer(); + + final AndroidWebViewController controller = AndroidWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setMediaPlaybackRequiresUserGesture(false); + final AndroidNavigationDelegate delegate = AndroidNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); + + await controller.addJavaScriptChannel( + JavaScriptChannelParams( + name: 'VideoTestTime', + onMessageReceived: (JavaScriptMessage message) { + final double currentTime = double.parse(message.message); + // Let it play for at least 1 second to make sure the related video's properties are set. + if (currentTime > 1 && !videoPlaying.isCompleted) { + videoPlaying.complete(null); + } + }, + ), + ); + + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$videoTestBase64', + ), + ), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await pageLoaded.future; + + // Makes sure we get the correct event that indicates the video is actually playing. + await videoPlaying.future; + + Object fullScreen = await controller.runJavaScriptReturningResult( + 'isFullScreen();', + ); + + if (fullScreen is String) { + fullScreen = fullScreen == 'true'; + } + + expect(fullScreen, false); + }); + + testWidgets('Video plays fullscreen', (WidgetTester tester) async { + final Completer fullscreenEntered = Completer(); + final Completer fullscreenExited = Completer(); + final Completer pageLoaded = Completer(); + + final AndroidWebViewController controller = AndroidWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setMediaPlaybackRequiresUserGesture(false); + final AndroidNavigationDelegate delegate = AndroidNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); + await controller.setCustomWidgetCallbacks( + onHideCustomWidget: () { + fullscreenExited.complete(); + }, + onShowCustomWidget: (Widget webView, void Function() onHideCustomView) { + fullscreenEntered.complete(); + onHideCustomView(); + }, + ); + + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$videoTestBase64', + ), + ), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams( + key: const Key('webview_widget'), + controller: controller, + ), + ).build(context); + }, + ), + ); + + await pageLoaded.future; + + await tester.pumpAndSettle(); + + // Due to security reasons, Chrome doesn't allow to programmatically + // toggle a video to fullscreen unless the call is directly coming from + // a user triggered event. + // The top half of the loaded web content contains a clickable div, which + // is tapped using the code below, triggering a user event. + // + // The offset of 20 x 20 is chosen at random. + await tester.tapAt(const Offset(20, 20)); + + await expectLater(fullscreenEntered.future, completes); + await expectLater(fullscreenExited.future, completes); + }); + }); + + group('Audio playback policy', () { + late String audioTestBase64; + setUpAll(() async { + final ByteData audioData = await rootBundle.load( + 'assets/sample_audio.ogg', + ); + final String base64AudioData = base64Encode( + Uint8List.view(audioData.buffer), + ); + final String audioTest = + ''' + + Audio auto play + + + + + + + '''; + audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest)); + }); + + testWidgets('Auto media playback', (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + AndroidWebViewController controller = AndroidWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.setMediaPlaybackRequiresUserGesture(false); + AndroidNavigationDelegate delegate = AndroidNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); + + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$audioTestBase64', + ), + ), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await pageLoaded.future; + + bool isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + expect(isPaused, false); + + pageLoaded = Completer(); + controller = AndroidWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + delegate = AndroidNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$audioTestBase64', + ), + ), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await pageLoaded.future; + + isPaused = + await controller.runJavaScriptReturningResult('isPaused();') as bool; + expect(isPaused, true); + }); + }); + + testWidgets('getTitle', (WidgetTester tester) async { + const String getTitleTest = ''' + + Some title + + + + + '''; + final String getTitleTestBase64 = base64Encode( + const Utf8Encoder().convert(getTitleTest), + ); + final Completer pageLoaded = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); + + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$getTitleTestBase64', + ), + ), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await pageLoaded.future; + + // On at least iOS, it does not appear to be guaranteed that the native + // code has the title when the page load completes. Execute some JavaScript + // before checking the title to ensure that the page has been fully parsed + // and processed. + await controller.runJavaScript('1;'); + + final String? title = await controller.getTitle(); + expect(title, 'Some title'); + }); + + group('Programmatic Scroll', () { + testWidgets('setAndGetAndListenScrollPosition', ( + WidgetTester tester, + ) async { + const String scrollTestPage = ''' + + + + + + +
+ + + '''; + + final String scrollTestPageBase64 = base64Encode( + const Utf8Encoder().convert(scrollTestPage), + ); + + final Completer pageLoaded = Completer(); + ScrollPositionChange? recordedPosition; + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); + await controller.setOnScrollPositionChange(( + ScrollPositionChange contentOffsetChange, + ) { + recordedPosition = contentOffsetChange; + }); + + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', + ), + ), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await pageLoaded.future; + + await tester.pumpAndSettle(const Duration(seconds: 3)); + + Offset scrollPos = await controller.getScrollPosition(); + + // Check scrollTo() + const int X_SCROLL = 123; + const int Y_SCROLL = 321; + // Get the initial position; this ensures that scrollTo is actually + // changing something, but also gives the native view's scroll position + // time to settle. + expect(scrollPos.dx, isNot(X_SCROLL)); + expect(scrollPos.dy, isNot(Y_SCROLL)); + expect(recordedPosition, null); + + await controller.scrollTo(X_SCROLL, Y_SCROLL); + scrollPos = await controller.getScrollPosition(); + expect(scrollPos.dx, X_SCROLL); + expect(scrollPos.dy, Y_SCROLL); + expect(recordedPosition?.x, X_SCROLL); + expect(recordedPosition?.y, Y_SCROLL); + + // Check scrollBy() (on top of scrollTo()) + await controller.scrollBy(X_SCROLL, Y_SCROLL); + scrollPos = await controller.getScrollPosition(); + expect(scrollPos.dx, X_SCROLL * 2); + expect(scrollPos.dy, Y_SCROLL * 2); + expect(recordedPosition?.x, X_SCROLL * 2); + expect(recordedPosition?.y, Y_SCROLL * 2); + }); + }); + + group('NavigationDelegate', () { + const String blankPage = ''; + final String blankPageEncoded = + 'data:text/html;charset=utf-8;base64,' + '${base64Encode(const Utf8Encoder().convert(blankPage))}'; + + testWidgets('can allow requests', (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await delegate.setOnNavigationRequest(( + NavigationRequest navigationRequest, + ) { + return navigationRequest.url.contains('youtube.com') + ? NavigationDecision.prevent + : NavigationDecision.navigate; + }); + await controller.setPlatformNavigationDelegate(delegate); + + await controller.loadRequest( + LoadRequestParams(uri: Uri.parse(blankPageEncoded)), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await pageLoaded.future; // Wait for initial page load. + + pageLoaded = Completer(); + await controller.runJavaScript('location.href = "$secondaryUrl"'); + await pageLoaded.future; // Wait for the next page load. + + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, secondaryUrl); + }); + + testWidgets('onWebResourceError', (WidgetTester tester) async { + final Completer errorCompleter = + Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnWebResourceError((WebResourceError error) { + errorCompleter.complete(error); + }); + await controller.setPlatformNavigationDelegate(delegate); + + await controller.loadRequest( + LoadRequestParams(uri: Uri.parse('https://www.notawebsite..com')), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + final WebResourceError error = await errorCompleter.future; + expect(error, isNotNull); + + expect(error.errorType, isNotNull); + expect(error.url?.startsWith('https://www.notawebsite..com'), isTrue); + }); + + testWidgets('onWebResourceError is not called with valid url', ( + WidgetTester tester, + ) async { + final Completer errorCompleter = + Completer(); + final Completer pageFinishCompleter = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageFinishCompleter.complete()); + await delegate.setOnWebResourceError((WebResourceError error) { + errorCompleter.complete(error); + }); + await controller.setPlatformNavigationDelegate(delegate); + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', + ), + ), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + expect(errorCompleter.future, doesNotComplete); + await pageFinishCompleter.future; + }); + + testWidgets('onHttpError', (WidgetTester tester) async { + final Completer errorCompleter = + Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnHttpError((HttpResponseError error) { + errorCompleter.complete(error); + }); + await controller.setPlatformNavigationDelegate(delegate); + await controller.loadRequest( + LoadRequestParams(uri: Uri.parse('$prefixUrl/favicon.ico')), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + final HttpResponseError error = await errorCompleter.future; + + expect(error, isNotNull); + expect(error.response?.statusCode, 404); + }); + + testWidgets('onHttpError is not called when no HTTP error is received', ( + WidgetTester tester, + ) async { + const String testPage = ''' + + + + + + '''; + + final Completer errorCompleter = + Completer(); + final Completer pageFinishCompleter = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnHttpError((HttpResponseError error) { + errorCompleter.complete(error); + }); + await delegate.setOnPageFinished((_) => pageFinishCompleter.complete()); + await controller.setPlatformNavigationDelegate(delegate); + await controller.loadHtmlString(testPage); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + expect(errorCompleter.future, doesNotComplete); + await pageFinishCompleter.future; + }); + + testWidgets('can block requests', (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await delegate.setOnNavigationRequest(( + NavigationRequest navigationRequest, + ) { + return navigationRequest.url.contains('youtube.com') + ? NavigationDecision.prevent + : NavigationDecision.navigate; + }); + await controller.setPlatformNavigationDelegate(delegate); + + await controller.loadRequest( + LoadRequestParams(uri: Uri.parse(blankPageEncoded)), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await pageLoaded.future; // Wait for initial page load. + + pageLoaded = Completer(); + await controller.runJavaScript( + 'location.href = "https://www.youtube.com/"', + ); + + // There should never be any second page load, since our new URL is + // blocked. Still wait for a potential page change for some time in order + // to give the test a chance to fail. + await pageLoaded.future.timeout( + const Duration(milliseconds: 500), + onTimeout: () => false, + ); + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, isNot(contains('youtube.com'))); + }); + + testWidgets('supports asynchronous decisions', (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await delegate.setOnNavigationRequest(( + NavigationRequest navigationRequest, + ) async { + NavigationDecision decision = NavigationDecision.prevent; + decision = await Future.delayed( + const Duration(milliseconds: 10), + () => NavigationDecision.navigate, + ); + return decision; + }); + await controller.setPlatformNavigationDelegate(delegate); + await controller.loadRequest( + LoadRequestParams(uri: Uri.parse(blankPageEncoded)), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await pageLoaded.future; // Wait for initial page load. + + pageLoaded = Completer(); + await controller.runJavaScript('location.href = "$secondaryUrl"'); + await pageLoaded.future; // Wait for second page to load. + + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, secondaryUrl); + }); + + testWidgets('can receive url changes', (WidgetTester tester) async { + final Completer pageLoaded = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); + await controller.loadRequest( + LoadRequestParams(uri: Uri.parse(blankPageEncoded)), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await pageLoaded.future; + await delegate.setOnPageFinished((_) {}); + + final Completer urlChangeCompleter = Completer(); + await delegate.setOnUrlChange((UrlChange change) { + urlChangeCompleter.complete(change.url); + }); + + await controller.runJavaScript('location.href = "$primaryUrl"'); + + await expectLater(urlChangeCompleter.future, completion(primaryUrl)); + }); + + testWidgets('can receive updates to history state', ( + WidgetTester tester, + ) async { + final Completer pageLoaded = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); + await controller.loadRequest( + LoadRequestParams(uri: Uri.parse(primaryUrl)), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await pageLoaded.future; + await delegate.setOnPageFinished((_) {}); + + final Completer urlChangeCompleter = Completer(); + await delegate.setOnUrlChange((UrlChange change) { + urlChangeCompleter.complete(change.url); + }); + + await controller.runJavaScript( + 'window.history.pushState({}, "", "secondary.txt");', + ); + + await expectLater(urlChangeCompleter.future, completion(secondaryUrl)); + }); + }); + + testWidgets('can receive HTTP basic auth requests', ( + WidgetTester tester, + ) async { + final Completer authRequested = Completer(); + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + + final PlatformNavigationDelegate navigationDelegate = + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await navigationDelegate.setOnHttpAuthRequest( + (HttpAuthRequest request) => authRequested.complete(), + ); + await controller.setPlatformNavigationDelegate(navigationDelegate); + + // Clear cache so that the auth request is always received and we don't get + // a cached response. + await controller.clearCache(); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + AndroidWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await controller.loadRequest( + LoadRequestParams(uri: Uri.parse(basicAuthUrl)), + ); + + await expectLater(authRequested.future, completes); + }); + + testWidgets('can reply to HTTP basic auth requests', ( + WidgetTester tester, + ) async { + final Completer pageFinished = Completer(); + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + + final PlatformNavigationDelegate navigationDelegate = + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await navigationDelegate.setOnPageFinished((_) => pageFinished.complete()); + await navigationDelegate.setOnHttpAuthRequest( + (HttpAuthRequest request) => request.onProceed( + const WebViewCredential(user: 'user', password: 'password'), + ), + ); + await controller.setPlatformNavigationDelegate(navigationDelegate); + + // Clear cache so that the auth request is always received and we do not get + // a cached response. + await controller.clearCache(); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + AndroidWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await controller.loadRequest( + LoadRequestParams(uri: Uri.parse(basicAuthUrl)), + ); + + await expectLater(pageFinished.future, completes); + }); + + testWidgets('target _blank opens in same window', ( + WidgetTester tester, + ) async { + final Completer pageLoaded = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await controller.runJavaScript('window.open("$primaryUrl", "_blank")'); + await pageLoaded.future; + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, primaryUrl); + }); + + testWidgets('can open new window and go back', (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); + await controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + expect(controller.currentUrl(), completion(primaryUrl)); + await pageLoaded.future; + pageLoaded = Completer(); + + await controller.runJavaScript('window.open("$secondaryUrl")'); + await pageLoaded.future; + pageLoaded = Completer(); + expect(controller.currentUrl(), completion(secondaryUrl)); + + expect(controller.canGoBack(), completion(true)); + await controller.goBack(); + await pageLoaded.future; + await expectLater(controller.currentUrl(), completion(primaryUrl)); + }); + + testWidgets('JavaScript does not run in parent window', ( + WidgetTester tester, + ) async { + const String iframe = ''' + + + '''; + final String iframeTestBase64 = base64Encode( + const Utf8Encoder().convert(iframe), + ); + + final String openWindowTest = + ''' + + + + XSS test + + + + + + '''; + final String openWindowTestBase64 = base64Encode( + const Utf8Encoder().convert(openWindowTest), + ); + + final Completer pageLoadCompleter = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageLoadCompleter.complete()); + await controller.setPlatformNavigationDelegate(delegate); + await controller.loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,$openWindowTestBase64', + ), + ), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await pageLoadCompleter.future; + + final bool iframeLoaded = + await controller.runJavaScriptReturningResult('iframeLoaded') as bool; + expect(iframeLoaded, true); + + final String elementText = + await controller.runJavaScriptReturningResult( + 'document.querySelector("p") && document.querySelector("p").textContent', + ) + as String; + expect(elementText, 'null'); + }); + + testWidgets( + '`AndroidWebViewController` can be reused with a new `AndroidWebViewWidget`', + (WidgetTester tester) async { + Completer pageLoaded = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + final PlatformNavigationDelegate delegate = PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ); + await delegate.setOnPageFinished((_) => pageLoaded.complete()); + await controller.setPlatformNavigationDelegate(delegate); + await controller.loadRequest( + LoadRequestParams(uri: Uri.parse(primaryUrl)), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await pageLoaded.future; + + await tester.pumpWidget(Container()); + await tester.pumpAndSettle(); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + pageLoaded = Completer(); + await controller.loadRequest( + LoadRequestParams(uri: Uri.parse(primaryUrl)), + ); + await expectLater(pageLoaded.future, completes); + }, + ); + + testWidgets('can receive JavaScript alert dialogs', ( + WidgetTester tester, + ) async { + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + + final Completer alertMessage = Completer(); + await controller.setOnJavaScriptAlertDialog(( + JavaScriptAlertDialogRequest request, + ) async { + alertMessage.complete(request.message); + }); + + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await controller.runJavaScript('alert("alert message")'); + await expectLater(alertMessage.future, completion('alert message')); + }); + + testWidgets('can receive JavaScript confirm dialogs', ( + WidgetTester tester, + ) async { + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + + final Completer confirmMessage = Completer(); + await controller.setOnJavaScriptConfirmDialog(( + JavaScriptConfirmDialogRequest request, + ) async { + confirmMessage.complete(request.message); + return true; + }); + + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await controller.runJavaScript('confirm("confirm message")'); + await expectLater(confirmMessage.future, completion('confirm message')); + }); + + testWidgets('can receive JavaScript prompt dialogs', ( + WidgetTester tester, + ) async { + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + + await controller.setOnJavaScriptTextInputDialog(( + JavaScriptTextInputDialogRequest request, + ) async { + return 'return message'; + }); + + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + await controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + final Object promptResponse = await controller.runJavaScriptReturningResult( + 'prompt("input message", "default text")', + ); + expect(promptResponse, '"return message"'); + }); + + group('Logging', () { + testWidgets('can receive console log messages', ( + WidgetTester tester, + ) async { + const String testPage = ''' + + + + WebResourceError test + + +

Test page

+ + + '''; + + final Completer debugMessageReceived = Completer(); + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + await controller.setJavaScriptMode(JavaScriptMode.unrestricted); + + await controller.setOnConsoleMessage((JavaScriptConsoleMessage message) { + debugMessageReceived.complete( + '${message.level.name}:${message.message}', + ); + }); + + await controller.loadHtmlString(testPage); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + ), + ); + + await expectLater( + debugMessageReceived.future, + completion('debug:Debug message'), + ); + }); + }); +} + +class ResizableWebView extends StatefulWidget { + const ResizableWebView({ + super.key, + required this.onResize, + required this.onPageFinished, + }); + + final VoidCallback onResize; + final VoidCallback onPageFinished; + + @override + State createState() => ResizableWebViewState(); +} + +class ResizableWebViewState extends State { + late final PlatformWebViewController controller = + PlatformWebViewController(const PlatformWebViewControllerCreationParams()) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => widget.onPageFinished()), + ) + ..addJavaScriptChannel( + JavaScriptChannelParams( + name: 'Resize', + onMessageReceived: (_) { + widget.onResize(); + }, + ), + ) + ..loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'data:text/html;charset=utf-8;base64,${base64Encode(const Utf8Encoder().convert(resizePage))}', + ), + ), + ); + + double webViewWidth = 200; + double webViewHeight = 200; + + static const String resizePage = ''' + + Resize test + + + + + + '''; + + @override + Widget build(BuildContext context) { + return Directionality( + textDirection: TextDirection.ltr, + child: Column( + children: [ + SizedBox( + width: webViewWidth, + height: webViewHeight, + child: PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context), + ), + TextButton( + key: const Key('resizeButton'), + onPressed: () { + setState(() { + webViewWidth += 100.0; + webViewHeight += 100.0; + }); + }, + child: const Text('ResizeButton'), + ), + ], + ), + ); + } +} + +class CopyableObjectWithCallback + extends android_webkit.PigeonInternalProxyApiBaseClass { + CopyableObjectWithCallback(this.callback); + + final VoidCallback callback; + + @override + // ignore: non_constant_identifier_names + CopyableObjectWithCallback pigeon_copy() { + return CopyableObjectWithCallback(callback); + } +} + +class ClassWithCallbackClass { + ClassWithCallbackClass() { + callbackClass = CopyableObjectWithCallback( + withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return () { + // Weak reference to `this` in callback. + // ignore: unnecessary_statements + weakReference; + }; + }), + ); + } + + late final CopyableObjectWithCallback callbackClass; +} diff --git a/local_packages/webview_flutter_android-4.10.5/example/integration_test/webview_flutter_test_legacy.dart b/local_packages/webview_flutter_android-4.10.5/example/integration_test/webview_flutter_test_legacy.dart new file mode 100644 index 0000000..aff6f1a --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/integration_test/webview_flutter_test_legacy.dart @@ -0,0 +1,1691 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This test is run using `flutter drive` by the CI (see /script/tool/README.md +// in this repository for details on driving that tooling manually), but can +// also be run using `flutter test` directly during development. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:webview_flutter_android/src/android_webkit.g.dart' as android; +import 'package:webview_flutter_android/src/weak_reference_utils.dart'; +import 'package:webview_flutter_android/src/webview_flutter_android_legacy.dart'; +import 'package:webview_flutter_android_example/legacy/navigation_decision.dart'; +import 'package:webview_flutter_android_example/legacy/navigation_request.dart'; +import 'package:webview_flutter_android_example/legacy/web_view.dart'; +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; + +Future main() async { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + final HttpServer server = await HttpServer.bind(InternetAddress.anyIPv4, 0); + unawaited( + server.forEach((HttpRequest request) { + if (request.uri.path == '/hello.txt') { + request.response.writeln('Hello, world.'); + } else if (request.uri.path == '/secondary.txt') { + request.response.writeln('How are you today?'); + } else if (request.uri.path == '/headers') { + request.response.writeln('${request.headers}'); + } else if (request.uri.path == '/favicon.ico') { + request.response.statusCode = HttpStatus.notFound; + } else { + fail('unexpected request: ${request.method} ${request.uri}'); + } + request.response.close(); + }), + ); + final String prefixUrl = 'http://${server.address.address}:${server.port}'; + final String primaryUrl = '$prefixUrl/hello.txt'; + final String secondaryUrl = '$prefixUrl/secondary.txt'; + final String headersUrl = '$prefixUrl/headers'; + + testWidgets('initialUrl', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final Completer pageFinishedCompleter = Completer(); + await tester.pumpWidget( + MaterialApp( + home: Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + onPageFinished: pageFinishedCompleter.complete, + ), + ), + ), + ); + + final WebViewController controller = await controllerCompleter.future; + await pageFinishedCompleter.future; + + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, primaryUrl); + }); + + testWidgets('loadUrl', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageLoads = StreamController(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + onPageFinished: (String url) { + pageLoads.add(url); + }, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + + await controller.loadUrl(secondaryUrl); + await expectLater( + pageLoads.stream.firstWhere((String url) => url == secondaryUrl), + completion(secondaryUrl), + ); + }); + + testWidgets( + 'withWeakRefenceTo allows encapsulating class to be garbage collected', + (WidgetTester tester) async { + final Completer gcCompleter = Completer(); + final android.PigeonInstanceManager instanceManager = + android.PigeonInstanceManager( + onWeakReferenceRemoved: gcCompleter.complete, + ); + + ClassWithCallbackClass? instance = ClassWithCallbackClass(); + instanceManager.addHostCreatedInstance(instance.callbackClass, 0); + instance = null; + + // Force garbage collection. + await IntegrationTestWidgetsFlutterBinding.instance.watchPerformance( + () async { + await tester.pumpAndSettle(); + }, + ); + + final int gcIdentifier = await gcCompleter.future; + expect(gcIdentifier, 0); + }, + timeout: const Timeout(Duration(seconds: 10)), + ); + + // TODO(bparrishMines): This test is skipped because of + // https://github.com/flutter/flutter/issues/123327 + testWidgets('WebView is released by garbage collection', ( + WidgetTester tester, + ) async { + final Completer webViewGCCompleter = Completer(); + + late final android.PigeonInstanceManager instanceManager; + instanceManager = android.PigeonInstanceManager( + onWeakReferenceRemoved: (int identifier) { + final android.PigeonInternalProxyApiBaseClass instance = instanceManager + .getInstanceWithWeakReference(identifier)!; + if (instance is android.WebView && !webViewGCCompleter.isCompleted) { + webViewGCCompleter.complete(); + } + }, + ); + + // Continually recreate web views until one is disposed through garbage + // collection. + while (!webViewGCCompleter.isCompleted) { + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return AndroidWebView(instanceManager: instanceManager).build( + context: context, + creationParams: CreationParams( + webSettings: WebSettings( + hasNavigationDelegate: false, + userAgent: const WebSetting.of('woeifj'), + ), + ), + javascriptChannelRegistry: JavascriptChannelRegistry( + {}, + ), + webViewPlatformCallbacksHandler: TestPlatformCallbacksHandler(), + ); + }, + ), + ); + await tester.pumpAndSettle(); + + await tester.pumpWidget(Container()); + await tester.pumpAndSettle(); + } + }, skip: true); + + testWidgets('evaluateJavascript', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + final String result = await controller.evaluateJavascript('1 + 1'); + expect(result, equals('2')); + }); + + testWidgets('loadUrl with headers', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageStarts = StreamController(); + final StreamController pageLoads = StreamController(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageStarted: (String url) { + pageStarts.add(url); + }, + onPageFinished: (String url) { + pageLoads.add(url); + }, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + final Map headers = { + 'test_header': 'flutter_test_header', + }; + await controller.loadUrl(headersUrl, headers: headers); + + await pageStarts.stream.firstWhere((String url) => url == headersUrl); + await pageLoads.stream.firstWhere((String url) => url == headersUrl); + + final String content = await controller.runJavascriptReturningResult( + 'document.documentElement.innerText', + ); + expect(content.contains('flutter_test_header'), isTrue); + }); + + testWidgets('JavascriptChannel', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final Completer pageStarted = Completer(); + final Completer pageLoaded = Completer(); + final Completer channelCompleter = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + // This is the data URL for: '' + initialUrl: + 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + javascriptChannels: { + JavascriptChannel( + name: 'Echo', + onMessageReceived: (JavascriptMessage message) { + channelCompleter.complete(message.message); + }, + ), + }, + onPageStarted: (String url) { + pageStarted.complete(null); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + await pageStarted.future; + await pageLoaded.future; + + expect(channelCompleter.isCompleted, isFalse); + await controller.runJavascript('Echo.postMessage("hello");'); + + await expectLater(channelCompleter.future, completion('hello')); + }); + + testWidgets('resize webview', (WidgetTester tester) async { + final Completer initialResizeCompleter = Completer(); + final Completer buttonTapResizeCompleter = Completer(); + final Completer onPageFinished = Completer(); + + bool resizeButtonTapped = false; + await tester.pumpWidget( + ResizableWebView( + onResize: (_) { + if (resizeButtonTapped) { + buttonTapResizeCompleter.complete(); + } else { + initialResizeCompleter.complete(); + } + }, + onPageFinished: () => onPageFinished.complete(), + ), + ); + await onPageFinished.future; + // Wait for a potential call to resize after page is loaded. + await initialResizeCompleter.future.timeout( + const Duration(seconds: 3), + onTimeout: () => null, + ); + + resizeButtonTapped = true; + await tester.tap(find.byKey(const ValueKey('resizeButton'))); + await tester.pumpAndSettle(); + expect(buttonTapResizeCompleter.future, completes); + }); + + testWidgets('set custom userAgent', (WidgetTester tester) async { + final Completer controllerCompleter1 = + Completer(); + final GlobalKey globalKey = GlobalKey(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: globalKey, + initialUrl: 'about:blank', + javascriptMode: JavascriptMode.unrestricted, + userAgent: 'Custom_User_Agent1', + onWebViewCreated: (WebViewController controller) { + controllerCompleter1.complete(controller); + }, + ), + ), + ); + final WebViewController controller1 = await controllerCompleter1.future; + final String customUserAgent1 = await _getUserAgent(controller1); + expect(customUserAgent1, 'Custom_User_Agent1'); + // rebuild the WebView with a different user agent. + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: globalKey, + initialUrl: 'about:blank', + javascriptMode: JavascriptMode.unrestricted, + userAgent: 'Custom_User_Agent2', + ), + ), + ); + + final String customUserAgent2 = await _getUserAgent(controller1); + expect(customUserAgent2, 'Custom_User_Agent2'); + }); + + testWidgets('use default platform userAgent after webView is rebuilt', ( + WidgetTester tester, + ) async { + final Completer controllerCompleter = + Completer(); + final GlobalKey globalKey = GlobalKey(); + // Build the webView with no user agent to get the default platform user agent. + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: globalKey, + initialUrl: primaryUrl, + javascriptMode: JavascriptMode.unrestricted, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + final String defaultPlatformUserAgent = await _getUserAgent(controller); + // rebuild the WebView with a custom user agent. + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: globalKey, + initialUrl: 'about:blank', + javascriptMode: JavascriptMode.unrestricted, + userAgent: 'Custom_User_Agent', + ), + ), + ); + final String customUserAgent = await _getUserAgent(controller); + expect(customUserAgent, 'Custom_User_Agent'); + // rebuilds the WebView with no user agent. + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: globalKey, + initialUrl: 'about:blank', + javascriptMode: JavascriptMode.unrestricted, + ), + ), + ); + + final String customUserAgent2 = await _getUserAgent(controller); + expect(customUserAgent2, defaultPlatformUserAgent); + }); + + group('Video playback policy', () { + late String videoTestBase64; + setUpAll(() async { + final ByteData videoData = await rootBundle.load( + 'assets/sample_video.mp4', + ); + final String base64VideoData = base64Encode( + Uint8List.view(videoData.buffer), + ); + final String videoTest = + ''' + + Video auto play + + + + + + + '''; + videoTestBase64 = base64Encode(const Utf8Encoder().convert(videoTest)); + }); + + testWidgets('Auto media playback', (WidgetTester tester) async { + Completer controllerCompleter = + Completer(); + Completer pageLoaded = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + ), + ), + ); + WebViewController controller = await controllerCompleter.future; + await pageLoaded.future; + + String isPaused = await controller.runJavascriptReturningResult( + 'isPaused();', + ); + expect(isPaused, _webviewBool(false)); + + controllerCompleter = Completer(); + pageLoaded = Completer(); + + // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + + controller = await controllerCompleter.future; + await pageLoaded.future; + + isPaused = await controller.runJavascriptReturningResult('isPaused();'); + expect(isPaused, _webviewBool(true)); + }); + + testWidgets('Changes to initialMediaPlaybackPolicy are ignored', ( + WidgetTester tester, + ) async { + final Completer controllerCompleter = + Completer(); + Completer pageLoaded = Completer(); + + final GlobalKey key = GlobalKey(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: key, + initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + await pageLoaded.future; + + String isPaused = await controller.runJavascriptReturningResult( + 'isPaused();', + ); + expect(isPaused, _webviewBool(false)); + + pageLoaded = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: key, + initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + + await controller.reload(); + + await pageLoaded.future; + + isPaused = await controller.runJavascriptReturningResult('isPaused();'); + expect(isPaused, _webviewBool(false)); + }); + + testWidgets('Video plays inline when allowsInlineMediaPlayback is true', ( + WidgetTester tester, + ) async { + final Completer controllerCompleter = + Completer(); + final Completer pageLoaded = Completer(); + final Completer videoPlaying = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + javascriptChannels: { + JavascriptChannel( + name: 'VideoTestTime', + onMessageReceived: (JavascriptMessage message) { + final double currentTime = double.parse(message.message); + // Let it play for at least 1 second to make sure the related video's properties are set. + if (currentTime > 1 && !videoPlaying.isCompleted) { + videoPlaying.complete(null); + } + }, + ), + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + allowsInlineMediaPlayback: true, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + await pageLoaded.future; + + // Pump once to trigger the video play. + await tester.pump(); + + // Makes sure we get the correct event that indicates the video is actually playing. + await videoPlaying.future; + + final String fullScreen = await controller.runJavascriptReturningResult( + 'isFullScreen();', + ); + expect(fullScreen, _webviewBool(false)); + }); + }); + + group('Audio playback policy', () { + late String audioTestBase64; + setUpAll(() async { + final ByteData audioData = await rootBundle.load( + 'assets/sample_audio.ogg', + ); + final String base64AudioData = base64Encode( + Uint8List.view(audioData.buffer), + ); + final String audioTest = + ''' + + Audio auto play + + + + + + + '''; + audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest)); + }); + + testWidgets('Auto media playback', (WidgetTester tester) async { + Completer controllerCompleter = + Completer(); + Completer pageStarted = Completer(); + Completer pageLoaded = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageStarted: (String url) { + pageStarted.complete(null); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + ), + ), + ); + WebViewController controller = await controllerCompleter.future; + await pageStarted.future; + await pageLoaded.future; + + String isPaused = await controller.runJavascriptReturningResult( + 'isPaused();', + ); + expect(isPaused, _webviewBool(false)); + + controllerCompleter = Completer(); + pageStarted = Completer(); + pageLoaded = Completer(); + + // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageStarted: (String url) { + pageStarted.complete(null); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + + controller = await controllerCompleter.future; + await pageStarted.future; + await pageLoaded.future; + + isPaused = await controller.runJavascriptReturningResult('isPaused();'); + expect(isPaused, _webviewBool(true)); + }); + + testWidgets('Changes to initialMediaPlaybackPolicy are ignored', ( + WidgetTester tester, + ) async { + final Completer controllerCompleter = + Completer(); + Completer pageStarted = Completer(); + Completer pageLoaded = Completer(); + + final GlobalKey key = GlobalKey(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: key, + initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageStarted: (String url) { + pageStarted.complete(null); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + await pageStarted.future; + await pageLoaded.future; + + String isPaused = await controller.runJavascriptReturningResult( + 'isPaused();', + ); + expect(isPaused, _webviewBool(false)); + + pageStarted = Completer(); + pageLoaded = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: key, + initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageStarted: (String url) { + pageStarted.complete(null); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + + await controller.reload(); + + await pageStarted.future; + await pageLoaded.future; + + isPaused = await controller.runJavascriptReturningResult('isPaused();'); + expect(isPaused, _webviewBool(false)); + }); + }); + + testWidgets('getTitle', (WidgetTester tester) async { + const String getTitleTest = ''' + + Some title + + + + + '''; + final String getTitleTestBase64 = base64Encode( + const Utf8Encoder().convert(getTitleTest), + ); + final Completer pageStarted = Completer(); + final Completer pageLoaded = Completer(); + final Completer controllerCompleter = + Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + initialUrl: 'data:text/html;charset=utf-8;base64,$getTitleTestBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + onPageStarted: (String url) { + pageStarted.complete(null); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + + final WebViewController controller = await controllerCompleter.future; + await pageStarted.future; + await pageLoaded.future; + + final String? title = await controller.getTitle(); + expect(title, 'Some title'); + }); + + group('Programmatic Scroll', () { + testWidgets('setAndGetScrollPosition', (WidgetTester tester) async { + const String scrollTestPage = ''' + + + + + + +
+ + + '''; + + final String scrollTestPageBase64 = base64Encode( + const Utf8Encoder().convert(scrollTestPage), + ); + + final Completer pageLoaded = Completer(); + final Completer controllerCompleter = + Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + initialUrl: + 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + + final WebViewController controller = await controllerCompleter.future; + await pageLoaded.future; + + await tester.pumpAndSettle(const Duration(seconds: 3)); + + int scrollPosX = await controller.getScrollX(); + int scrollPosY = await controller.getScrollY(); + + // Check scrollTo() + const int X_SCROLL = 123; + const int Y_SCROLL = 321; + // Get the initial position; this ensures that scrollTo is actually + // changing something, but also gives the native view's scroll position + // time to settle. + expect(scrollPosX, isNot(X_SCROLL)); + expect(scrollPosX, isNot(Y_SCROLL)); + + await controller.scrollTo(X_SCROLL, Y_SCROLL); + scrollPosX = await controller.getScrollX(); + scrollPosY = await controller.getScrollY(); + expect(scrollPosX, X_SCROLL); + expect(scrollPosY, Y_SCROLL); + + // Check scrollBy() (on top of scrollTo()) + await controller.scrollBy(X_SCROLL, Y_SCROLL); + scrollPosX = await controller.getScrollX(); + scrollPosY = await controller.getScrollY(); + expect(scrollPosX, X_SCROLL * 2); + expect(scrollPosY, Y_SCROLL * 2); + }); + }); + + group('SurfaceAndroidWebView', () { + setUpAll(() { + WebView.platform = SurfaceAndroidWebView(); + }); + + tearDownAll(() { + WebView.platform = AndroidWebView(); + }); + + testWidgets('setAndGetScrollPosition', (WidgetTester tester) async { + const String scrollTestPage = ''' + + + + + + +
+ + + '''; + + final String scrollTestPageBase64 = base64Encode( + const Utf8Encoder().convert(scrollTestPage), + ); + + final Completer pageLoaded = Completer(); + final Completer controllerCompleter = + Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + initialUrl: + 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + + final WebViewController controller = await controllerCompleter.future; + await pageLoaded.future; + + await tester.pumpAndSettle(const Duration(seconds: 3)); + + // Check scrollTo() + const int X_SCROLL = 123; + const int Y_SCROLL = 321; + + await controller.scrollTo(X_SCROLL, Y_SCROLL); + int scrollPosX = await controller.getScrollX(); + int scrollPosY = await controller.getScrollY(); + expect(X_SCROLL, scrollPosX); + expect(Y_SCROLL, scrollPosY); + + // Check scrollBy() (on top of scrollTo()) + await controller.scrollBy(X_SCROLL, Y_SCROLL); + scrollPosX = await controller.getScrollX(); + scrollPosY = await controller.getScrollY(); + expect(X_SCROLL * 2, scrollPosX); + expect(Y_SCROLL * 2, scrollPosY); + }); + + testWidgets('inputs are scrolled into view when focused', ( + WidgetTester tester, + ) async { + const String scrollTestPage = ''' + + + + + + +
+ + + + '''; + + final String scrollTestPageBase64 = base64Encode( + const Utf8Encoder().convert(scrollTestPage), + ); + + final Completer pageLoaded = Completer(); + final Completer controllerCompleter = + Completer(); + + await tester.runAsync(() async { + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: SizedBox( + width: 200, + height: 200, + child: WebView( + initialUrl: + 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + javascriptMode: JavascriptMode.unrestricted, + ), + ), + ), + ); + await Future.delayed(const Duration(milliseconds: 20)); + await tester.pump(); + }); + + final WebViewController controller = await controllerCompleter.future; + await pageLoaded.future; + final String viewportRectJSON = await _runJavaScriptReturningResult( + controller, + 'JSON.stringify(viewport.getBoundingClientRect())', + ); + final Map viewportRectRelativeToViewport = + jsonDecode(viewportRectJSON) as Map; + + num getDomRectComponent( + Map rectAsJson, + String component, + ) { + return rectAsJson[component]! as num; + } + + // Check that the input is originally outside of the viewport. + + final String initialInputClientRectJSON = + await _runJavaScriptReturningResult( + controller, + 'JSON.stringify(inputEl.getBoundingClientRect())', + ); + final Map initialInputClientRectRelativeToViewport = + jsonDecode(initialInputClientRectJSON) as Map; + + expect( + getDomRectComponent( + initialInputClientRectRelativeToViewport, + 'bottom', + ) <= + getDomRectComponent(viewportRectRelativeToViewport, 'bottom'), + isFalse, + ); + + await controller.runJavascript('inputEl.focus()'); + + // Check that focusing the input brought it into view. + + final String lastInputClientRectJSON = + await _runJavaScriptReturningResult( + controller, + 'JSON.stringify(inputEl.getBoundingClientRect())', + ); + final Map lastInputClientRectRelativeToViewport = + jsonDecode(lastInputClientRectJSON) as Map; + + expect( + getDomRectComponent(lastInputClientRectRelativeToViewport, 'top') >= + getDomRectComponent(viewportRectRelativeToViewport, 'top'), + isTrue, + ); + expect( + getDomRectComponent(lastInputClientRectRelativeToViewport, 'bottom') <= + getDomRectComponent(viewportRectRelativeToViewport, 'bottom'), + isTrue, + ); + + expect( + getDomRectComponent(lastInputClientRectRelativeToViewport, 'left') >= + getDomRectComponent(viewportRectRelativeToViewport, 'left'), + isTrue, + ); + expect( + getDomRectComponent(lastInputClientRectRelativeToViewport, 'right') <= + getDomRectComponent(viewportRectRelativeToViewport, 'right'), + isTrue, + ); + }); + }); + + group('NavigationDelegate', () { + const String blankPage = ''; + final String blankPageEncoded = + 'data:text/html;charset=utf-8;base64,' + '${base64Encode(const Utf8Encoder().convert(blankPage))}'; + + testWidgets('can allow requests', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageLoads = + StreamController.broadcast(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: blankPageEncoded, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + navigationDelegate: (NavigationRequest request) { + return request.url.contains('youtube.com') + ? NavigationDecision.prevent + : NavigationDecision.navigate; + }, + onPageFinished: (String url) => pageLoads.add(url), + ), + ), + ); + + await pageLoads.stream.first; // Wait for initial page load. + final WebViewController controller = await controllerCompleter.future; + await controller.runJavascript('location.href = "$secondaryUrl"'); + + await pageLoads.stream.first; // Wait for the next page load. + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, secondaryUrl); + }); + + testWidgets('onWebResourceError', (WidgetTester tester) async { + final Completer errorCompleter = + Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: 'https://www.notawebsite..com', + onWebResourceError: (WebResourceError error) { + errorCompleter.complete(error); + }, + ), + ), + ); + + final WebResourceError error = await errorCompleter.future; + expect(error, isNotNull); + + expect(error.errorType, isNotNull); + expect( + error.failingUrl?.startsWith('https://www.notawebsite..com'), + isTrue, + ); + }); + + testWidgets('onWebResourceError is not called with valid url', ( + WidgetTester tester, + ) async { + final Completer errorCompleter = + Completer(); + final Completer pageFinishCompleter = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: + 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', + onWebResourceError: (WebResourceError error) { + errorCompleter.complete(error); + }, + onPageFinished: (_) => pageFinishCompleter.complete(), + ), + ), + ); + + expect(errorCompleter.future, doesNotComplete); + await pageFinishCompleter.future; + }); + + testWidgets('onWebResourceError only called for main frame', ( + WidgetTester tester, + ) async { + const String iframeTest = ''' + + + + WebResourceError test + + + + + + '''; + final String iframeTestBase64 = base64Encode( + const Utf8Encoder().convert(iframeTest), + ); + + final Completer errorCompleter = + Completer(); + final Completer pageFinishCompleter = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: 'data:text/html;charset=utf-8;base64,$iframeTestBase64', + onWebResourceError: (WebResourceError error) { + errorCompleter.complete(error); + }, + onPageFinished: (_) => pageFinishCompleter.complete(), + ), + ), + ); + + expect(errorCompleter.future, doesNotComplete); + await pageFinishCompleter.future; + }); + + testWidgets('can block requests', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageLoads = + StreamController.broadcast(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: blankPageEncoded, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + navigationDelegate: (NavigationRequest request) { + return request.url.contains('youtube.com') + ? NavigationDecision.prevent + : NavigationDecision.navigate; + }, + onPageFinished: (String url) => pageLoads.add(url), + ), + ), + ); + + await pageLoads.stream.first; // Wait for initial page load. + final WebViewController controller = await controllerCompleter.future; + await controller.runJavascript( + 'location.href = "https://www.youtube.com/"', + ); + + // There should never be any second page load, since our new URL is + // blocked. Still wait for a potential page change for some time in order + // to give the test a chance to fail. + await pageLoads.stream.first.timeout( + const Duration(milliseconds: 500), + onTimeout: () => '', + ); + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, isNot(contains('youtube.com'))); + }); + + testWidgets('supports asynchronous decisions', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageLoads = + StreamController.broadcast(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: blankPageEncoded, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + navigationDelegate: (NavigationRequest request) async { + NavigationDecision decision = NavigationDecision.prevent; + decision = await Future.delayed( + const Duration(milliseconds: 10), + () => NavigationDecision.navigate, + ); + return decision; + }, + onPageFinished: (String url) => pageLoads.add(url), + ), + ), + ); + + await pageLoads.stream.first; // Wait for initial page load. + final WebViewController controller = await controllerCompleter.future; + await controller.runJavascript('location.href = "$secondaryUrl"'); + + await pageLoads.stream.first; // Wait for second page to load. + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, secondaryUrl); + }); + }); + + testWidgets('launches with gestureNavigationEnabled on iOS', ( + WidgetTester tester, + ) async { + final Completer controllerCompleter = + Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: SizedBox( + width: 400, + height: 300, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + gestureNavigationEnabled: true, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + ), + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, primaryUrl); + }); + + testWidgets('target _blank opens in same window', ( + WidgetTester tester, + ) async { + final Completer controllerCompleter = + Completer(); + final Completer pageLoaded = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (String url) { + pageLoaded.complete(null); + }, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + await controller.runJavascript('window.open("$primaryUrl", "_blank")'); + await pageLoaded.future; + final String? currentUrl = await controller.currentUrl(); + expect(currentUrl, primaryUrl); + }); + + testWidgets('can open new window and go back', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + Completer pageLoaded = Completer(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (String url) { + pageLoaded.complete(); + }, + initialUrl: primaryUrl, + ), + ), + ); + final WebViewController controller = await controllerCompleter.future; + expect(controller.currentUrl(), completion(primaryUrl)); + await pageLoaded.future; + pageLoaded = Completer(); + + await controller.runJavascript('window.open("$secondaryUrl")'); + await pageLoaded.future; + pageLoaded = Completer(); + expect(controller.currentUrl(), completion(secondaryUrl)); + + expect(controller.canGoBack(), completion(true)); + await controller.goBack(); + await pageLoaded.future; + await expectLater(controller.currentUrl(), completion(primaryUrl)); + }); + + testWidgets('JavaScript does not run in parent window', ( + WidgetTester tester, + ) async { + const String iframe = ''' + + + '''; + final String iframeTestBase64 = base64Encode( + const Utf8Encoder().convert(iframe), + ); + + final String openWindowTest = + ''' + + + + XSS test + + + + + + '''; + final String openWindowTestBase64 = base64Encode( + const Utf8Encoder().convert(openWindowTest), + ); + final Completer controllerCompleter = + Completer(); + final Completer pageLoadCompleter = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + initialUrl: + 'data:text/html;charset=utf-8;base64,$openWindowTestBase64', + onPageFinished: (String url) { + pageLoadCompleter.complete(); + }, + ), + ), + ); + + final WebViewController controller = await controllerCompleter.future; + await pageLoadCompleter.future; + + final String iframeLoaded = await controller.runJavascriptReturningResult( + 'iframeLoaded', + ); + expect(iframeLoaded, 'true'); + + final String elementText = await controller.runJavascriptReturningResult( + 'document.querySelector("p") && document.querySelector("p").textContent', + ); + expect(elementText, 'null'); + }); + + testWidgets('clearCache should clear local storage', ( + WidgetTester tester, + ) async { + final Completer controllerCompleter = + Completer(); + + Completer pageLoadCompleter = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: primaryUrl, + javascriptMode: JavascriptMode.unrestricted, + onPageFinished: (_) => pageLoadCompleter.complete(), + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + ), + ), + ); + + await pageLoadCompleter.future; + pageLoadCompleter = Completer(); + + final WebViewController controller = await controllerCompleter.future; + await controller.runJavascript('localStorage.setItem("myCat", "Tom");'); + final String myCatItem = await controller.runJavascriptReturningResult( + 'localStorage.getItem("myCat");', + ); + expect(myCatItem, '"Tom"'); + + await controller.clearCache(); + await pageLoadCompleter.future; + + final String nullItem = await controller.runJavascriptReturningResult( + 'localStorage.getItem("myCat");', + ); + expect(nullItem, 'null'); + }); +} + +// JavaScript booleans evaluate to different string values on Android and iOS. +// This utility method returns the string boolean value of the current platform. +String _webviewBool(bool value) { + if (defaultTargetPlatform == TargetPlatform.iOS) { + return value ? '1' : '0'; + } + return value ? 'true' : 'false'; +} + +/// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests. +Future _getUserAgent(WebViewController controller) async { + return _runJavaScriptReturningResult(controller, 'navigator.userAgent;'); +} + +Future _runJavaScriptReturningResult( + WebViewController controller, + String js, +) async { + return jsonDecode(await controller.runJavascriptReturningResult(js)) + as String; +} + +class ResizableWebView extends StatefulWidget { + const ResizableWebView({ + super.key, + required this.onResize, + required this.onPageFinished, + }); + + final JavascriptMessageHandler onResize; + final VoidCallback onPageFinished; + + @override + State createState() => ResizableWebViewState(); +} + +class ResizableWebViewState extends State { + double webViewWidth = 200; + double webViewHeight = 200; + + static const String resizePage = ''' + + Resize test + + + + + + '''; + + @override + Widget build(BuildContext context) { + final String resizeTestBase64 = base64Encode( + const Utf8Encoder().convert(resizePage), + ); + return Directionality( + textDirection: TextDirection.ltr, + child: Column( + children: [ + SizedBox( + width: webViewWidth, + height: webViewHeight, + child: WebView( + initialUrl: + 'data:text/html;charset=utf-8;base64,$resizeTestBase64', + javascriptChannels: { + JavascriptChannel( + name: 'Resize', + onMessageReceived: widget.onResize, + ), + }, + onPageFinished: (_) => widget.onPageFinished(), + javascriptMode: JavascriptMode.unrestricted, + ), + ), + TextButton( + key: const Key('resizeButton'), + onPressed: () { + setState(() { + webViewWidth += 100.0; + webViewHeight += 100.0; + }); + }, + child: const Text('ResizeButton'), + ), + ], + ), + ); + } +} + +class CopyableObjectWithCallback + extends android.PigeonInternalProxyApiBaseClass { + CopyableObjectWithCallback(this.callback); + + final VoidCallback callback; + + @override + // ignore: non_constant_identifier_names + CopyableObjectWithCallback pigeon_copy() { + return CopyableObjectWithCallback(callback); + } +} + +class ClassWithCallbackClass { + ClassWithCallbackClass() { + callbackClass = CopyableObjectWithCallback( + withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return () { + // Weak reference to `this` in callback. + // ignore: unnecessary_statements + weakReference; + }; + }), + ); + } + + late final CopyableObjectWithCallback callbackClass; +} + +class TestPlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { + @override + FutureOr onNavigationRequest({ + required String url, + required bool isForMainFrame, + }) async { + return true; + } + + @override + void onPageStarted(String url) {} + + @override + void onPageFinished(String url) {} + + @override + void onProgress(int progress) {} + + @override + void onWebResourceError(WebResourceError error) {} +} diff --git a/local_packages/webview_flutter_android-4.10.5/example/lib/legacy/navigation_decision.dart b/local_packages/webview_flutter_android-4.10.5/example/lib/legacy/navigation_decision.dart new file mode 100644 index 0000000..1c8b916 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/lib/legacy/navigation_decision.dart @@ -0,0 +1,12 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// A decision on how to handle a navigation request. +enum NavigationDecision { + /// Prevent the navigation from taking place. + prevent, + + /// Allow the navigation to take place. + navigate, +} diff --git a/local_packages/webview_flutter_android-4.10.5/example/lib/legacy/navigation_request.dart b/local_packages/webview_flutter_android-4.10.5/example/lib/legacy/navigation_request.dart new file mode 100644 index 0000000..c02e0a5 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/lib/legacy/navigation_request.dart @@ -0,0 +1,19 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// Information about a navigation action that is about to be executed. +class NavigationRequest { + NavigationRequest._({required this.url, required this.isForMainFrame}); + + /// The URL that will be loaded if the navigation is executed. + final String url; + + /// Whether the navigation request is to be loaded as the main frame. + final bool isForMainFrame; + + @override + String toString() { + return '$NavigationRequest(url: $url, isForMainFrame: $isForMainFrame)'; + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/example/lib/legacy/web_view.dart b/local_packages/webview_flutter_android-4.10.5/example/lib/legacy/web_view.dart new file mode 100644 index 0000000..d8aaa65 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/lib/legacy/web_view.dart @@ -0,0 +1,711 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +// ignore: implementation_imports +import 'package:webview_flutter_android/src/webview_flutter_android_legacy.dart'; +// ignore: implementation_imports +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; + +import 'navigation_decision.dart'; +import 'navigation_request.dart'; + +/// Optional callback invoked when a web view is first created. [controller] is +/// the [WebViewController] for the created web view. +typedef WebViewCreatedCallback = void Function(WebViewController controller); + +/// Decides how to handle a specific navigation request. +/// +/// The returned [NavigationDecision] determines how the navigation described by +/// `navigation` should be handled. +/// +/// See also: [WebView.navigationDelegate]. +typedef NavigationDelegate = + FutureOr Function(NavigationRequest navigation); + +/// Signature for when a [WebView] has started loading a page. +typedef PageStartedCallback = void Function(String url); + +/// Signature for when a [WebView] has finished loading a page. +typedef PageFinishedCallback = void Function(String url); + +/// Signature for when a [WebView] is loading a page. +typedef PageLoadingCallback = void Function(int progress); + +/// Signature for when a [WebView] has failed to load a resource. +typedef WebResourceErrorCallback = void Function(WebResourceError error); + +/// A web view widget for showing html content. +/// +/// The [WebView] widget wraps around the [AndroidWebView] or +/// [SurfaceAndroidWebView] classes and acts like a facade which makes it easier +/// to inject a [AndroidWebView] or [SurfaceAndroidWebView] control into the +/// widget tree. +/// +/// The [WebView] widget is controlled using the [WebViewController] which is +/// provided through the `onWebViewCreated` callback. +/// +/// In this example project it's main purpose is to facilitate integration +/// testing of the `webview_flutter_android` package. +class WebView extends StatefulWidget { + /// Creates a new web view. + /// + /// The web view can be controlled using a `WebViewController` that is passed to the + /// `onWebViewCreated` callback once the web view is created. + /// + /// The `javascriptMode` and `autoMediaPlaybackPolicy` parameters must not be null. + const WebView({ + super.key, + this.onWebViewCreated, + this.initialUrl, + this.initialCookies = const [], + this.javascriptMode = JavascriptMode.disabled, + this.javascriptChannels, + this.navigationDelegate, + this.gestureRecognizers, + this.onPageStarted, + this.onPageFinished, + this.onProgress, + this.onWebResourceError, + this.debuggingEnabled = false, + this.gestureNavigationEnabled = false, + this.userAgent, + this.zoomEnabled = true, + this.initialMediaPlaybackPolicy = + AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, + this.allowsInlineMediaPlayback = false, + this.backgroundColor, + }); + + /// The WebView platform that's used by this WebView. + /// + /// The default value is [AndroidWebView]. + static WebViewPlatform platform = AndroidWebView(); + + /// If not null invoked once the web view is created. + final WebViewCreatedCallback? onWebViewCreated; + + /// Which gestures should be consumed by the web view. + /// + /// It is possible for other gesture recognizers to be competing with the web view on pointer + /// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle + /// vertical drags. The web view will claim gestures that are recognized by any of the + /// recognizers on this list. + /// + /// When this set is empty or null, the web view will only handle pointer events for gestures that + /// were not claimed by any other gesture recognizer. + final Set>? gestureRecognizers; + + /// The initial URL to load. + final String? initialUrl; + + /// The initial cookies to set. + final List initialCookies; + + /// Whether JavaScript execution is enabled. + final JavascriptMode javascriptMode; + + /// The set of [JavascriptChannel]s available to JavaScript code running in the web view. + /// + /// For each [JavascriptChannel] in the set, a channel object is made available for the + /// JavaScript code in a window property named [JavascriptChannel.name]. + /// The JavaScript code can then call `postMessage` on that object to send a message that will be + /// passed to [JavascriptChannel.onMessageReceived]. + /// + /// For example for the following [JavascriptChannel]: + /// + /// ```dart + /// JavascriptChannel(name: 'Print', onMessageReceived: (JavascriptMessage message) { print(message.message); }); + /// ``` + /// + /// JavaScript code can call: + /// + /// ```javascript + /// Print.postMessage('Hello'); + /// ``` + /// + /// To asynchronously invoke the message handler which will print the message to standard output. + /// + /// Adding a new JavaScript channel only takes affect after the next page is loaded. + /// + /// Set values must not be null. A [JavascriptChannel.name] cannot be the same for multiple + /// channels in the list. + /// + /// A null value is equivalent to an empty set. + final Set? javascriptChannels; + + /// A delegate function that decides how to handle navigation actions. + /// + /// When a navigation is initiated by the WebView (e.g when a user clicks a link) + /// this delegate is called and has to decide how to proceed with the navigation. + /// + /// See [NavigationDecision] for possible decisions the delegate can take. + /// + /// When null all navigation actions are allowed. + /// + /// Caveats on Android: + /// + /// * Navigation actions targeted to the main frame can be intercepted, + /// navigation actions targeted to subframes are allowed regardless of the value + /// returned by this delegate. + /// * Setting a navigationDelegate makes the WebView treat all navigations as if they were + /// triggered by a user gesture, this disables some of Chromium's security mechanisms. + /// A navigationDelegate should only be set when loading trusted content. + /// * On Android WebView versions earlier than 67(most devices running at least Android L+ should have + /// a later version): + /// * When a navigationDelegate is set pages with frames are not properly handled by the + /// webview, and frames will be opened in the main frame. + /// * When a navigationDelegate is set HTTP requests do not include the HTTP referer header. + final NavigationDelegate? navigationDelegate; + + /// Controls whether inline playback of HTML5 videos is allowed on iOS. + /// + /// This field is ignored on Android because Android allows it by default. + /// + /// By default `allowsInlineMediaPlayback` is false. + final bool allowsInlineMediaPlayback; + + /// Invoked when a page starts loading. + final PageStartedCallback? onPageStarted; + + /// Invoked when a page has finished loading. + /// + /// This is invoked only for the main frame. + /// + /// When [onPageFinished] is invoked on Android, the page being rendered may + /// not be updated yet. + /// + /// When invoked on iOS or Android, any JavaScript code that is embedded + /// directly in the HTML has been loaded and code injected with + /// [WebViewController.evaluateJavascript] can assume this. + final PageFinishedCallback? onPageFinished; + + /// Invoked when a page is loading. + final PageLoadingCallback? onProgress; + + /// Invoked when a web resource has failed to load. + /// + /// This callback is only called for the main page. + final WebResourceErrorCallback? onWebResourceError; + + /// Controls whether WebView debugging is enabled. + /// + /// Setting this to true enables [WebView debugging on Android](https://developers.google.com/web/tools/chrome-devtools/remote-debugging/). + /// + /// WebView debugging is enabled by default in dev builds on iOS. + /// + /// To debug WebViews on iOS: + /// - Enable developer options (Open Safari, go to Preferences -> Advanced and make sure "Show Develop Menu in Menubar" is on.) + /// - From the Menu-bar (of Safari) select Develop -> iPhone Simulator -> + /// + /// By default `debuggingEnabled` is false. + final bool debuggingEnabled; + + /// A Boolean value indicating whether horizontal swipe gestures will trigger back-forward list navigations. + /// + /// This only works on iOS. + /// + /// By default `gestureNavigationEnabled` is false. + final bool gestureNavigationEnabled; + + /// A Boolean value indicating whether the WebView should support zooming using its on-screen zoom controls and gestures. + /// + /// By default 'zoomEnabled' is true + final bool zoomEnabled; + + /// The value used for the HTTP User-Agent: request header. + /// + /// When null the platform's webview default is used for the User-Agent header. + /// + /// When the [WebView] is rebuilt with a different `userAgent`, the page reloads and the request uses the new User Agent. + /// + /// When [WebViewController.goBack] is called after changing `userAgent` the previous `userAgent` value is used until the page is reloaded. + /// + /// This field is ignored on iOS versions prior to 9 as the platform does not support a custom + /// user agent. + /// + /// By default `userAgent` is null. + final String? userAgent; + + /// Which restrictions apply on automatic media playback. + /// + /// This initial value is applied to the platform's webview upon creation. Any following + /// changes to this parameter are ignored (as long as the state of the [WebView] is preserved). + /// + /// The default policy is [AutoMediaPlaybackPolicy.require_user_action_for_all_media_types]. + final AutoMediaPlaybackPolicy initialMediaPlaybackPolicy; + + /// The background color of the [WebView]. + /// + /// When `null` the platform's webview default background color is used. By + /// default [backgroundColor] is `null`. + final Color? backgroundColor; + + @override + State createState() => _WebViewState(); +} + +class _WebViewState extends State { + final Completer _controller = + Completer(); + late final JavascriptChannelRegistry _javascriptChannelRegistry; + late final _PlatformCallbacksHandler _platformCallbacksHandler; + + @override + void initState() { + super.initState(); + _platformCallbacksHandler = _PlatformCallbacksHandler(widget); + _javascriptChannelRegistry = JavascriptChannelRegistry( + widget.javascriptChannels, + ); + } + + @override + void didUpdateWidget(WebView oldWidget) { + super.didUpdateWidget(oldWidget); + _controller.future.then((WebViewController controller) { + controller.updateWidget(widget); + }); + } + + @override + Widget build(BuildContext context) { + return WebView.platform.build( + context: context, + onWebViewPlatformCreated: + (WebViewPlatformController? webViewPlatformController) { + final WebViewController controller = WebViewController( + widget, + webViewPlatformController!, + _javascriptChannelRegistry, + ); + _controller.complete(controller); + + if (widget.onWebViewCreated != null) { + widget.onWebViewCreated!(controller); + } + }, + webViewPlatformCallbacksHandler: _platformCallbacksHandler, + creationParams: CreationParams( + initialUrl: widget.initialUrl, + webSettings: _webSettingsFromWidget(widget), + javascriptChannelNames: _javascriptChannelRegistry.channels.keys + .toSet(), + autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy, + userAgent: widget.userAgent, + backgroundColor: widget.backgroundColor, + cookies: widget.initialCookies, + ), + javascriptChannelRegistry: _javascriptChannelRegistry, + ); + } +} + +class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { + _PlatformCallbacksHandler(this._webView); + + final WebView _webView; + + @override + FutureOr onNavigationRequest({ + required String url, + required bool isForMainFrame, + }) async { + if (url.startsWith('https://www.youtube.com/')) { + debugPrint('blocking navigation to $url'); + return false; + } + debugPrint('allowing navigation to $url'); + return true; + } + + @override + void onPageStarted(String url) { + if (_webView.onPageStarted != null) { + _webView.onPageStarted!(url); + } + } + + @override + void onPageFinished(String url) { + if (_webView.onPageFinished != null) { + _webView.onPageFinished!(url); + } + } + + @override + void onProgress(int progress) { + if (_webView.onProgress != null) { + _webView.onProgress!(progress); + } + } + + @override + void onWebResourceError(WebResourceError error) { + if (_webView.onWebResourceError != null) { + _webView.onWebResourceError!(error); + } + } +} + +/// Controls a [WebView]. +/// +/// A [WebViewController] instance can be obtained by setting the [WebView.onWebViewCreated] +/// callback for a [WebView] widget. +class WebViewController { + /// Creates a [WebViewController] which can be used to control the provided + /// [WebView] widget. + WebViewController( + this._widget, + this._webViewPlatformController, + this._javascriptChannelRegistry, + ) { + _settings = _webSettingsFromWidget(_widget); + } + + final JavascriptChannelRegistry _javascriptChannelRegistry; + + final WebViewPlatformController _webViewPlatformController; + + late WebSettings _settings; + + WebView _widget; + + /// Loads the file located on the specified [absoluteFilePath]. + /// + /// The [absoluteFilePath] parameter should contain the absolute path to the + /// file as it is stored on the device. For example: + /// `/Users/username/Documents/www/index.html`. + /// + /// Throws an ArgumentError if the [absoluteFilePath] does not exist. + Future loadFile(String absoluteFilePath) { + return _webViewPlatformController.loadFile(absoluteFilePath); + } + + /// Loads the Flutter asset specified in the pubspec.yaml file. + /// + /// Throws an ArgumentError if [key] is not part of the specified assets + /// in the pubspec.yaml file. + Future loadFlutterAsset(String key) { + return _webViewPlatformController.loadFlutterAsset(key); + } + + /// Loads the supplied HTML string. + /// + /// The [baseUrl] parameter is used when resolving relative URLs within the + /// HTML string. + Future loadHtmlString(String html, {String? baseUrl}) { + return _webViewPlatformController.loadHtmlString(html, baseUrl: baseUrl); + } + + /// Loads the specified URL. + /// + /// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will + /// be added as key value pairs of HTTP headers for the request. + /// + /// `url` must not be null. + /// + /// Throws an ArgumentError if `url` is not a valid URL string. + Future loadUrl(String url, {Map? headers}) async { + _validateUrlString(url); + return _webViewPlatformController.loadUrl(url, headers); + } + + /// Loads a page by making the specified request. + Future loadRequest(WebViewRequest request) async { + return _webViewPlatformController.loadRequest(request); + } + + /// Accessor to the current URL that the WebView is displaying. + /// + /// If [WebView.initialUrl] was never specified, returns `null`. + /// Note that this operation is asynchronous, and it is possible that the + /// current URL changes again by the time this function returns (in other + /// words, by the time this future completes, the WebView may be displaying a + /// different URL). + Future currentUrl() { + return _webViewPlatformController.currentUrl(); + } + + /// Checks whether there's a back history item. + /// + /// Note that this operation is asynchronous, and it is possible that the "canGoBack" state has + /// changed by the time the future completed. + Future canGoBack() { + return _webViewPlatformController.canGoBack(); + } + + /// Checks whether there's a forward history item. + /// + /// Note that this operation is asynchronous, and it is possible that the "canGoForward" state has + /// changed by the time the future completed. + Future canGoForward() { + return _webViewPlatformController.canGoForward(); + } + + /// Goes back in the history of this WebView. + /// + /// If there is no back history item this is a no-op. + Future goBack() { + return _webViewPlatformController.goBack(); + } + + /// Goes forward in the history of this WebView. + /// + /// If there is no forward history item this is a no-op. + Future goForward() { + return _webViewPlatformController.goForward(); + } + + /// Reloads the current URL. + Future reload() { + return _webViewPlatformController.reload(); + } + + /// Clears all caches used by the [WebView]. + /// + /// The following caches are cleared: + /// 1. Browser HTTP Cache. + /// 2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) caches. + /// These are not yet supported in iOS WkWebView. Service workers tend to use this cache. + /// 3. Application cache. + /// 4. Local Storage. + /// + /// Note: Calling this method also triggers a reload. + Future clearCache() async { + await _webViewPlatformController.clearCache(); + return reload(); + } + + /// Update the widget managed by the [WebViewController]. + Future updateWidget(WebView widget) async { + _widget = widget; + await _updateSettings(_webSettingsFromWidget(widget)); + await _updateJavascriptChannels( + _javascriptChannelRegistry.channels.values.toSet(), + ); + } + + Future _updateSettings(WebSettings newSettings) { + final WebSettings update = _clearUnchangedWebSettings( + _settings, + newSettings, + ); + _settings = newSettings; + return _webViewPlatformController.updateSettings(update); + } + + Future _updateJavascriptChannels( + Set? newChannels, + ) async { + final Set currentChannels = _javascriptChannelRegistry.channels.keys + .toSet(); + final Set newChannelNames = _extractChannelNames(newChannels); + final Set channelsToAdd = newChannelNames.difference( + currentChannels, + ); + final Set channelsToRemove = currentChannels.difference( + newChannelNames, + ); + if (channelsToRemove.isNotEmpty) { + await _webViewPlatformController.removeJavascriptChannels( + channelsToRemove, + ); + } + if (channelsToAdd.isNotEmpty) { + await _webViewPlatformController.addJavascriptChannels(channelsToAdd); + } + _javascriptChannelRegistry.updateJavascriptChannelsFromSet(newChannels); + } + + @visibleForTesting + // ignore: public_member_api_docs + Future evaluateJavascript(String javascriptString) { + if (_settings.javascriptMode == JavascriptMode.disabled) { + return Future.error( + FlutterError( + 'JavaScript mode must be enabled/unrestricted when calling evaluateJavascript.', + ), + ); + } + return _webViewPlatformController.evaluateJavascript(javascriptString); + } + + /// Runs the given JavaScript in the context of the current page. + /// If you are looking for the result, use [runJavascriptReturningResult] instead. + /// The Future completes with an error if a JavaScript error occurred. + /// + /// When running JavaScript in a [WebView], it is best practice to wait for + // the [WebView.onPageFinished] callback. This guarantees all the JavaScript + // embedded in the main frame HTML has been loaded. + Future runJavascript(String javaScriptString) { + if (_settings.javascriptMode == JavascriptMode.disabled) { + return Future.error( + FlutterError( + 'Javascript mode must be enabled/unrestricted when calling runJavascript.', + ), + ); + } + return _webViewPlatformController.runJavascript(javaScriptString); + } + + /// Runs the given JavaScript in the context of the current page, and returns the result. + /// + /// Returns the evaluation result as a JSON formatted string. + /// The Future completes with an error if a JavaScript error occurred. + /// + /// When evaluating JavaScript in a [WebView], it is best practice to wait for + /// the [WebView.onPageFinished] callback. This guarantees all the JavaScript + /// embedded in the main frame HTML has been loaded. + Future runJavascriptReturningResult(String javaScriptString) { + if (_settings.javascriptMode == JavascriptMode.disabled) { + return Future.error( + FlutterError( + 'Javascript mode must be enabled/unrestricted when calling runJavascriptReturningResult.', + ), + ); + } + return _webViewPlatformController.runJavascriptReturningResult( + javaScriptString, + ); + } + + /// Returns the title of the currently loaded page. + Future getTitle() { + return _webViewPlatformController.getTitle(); + } + + /// Sets the WebView's content scroll position. + /// + /// The parameters `x` and `y` specify the scroll position in WebView pixels. + Future scrollTo(int x, int y) { + return _webViewPlatformController.scrollTo(x, y); + } + + /// Move the scrolled position of this view. + /// + /// The parameters `x` and `y` specify the amount of WebView pixels to scroll by horizontally and vertically respectively. + Future scrollBy(int x, int y) { + return _webViewPlatformController.scrollBy(x, y); + } + + /// Return the horizontal scroll position, in WebView pixels, of this view. + /// + /// Scroll position is measured from left. + Future getScrollX() { + return _webViewPlatformController.getScrollX(); + } + + /// Return the vertical scroll position, in WebView pixels, of this view. + /// + /// Scroll position is measured from top. + Future getScrollY() { + return _webViewPlatformController.getScrollY(); + } + + // This method assumes that no fields in `currentValue` are null. + WebSettings _clearUnchangedWebSettings( + WebSettings currentValue, + WebSettings newValue, + ) { + assert(currentValue.javascriptMode != null); + assert(currentValue.hasNavigationDelegate != null); + assert(currentValue.hasProgressTracking != null); + assert(currentValue.debuggingEnabled != null); + assert(newValue.javascriptMode != null); + assert(newValue.hasNavigationDelegate != null); + assert(newValue.debuggingEnabled != null); + assert(newValue.zoomEnabled != null); + + JavascriptMode? javascriptMode; + bool? hasNavigationDelegate; + bool? hasProgressTracking; + bool? debuggingEnabled; + WebSetting userAgent = const WebSetting.absent(); + bool? zoomEnabled; + if (currentValue.javascriptMode != newValue.javascriptMode) { + javascriptMode = newValue.javascriptMode; + } + if (currentValue.hasNavigationDelegate != newValue.hasNavigationDelegate) { + hasNavigationDelegate = newValue.hasNavigationDelegate; + } + if (currentValue.hasProgressTracking != newValue.hasProgressTracking) { + hasProgressTracking = newValue.hasProgressTracking; + } + if (currentValue.debuggingEnabled != newValue.debuggingEnabled) { + debuggingEnabled = newValue.debuggingEnabled; + } + if (currentValue.userAgent != newValue.userAgent) { + userAgent = newValue.userAgent; + } + if (currentValue.zoomEnabled != newValue.zoomEnabled) { + zoomEnabled = newValue.zoomEnabled; + } + + return WebSettings( + javascriptMode: javascriptMode, + hasNavigationDelegate: hasNavigationDelegate, + hasProgressTracking: hasProgressTracking, + debuggingEnabled: debuggingEnabled, + userAgent: userAgent, + zoomEnabled: zoomEnabled, + ); + } + + Set _extractChannelNames(Set? channels) { + final Set channelNames = channels == null + ? {} + : channels.map((JavascriptChannel channel) => channel.name).toSet(); + return channelNames; + } + + // Throws an ArgumentError if `url` is not a valid URL string. + void _validateUrlString(String url) { + try { + final Uri uri = Uri.parse(url); + if (uri.scheme.isEmpty) { + throw ArgumentError('Missing scheme in URL string: "$url"'); + } + } on FormatException catch (e) { + throw ArgumentError(e); + } + } +} + +WebSettings _webSettingsFromWidget(WebView widget) { + return WebSettings( + javascriptMode: widget.javascriptMode, + hasNavigationDelegate: widget.navigationDelegate != null, + hasProgressTracking: widget.onProgress != null, + debuggingEnabled: widget.debuggingEnabled, + gestureNavigationEnabled: widget.gestureNavigationEnabled, + allowsInlineMediaPlayback: widget.allowsInlineMediaPlayback, + userAgent: WebSetting.of(widget.userAgent), + zoomEnabled: widget.zoomEnabled, + ); +} + +/// App-facing cookie manager that exposes the correct platform implementation. +class WebViewCookieManager extends WebViewCookieManagerPlatform { + WebViewCookieManager._(); + + /// Returns an instance of the cookie manager for the current platform. + static WebViewCookieManagerPlatform get instance { + if (WebViewCookieManagerPlatform.instance == null) { + if (Platform.isAndroid) { + WebViewCookieManagerPlatform.instance = WebViewAndroidCookieManager(); + } else { + throw AssertionError( + 'This platform is currently unsupported for webview_flutter_android.', + ); + } + } + return WebViewCookieManagerPlatform.instance!; + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/example/lib/main.dart b/local_packages/webview_flutter_android-4.10.5/example/lib/main.dart new file mode 100644 index 0000000..2d126b9 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/lib/main.dart @@ -0,0 +1,856 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: public_member_api_docs + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:webview_flutter_android/webview_flutter_android.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +void main() { + runApp(const MaterialApp(home: WebViewExample())); +} + +const String kNavigationExamplePage = ''' + +Navigation Delegate Example + +

+The navigation delegate is set to block navigation to the pub.dev website. +

+ + + +'''; + +const String kLocalExamplePage = ''' + + + +Load file or HTML string example + + + +

Local demo page

+

+ This is an example page used to demonstrate how to load a local file or HTML + string using the Flutter + webview plugin. +

+ + + +'''; + +const String kTransparentBackgroundPage = ''' + + + + Transparent background test + + + +
+

Transparent background test

+
+
+ + +'''; + +const String kLogExamplePage = ''' + + + +Load file or HTML string example + + + +

Local demo page

+

+ This page is used to test the forwarding of console logs to Dart. +

+ + + +
+ + + + + +
+ + + +'''; + +const String kAlertTestPage = ''' + + + + + + + +

Click the following button to see the effect

+
+ + + +
+ + +'''; + +const String kViewportMetaPage = ''' + + + + Viewport meta example + + + + +
+

Viewport meta example

+ +
+ + +'''; + +class WebViewExample extends StatefulWidget { + const WebViewExample({super.key, this.cookieManager}); + + final PlatformWebViewCookieManager? cookieManager; + + @override + State createState() => _WebViewExampleState(); +} + +class _WebViewExampleState extends State { + late final PlatformWebViewController _controller; + + @override + void initState() { + super.initState(); + + _controller = + PlatformWebViewController(AndroidWebViewControllerCreationParams()) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(const Color(0x80000000)) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ) + ..setOnProgress((int progress) { + debugPrint('WebView is loading (progress : $progress%)'); + }) + ..setOnPageStarted((String url) { + debugPrint('Page started loading: $url'); + }) + ..setOnPageFinished((String url) { + debugPrint('Page finished loading: $url'); + }) + ..setOnHttpError((HttpResponseError error) { + debugPrint( + 'HTTP error occured on page: ${error.response?.statusCode}', + ); + }) + ..setOnWebResourceError((WebResourceError error) { + debugPrint(''' +Page resource error: + code: ${error.errorCode} + description: ${error.description} + errorType: ${error.errorType} + isForMainFrame: ${error.isForMainFrame} + url: ${error.url} + '''); + }) + ..setOnNavigationRequest((NavigationRequest request) { + if (request.url.contains('pub.dev')) { + debugPrint('blocking navigation to ${request.url}'); + return NavigationDecision.prevent; + } + debugPrint('allowing navigation to ${request.url}'); + return NavigationDecision.navigate; + }) + ..setOnUrlChange((UrlChange change) { + debugPrint('url change to ${change.url}'); + }) + ..setOnHttpAuthRequest((HttpAuthRequest request) { + openDialog(request); + }) + ..setOnSSlAuthError((PlatformSslAuthError error) { + debugPrint( + 'SSL error from ${(error as AndroidSslAuthError).url}', + ); + error.cancel(); + }), + ) + ..addJavaScriptChannel( + JavaScriptChannelParams( + name: 'Toaster', + onMessageReceived: (JavaScriptMessage message) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(message.message))); + }, + ), + ) + ..setOnPlatformPermissionRequest(( + PlatformWebViewPermissionRequest request, + ) { + debugPrint( + 'requesting permissions for ${request.types.map((WebViewPermissionResourceType type) => type.name)}', + ); + request.grant(); + }) + ..loadRequest( + LoadRequestParams(uri: Uri.parse('https://flutter.dev')), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFF4CAF50), + appBar: AppBar( + title: const Text('Flutter WebView example'), + // This drop down menu demonstrates that Flutter widgets can be shown over the web view. + actions: [ + NavigationControls(webViewController: _controller), + SampleMenu( + webViewController: _controller, + cookieManager: widget.cookieManager, + ), + ], + ), + body: PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: _controller), + ).build(context), + floatingActionButton: favoriteButton(), + ); + } + + Widget favoriteButton() { + return FloatingActionButton( + onPressed: () async { + final String? url = await _controller.currentUrl(); + if (mounted) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('Favorited $url'))); + } + }, + child: const Icon(Icons.favorite), + ); + } + + Future openDialog(HttpAuthRequest httpRequest) async { + final TextEditingController usernameTextController = + TextEditingController(); + final TextEditingController passwordTextController = + TextEditingController(); + + return showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('${httpRequest.host}: ${httpRequest.realm ?? '-'}'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + decoration: const InputDecoration(labelText: 'Username'), + autofocus: true, + controller: usernameTextController, + ), + TextField( + decoration: const InputDecoration(labelText: 'Password'), + controller: passwordTextController, + ), + ], + ), + actions: [ + TextButton( + onPressed: () { + httpRequest.onProceed( + WebViewCredential( + user: usernameTextController.text, + password: passwordTextController.text, + ), + ); + Navigator.of(context).pop(); + }, + child: const Text('Authenticate'), + ), + ], + ); + }, + ); + } +} + +enum MenuOptions { + showUserAgent, + listCookies, + clearCookies, + addToCache, + listCache, + clearCache, + navigationDelegate, + doPostRequest, + loadLocalFile, + loadFlutterAsset, + loadHtmlString, + transparentBackground, + setCookie, + videoExample, + logExample, + basicAuthentication, + javaScriptAlert, + viewportMeta, +} + +class SampleMenu extends StatelessWidget { + SampleMenu({ + super.key, + required this.webViewController, + PlatformWebViewCookieManager? cookieManager, + }) : cookieManager = + cookieManager ?? + PlatformWebViewCookieManager( + const PlatformWebViewCookieManagerCreationParams(), + ); + + final PlatformWebViewController webViewController; + late final PlatformWebViewCookieManager cookieManager; + + @override + Widget build(BuildContext context) { + return PopupMenuButton( + key: const ValueKey('ShowPopupMenu'), + onSelected: (MenuOptions value) { + switch (value) { + case MenuOptions.showUserAgent: + _onShowUserAgent(); + case MenuOptions.listCookies: + _onListCookies(context); + case MenuOptions.clearCookies: + _onClearCookies(context); + case MenuOptions.addToCache: + _onAddToCache(context); + case MenuOptions.listCache: + _onListCache(); + case MenuOptions.clearCache: + _onClearCache(context); + case MenuOptions.navigationDelegate: + _onNavigationDelegateExample(); + case MenuOptions.doPostRequest: + _onDoPostRequest(); + case MenuOptions.loadLocalFile: + _onLoadLocalFileExample(); + case MenuOptions.loadFlutterAsset: + _onLoadFlutterAssetExample(); + case MenuOptions.loadHtmlString: + _onLoadHtmlStringExample(); + case MenuOptions.transparentBackground: + _onTransparentBackground(); + case MenuOptions.setCookie: + _onSetCookie(); + case MenuOptions.videoExample: + _onVideoExample(context); + case MenuOptions.logExample: + _onLogExample(); + case MenuOptions.basicAuthentication: + _promptForUrl(context); + case MenuOptions.javaScriptAlert: + _onJavaScriptAlertExample(context); + case MenuOptions.viewportMeta: + _onViewportMetaExample(); + } + }, + itemBuilder: (BuildContext context) => >[ + const PopupMenuItem( + value: MenuOptions.showUserAgent, + child: Text('Show user agent'), + ), + const PopupMenuItem( + value: MenuOptions.listCookies, + child: Text('List cookies'), + ), + const PopupMenuItem( + value: MenuOptions.clearCookies, + child: Text('Clear cookies'), + ), + const PopupMenuItem( + value: MenuOptions.addToCache, + child: Text('Add to cache'), + ), + const PopupMenuItem( + value: MenuOptions.listCache, + child: Text('List cache'), + ), + const PopupMenuItem( + value: MenuOptions.clearCache, + child: Text('Clear cache'), + ), + const PopupMenuItem( + value: MenuOptions.navigationDelegate, + child: Text('Navigation Delegate example'), + ), + const PopupMenuItem( + value: MenuOptions.doPostRequest, + child: Text('Post Request'), + ), + const PopupMenuItem( + value: MenuOptions.loadHtmlString, + child: Text('Load HTML string'), + ), + const PopupMenuItem( + value: MenuOptions.loadLocalFile, + child: Text('Load local file'), + ), + const PopupMenuItem( + value: MenuOptions.loadFlutterAsset, + child: Text('Load Flutter Asset'), + ), + const PopupMenuItem( + value: MenuOptions.setCookie, + child: Text('Set cookie'), + ), + const PopupMenuItem( + key: ValueKey('ShowTransparentBackgroundExample'), + value: MenuOptions.transparentBackground, + child: Text('Transparent background example'), + ), + const PopupMenuItem( + value: MenuOptions.logExample, + child: Text('Log example'), + ), + const PopupMenuItem( + value: MenuOptions.videoExample, + child: Text('Video example'), + ), + const PopupMenuItem( + value: MenuOptions.basicAuthentication, + child: Text('Basic Authentication Example'), + ), + const PopupMenuItem( + value: MenuOptions.javaScriptAlert, + child: Text('JavaScript Alert Example'), + ), + const PopupMenuItem( + value: MenuOptions.viewportMeta, + child: Text('Viewport meta example'), + ), + ], + ); + } + + Future _onShowUserAgent() { + // Send a message with the user agent string to the Toaster JavaScript channel we registered + // with the WebView. + return webViewController.runJavaScript( + 'Toaster.postMessage("User Agent: " + navigator.userAgent);', + ); + } + + Future _onListCookies(BuildContext context) async { + final String cookies = + await webViewController.runJavaScriptReturningResult('document.cookie') + as String; + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Column( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [const Text('Cookies:'), _getCookieList(cookies)], + ), + ), + ); + } + } + + Future _onAddToCache(BuildContext context) async { + await webViewController.runJavaScript( + 'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";', + ); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Added a test entry to cache.')), + ); + } + } + + Future _onListCache() { + return webViewController.runJavaScript( + 'caches.keys()' + // ignore: missing_whitespace_between_adjacent_strings + '.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))' + '.then((caches) => Toaster.postMessage(caches))', + ); + } + + Future _onClearCache(BuildContext context) async { + await webViewController.clearCache(); + await webViewController.clearLocalStorage(); + if (context.mounted) { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Cache cleared.'))); + } + } + + Future _onClearCookies(BuildContext context) async { + final bool hadCookies = await cookieManager.clearCookies(); + String message = 'There were cookies. Now, they are gone!'; + if (!hadCookies) { + message = 'There are no cookies.'; + } + if (context.mounted) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(message))); + } + } + + Future _onNavigationDelegateExample() { + final String contentBase64 = base64Encode( + const Utf8Encoder().convert(kNavigationExamplePage), + ); + return webViewController.loadRequest( + LoadRequestParams(uri: Uri.parse('data:text/html;base64,$contentBase64')), + ); + } + + Future _onSetCookie() async { + await cookieManager.setCookie( + const WebViewCookie( + name: 'foo', + value: 'bar', + domain: 'httpbin.org', + path: '/anything', + ), + ); + await webViewController.loadRequest( + LoadRequestParams(uri: Uri.parse('https://httpbin.org/anything')), + ); + } + + Future _onVideoExample(BuildContext context) { + final AndroidWebViewController androidController = + webViewController as AndroidWebViewController; + // #docregion fullscreen_example + androidController.setCustomWidgetCallbacks( + onShowCustomWidget: (Widget widget, OnHideCustomWidgetCallback callback) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => widget, + fullscreenDialog: true, + ), + ); + }, + onHideCustomWidget: () { + Navigator.of(context).pop(); + }, + ); + // #enddocregion fullscreen_example + + return androidController.loadRequest( + LoadRequestParams( + uri: Uri.parse('https://www.youtube.com/watch?v=4AoFA19gbLo'), + ), + ); + } + + Future _onDoPostRequest() { + return webViewController.loadRequest( + LoadRequestParams( + uri: Uri.parse('https://httpbin.org/post'), + method: LoadRequestMethod.post, + headers: const { + 'foo': 'bar', + 'Content-Type': 'text/plain', + }, + body: Uint8List.fromList('Test Body'.codeUnits), + ), + ); + } + + Future _onLoadLocalFileExample() async { + final String pathToIndex = await _prepareLocalFile(); + await webViewController.loadFile(pathToIndex); + } + + Future _onLoadFlutterAssetExample() { + return webViewController.loadFlutterAsset('assets/www/index.html'); + } + + Future _onLoadHtmlStringExample() { + return webViewController.loadHtmlString(kLocalExamplePage); + } + + Future _onTransparentBackground() { + return webViewController.loadHtmlString(kTransparentBackgroundPage); + } + + Future _onJavaScriptAlertExample(BuildContext context) { + webViewController.setOnJavaScriptAlertDialog(( + JavaScriptAlertDialogRequest request, + ) async { + await _showAlert(context, request.message); + }); + + webViewController.setOnJavaScriptConfirmDialog(( + JavaScriptConfirmDialogRequest request, + ) async { + final bool result = await _showConfirm(context, request.message); + return result; + }); + + webViewController.setOnJavaScriptTextInputDialog(( + JavaScriptTextInputDialogRequest request, + ) async { + final String result = await _showTextInput( + context, + request.message, + request.defaultText, + ); + return result; + }); + + return webViewController.loadHtmlString(kAlertTestPage); + } + + Widget _getCookieList(String cookies) { + if (cookies == '""') { + return Container(); + } + final List cookieList = cookies.split(';'); + final Iterable cookieWidgets = cookieList.map( + (String cookie) => Text(cookie), + ); + return Column( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: cookieWidgets.toList(), + ); + } + + static Future _prepareLocalFile() async { + final String tmpDir = (await getTemporaryDirectory()).path; + final File indexFile = File( + {tmpDir, 'www', 'index.html'}.join(Platform.pathSeparator), + ); + + await indexFile.create(recursive: true); + await indexFile.writeAsString(kLocalExamplePage); + + return indexFile.path; + } + + Future _onLogExample() { + webViewController.setOnConsoleMessage(( + JavaScriptConsoleMessage consoleMessage, + ) { + debugPrint( + '== JS == ${consoleMessage.level.name}: ${consoleMessage.message}', + ); + }); + return webViewController.loadHtmlString(kLogExamplePage); + } + + Future _promptForUrl(BuildContext context) { + final TextEditingController urlTextController = TextEditingController(); + + return showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Input URL to visit'), + content: TextField( + decoration: const InputDecoration(labelText: 'URL'), + autofocus: true, + controller: urlTextController, + ), + actions: [ + TextButton( + onPressed: () { + if (urlTextController.text.isNotEmpty) { + final Uri? uri = Uri.tryParse(urlTextController.text); + if (uri != null && uri.scheme.isNotEmpty) { + webViewController.loadRequest(LoadRequestParams(uri: uri)); + Navigator.pop(context); + } + } + }, + child: const Text('Visit'), + ), + ], + ); + }, + ); + } + + Future _showAlert(BuildContext context, String message) async { + return showDialog( + context: context, + builder: (BuildContext ctx) { + return AlertDialog( + content: Text(message), + actions: [ + TextButton( + onPressed: () { + Navigator.of(ctx).pop(); + }, + child: const Text('OK'), + ), + ], + ); + }, + ); + } + + Future _showConfirm(BuildContext context, String message) async { + return await showDialog( + context: context, + builder: (BuildContext ctx) { + return AlertDialog( + content: Text(message), + actions: [ + TextButton( + onPressed: () { + Navigator.of(ctx).pop(false); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + Navigator.of(ctx).pop(true); + }, + child: const Text('OK'), + ), + ], + ); + }, + ) ?? + false; + } + + Future _showTextInput( + BuildContext context, + String message, + String? defaultText, + ) async { + return await showDialog( + context: context, + builder: (BuildContext ctx) { + return AlertDialog( + content: Text(message), + actions: [ + TextButton( + onPressed: () { + Navigator.of(ctx).pop('Text test'); + }, + child: const Text('Enter'), + ), + ], + ); + }, + ) ?? + ''; + } + + Future _onViewportMetaExample() { + return webViewController.loadHtmlString(kViewportMetaPage); + } +} + +class NavigationControls extends StatelessWidget { + const NavigationControls({super.key, required this.webViewController}); + + final PlatformWebViewController webViewController; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + IconButton( + icon: const Icon(Icons.arrow_back_ios), + onPressed: () async { + if (await webViewController.canGoBack()) { + await webViewController.goBack(); + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No back history item')), + ); + } + } + }, + ), + IconButton( + icon: const Icon(Icons.arrow_forward_ios), + onPressed: () async { + if (await webViewController.canGoForward()) { + await webViewController.goForward(); + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No forward history item')), + ); + } + } + }, + ), + IconButton( + icon: const Icon(Icons.replay), + onPressed: () => webViewController.reload(), + ), + ], + ); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/example/lib/readme_excerpts.dart b/local_packages/webview_flutter_android-4.10.5/example/lib/readme_excerpts.dart new file mode 100644 index 0000000..4ae6269 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/lib/readme_excerpts.dart @@ -0,0 +1,42 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:webview_flutter_android/webview_flutter_android.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +/// Example function for README demonstration of Payment Request API. +Future enablePaymentRequest() async { + final PlatformWebViewController controller = PlatformWebViewController( + AndroidWebViewControllerCreationParams(), + ); + final AndroidWebViewController androidController = + controller as AndroidWebViewController; + // #docregion payment_request_example + final bool paymentRequestEnabled = await androidController + .isWebViewFeatureSupported(WebViewFeatureType.paymentRequest); + + if (paymentRequestEnabled) { + await androidController.setPaymentRequestEnabled(true); + } + // #enddocregion payment_request_example +} + +/// Example function for README demonstration of geolocation permissions for +/// a use case where the content is always trusted (for example, it only shows +/// content from a domain controlled by the app developer) and geolocation +/// should always be allowed. +Future setGeolocationPermissionsPrompt() async { + final PlatformWebViewController controller = PlatformWebViewController( + AndroidWebViewControllerCreationParams(), + ); + final AndroidWebViewController androidController = + controller as AndroidWebViewController; + // #docregion geolocation_example + await androidController.setGeolocationPermissionsPromptCallbacks( + onShowPrompt: (GeolocationPermissionsRequestParams request) async { + return const GeolocationPermissionsResponse(allow: true, retain: true); + }, + ); + // #enddocregion geolocation_example +} diff --git a/local_packages/webview_flutter_android-4.10.5/example/pubspec.yaml b/local_packages/webview_flutter_android-4.10.5/example/pubspec.yaml new file mode 100644 index 0000000..e5a6431 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/pubspec.yaml @@ -0,0 +1,35 @@ +name: webview_flutter_android_example +description: Demonstrates how to use the webview_flutter_android plugin. +publish_to: none + +environment: + sdk: ^3.9.0 + flutter: ">=3.35.0" + +dependencies: + flutter: + sdk: flutter + path_provider: ^2.0.6 + webview_flutter_android: + # When depending on this package from a real application you should use: + # webview_flutter: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + webview_flutter_platform_interface: ^2.14.0 + +dev_dependencies: + espresso: ^0.4.0 + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + +flutter: + uses-material-design: true + assets: + - assets/sample_audio.ogg + - assets/sample_video.mp4 + - assets/www/index.html + - assets/www/styles/style.css diff --git a/local_packages/webview_flutter_android-4.10.5/example/test_driver/integration_test.dart b/local_packages/webview_flutter_android-4.10.5/example/test_driver/integration_test.dart new file mode 100644 index 0000000..fb3dec0 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/example/test_driver/integration_test.dart @@ -0,0 +1,7 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); diff --git a/local_packages/webview_flutter_android-4.10.5/lib/src/android_proxy.dart b/local_packages/webview_flutter_android-4.10.5/lib/src/android_proxy.dart new file mode 100644 index 0000000..dc23092 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/lib/src/android_proxy.dart @@ -0,0 +1,148 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'android_webkit.g.dart'; + +/// Handles constructing objects and calling static methods for the Android +/// WebView native library. +/// +/// This class provides dependency injection for the implementations of the +/// platform interface classes. Improving the ease of unit testing and/or +/// overriding the underlying Android WebView classes. +/// +/// By default each function calls the default constructor of the WebView class +/// it intends to return. +class AndroidWebViewProxy { + /// Constructs an [AndroidWebViewProxy]. + const AndroidWebViewProxy({ + this.newWebView = WebView.new, + this.newJavaScriptChannel = JavaScriptChannel.new, + this.newWebViewClient = WebViewClient.new, + this.newDownloadListener = DownloadListener.new, + this.newWebChromeClient = WebChromeClient.new, + this.setWebContentsDebuggingEnabledWebView = + WebView.setWebContentsDebuggingEnabled, + this.instanceCookieManager = _instanceCookieManager, + this.instanceFlutterAssetManager = _instanceFlutterAssetManager, + this.instanceWebStorage = _instanceWebStorage, + this.isWebViewFeatureSupported = WebViewFeature.isFeatureSupported, + this.setPaymentRequestEnabled = WebSettingsCompat.setPaymentRequestEnabled, + }); + + /// Constructs [WebView]. + final WebView Function({ + void Function(WebView, int left, int top, int oldLeft, int oldTop)? + onScrollChanged, + }) + newWebView; + + /// Constructs [JavaScriptChannel]. + final JavaScriptChannel Function({ + required String channelName, + required void Function(JavaScriptChannel, String) postMessage, + }) + newJavaScriptChannel; + + /// Constructs [WebViewClient]. + final WebViewClient Function({ + void Function(WebViewClient, WebView, String)? onPageStarted, + void Function(WebViewClient, WebView, String)? onPageFinished, + void Function( + WebViewClient, + WebView, + WebResourceRequest, + WebResourceResponse, + )? + onReceivedHttpError, + void Function(WebViewClient, WebView, WebResourceRequest, WebResourceError)? + onReceivedRequestError, + void Function( + WebViewClient, + WebView, + WebResourceRequest, + WebResourceErrorCompat, + )? + onReceivedRequestErrorCompat, + void Function(WebViewClient, WebView, WebResourceRequest)? requestLoading, + void Function(WebViewClient, WebView, String)? urlLoading, + void Function(WebViewClient, WebView, String, bool)? doUpdateVisitedHistory, + void Function(WebViewClient, WebView, HttpAuthHandler, String, String)? + onReceivedHttpAuthRequest, + void Function(WebViewClient, WebView, AndroidMessage, AndroidMessage)? + onFormResubmission, + void Function(WebViewClient, WebView, String)? onLoadResource, + void Function(WebViewClient, WebView, String)? onPageCommitVisible, + void Function(WebViewClient, WebView, ClientCertRequest)? + onReceivedClientCertRequest, + void Function(WebViewClient, WebView, String, String?, String)? + onReceivedLoginRequest, + void Function(WebViewClient, WebView, SslErrorHandler, SslError)? + onReceivedSslError, + void Function(WebViewClient, WebView, double, double)? onScaleChanged, + }) + newWebViewClient; + + /// Constructs [DownloadListener]. + final DownloadListener Function({ + required void Function( + DownloadListener, + String, + String, + String, + String, + int, + ) + onDownloadStart, + }) + newDownloadListener; + + /// Constructs [WebChromeClient]. + final WebChromeClient Function({ + void Function(WebChromeClient, WebView, int)? onProgressChanged, + required Future> Function( + WebChromeClient, + WebView, + FileChooserParams, + ) + onShowFileChooser, + void Function(WebChromeClient, PermissionRequest)? onPermissionRequest, + void Function(WebChromeClient, View, CustomViewCallback)? onShowCustomView, + void Function(WebChromeClient)? onHideCustomView, + void Function(WebChromeClient, String, GeolocationPermissionsCallback)? + onGeolocationPermissionsShowPrompt, + void Function(WebChromeClient)? onGeolocationPermissionsHidePrompt, + void Function(WebChromeClient, ConsoleMessage)? onConsoleMessage, + Future Function(WebChromeClient, WebView, String, String)? onJsAlert, + required Future Function(WebChromeClient, WebView, String, String) + onJsConfirm, + Future Function(WebChromeClient, WebView, String, String, String)? + onJsPrompt, + }) + newWebChromeClient; + + /// Calls to [WebView.setWebContentsDebuggingEnabled]. + final Future Function(bool) setWebContentsDebuggingEnabledWebView; + + /// Calls to [CookieManager.instance]. + final CookieManager Function() instanceCookieManager; + + /// Calls to [FlutterAssetManager.instance]. + final FlutterAssetManager Function() instanceFlutterAssetManager; + + /// Calls to [WebStorage.instance]. + final WebStorage Function() instanceWebStorage; + + /// Calls to [WebViewFeature.isFeatureSupported]. + final Future Function(String) isWebViewFeatureSupported; + + /// Calls to [WebSettingsCompat.setPaymentRequestEnabled]. + final Future Function(WebSettings, bool) setPaymentRequestEnabled; + + static CookieManager _instanceCookieManager() => CookieManager.instance; + + static FlutterAssetManager _instanceFlutterAssetManager() => + FlutterAssetManager.instance; + + static WebStorage _instanceWebStorage() => WebStorage.instance; +} diff --git a/local_packages/webview_flutter_android-4.10.5/lib/src/android_ssl_auth_error.dart b/local_packages/webview_flutter_android-4.10.5/lib/src/android_ssl_auth_error.dart new file mode 100644 index 0000000..055520d --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/lib/src/android_ssl_auth_error.dart @@ -0,0 +1,65 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:meta/meta.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; +import 'android_webkit.g.dart' as android; + +/// Implementation of the [PlatformSslAuthError] with the Android WebView API. +class AndroidSslAuthError extends PlatformSslAuthError { + /// Creates an [AndroidSslAuthError]. + AndroidSslAuthError._({ + required super.certificate, + required super.description, + required android.SslErrorHandler handler, + required this.url, + }) : _handler = handler; + + final android.SslErrorHandler _handler; + + /// The URL associated with the error. + final String url; + + /// Creates an [AndroidSslAuthError] from the parameters from the native + /// `WebViewClient.onReceivedSslError`. + @internal + static Future fromNativeCallback({ + required android.SslError error, + required android.SslErrorHandler handler, + }) async { + final android.SslCertificate certificate = error.certificate; + final android.X509Certificate? x509Certificate = await certificate + .getX509Certificate(); + + final android.SslErrorType errorType = await error.getPrimaryError(); + final String errorDescription = switch (errorType) { + android.SslErrorType.dateInvalid => + 'The date of the certificate is invalid.', + android.SslErrorType.expired => 'The certificate has expired.', + android.SslErrorType.idMismatch => 'Hostname mismatch.', + android.SslErrorType.invalid => 'A generic error occurred.', + android.SslErrorType.notYetValid => 'The certificate is not yet valid.', + android.SslErrorType.untrusted => + 'The certificate authority is not trusted.', + android.SslErrorType.unknown => 'The certificate has an unknown error.', + }; + + return AndroidSslAuthError._( + certificate: X509Certificate( + data: x509Certificate != null + ? await x509Certificate.getEncoded() + : null, + ), + handler: handler, + description: errorDescription, + url: error.url, + ); + } + + @override + Future cancel() => _handler.cancel(); + + @override + Future proceed() => _handler.proceed(); +} diff --git a/local_packages/webview_flutter_android-4.10.5/lib/src/android_webkit.g.dart b/local_packages/webview_flutter_android-4.10.5/lib/src/android_webkit.g.dart new file mode 100644 index 0000000..c4b2f6c --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/lib/src/android_webkit.g.dart @@ -0,0 +1,9229 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v25.5.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:io' show Platform; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' + show ReadBuffer, WriteBuffer, immutable, protected; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart' show WidgetsFlutterBinding; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +List wrapResponse({ + Object? result, + PlatformException? error, + bool empty = false, +}) { + if (empty) { + return []; + } + if (error == null) { + return [result]; + } + return [error.code, error.message, error.details]; +} + +/// An immutable object that serves as the base class for all ProxyApis and +/// can provide functional copies of itself. +/// +/// All implementers are expected to be [immutable] as defined by the annotation +/// and override [pigeon_copy] returning an instance of itself. +@immutable +abstract class PigeonInternalProxyApiBaseClass { + /// Construct a [PigeonInternalProxyApiBaseClass]. + PigeonInternalProxyApiBaseClass({ + this.pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + }) : pigeon_instanceManager = + pigeon_instanceManager ?? PigeonInstanceManager.instance; + + /// Sends and receives binary data across the Flutter platform barrier. + /// + /// If it is null, the default BinaryMessenger will be used, which routes to + /// the host platform. + @protected + final BinaryMessenger? pigeon_binaryMessenger; + + /// Maintains instances stored to communicate with native language objects. + final PigeonInstanceManager pigeon_instanceManager; + + /// Instantiates and returns a functionally identical object to oneself. + /// + /// Outside of tests, this method should only ever be called by + /// [PigeonInstanceManager]. + /// + /// Subclasses should always override their parent's implementation of this + /// method. + @protected + PigeonInternalProxyApiBaseClass pigeon_copy(); +} + +/// Maintains instances used to communicate with the native objects they +/// represent. +/// +/// Added instances are stored as weak references and their copies are stored +/// as strong references to maintain access to their variables and callback +/// methods. Both are stored with the same identifier. +/// +/// When a weak referenced instance becomes inaccessible, +/// [onWeakReferenceRemoved] is called with its associated identifier. +/// +/// If an instance is retrieved and has the possibility to be used, +/// (e.g. calling [getInstanceWithWeakReference]) a copy of the strong reference +/// is added as a weak reference with the same identifier. This prevents a +/// scenario where the weak referenced instance was released and then later +/// returned by the host platform. +class PigeonInstanceManager { + /// Constructs a [PigeonInstanceManager]. + PigeonInstanceManager({required void Function(int) onWeakReferenceRemoved}) { + this.onWeakReferenceRemoved = (int identifier) { + _weakInstances.remove(identifier); + onWeakReferenceRemoved(identifier); + }; + _finalizer = Finalizer(this.onWeakReferenceRemoved); + } + + // Identifiers are locked to a specific range to avoid collisions with objects + // created simultaneously by the host platform. + // Host uses identifiers >= 2^16 and Dart is expected to use values n where, + // 0 <= n < 2^16. + static const int _maxDartCreatedIdentifier = 65536; + + /// The default [PigeonInstanceManager] used by ProxyApis. + /// + /// On creation, this manager makes a call to clear the native + /// InstanceManager. This is to prevent identifier conflicts after a host + /// restart. + static final PigeonInstanceManager instance = _initInstance(); + + // Expando is used because it doesn't prevent its keys from becoming + // inaccessible. This allows the manager to efficiently retrieve an identifier + // of an instance without holding a strong reference to that instance. + // + // It also doesn't use `==` to search for identifiers, which would lead to an + // infinite loop when comparing an object to its copy. (i.e. which was caused + // by calling instanceManager.getIdentifier() inside of `==` while this was a + // HashMap). + final Expando _identifiers = Expando(); + final Map> + _weakInstances = >{}; + final Map _strongInstances = + {}; + late final Finalizer _finalizer; + int _nextIdentifier = 0; + + /// Called when a weak referenced instance is removed by [removeWeakReference] + /// or becomes inaccessible. + late final void Function(int) onWeakReferenceRemoved; + + static PigeonInstanceManager _initInstance() { + if (Platform.environment['FLUTTER_TEST'] == 'true') { + return PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + } + WidgetsFlutterBinding.ensureInitialized(); + final _PigeonInternalInstanceManagerApi api = + _PigeonInternalInstanceManagerApi(); + // Clears the native `PigeonInstanceManager` on the initial use of the Dart one. + api.clear(); + final PigeonInstanceManager instanceManager = PigeonInstanceManager( + onWeakReferenceRemoved: (int identifier) { + api.removeStrongReference(identifier); + }, + ); + _PigeonInternalInstanceManagerApi.setUpMessageHandlers( + instanceManager: instanceManager, + ); + WebResourceRequest.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + WebResourceResponse.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + WebResourceError.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + WebResourceErrorCompat.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + WebViewPoint.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + ConsoleMessage.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + CookieManager.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + WebView.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + WebSettings.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + JavaScriptChannel.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + WebViewClient.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + DownloadListener.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + WebChromeClient.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + FlutterAssetManager.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + WebStorage.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + FileChooserParams.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + PermissionRequest.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + CustomViewCallback.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + View.pigeon_setUpMessageHandlers(pigeon_instanceManager: instanceManager); + GeolocationPermissionsCallback.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + HttpAuthHandler.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + AndroidMessage.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + ClientCertRequest.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + PrivateKey.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + X509Certificate.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + SslErrorHandler.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + SslError.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + SslCertificateDName.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + SslCertificate.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + Certificate.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + WebSettingsCompat.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + WebViewFeature.pigeon_setUpMessageHandlers( + pigeon_instanceManager: instanceManager, + ); + return instanceManager; + } + + /// Adds a new instance that was instantiated by Dart. + /// + /// In other words, Dart wants to add a new instance that will represent + /// an object that will be instantiated on the host platform. + /// + /// Throws assertion error if the instance has already been added. + /// + /// Returns the randomly generated id of the [instance] added. + int addDartCreatedInstance(PigeonInternalProxyApiBaseClass instance) { + assert(getIdentifier(instance) == null); + + final int identifier = _nextUniqueIdentifier(); + _identifiers[instance] = identifier; + _weakInstances[identifier] = WeakReference( + instance, + ); + _finalizer.attach(instance, identifier, detach: instance); + + final PigeonInternalProxyApiBaseClass copy = instance.pigeon_copy(); + _identifiers[copy] = identifier; + _strongInstances[identifier] = copy; + return identifier; + } + + /// Removes the instance, if present, and call [onWeakReferenceRemoved] with + /// its identifier. + /// + /// Returns the identifier associated with the removed instance. Otherwise, + /// `null` if the instance was not found in this manager. + /// + /// This does not remove the strong referenced instance associated with + /// [instance]. This can be done with [remove]. + int? removeWeakReference(PigeonInternalProxyApiBaseClass instance) { + final int? identifier = getIdentifier(instance); + if (identifier == null) { + return null; + } + + _identifiers[instance] = null; + _finalizer.detach(instance); + onWeakReferenceRemoved(identifier); + + return identifier; + } + + /// Removes [identifier] and its associated strongly referenced instance, if + /// present, from the manager. + /// + /// Returns the strong referenced instance associated with [identifier] before + /// it was removed. Returns `null` if [identifier] was not associated with + /// any strong reference. + /// + /// Throws an `AssertionError` if the weak referenced instance associated with + /// [identifier] is not removed first. This can be done with + /// [removeWeakReference]. + T? remove(int identifier) { + final T? instance = _weakInstances[identifier]?.target as T?; + assert( + instance == null, + 'A strong instance with identifier $identifier is being removed despite the weak reference still existing: $instance', + ); + return _strongInstances.remove(identifier) as T?; + } + + /// Retrieves the instance associated with identifier. + /// + /// The value returned is chosen from the following order: + /// + /// 1. A weakly referenced instance associated with identifier. + /// 2. If the only instance associated with identifier is a strongly + /// referenced instance, a copy of the instance is added as a weak reference + /// with the same identifier. Returning the newly created copy. + /// 3. If no instance is associated with identifier, returns null. + /// + /// This method also expects the host `InstanceManager` to have a strong + /// reference to the instance the identifier is associated with. + T? getInstanceWithWeakReference( + int identifier, + ) { + final PigeonInternalProxyApiBaseClass? weakInstance = + _weakInstances[identifier]?.target; + + if (weakInstance == null) { + final PigeonInternalProxyApiBaseClass? strongInstance = + _strongInstances[identifier]; + if (strongInstance != null) { + final PigeonInternalProxyApiBaseClass copy = strongInstance + .pigeon_copy(); + _identifiers[copy] = identifier; + _weakInstances[identifier] = + WeakReference(copy); + _finalizer.attach(copy, identifier, detach: copy); + return copy as T; + } + return strongInstance as T?; + } + + return weakInstance as T; + } + + /// Retrieves the identifier associated with instance. + int? getIdentifier(PigeonInternalProxyApiBaseClass instance) { + return _identifiers[instance]; + } + + /// Adds a new instance that was instantiated by the host platform. + /// + /// In other words, the host platform wants to add a new instance that + /// represents an object on the host platform. Stored with [identifier]. + /// + /// Throws assertion error if the instance or its identifier has already been + /// added. + void addHostCreatedInstance( + PigeonInternalProxyApiBaseClass instance, + int identifier, + ) { + assert(!containsIdentifier(identifier)); + assert(getIdentifier(instance) == null); + assert(identifier >= 0); + + _identifiers[instance] = identifier; + _strongInstances[identifier] = instance; + } + + /// Whether this manager contains the given [identifier]. + bool containsIdentifier(int identifier) { + return _weakInstances.containsKey(identifier) || + _strongInstances.containsKey(identifier); + } + + int _nextUniqueIdentifier() { + late int identifier; + do { + identifier = _nextIdentifier; + _nextIdentifier = (_nextIdentifier + 1) % _maxDartCreatedIdentifier; + } while (containsIdentifier(identifier)); + return identifier; + } +} + +/// Generated API for managing the Dart and native `PigeonInstanceManager`s. +class _PigeonInternalInstanceManagerApi { + /// Constructor for [_PigeonInternalInstanceManagerApi]. + _PigeonInternalInstanceManagerApi({BinaryMessenger? binaryMessenger}) + : pigeonVar_binaryMessenger = binaryMessenger; + + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + static void setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? binaryMessenger, + PigeonInstanceManager? instanceManager, + }) { + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.PigeonInternalInstanceManager.removeStrongReference', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.PigeonInternalInstanceManager.removeStrongReference was null.', + ); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert( + arg_identifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.PigeonInternalInstanceManager.removeStrongReference was null, expected non-null int.', + ); + try { + (instanceManager ?? PigeonInstanceManager.instance).remove( + arg_identifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + Future removeStrongReference(int identifier) async { + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.PigeonInternalInstanceManager.removeStrongReference'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [identifier], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Clear the native `PigeonInstanceManager`. + /// + /// This is typically called after a hot restart. + Future clear() async { + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.PigeonInternalInstanceManager.clear'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } +} + +class _PigeonInternalProxyApiBaseCodec extends _PigeonCodec { + const _PigeonInternalProxyApiBaseCodec(this.instanceManager); + final PigeonInstanceManager instanceManager; + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is PigeonInternalProxyApiBaseClass) { + buffer.putUint8(128); + writeValue(buffer, instanceManager.getIdentifier(value)); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return instanceManager.getInstanceWithWeakReference( + readValue(buffer)! as int, + ); + default: + return super.readValueOfType(type, buffer); + } + } +} + +/// Mode of how to select files for a file chooser. +/// +/// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams. +enum FileChooserMode { + /// Open single file and requires that the file exists before allowing the + /// user to pick it. + /// + /// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_OPEN. + open, + + /// Similar to [open] but allows multiple files to be selected. + /// + /// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_OPEN_MULTIPLE. + openMultiple, + + /// Allows picking a nonexistent file and saving it. + /// + /// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_SAVE. + save, + + /// Indicates a `FileChooserMode` with an unknown mode. + /// + /// This does not represent an actual value provided by the platform and only + /// indicates a value was provided that isn't currently supported. + unknown, +} + +/// Indicates the type of message logged to the console. +/// +/// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel. +enum ConsoleMessageLevel { + /// Indicates a message is logged for debugging. + /// + /// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#DEBUG. + debug, + + /// Indicates a message is provided as an error. + /// + /// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#ERROR. + error, + + /// Indicates a message is provided as a basic log message. + /// + /// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#LOG. + log, + + /// Indicates a message is provided as a tip. + /// + /// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#TIP. + tip, + + /// Indicates a message is provided as a warning. + /// + /// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#WARNING. + warning, + + /// Indicates a message with an unknown level. + /// + /// This does not represent an actual value provided by the platform and only + /// indicates a value was provided that isn't currently supported. + unknown, +} + +/// The over-scroll mode for a view. +/// +/// See https://developer.android.com/reference/android/view/View#OVER_SCROLL_ALWAYS. +enum OverScrollMode { + /// Always allow a user to over-scroll this view, provided it is a view that + /// can scroll. + always, + + /// Allow a user to over-scroll this view only if the content is large enough + /// to meaningfully scroll, provided it is a view that can scroll. + ifContentScrolls, + + /// Never allow a user to over-scroll this view. + never, + + /// The type is not recognized by this wrapper. + unknown, +} + +/// Type of error for a SslCertificate. +/// +/// See https://developer.android.com/reference/android/net/http/SslError#SSL_DATE_INVALID. +enum SslErrorType { + /// The date of the certificate is invalid. + dateInvalid, + + /// The certificate has expired. + expired, + + /// Hostname mismatch. + idMismatch, + + /// A generic error occurred. + invalid, + + /// The certificate is not yet valid. + notYetValid, + + /// The certificate authority is not trusted. + untrusted, + + /// The type is not recognized by this wrapper. + unknown, +} + +/// Options for mixed content mode support. +/// +/// See https://developer.android.com/reference/android/webkit/WebSettings#MIXED_CONTENT_ALWAYS_ALLOW +enum MixedContentMode { + /// The WebView will allow a secure origin to load content from any other + /// origin, even if that origin is insecure. + alwaysAllow, + + /// The WebView will attempt to be compatible with the approach of a modern + /// web browser with regard to mixed content. + compatibilityMode, + + /// The WebView will not allow a secure origin to load content from an + /// insecure origin. + neverAllow, +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else if (value is FileChooserMode) { + buffer.putUint8(129); + writeValue(buffer, value.index); + } else if (value is ConsoleMessageLevel) { + buffer.putUint8(130); + writeValue(buffer, value.index); + } else if (value is OverScrollMode) { + buffer.putUint8(131); + writeValue(buffer, value.index); + } else if (value is SslErrorType) { + buffer.putUint8(132); + writeValue(buffer, value.index); + } else if (value is MixedContentMode) { + buffer.putUint8(133); + writeValue(buffer, value.index); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 129: + final int? value = readValue(buffer) as int?; + return value == null ? null : FileChooserMode.values[value]; + case 130: + final int? value = readValue(buffer) as int?; + return value == null ? null : ConsoleMessageLevel.values[value]; + case 131: + final int? value = readValue(buffer) as int?; + return value == null ? null : OverScrollMode.values[value]; + case 132: + final int? value = readValue(buffer) as int?; + return value == null ? null : SslErrorType.values[value]; + case 133: + final int? value = readValue(buffer) as int?; + return value == null ? null : MixedContentMode.values[value]; + default: + return super.readValueOfType(type, buffer); + } + } +} + +/// Encompasses parameters to the `WebViewClient.shouldInterceptRequest` method. +/// +/// See https://developer.android.com/reference/android/webkit/WebResourceRequest. +class WebResourceRequest extends PigeonInternalProxyApiBaseClass { + /// Constructs [WebResourceRequest] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + WebResourceRequest.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.url, + required this.isForMainFrame, + required this.isRedirect, + required this.hasGesture, + required this.method, + this.requestHeaders, + }); + + /// The URL for which the resource request was made. + final String url; + + /// Whether the request was made in order to fetch the main frame's document. + final bool isForMainFrame; + + /// Whether the request was a result of a server-side redirect. + final bool isRedirect; + + /// Whether a gesture (such as a click) was associated with the request. + final bool hasGesture; + + /// The method associated with the request, for example "GET". + final String method; + + /// The headers associated with the request. + final Map? requestHeaders; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + WebResourceRequest Function( + String url, + bool isForMainFrame, + bool isRedirect, + bool hasGesture, + String method, + Map? requestHeaders, + )? + pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebResourceRequest.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebResourceRequest.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebResourceRequest.pigeon_newInstance was null, expected non-null int.', + ); + final String? arg_url = (args[1] as String?); + assert( + arg_url != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebResourceRequest.pigeon_newInstance was null, expected non-null String.', + ); + final bool? arg_isForMainFrame = (args[2] as bool?); + assert( + arg_isForMainFrame != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebResourceRequest.pigeon_newInstance was null, expected non-null bool.', + ); + final bool? arg_isRedirect = (args[3] as bool?); + assert( + arg_isRedirect != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebResourceRequest.pigeon_newInstance was null, expected non-null bool.', + ); + final bool? arg_hasGesture = (args[4] as bool?); + assert( + arg_hasGesture != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebResourceRequest.pigeon_newInstance was null, expected non-null bool.', + ); + final String? arg_method = (args[5] as String?); + assert( + arg_method != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebResourceRequest.pigeon_newInstance was null, expected non-null String.', + ); + final Map? arg_requestHeaders = + (args[6] as Map?)?.cast(); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call( + arg_url!, + arg_isForMainFrame!, + arg_isRedirect!, + arg_hasGesture!, + arg_method!, + arg_requestHeaders, + ) ?? + WebResourceRequest.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + url: arg_url!, + isForMainFrame: arg_isForMainFrame!, + isRedirect: arg_isRedirect!, + hasGesture: arg_hasGesture!, + method: arg_method!, + requestHeaders: arg_requestHeaders, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + @override + WebResourceRequest pigeon_copy() { + return WebResourceRequest.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + url: url, + isForMainFrame: isForMainFrame, + isRedirect: isRedirect, + hasGesture: hasGesture, + method: method, + requestHeaders: requestHeaders, + ); + } +} + +/// Encapsulates a resource response. +/// +/// See https://developer.android.com/reference/android/webkit/WebResourceResponse. +class WebResourceResponse extends PigeonInternalProxyApiBaseClass { + /// Constructs [WebResourceResponse] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + WebResourceResponse.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.statusCode, + }); + + /// The resource response's status code. + final int statusCode; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + WebResourceResponse Function(int statusCode)? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebResourceResponse.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebResourceResponse.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebResourceResponse.pigeon_newInstance was null, expected non-null int.', + ); + final int? arg_statusCode = (args[1] as int?); + assert( + arg_statusCode != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebResourceResponse.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call(arg_statusCode!) ?? + WebResourceResponse.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + statusCode: arg_statusCode!, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + @override + WebResourceResponse pigeon_copy() { + return WebResourceResponse.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + statusCode: statusCode, + ); + } +} + +/// Encapsulates information about errors that occurred during loading of web +/// resources. +/// +/// See https://developer.android.com/reference/android/webkit/WebResourceError. +class WebResourceError extends PigeonInternalProxyApiBaseClass { + /// Constructs [WebResourceError] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + WebResourceError.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.errorCode, + required this.description, + }); + + /// The error code of the error. + final int errorCode; + + /// The string describing the error. + final String description; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + WebResourceError Function(int errorCode, String description)? + pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebResourceError.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebResourceError.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebResourceError.pigeon_newInstance was null, expected non-null int.', + ); + final int? arg_errorCode = (args[1] as int?); + assert( + arg_errorCode != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebResourceError.pigeon_newInstance was null, expected non-null int.', + ); + final String? arg_description = (args[2] as String?); + assert( + arg_description != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebResourceError.pigeon_newInstance was null, expected non-null String.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call(arg_errorCode!, arg_description!) ?? + WebResourceError.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + errorCode: arg_errorCode!, + description: arg_description!, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + @override + WebResourceError pigeon_copy() { + return WebResourceError.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + errorCode: errorCode, + description: description, + ); + } +} + +/// Encapsulates information about errors that occurred during loading of web +/// resources. +/// +/// See https://developer.android.com/reference/androidx/webkit/WebResourceErrorCompat. +class WebResourceErrorCompat extends PigeonInternalProxyApiBaseClass { + /// Constructs [WebResourceErrorCompat] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + WebResourceErrorCompat.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.errorCode, + required this.description, + }); + + /// The error code of the error. + final int errorCode; + + /// The string describing the error. + final String description; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + WebResourceErrorCompat Function(int errorCode, String description)? + pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebResourceErrorCompat.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebResourceErrorCompat.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebResourceErrorCompat.pigeon_newInstance was null, expected non-null int.', + ); + final int? arg_errorCode = (args[1] as int?); + assert( + arg_errorCode != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebResourceErrorCompat.pigeon_newInstance was null, expected non-null int.', + ); + final String? arg_description = (args[2] as String?); + assert( + arg_description != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebResourceErrorCompat.pigeon_newInstance was null, expected non-null String.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call(arg_errorCode!, arg_description!) ?? + WebResourceErrorCompat.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + errorCode: arg_errorCode!, + description: arg_description!, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + @override + WebResourceErrorCompat pigeon_copy() { + return WebResourceErrorCompat.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + errorCode: errorCode, + description: description, + ); + } +} + +/// Represents a position on a web page. +/// +/// This is a custom class created for convenience of the wrapper. +class WebViewPoint extends PigeonInternalProxyApiBaseClass { + /// Constructs [WebViewPoint] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + WebViewPoint.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.x, + required this.y, + }); + + final int x; + + final int y; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + WebViewPoint Function(int x, int y)? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebViewPoint.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewPoint.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewPoint.pigeon_newInstance was null, expected non-null int.', + ); + final int? arg_x = (args[1] as int?); + assert( + arg_x != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewPoint.pigeon_newInstance was null, expected non-null int.', + ); + final int? arg_y = (args[2] as int?); + assert( + arg_y != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewPoint.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call(arg_x!, arg_y!) ?? + WebViewPoint.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + x: arg_x!, + y: arg_y!, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + @override + WebViewPoint pigeon_copy() { + return WebViewPoint.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + x: x, + y: y, + ); + } +} + +/// Represents a JavaScript console message from WebCore. +/// +/// See https://developer.android.com/reference/android/webkit/ConsoleMessage +class ConsoleMessage extends PigeonInternalProxyApiBaseClass { + /// Constructs [ConsoleMessage] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + ConsoleMessage.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.lineNumber, + required this.message, + required this.level, + required this.sourceId, + }); + + final int lineNumber; + + final String message; + + final ConsoleMessageLevel level; + + final String sourceId; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + ConsoleMessage Function( + int lineNumber, + String message, + ConsoleMessageLevel level, + String sourceId, + )? + pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.ConsoleMessage.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.ConsoleMessage.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.ConsoleMessage.pigeon_newInstance was null, expected non-null int.', + ); + final int? arg_lineNumber = (args[1] as int?); + assert( + arg_lineNumber != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.ConsoleMessage.pigeon_newInstance was null, expected non-null int.', + ); + final String? arg_message = (args[2] as String?); + assert( + arg_message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.ConsoleMessage.pigeon_newInstance was null, expected non-null String.', + ); + final ConsoleMessageLevel? arg_level = + (args[3] as ConsoleMessageLevel?); + assert( + arg_level != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.ConsoleMessage.pigeon_newInstance was null, expected non-null ConsoleMessageLevel.', + ); + final String? arg_sourceId = (args[4] as String?); + assert( + arg_sourceId != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.ConsoleMessage.pigeon_newInstance was null, expected non-null String.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call( + arg_lineNumber!, + arg_message!, + arg_level!, + arg_sourceId!, + ) ?? + ConsoleMessage.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + lineNumber: arg_lineNumber!, + message: arg_message!, + level: arg_level!, + sourceId: arg_sourceId!, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + @override + ConsoleMessage pigeon_copy() { + return ConsoleMessage.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + lineNumber: lineNumber, + message: message, + level: level, + sourceId: sourceId, + ); + } +} + +/// Manages the cookies used by an application's `WebView` instances. +/// +/// See https://developer.android.com/reference/android/webkit/CookieManager. +class CookieManager extends PigeonInternalProxyApiBaseClass { + /// Constructs [CookieManager] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + CookieManager.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec _pigeonVar_codecCookieManager = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + static final CookieManager instance = pigeonVar_instance(); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + CookieManager Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.CookieManager.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.CookieManager.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.CookieManager.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + CookieManager.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + static CookieManager pigeonVar_instance() { + final CookieManager pigeonVar_instance = CookieManager.pigeon_detached(); + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec(PigeonInstanceManager.instance); + final BinaryMessenger pigeonVar_binaryMessenger = + ServicesBinding.instance.defaultBinaryMessenger; + final int pigeonVar_instanceIdentifier = PigeonInstanceManager.instance + .addDartCreatedInstance(pigeonVar_instance); + () async { + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.CookieManager.instance'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [pigeonVar_instanceIdentifier], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + }(); + return pigeonVar_instance; + } + + /// Sets a single cookie (key-value pair) for the given URL. + Future setCookie(String url, String value) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecCookieManager; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.CookieManager.setCookie'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, url, value], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Removes all cookies. + Future removeAllCookies() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecCookieManager; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.CookieManager.removeAllCookies'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } + + /// Sets whether the `WebView` should allow third party cookies to be set. + Future setAcceptThirdPartyCookies(WebView webView, bool accept) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecCookieManager; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.CookieManager.setAcceptThirdPartyCookies'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, webView, accept], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + @override + CookieManager pigeon_copy() { + return CookieManager.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// A View that displays web pages. +/// +/// See https://developer.android.com/reference/android/webkit/WebView. +class WebView extends View { + WebView({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + this.onScrollChanged, + }) : super.pigeon_detached() { + final int pigeonVar_instanceIdentifier = pigeon_instanceManager + .addDartCreatedInstance(this); + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.pigeon_defaultConstructor'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [pigeonVar_instanceIdentifier], + ); + () async { + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + }(); + } + + /// Constructs [WebView] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + WebView.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + this.onScrollChanged, + }) : super.pigeon_detached(); + + late final _PigeonInternalProxyApiBaseCodec _pigeonVar_codecWebView = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + /// This is called in response to an internal scroll in this view (i.e., the + /// view scrolled its own contents). + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebView instance = WebView( + /// onScrollChanged: (WebView pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + WebView pigeon_instance, + int left, + int top, + int oldLeft, + int oldTop, + )? + onScrollChanged; + + /// The WebSettings object used to control the settings for this WebView. + late final WebSettings settings = pigeonVar_settings(); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + WebView Function()? pigeon_newInstance, + void Function( + WebView pigeon_instance, + int left, + int top, + int oldLeft, + int oldTop, + )? + onScrollChanged, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebView.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebView.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebView.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + WebView.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebView.onScrollChanged', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebView.onScrollChanged was null.', + ); + final List args = (message as List?)!; + final WebView? arg_pigeon_instance = (args[0] as WebView?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebView.onScrollChanged was null, expected non-null WebView.', + ); + final int? arg_left = (args[1] as int?); + assert( + arg_left != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebView.onScrollChanged was null, expected non-null int.', + ); + final int? arg_top = (args[2] as int?); + assert( + arg_top != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebView.onScrollChanged was null, expected non-null int.', + ); + final int? arg_oldLeft = (args[3] as int?); + assert( + arg_oldLeft != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebView.onScrollChanged was null, expected non-null int.', + ); + final int? arg_oldTop = (args[4] as int?); + assert( + arg_oldTop != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebView.onScrollChanged was null, expected non-null int.', + ); + try { + (onScrollChanged ?? arg_pigeon_instance!.onScrollChanged)?.call( + arg_pigeon_instance!, + arg_left!, + arg_top!, + arg_oldLeft!, + arg_oldTop!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + WebSettings pigeonVar_settings() { + final WebSettings pigeonVar_instance = WebSettings.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + final int pigeonVar_instanceIdentifier = pigeon_instanceManager + .addDartCreatedInstance(pigeonVar_instance); + () async { + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.settings'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, pigeonVar_instanceIdentifier], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + }(); + return pigeonVar_instance; + } + + /// Loads the given data into this WebView using a 'data' scheme URL. + Future loadData(String data, String? mimeType, String? encoding) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.loadData'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, data, mimeType, encoding], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Loads the given data into this WebView, using baseUrl as the base URL for + /// the content. + Future loadDataWithBaseUrl( + String? baseUrl, + String data, + String? mimeType, + String? encoding, + String? historyUrl, + ) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.loadDataWithBaseUrl'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, baseUrl, data, mimeType, encoding, historyUrl], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Loads the given URL. + Future loadUrl(String url, Map headers) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.loadUrl'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, url, headers], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Loads the URL with postData using "POST" method into this WebView. + Future postUrl(String url, Uint8List data) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.postUrl'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, url, data], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Gets the URL for the current page. + Future getUrl() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.getUrl'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as String?); + } + } + + /// Gets whether this WebView has a back history item. + Future canGoBack() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.canGoBack'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } + + /// Gets whether this WebView has a forward history item. + Future canGoForward() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.canGoForward'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } + + /// Goes back in the history of this WebView. + Future goBack() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.goBack'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Goes forward in the history of this WebView. + Future goForward() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.goForward'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Reloads the current URL. + Future reload() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.reload'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Clears the resource cache. + Future clearCache(bool includeDiskFiles) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.clearCache'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, includeDiskFiles], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Asynchronously evaluates JavaScript in the context of the currently + /// displayed page. + Future evaluateJavascript(String javascriptString) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.evaluateJavascript'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, javascriptString], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as String?); + } + } + + /// Gets the title for the current page. + Future getTitle() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.getTitle'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as String?); + } + } + + /// Enables debugging of web contents (HTML / CSS / JavaScript) loaded into + /// any WebViews of this application. + static Future setWebContentsDebuggingEnabled( + bool enabled, { + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + }) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.setWebContentsDebuggingEnabled'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [enabled], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets the WebViewClient that will receive various notifications and + /// requests. + Future setWebViewClient(WebViewClient? client) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.setWebViewClient'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, client], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Injects the supplied Java object into this WebView. + Future addJavaScriptChannel(JavaScriptChannel channel) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.addJavaScriptChannel'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, channel], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Removes a previously injected Java object from this WebView. + Future removeJavaScriptChannel(String name) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.removeJavaScriptChannel'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, name], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Registers the interface to be used when content can not be handled by the + /// rendering engine, and should be downloaded instead. + Future setDownloadListener(DownloadListener? listener) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.setDownloadListener'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, listener], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets the chrome handler. + Future setWebChromeClient(WebChromeClient? client) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.setWebChromeClient'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, client], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets the background color for this view. + Future setBackgroundColor(int color) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.setBackgroundColor'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, color], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Destroys the internal state of this WebView. + Future destroy() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebView.destroy'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + @override + WebView pigeon_copy() { + return WebView.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + onScrollChanged: onScrollChanged, + ); + } +} + +/// Manages settings state for a `WebView`. +/// +/// See https://developer.android.com/reference/android/webkit/WebSettings. +class WebSettings extends PigeonInternalProxyApiBaseClass { + /// Constructs [WebSettings] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + WebSettings.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec _pigeonVar_codecWebSettings = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + WebSettings Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebSettings.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebSettings.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebSettings.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + WebSettings.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + /// Sets whether the DOM storage API is enabled. + Future setDomStorageEnabled(bool flag) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebSettings; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebSettings.setDomStorageEnabled'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, flag], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Tells JavaScript to open windows automatically. + Future setJavaScriptCanOpenWindowsAutomatically(bool flag) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebSettings; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebSettings.setJavaScriptCanOpenWindowsAutomatically'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, flag], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets whether the WebView whether supports multiple windows. + Future setSupportMultipleWindows(bool support) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebSettings; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebSettings.setSupportMultipleWindows'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, support], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Tells the WebView to enable JavaScript execution. + Future setJavaScriptEnabled(bool flag) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebSettings; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebSettings.setJavaScriptEnabled'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, flag], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets the WebView's user-agent string. + Future setUserAgentString(String? userAgentString) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebSettings; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebSettings.setUserAgentString'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, userAgentString], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets whether the WebView requires a user gesture to play media. + Future setMediaPlaybackRequiresUserGesture(bool require) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebSettings; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebSettings.setMediaPlaybackRequiresUserGesture'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, require], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets whether the WebView should support zooming using its on-screen zoom + /// controls and gestures. + Future setSupportZoom(bool support) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebSettings; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebSettings.setSupportZoom'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, support], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets whether the WebView loads pages in overview mode, that is, zooms out + /// the content to fit on screen by width. + Future setLoadWithOverviewMode(bool overview) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebSettings; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebSettings.setLoadWithOverviewMode'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, overview], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets whether the WebView should enable support for the "viewport" HTML + /// meta tag or should use a wide viewport. + Future setUseWideViewPort(bool use) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebSettings; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebSettings.setUseWideViewPort'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, use], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets whether the WebView should display on-screen zoom controls when using + /// the built-in zoom mechanisms. + Future setDisplayZoomControls(bool enabled) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebSettings; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebSettings.setDisplayZoomControls'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, enabled], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets whether the WebView should display on-screen zoom controls when using + /// the built-in zoom mechanisms. + Future setBuiltInZoomControls(bool enabled) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebSettings; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebSettings.setBuiltInZoomControls'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, enabled], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Enables or disables file access within WebView. + Future setAllowFileAccess(bool enabled) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebSettings; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebSettings.setAllowFileAccess'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, enabled], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Enables or disables content URL access within WebView. + Future setAllowContentAccess(bool enabled) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebSettings; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebSettings.setAllowContentAccess'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, enabled], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets whether Geolocation is enabled within WebView. + Future setGeolocationEnabled(bool enabled) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebSettings; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebSettings.setGeolocationEnabled'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, enabled], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets the text zoom of the page in percent. + Future setTextZoom(int textZoom) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebSettings; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebSettings.setTextZoom'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, textZoom], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Gets the WebView's user-agent string. + Future getUserAgentString() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebSettings; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebSettings.getUserAgentString'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as String?)!; + } + } + + /// Configures the WebView's behavior when handling mixed content. + Future setMixedContentMode(MixedContentMode mode) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebSettings; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebSettings.setMixedContentMode'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, mode], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + @override + WebSettings pigeon_copy() { + return WebSettings.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// A JavaScript interface for exposing Javascript callbacks to Dart. +/// +/// This is a custom class for the wrapper that is annotated with +/// [JavascriptInterface](https://developer.android.com/reference/android/webkit/JavascriptInterface). +class JavaScriptChannel extends PigeonInternalProxyApiBaseClass { + JavaScriptChannel({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.channelName, + required this.postMessage, + }) { + final int pigeonVar_instanceIdentifier = pigeon_instanceManager + .addDartCreatedInstance(this); + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecJavaScriptChannel; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.JavaScriptChannel.pigeon_defaultConstructor'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [pigeonVar_instanceIdentifier, channelName], + ); + () async { + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + }(); + } + + /// Constructs [JavaScriptChannel] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + JavaScriptChannel.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.channelName, + required this.postMessage, + }); + + late final _PigeonInternalProxyApiBaseCodec + _pigeonVar_codecJavaScriptChannel = _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager, + ); + + final String channelName; + + /// Handles callbacks messages from JavaScript. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final JavaScriptChannel instance = JavaScriptChannel( + /// postMessage: (JavaScriptChannel pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function(JavaScriptChannel pigeon_instance, String message) + postMessage; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + void Function(JavaScriptChannel pigeon_instance, String message)? + postMessage, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.JavaScriptChannel.postMessage', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.JavaScriptChannel.postMessage was null.', + ); + final List args = (message as List?)!; + final JavaScriptChannel? arg_pigeon_instance = + (args[0] as JavaScriptChannel?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.JavaScriptChannel.postMessage was null, expected non-null JavaScriptChannel.', + ); + final String? arg_message = (args[1] as String?); + assert( + arg_message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.JavaScriptChannel.postMessage was null, expected non-null String.', + ); + try { + (postMessage ?? arg_pigeon_instance!.postMessage).call( + arg_pigeon_instance!, + arg_message!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + @override + JavaScriptChannel pigeon_copy() { + return JavaScriptChannel.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + channelName: channelName, + postMessage: postMessage, + ); + } +} + +/// Receives various notifications and requests from a `WebView`. +/// +/// See https://developer.android.com/reference/android/webkit/WebViewClient. +class WebViewClient extends PigeonInternalProxyApiBaseClass { + WebViewClient({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + this.onPageStarted, + this.onPageFinished, + this.onReceivedHttpError, + this.onReceivedRequestError, + this.onReceivedRequestErrorCompat, + this.requestLoading, + this.urlLoading, + this.doUpdateVisitedHistory, + this.onReceivedHttpAuthRequest, + this.onFormResubmission, + this.onLoadResource, + this.onPageCommitVisible, + this.onReceivedClientCertRequest, + this.onReceivedLoginRequest, + this.onReceivedSslError, + this.onScaleChanged, + }) { + final int pigeonVar_instanceIdentifier = pigeon_instanceManager + .addDartCreatedInstance(this); + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebViewClient; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebViewClient.pigeon_defaultConstructor'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [pigeonVar_instanceIdentifier], + ); + () async { + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + }(); + } + + /// Constructs [WebViewClient] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + WebViewClient.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + this.onPageStarted, + this.onPageFinished, + this.onReceivedHttpError, + this.onReceivedRequestError, + this.onReceivedRequestErrorCompat, + this.requestLoading, + this.urlLoading, + this.doUpdateVisitedHistory, + this.onReceivedHttpAuthRequest, + this.onFormResubmission, + this.onLoadResource, + this.onPageCommitVisible, + this.onReceivedClientCertRequest, + this.onReceivedLoginRequest, + this.onReceivedSslError, + this.onScaleChanged, + }); + + late final _PigeonInternalProxyApiBaseCodec _pigeonVar_codecWebViewClient = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + /// Notify the host application that a page has started loading. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebViewClient instance = WebViewClient( + /// onPageStarted: (WebViewClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + WebViewClient pigeon_instance, + WebView webView, + String url, + )? + onPageStarted; + + /// Notify the host application that a page has finished loading. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebViewClient instance = WebViewClient( + /// onPageFinished: (WebViewClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + WebViewClient pigeon_instance, + WebView webView, + String url, + )? + onPageFinished; + + /// Notify the host application that an HTTP error has been received from the + /// server while loading a resource. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebViewClient instance = WebViewClient( + /// onReceivedHttpError: (WebViewClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + WebViewClient pigeon_instance, + WebView webView, + WebResourceRequest request, + WebResourceResponse response, + )? + onReceivedHttpError; + + /// Report web resource loading error to the host application. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebViewClient instance = WebViewClient( + /// onReceivedRequestError: (WebViewClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + WebViewClient pigeon_instance, + WebView webView, + WebResourceRequest request, + WebResourceError error, + )? + onReceivedRequestError; + + /// Report web resource loading error to the host application. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebViewClient instance = WebViewClient( + /// onReceivedRequestErrorCompat: (WebViewClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + WebViewClient pigeon_instance, + WebView webView, + WebResourceRequest request, + WebResourceErrorCompat error, + )? + onReceivedRequestErrorCompat; + + /// Give the host application a chance to take control when a URL is about to + /// be loaded in the current WebView. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebViewClient instance = WebViewClient( + /// requestLoading: (WebViewClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + WebViewClient pigeon_instance, + WebView webView, + WebResourceRequest request, + )? + requestLoading; + + /// Give the host application a chance to take control when a URL is about to + /// be loaded in the current WebView. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebViewClient instance = WebViewClient( + /// urlLoading: (WebViewClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + WebViewClient pigeon_instance, + WebView webView, + String url, + )? + urlLoading; + + /// Notify the host application to update its visited links database. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebViewClient instance = WebViewClient( + /// doUpdateVisitedHistory: (WebViewClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + WebViewClient pigeon_instance, + WebView webView, + String url, + bool isReload, + )? + doUpdateVisitedHistory; + + /// Notifies the host application that the WebView received an HTTP + /// authentication request. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebViewClient instance = WebViewClient( + /// onReceivedHttpAuthRequest: (WebViewClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + WebViewClient pigeon_instance, + WebView webView, + HttpAuthHandler handler, + String host, + String realm, + )? + onReceivedHttpAuthRequest; + + /// Ask the host application if the browser should resend data as the + /// requested page was a result of a POST. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebViewClient instance = WebViewClient( + /// onFormResubmission: (WebViewClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + WebViewClient pigeon_instance, + WebView view, + AndroidMessage dontResend, + AndroidMessage resend, + )? + onFormResubmission; + + /// Notify the host application that the WebView will load the resource + /// specified by the given url. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebViewClient instance = WebViewClient( + /// onLoadResource: (WebViewClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function(WebViewClient pigeon_instance, WebView view, String url)? + onLoadResource; + + /// Notify the host application that WebView content left over from previous + /// page navigations will no longer be drawn. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebViewClient instance = WebViewClient( + /// onPageCommitVisible: (WebViewClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function(WebViewClient pigeon_instance, WebView view, String url)? + onPageCommitVisible; + + /// Notify the host application to handle a SSL client certificate request. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebViewClient instance = WebViewClient( + /// onReceivedClientCertRequest: (WebViewClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + WebViewClient pigeon_instance, + WebView view, + ClientCertRequest request, + )? + onReceivedClientCertRequest; + + /// Notify the host application that a request to automatically log in the + /// user has been processed. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebViewClient instance = WebViewClient( + /// onReceivedLoginRequest: (WebViewClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + WebViewClient pigeon_instance, + WebView view, + String realm, + String? account, + String args, + )? + onReceivedLoginRequest; + + /// Notifies the host application that an SSL error occurred while loading a + /// resource. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebViewClient instance = WebViewClient( + /// onReceivedSslError: (WebViewClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + WebViewClient pigeon_instance, + WebView view, + SslErrorHandler handler, + SslError error, + )? + onReceivedSslError; + + /// Notify the host application that the scale applied to the WebView has + /// changed. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebViewClient instance = WebViewClient( + /// onScaleChanged: (WebViewClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + WebViewClient pigeon_instance, + WebView view, + double oldScale, + double newScale, + )? + onScaleChanged; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + WebViewClient Function()? pigeon_newInstance, + void Function(WebViewClient pigeon_instance, WebView webView, String url)? + onPageStarted, + void Function(WebViewClient pigeon_instance, WebView webView, String url)? + onPageFinished, + void Function( + WebViewClient pigeon_instance, + WebView webView, + WebResourceRequest request, + WebResourceResponse response, + )? + onReceivedHttpError, + void Function( + WebViewClient pigeon_instance, + WebView webView, + WebResourceRequest request, + WebResourceError error, + )? + onReceivedRequestError, + void Function( + WebViewClient pigeon_instance, + WebView webView, + WebResourceRequest request, + WebResourceErrorCompat error, + )? + onReceivedRequestErrorCompat, + void Function( + WebViewClient pigeon_instance, + WebView webView, + WebResourceRequest request, + )? + requestLoading, + void Function(WebViewClient pigeon_instance, WebView webView, String url)? + urlLoading, + void Function( + WebViewClient pigeon_instance, + WebView webView, + String url, + bool isReload, + )? + doUpdateVisitedHistory, + void Function( + WebViewClient pigeon_instance, + WebView webView, + HttpAuthHandler handler, + String host, + String realm, + )? + onReceivedHttpAuthRequest, + void Function( + WebViewClient pigeon_instance, + WebView view, + AndroidMessage dontResend, + AndroidMessage resend, + )? + onFormResubmission, + void Function(WebViewClient pigeon_instance, WebView view, String url)? + onLoadResource, + void Function(WebViewClient pigeon_instance, WebView view, String url)? + onPageCommitVisible, + void Function( + WebViewClient pigeon_instance, + WebView view, + ClientCertRequest request, + )? + onReceivedClientCertRequest, + void Function( + WebViewClient pigeon_instance, + WebView view, + String realm, + String? account, + String args, + )? + onReceivedLoginRequest, + void Function( + WebViewClient pigeon_instance, + WebView view, + SslErrorHandler handler, + SslError error, + )? + onReceivedSslError, + void Function( + WebViewClient pigeon_instance, + WebView view, + double oldScale, + double newScale, + )? + onScaleChanged, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebViewClient.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + WebViewClient.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebViewClient.onPageStarted', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onPageStarted was null.', + ); + final List args = (message as List?)!; + final WebViewClient? arg_pigeon_instance = + (args[0] as WebViewClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onPageStarted was null, expected non-null WebViewClient.', + ); + final WebView? arg_webView = (args[1] as WebView?); + assert( + arg_webView != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onPageStarted was null, expected non-null WebView.', + ); + final String? arg_url = (args[2] as String?); + assert( + arg_url != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onPageStarted was null, expected non-null String.', + ); + try { + (onPageStarted ?? arg_pigeon_instance!.onPageStarted)?.call( + arg_pigeon_instance!, + arg_webView!, + arg_url!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebViewClient.onPageFinished', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onPageFinished was null.', + ); + final List args = (message as List?)!; + final WebViewClient? arg_pigeon_instance = + (args[0] as WebViewClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onPageFinished was null, expected non-null WebViewClient.', + ); + final WebView? arg_webView = (args[1] as WebView?); + assert( + arg_webView != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onPageFinished was null, expected non-null WebView.', + ); + final String? arg_url = (args[2] as String?); + assert( + arg_url != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onPageFinished was null, expected non-null String.', + ); + try { + (onPageFinished ?? arg_pigeon_instance!.onPageFinished)?.call( + arg_pigeon_instance!, + arg_webView!, + arg_url!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedHttpError', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedHttpError was null.', + ); + final List args = (message as List?)!; + final WebViewClient? arg_pigeon_instance = + (args[0] as WebViewClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedHttpError was null, expected non-null WebViewClient.', + ); + final WebView? arg_webView = (args[1] as WebView?); + assert( + arg_webView != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedHttpError was null, expected non-null WebView.', + ); + final WebResourceRequest? arg_request = + (args[2] as WebResourceRequest?); + assert( + arg_request != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedHttpError was null, expected non-null WebResourceRequest.', + ); + final WebResourceResponse? arg_response = + (args[3] as WebResourceResponse?); + assert( + arg_response != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedHttpError was null, expected non-null WebResourceResponse.', + ); + try { + (onReceivedHttpError ?? arg_pigeon_instance!.onReceivedHttpError) + ?.call( + arg_pigeon_instance!, + arg_webView!, + arg_request!, + arg_response!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedRequestError', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedRequestError was null.', + ); + final List args = (message as List?)!; + final WebViewClient? arg_pigeon_instance = + (args[0] as WebViewClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedRequestError was null, expected non-null WebViewClient.', + ); + final WebView? arg_webView = (args[1] as WebView?); + assert( + arg_webView != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedRequestError was null, expected non-null WebView.', + ); + final WebResourceRequest? arg_request = + (args[2] as WebResourceRequest?); + assert( + arg_request != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedRequestError was null, expected non-null WebResourceRequest.', + ); + final WebResourceError? arg_error = (args[3] as WebResourceError?); + assert( + arg_error != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedRequestError was null, expected non-null WebResourceError.', + ); + try { + (onReceivedRequestError ?? + arg_pigeon_instance!.onReceivedRequestError) + ?.call( + arg_pigeon_instance!, + arg_webView!, + arg_request!, + arg_error!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedRequestErrorCompat', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedRequestErrorCompat was null.', + ); + final List args = (message as List?)!; + final WebViewClient? arg_pigeon_instance = + (args[0] as WebViewClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedRequestErrorCompat was null, expected non-null WebViewClient.', + ); + final WebView? arg_webView = (args[1] as WebView?); + assert( + arg_webView != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedRequestErrorCompat was null, expected non-null WebView.', + ); + final WebResourceRequest? arg_request = + (args[2] as WebResourceRequest?); + assert( + arg_request != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedRequestErrorCompat was null, expected non-null WebResourceRequest.', + ); + final WebResourceErrorCompat? arg_error = + (args[3] as WebResourceErrorCompat?); + assert( + arg_error != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedRequestErrorCompat was null, expected non-null WebResourceErrorCompat.', + ); + try { + (onReceivedRequestErrorCompat ?? + arg_pigeon_instance!.onReceivedRequestErrorCompat) + ?.call( + arg_pigeon_instance!, + arg_webView!, + arg_request!, + arg_error!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebViewClient.requestLoading', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.requestLoading was null.', + ); + final List args = (message as List?)!; + final WebViewClient? arg_pigeon_instance = + (args[0] as WebViewClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.requestLoading was null, expected non-null WebViewClient.', + ); + final WebView? arg_webView = (args[1] as WebView?); + assert( + arg_webView != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.requestLoading was null, expected non-null WebView.', + ); + final WebResourceRequest? arg_request = + (args[2] as WebResourceRequest?); + assert( + arg_request != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.requestLoading was null, expected non-null WebResourceRequest.', + ); + try { + (requestLoading ?? arg_pigeon_instance!.requestLoading)?.call( + arg_pigeon_instance!, + arg_webView!, + arg_request!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebViewClient.urlLoading', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.urlLoading was null.', + ); + final List args = (message as List?)!; + final WebViewClient? arg_pigeon_instance = + (args[0] as WebViewClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.urlLoading was null, expected non-null WebViewClient.', + ); + final WebView? arg_webView = (args[1] as WebView?); + assert( + arg_webView != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.urlLoading was null, expected non-null WebView.', + ); + final String? arg_url = (args[2] as String?); + assert( + arg_url != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.urlLoading was null, expected non-null String.', + ); + try { + (urlLoading ?? arg_pigeon_instance!.urlLoading)?.call( + arg_pigeon_instance!, + arg_webView!, + arg_url!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebViewClient.doUpdateVisitedHistory', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.doUpdateVisitedHistory was null.', + ); + final List args = (message as List?)!; + final WebViewClient? arg_pigeon_instance = + (args[0] as WebViewClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.doUpdateVisitedHistory was null, expected non-null WebViewClient.', + ); + final WebView? arg_webView = (args[1] as WebView?); + assert( + arg_webView != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.doUpdateVisitedHistory was null, expected non-null WebView.', + ); + final String? arg_url = (args[2] as String?); + assert( + arg_url != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.doUpdateVisitedHistory was null, expected non-null String.', + ); + final bool? arg_isReload = (args[3] as bool?); + assert( + arg_isReload != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.doUpdateVisitedHistory was null, expected non-null bool.', + ); + try { + (doUpdateVisitedHistory ?? + arg_pigeon_instance!.doUpdateVisitedHistory) + ?.call( + arg_pigeon_instance!, + arg_webView!, + arg_url!, + arg_isReload!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedHttpAuthRequest', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedHttpAuthRequest was null.', + ); + final List args = (message as List?)!; + final WebViewClient? arg_pigeon_instance = + (args[0] as WebViewClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedHttpAuthRequest was null, expected non-null WebViewClient.', + ); + final WebView? arg_webView = (args[1] as WebView?); + assert( + arg_webView != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedHttpAuthRequest was null, expected non-null WebView.', + ); + final HttpAuthHandler? arg_handler = (args[2] as HttpAuthHandler?); + assert( + arg_handler != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedHttpAuthRequest was null, expected non-null HttpAuthHandler.', + ); + final String? arg_host = (args[3] as String?); + assert( + arg_host != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedHttpAuthRequest was null, expected non-null String.', + ); + final String? arg_realm = (args[4] as String?); + assert( + arg_realm != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedHttpAuthRequest was null, expected non-null String.', + ); + try { + (onReceivedHttpAuthRequest ?? + arg_pigeon_instance!.onReceivedHttpAuthRequest) + ?.call( + arg_pigeon_instance!, + arg_webView!, + arg_handler!, + arg_host!, + arg_realm!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebViewClient.onFormResubmission', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onFormResubmission was null.', + ); + final List args = (message as List?)!; + final WebViewClient? arg_pigeon_instance = + (args[0] as WebViewClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onFormResubmission was null, expected non-null WebViewClient.', + ); + final WebView? arg_view = (args[1] as WebView?); + assert( + arg_view != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onFormResubmission was null, expected non-null WebView.', + ); + final AndroidMessage? arg_dontResend = (args[2] as AndroidMessage?); + assert( + arg_dontResend != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onFormResubmission was null, expected non-null AndroidMessage.', + ); + final AndroidMessage? arg_resend = (args[3] as AndroidMessage?); + assert( + arg_resend != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onFormResubmission was null, expected non-null AndroidMessage.', + ); + try { + (onFormResubmission ?? arg_pigeon_instance!.onFormResubmission) + ?.call( + arg_pigeon_instance!, + arg_view!, + arg_dontResend!, + arg_resend!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebViewClient.onLoadResource', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onLoadResource was null.', + ); + final List args = (message as List?)!; + final WebViewClient? arg_pigeon_instance = + (args[0] as WebViewClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onLoadResource was null, expected non-null WebViewClient.', + ); + final WebView? arg_view = (args[1] as WebView?); + assert( + arg_view != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onLoadResource was null, expected non-null WebView.', + ); + final String? arg_url = (args[2] as String?); + assert( + arg_url != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onLoadResource was null, expected non-null String.', + ); + try { + (onLoadResource ?? arg_pigeon_instance!.onLoadResource)?.call( + arg_pigeon_instance!, + arg_view!, + arg_url!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebViewClient.onPageCommitVisible', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onPageCommitVisible was null.', + ); + final List args = (message as List?)!; + final WebViewClient? arg_pigeon_instance = + (args[0] as WebViewClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onPageCommitVisible was null, expected non-null WebViewClient.', + ); + final WebView? arg_view = (args[1] as WebView?); + assert( + arg_view != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onPageCommitVisible was null, expected non-null WebView.', + ); + final String? arg_url = (args[2] as String?); + assert( + arg_url != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onPageCommitVisible was null, expected non-null String.', + ); + try { + (onPageCommitVisible ?? arg_pigeon_instance!.onPageCommitVisible) + ?.call(arg_pigeon_instance!, arg_view!, arg_url!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedClientCertRequest', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedClientCertRequest was null.', + ); + final List args = (message as List?)!; + final WebViewClient? arg_pigeon_instance = + (args[0] as WebViewClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedClientCertRequest was null, expected non-null WebViewClient.', + ); + final WebView? arg_view = (args[1] as WebView?); + assert( + arg_view != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedClientCertRequest was null, expected non-null WebView.', + ); + final ClientCertRequest? arg_request = + (args[2] as ClientCertRequest?); + assert( + arg_request != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedClientCertRequest was null, expected non-null ClientCertRequest.', + ); + try { + (onReceivedClientCertRequest ?? + arg_pigeon_instance!.onReceivedClientCertRequest) + ?.call(arg_pigeon_instance!, arg_view!, arg_request!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedLoginRequest', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedLoginRequest was null.', + ); + final List args = (message as List?)!; + final WebViewClient? arg_pigeon_instance = + (args[0] as WebViewClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedLoginRequest was null, expected non-null WebViewClient.', + ); + final WebView? arg_view = (args[1] as WebView?); + assert( + arg_view != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedLoginRequest was null, expected non-null WebView.', + ); + final String? arg_realm = (args[2] as String?); + assert( + arg_realm != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedLoginRequest was null, expected non-null String.', + ); + final String? arg_account = (args[3] as String?); + final String? arg_args = (args[4] as String?); + assert( + arg_args != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedLoginRequest was null, expected non-null String.', + ); + try { + (onReceivedLoginRequest ?? + arg_pigeon_instance!.onReceivedLoginRequest) + ?.call( + arg_pigeon_instance!, + arg_view!, + arg_realm!, + arg_account, + arg_args!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedSslError', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedSslError was null.', + ); + final List args = (message as List?)!; + final WebViewClient? arg_pigeon_instance = + (args[0] as WebViewClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedSslError was null, expected non-null WebViewClient.', + ); + final WebView? arg_view = (args[1] as WebView?); + assert( + arg_view != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedSslError was null, expected non-null WebView.', + ); + final SslErrorHandler? arg_handler = (args[2] as SslErrorHandler?); + assert( + arg_handler != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedSslError was null, expected non-null SslErrorHandler.', + ); + final SslError? arg_error = (args[3] as SslError?); + assert( + arg_error != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onReceivedSslError was null, expected non-null SslError.', + ); + try { + (onReceivedSslError ?? arg_pigeon_instance!.onReceivedSslError) + ?.call( + arg_pigeon_instance!, + arg_view!, + arg_handler!, + arg_error!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebViewClient.onScaleChanged', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onScaleChanged was null.', + ); + final List args = (message as List?)!; + final WebViewClient? arg_pigeon_instance = + (args[0] as WebViewClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onScaleChanged was null, expected non-null WebViewClient.', + ); + final WebView? arg_view = (args[1] as WebView?); + assert( + arg_view != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onScaleChanged was null, expected non-null WebView.', + ); + final double? arg_oldScale = (args[2] as double?); + assert( + arg_oldScale != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onScaleChanged was null, expected non-null double.', + ); + final double? arg_newScale = (args[3] as double?); + assert( + arg_newScale != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewClient.onScaleChanged was null, expected non-null double.', + ); + try { + (onScaleChanged ?? arg_pigeon_instance!.onScaleChanged)?.call( + arg_pigeon_instance!, + arg_view!, + arg_oldScale!, + arg_newScale!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + /// Sets the required synchronous return value for the Java method, + /// `WebViewClient.shouldOverrideUrlLoading(...)`. + /// + /// The Java method, `WebViewClient.shouldOverrideUrlLoading(...)`, requires + /// a boolean to be returned and this method sets the returned value for all + /// calls to the Java method. + /// + /// Setting this to true causes the current [WebView] to abort loading any URL + /// received by [requestLoading] or [urlLoading], while setting this to false + /// causes the [WebView] to continue loading a URL as usual. + /// + /// Defaults to false. + Future setSynchronousReturnValueForShouldOverrideUrlLoading( + bool value, + ) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebViewClient; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebViewClient.setSynchronousReturnValueForShouldOverrideUrlLoading'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, value], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + @override + WebViewClient pigeon_copy() { + return WebViewClient.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + onPageStarted: onPageStarted, + onPageFinished: onPageFinished, + onReceivedHttpError: onReceivedHttpError, + onReceivedRequestError: onReceivedRequestError, + onReceivedRequestErrorCompat: onReceivedRequestErrorCompat, + requestLoading: requestLoading, + urlLoading: urlLoading, + doUpdateVisitedHistory: doUpdateVisitedHistory, + onReceivedHttpAuthRequest: onReceivedHttpAuthRequest, + onFormResubmission: onFormResubmission, + onLoadResource: onLoadResource, + onPageCommitVisible: onPageCommitVisible, + onReceivedClientCertRequest: onReceivedClientCertRequest, + onReceivedLoginRequest: onReceivedLoginRequest, + onReceivedSslError: onReceivedSslError, + onScaleChanged: onScaleChanged, + ); + } +} + +/// Handles notifications that a file should be downloaded. +/// +/// See https://developer.android.com/reference/android/webkit/DownloadListener. +class DownloadListener extends PigeonInternalProxyApiBaseClass { + DownloadListener({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.onDownloadStart, + }) { + final int pigeonVar_instanceIdentifier = pigeon_instanceManager + .addDartCreatedInstance(this); + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecDownloadListener; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.DownloadListener.pigeon_defaultConstructor'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [pigeonVar_instanceIdentifier], + ); + () async { + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + }(); + } + + /// Constructs [DownloadListener] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + DownloadListener.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.onDownloadStart, + }); + + late final _PigeonInternalProxyApiBaseCodec _pigeonVar_codecDownloadListener = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + /// Notify the host application that a file should be downloaded. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final DownloadListener instance = DownloadListener( + /// onDownloadStart: (DownloadListener pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + DownloadListener pigeon_instance, + String url, + String userAgent, + String contentDisposition, + String mimetype, + int contentLength, + ) + onDownloadStart; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + void Function( + DownloadListener pigeon_instance, + String url, + String userAgent, + String contentDisposition, + String mimetype, + int contentLength, + )? + onDownloadStart, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.DownloadListener.onDownloadStart', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.DownloadListener.onDownloadStart was null.', + ); + final List args = (message as List?)!; + final DownloadListener? arg_pigeon_instance = + (args[0] as DownloadListener?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.DownloadListener.onDownloadStart was null, expected non-null DownloadListener.', + ); + final String? arg_url = (args[1] as String?); + assert( + arg_url != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.DownloadListener.onDownloadStart was null, expected non-null String.', + ); + final String? arg_userAgent = (args[2] as String?); + assert( + arg_userAgent != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.DownloadListener.onDownloadStart was null, expected non-null String.', + ); + final String? arg_contentDisposition = (args[3] as String?); + assert( + arg_contentDisposition != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.DownloadListener.onDownloadStart was null, expected non-null String.', + ); + final String? arg_mimetype = (args[4] as String?); + assert( + arg_mimetype != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.DownloadListener.onDownloadStart was null, expected non-null String.', + ); + final int? arg_contentLength = (args[5] as int?); + assert( + arg_contentLength != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.DownloadListener.onDownloadStart was null, expected non-null int.', + ); + try { + (onDownloadStart ?? arg_pigeon_instance!.onDownloadStart).call( + arg_pigeon_instance!, + arg_url!, + arg_userAgent!, + arg_contentDisposition!, + arg_mimetype!, + arg_contentLength!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + @override + DownloadListener pigeon_copy() { + return DownloadListener.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + onDownloadStart: onDownloadStart, + ); + } +} + +/// Handles notification of JavaScript dialogs, favicons, titles, and the +/// progress. +/// +/// See https://developer.android.com/reference/android/webkit/WebChromeClient. +class WebChromeClient extends PigeonInternalProxyApiBaseClass { + WebChromeClient({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + this.onProgressChanged, + required this.onShowFileChooser, + this.onPermissionRequest, + this.onShowCustomView, + this.onHideCustomView, + this.onGeolocationPermissionsShowPrompt, + this.onGeolocationPermissionsHidePrompt, + this.onConsoleMessage, + this.onJsAlert, + required this.onJsConfirm, + this.onJsPrompt, + }) { + final int pigeonVar_instanceIdentifier = pigeon_instanceManager + .addDartCreatedInstance(this); + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebChromeClient; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClient.pigeon_defaultConstructor'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [pigeonVar_instanceIdentifier], + ); + () async { + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + }(); + } + + /// Constructs [WebChromeClient] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + WebChromeClient.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + this.onProgressChanged, + required this.onShowFileChooser, + this.onPermissionRequest, + this.onShowCustomView, + this.onHideCustomView, + this.onGeolocationPermissionsShowPrompt, + this.onGeolocationPermissionsHidePrompt, + this.onConsoleMessage, + this.onJsAlert, + required this.onJsConfirm, + this.onJsPrompt, + }); + + late final _PigeonInternalProxyApiBaseCodec _pigeonVar_codecWebChromeClient = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + /// Tell the host application the current progress of loading a page. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebChromeClient instance = WebChromeClient( + /// onProgressChanged: (WebChromeClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + WebChromeClient pigeon_instance, + WebView webView, + int progress, + )? + onProgressChanged; + + /// Tell the client to show a file chooser. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebChromeClient instance = WebChromeClient( + /// onShowFileChooser: (WebChromeClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final Future> Function( + WebChromeClient pigeon_instance, + WebView webView, + FileChooserParams params, + ) + onShowFileChooser; + + /// Notify the host application that web content is requesting permission to + /// access the specified resources and the permission currently isn't granted + /// or denied. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebChromeClient instance = WebChromeClient( + /// onPermissionRequest: (WebChromeClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + WebChromeClient pigeon_instance, + PermissionRequest request, + )? + onPermissionRequest; + + /// Callback to Dart function `WebChromeClient.onShowCustomView`. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebChromeClient instance = WebChromeClient( + /// onShowCustomView: (WebChromeClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + WebChromeClient pigeon_instance, + View view, + CustomViewCallback callback, + )? + onShowCustomView; + + /// Notify the host application that the current page has entered full screen + /// mode. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebChromeClient instance = WebChromeClient( + /// onHideCustomView: (WebChromeClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function(WebChromeClient pigeon_instance)? onHideCustomView; + + /// Notify the host application that web content from the specified origin is + /// attempting to use the Geolocation API, but no permission state is + /// currently set for that origin. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebChromeClient instance = WebChromeClient( + /// onGeolocationPermissionsShowPrompt: (WebChromeClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function( + WebChromeClient pigeon_instance, + String origin, + GeolocationPermissionsCallback callback, + )? + onGeolocationPermissionsShowPrompt; + + /// Notify the host application that a request for Geolocation permissions, + /// made with a previous call to `onGeolocationPermissionsShowPrompt` has been + /// canceled. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebChromeClient instance = WebChromeClient( + /// onGeolocationPermissionsHidePrompt: (WebChromeClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function(WebChromeClient pigeon_instance)? + onGeolocationPermissionsHidePrompt; + + /// Report a JavaScript console message to the host application. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebChromeClient instance = WebChromeClient( + /// onConsoleMessage: (WebChromeClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final void Function(WebChromeClient pigeon_instance, ConsoleMessage message)? + onConsoleMessage; + + /// Notify the host application that the web page wants to display a + /// JavaScript `alert()` dialog. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebChromeClient instance = WebChromeClient( + /// onJsAlert: (WebChromeClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final Future Function( + WebChromeClient pigeon_instance, + WebView webView, + String url, + String message, + )? + onJsAlert; + + /// Notify the host application that the web page wants to display a + /// JavaScript `confirm()` dialog. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebChromeClient instance = WebChromeClient( + /// onJsConfirm: (WebChromeClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final Future Function( + WebChromeClient pigeon_instance, + WebView webView, + String url, + String message, + ) + onJsConfirm; + + /// Notify the host application that the web page wants to display a + /// JavaScript `prompt()` dialog. + /// + /// For the associated Native object to be automatically garbage collected, + /// it is required that the implementation of this `Function` doesn't have a + /// strong reference to the encapsulating class instance. When this `Function` + /// references a non-local variable, it is strongly recommended to access it + /// with a `WeakReference`: + /// + /// ```dart + /// final WeakReference weakMyVariable = WeakReference(myVariable); + /// final WebChromeClient instance = WebChromeClient( + /// onJsPrompt: (WebChromeClient pigeon_instance, ...) { + /// print(weakMyVariable?.target); + /// }, + /// ); + /// ``` + /// + /// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to + /// release the associated Native object manually. + final Future Function( + WebChromeClient pigeon_instance, + WebView webView, + String url, + String message, + String defaultValue, + )? + onJsPrompt; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + void Function( + WebChromeClient pigeon_instance, + WebView webView, + int progress, + )? + onProgressChanged, + Future> Function( + WebChromeClient pigeon_instance, + WebView webView, + FileChooserParams params, + )? + onShowFileChooser, + void Function(WebChromeClient pigeon_instance, PermissionRequest request)? + onPermissionRequest, + void Function( + WebChromeClient pigeon_instance, + View view, + CustomViewCallback callback, + )? + onShowCustomView, + void Function(WebChromeClient pigeon_instance)? onHideCustomView, + void Function( + WebChromeClient pigeon_instance, + String origin, + GeolocationPermissionsCallback callback, + )? + onGeolocationPermissionsShowPrompt, + void Function(WebChromeClient pigeon_instance)? + onGeolocationPermissionsHidePrompt, + void Function(WebChromeClient pigeon_instance, ConsoleMessage message)? + onConsoleMessage, + Future Function( + WebChromeClient pigeon_instance, + WebView webView, + String url, + String message, + )? + onJsAlert, + Future Function( + WebChromeClient pigeon_instance, + WebView webView, + String url, + String message, + )? + onJsConfirm, + Future Function( + WebChromeClient pigeon_instance, + WebView webView, + String url, + String message, + String defaultValue, + )? + onJsPrompt, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onProgressChanged', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onProgressChanged was null.', + ); + final List args = (message as List?)!; + final WebChromeClient? arg_pigeon_instance = + (args[0] as WebChromeClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onProgressChanged was null, expected non-null WebChromeClient.', + ); + final WebView? arg_webView = (args[1] as WebView?); + assert( + arg_webView != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onProgressChanged was null, expected non-null WebView.', + ); + final int? arg_progress = (args[2] as int?); + assert( + arg_progress != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onProgressChanged was null, expected non-null int.', + ); + try { + (onProgressChanged ?? arg_pigeon_instance!.onProgressChanged)?.call( + arg_pigeon_instance!, + arg_webView!, + arg_progress!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onShowFileChooser', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onShowFileChooser was null.', + ); + final List args = (message as List?)!; + final WebChromeClient? arg_pigeon_instance = + (args[0] as WebChromeClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onShowFileChooser was null, expected non-null WebChromeClient.', + ); + final WebView? arg_webView = (args[1] as WebView?); + assert( + arg_webView != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onShowFileChooser was null, expected non-null WebView.', + ); + final FileChooserParams? arg_params = (args[2] as FileChooserParams?); + assert( + arg_params != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onShowFileChooser was null, expected non-null FileChooserParams.', + ); + try { + final List output = + await (onShowFileChooser ?? + arg_pigeon_instance!.onShowFileChooser) + .call(arg_pigeon_instance!, arg_webView!, arg_params!); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onPermissionRequest', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onPermissionRequest was null.', + ); + final List args = (message as List?)!; + final WebChromeClient? arg_pigeon_instance = + (args[0] as WebChromeClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onPermissionRequest was null, expected non-null WebChromeClient.', + ); + final PermissionRequest? arg_request = + (args[1] as PermissionRequest?); + assert( + arg_request != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onPermissionRequest was null, expected non-null PermissionRequest.', + ); + try { + (onPermissionRequest ?? arg_pigeon_instance!.onPermissionRequest) + ?.call(arg_pigeon_instance!, arg_request!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onShowCustomView', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onShowCustomView was null.', + ); + final List args = (message as List?)!; + final WebChromeClient? arg_pigeon_instance = + (args[0] as WebChromeClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onShowCustomView was null, expected non-null WebChromeClient.', + ); + final View? arg_view = (args[1] as View?); + assert( + arg_view != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onShowCustomView was null, expected non-null View.', + ); + final CustomViewCallback? arg_callback = + (args[2] as CustomViewCallback?); + assert( + arg_callback != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onShowCustomView was null, expected non-null CustomViewCallback.', + ); + try { + (onShowCustomView ?? arg_pigeon_instance!.onShowCustomView)?.call( + arg_pigeon_instance!, + arg_view!, + arg_callback!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onHideCustomView', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onHideCustomView was null.', + ); + final List args = (message as List?)!; + final WebChromeClient? arg_pigeon_instance = + (args[0] as WebChromeClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onHideCustomView was null, expected non-null WebChromeClient.', + ); + try { + (onHideCustomView ?? arg_pigeon_instance!.onHideCustomView)?.call( + arg_pigeon_instance!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onGeolocationPermissionsShowPrompt', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onGeolocationPermissionsShowPrompt was null.', + ); + final List args = (message as List?)!; + final WebChromeClient? arg_pigeon_instance = + (args[0] as WebChromeClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onGeolocationPermissionsShowPrompt was null, expected non-null WebChromeClient.', + ); + final String? arg_origin = (args[1] as String?); + assert( + arg_origin != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onGeolocationPermissionsShowPrompt was null, expected non-null String.', + ); + final GeolocationPermissionsCallback? arg_callback = + (args[2] as GeolocationPermissionsCallback?); + assert( + arg_callback != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onGeolocationPermissionsShowPrompt was null, expected non-null GeolocationPermissionsCallback.', + ); + try { + (onGeolocationPermissionsShowPrompt ?? + arg_pigeon_instance!.onGeolocationPermissionsShowPrompt) + ?.call(arg_pigeon_instance!, arg_origin!, arg_callback!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onGeolocationPermissionsHidePrompt', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onGeolocationPermissionsHidePrompt was null.', + ); + final List args = (message as List?)!; + final WebChromeClient? arg_pigeon_instance = + (args[0] as WebChromeClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onGeolocationPermissionsHidePrompt was null, expected non-null WebChromeClient.', + ); + try { + (onGeolocationPermissionsHidePrompt ?? + arg_pigeon_instance!.onGeolocationPermissionsHidePrompt) + ?.call(arg_pigeon_instance!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onConsoleMessage', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onConsoleMessage was null.', + ); + final List args = (message as List?)!; + final WebChromeClient? arg_pigeon_instance = + (args[0] as WebChromeClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onConsoleMessage was null, expected non-null WebChromeClient.', + ); + final ConsoleMessage? arg_message = (args[1] as ConsoleMessage?); + assert( + arg_message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onConsoleMessage was null, expected non-null ConsoleMessage.', + ); + try { + (onConsoleMessage ?? arg_pigeon_instance!.onConsoleMessage)?.call( + arg_pigeon_instance!, + arg_message!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsAlert', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsAlert was null.', + ); + final List args = (message as List?)!; + final WebChromeClient? arg_pigeon_instance = + (args[0] as WebChromeClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsAlert was null, expected non-null WebChromeClient.', + ); + final WebView? arg_webView = (args[1] as WebView?); + assert( + arg_webView != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsAlert was null, expected non-null WebView.', + ); + final String? arg_url = (args[2] as String?); + assert( + arg_url != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsAlert was null, expected non-null String.', + ); + final String? arg_message = (args[3] as String?); + assert( + arg_message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsAlert was null, expected non-null String.', + ); + try { + await (onJsAlert ?? arg_pigeon_instance!.onJsAlert)?.call( + arg_pigeon_instance!, + arg_webView!, + arg_url!, + arg_message!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsConfirm', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsConfirm was null.', + ); + final List args = (message as List?)!; + final WebChromeClient? arg_pigeon_instance = + (args[0] as WebChromeClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsConfirm was null, expected non-null WebChromeClient.', + ); + final WebView? arg_webView = (args[1] as WebView?); + assert( + arg_webView != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsConfirm was null, expected non-null WebView.', + ); + final String? arg_url = (args[2] as String?); + assert( + arg_url != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsConfirm was null, expected non-null String.', + ); + final String? arg_message = (args[3] as String?); + assert( + arg_message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsConfirm was null, expected non-null String.', + ); + try { + final bool output = + await (onJsConfirm ?? arg_pigeon_instance!.onJsConfirm).call( + arg_pigeon_instance!, + arg_webView!, + arg_url!, + arg_message!, + ); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsPrompt', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsPrompt was null.', + ); + final List args = (message as List?)!; + final WebChromeClient? arg_pigeon_instance = + (args[0] as WebChromeClient?); + assert( + arg_pigeon_instance != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsPrompt was null, expected non-null WebChromeClient.', + ); + final WebView? arg_webView = (args[1] as WebView?); + assert( + arg_webView != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsPrompt was null, expected non-null WebView.', + ); + final String? arg_url = (args[2] as String?); + assert( + arg_url != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsPrompt was null, expected non-null String.', + ); + final String? arg_message = (args[3] as String?); + assert( + arg_message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsPrompt was null, expected non-null String.', + ); + final String? arg_defaultValue = (args[4] as String?); + assert( + arg_defaultValue != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClient.onJsPrompt was null, expected non-null String.', + ); + try { + final String? output = + await (onJsPrompt ?? arg_pigeon_instance!.onJsPrompt)?.call( + arg_pigeon_instance!, + arg_webView!, + arg_url!, + arg_message!, + arg_defaultValue!, + ); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + /// Sets the required synchronous return value for the Java method, + /// `WebChromeClient.onShowFileChooser(...)`. + /// + /// The Java method, `WebChromeClient.onShowFileChooser(...)`, requires + /// a boolean to be returned and this method sets the returned value for all + /// calls to the Java method. + /// + /// Setting this to true indicates that all file chooser requests should be + /// handled by `onShowFileChooser` and the returned list of Strings will be + /// returned to the WebView. Otherwise, the client will use the default + /// handling and the returned value in `onShowFileChooser` will be ignored. + /// + /// Requires `onShowFileChooser` to be nonnull. + /// + /// Defaults to false. + Future setSynchronousReturnValueForOnShowFileChooser(bool value) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebChromeClient; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClient.setSynchronousReturnValueForOnShowFileChooser'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, value], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets the required synchronous return value for the Java method, + /// `WebChromeClient.onConsoleMessage(...)`. + /// + /// The Java method, `WebChromeClient.onConsoleMessage(...)`, requires + /// a boolean to be returned and this method sets the returned value for all + /// calls to the Java method. + /// + /// Setting this to true indicates that the client is handling all console + /// messages. + /// + /// Requires `onConsoleMessage` to be nonnull. + /// + /// Defaults to false. + Future setSynchronousReturnValueForOnConsoleMessage(bool value) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebChromeClient; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClient.setSynchronousReturnValueForOnConsoleMessage'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, value], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets the required synchronous return value for the Java method, + /// `WebChromeClient.onJsAlert(...)`. + /// + /// The Java method, `WebChromeClient.onJsAlert(...)`, requires a boolean to + /// be returned and this method sets the returned value for all calls to the + /// Java method. + /// + /// Setting this to true indicates that the client is handling all console + /// messages. + /// + /// Requires `onJsAlert` to be nonnull. + /// + /// Defaults to false. + Future setSynchronousReturnValueForOnJsAlert(bool value) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebChromeClient; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClient.setSynchronousReturnValueForOnJsAlert'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, value], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets the required synchronous return value for the Java method, + /// `WebChromeClient.onJsConfirm(...)`. + /// + /// The Java method, `WebChromeClient.onJsConfirm(...)`, requires a boolean to + /// be returned and this method sets the returned value for all calls to the + /// Java method. + /// + /// Setting this to true indicates that the client is handling all console + /// messages. + /// + /// Requires `onJsConfirm` to be nonnull. + /// + /// Defaults to false. + Future setSynchronousReturnValueForOnJsConfirm(bool value) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebChromeClient; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClient.setSynchronousReturnValueForOnJsConfirm'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, value], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Sets the required synchronous return value for the Java method, + /// `WebChromeClient.onJsPrompt(...)`. + /// + /// The Java method, `WebChromeClient.onJsPrompt(...)`, requires a boolean to + /// be returned and this method sets the returned value for all calls to the + /// Java method. + /// + /// Setting this to true indicates that the client is handling all console + /// messages. + /// + /// Requires `onJsPrompt` to be nonnull. + /// + /// Defaults to false. + Future setSynchronousReturnValueForOnJsPrompt(bool value) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebChromeClient; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClient.setSynchronousReturnValueForOnJsPrompt'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, value], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + @override + WebChromeClient pigeon_copy() { + return WebChromeClient.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + onProgressChanged: onProgressChanged, + onShowFileChooser: onShowFileChooser, + onPermissionRequest: onPermissionRequest, + onShowCustomView: onShowCustomView, + onHideCustomView: onHideCustomView, + onGeolocationPermissionsShowPrompt: onGeolocationPermissionsShowPrompt, + onGeolocationPermissionsHidePrompt: onGeolocationPermissionsHidePrompt, + onConsoleMessage: onConsoleMessage, + onJsAlert: onJsAlert, + onJsConfirm: onJsConfirm, + onJsPrompt: onJsPrompt, + ); + } +} + +/// Provides access to the assets registered as part of the App bundle. +/// +/// Convenience class for accessing Flutter asset resources. +class FlutterAssetManager extends PigeonInternalProxyApiBaseClass { + /// Constructs [FlutterAssetManager] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + FlutterAssetManager.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec + _pigeonVar_codecFlutterAssetManager = _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager, + ); + + /// The global instance of the `FlutterAssetManager`. + static final FlutterAssetManager instance = pigeonVar_instance(); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + FlutterAssetManager Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.FlutterAssetManager.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.FlutterAssetManager.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.FlutterAssetManager.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + FlutterAssetManager.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + static FlutterAssetManager pigeonVar_instance() { + final FlutterAssetManager pigeonVar_instance = + FlutterAssetManager.pigeon_detached(); + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec(PigeonInstanceManager.instance); + final BinaryMessenger pigeonVar_binaryMessenger = + ServicesBinding.instance.defaultBinaryMessenger; + final int pigeonVar_instanceIdentifier = PigeonInstanceManager.instance + .addDartCreatedInstance(pigeonVar_instance); + () async { + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.FlutterAssetManager.instance'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [pigeonVar_instanceIdentifier], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + }(); + return pigeonVar_instance; + } + + /// Returns a String array of all the assets at the given path. + /// + /// Throws an IOException in case I/O operations were interrupted. + Future> list(String path) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecFlutterAssetManager; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.FlutterAssetManager.list'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, path], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as List?)!.cast(); + } + } + + /// Gets the relative file path to the Flutter asset with the given name, including the file's + /// extension, e.g., "myImage.jpg". + /// + /// The returned file path is relative to the Android app's standard asset's + /// directory. Therefore, the returned path is appropriate to pass to + /// Android's AssetManager, but the path is not appropriate to load as an + /// absolute path. + Future getAssetFilePathByName(String name) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecFlutterAssetManager; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.FlutterAssetManager.getAssetFilePathByName'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, name], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as String?)!; + } + } + + @override + FlutterAssetManager pigeon_copy() { + return FlutterAssetManager.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// This class is used to manage the JavaScript storage APIs provided by the +/// WebView. +/// +/// See https://developer.android.com/reference/android/webkit/WebStorage. +class WebStorage extends PigeonInternalProxyApiBaseClass { + /// Constructs [WebStorage] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + WebStorage.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec _pigeonVar_codecWebStorage = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + static final WebStorage instance = pigeonVar_instance(); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + WebStorage Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebStorage.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebStorage.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebStorage.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + WebStorage.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + static WebStorage pigeonVar_instance() { + final WebStorage pigeonVar_instance = WebStorage.pigeon_detached(); + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec(PigeonInstanceManager.instance); + final BinaryMessenger pigeonVar_binaryMessenger = + ServicesBinding.instance.defaultBinaryMessenger; + final int pigeonVar_instanceIdentifier = PigeonInstanceManager.instance + .addDartCreatedInstance(pigeonVar_instance); + () async { + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebStorage.instance'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [pigeonVar_instanceIdentifier], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + }(); + return pigeonVar_instance; + } + + /// Clears all storage currently being used by the JavaScript storage APIs. + Future deleteAllData() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebStorage; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebStorage.deleteAllData'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + @override + WebStorage pigeon_copy() { + return WebStorage.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// Parameters used in the `WebChromeClient.onShowFileChooser` method. +/// +/// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams. +class FileChooserParams extends PigeonInternalProxyApiBaseClass { + /// Constructs [FileChooserParams] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + FileChooserParams.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.isCaptureEnabled, + required this.acceptTypes, + required this.mode, + this.filenameHint, + }); + + /// Preference for a live media captured value (e.g. Camera, Microphone). + final bool isCaptureEnabled; + + /// An array of acceptable MIME types. + final List acceptTypes; + + /// File chooser mode. + final FileChooserMode mode; + + /// File name of a default selection if specified, or null. + final String? filenameHint; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + FileChooserParams Function( + bool isCaptureEnabled, + List acceptTypes, + FileChooserMode mode, + String? filenameHint, + )? + pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.FileChooserParams.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.FileChooserParams.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.FileChooserParams.pigeon_newInstance was null, expected non-null int.', + ); + final bool? arg_isCaptureEnabled = (args[1] as bool?); + assert( + arg_isCaptureEnabled != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.FileChooserParams.pigeon_newInstance was null, expected non-null bool.', + ); + final List? arg_acceptTypes = (args[2] as List?) + ?.cast(); + assert( + arg_acceptTypes != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.FileChooserParams.pigeon_newInstance was null, expected non-null List.', + ); + final FileChooserMode? arg_mode = (args[3] as FileChooserMode?); + assert( + arg_mode != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.FileChooserParams.pigeon_newInstance was null, expected non-null FileChooserMode.', + ); + final String? arg_filenameHint = (args[4] as String?); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call( + arg_isCaptureEnabled!, + arg_acceptTypes!, + arg_mode!, + arg_filenameHint, + ) ?? + FileChooserParams.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + isCaptureEnabled: arg_isCaptureEnabled!, + acceptTypes: arg_acceptTypes!, + mode: arg_mode!, + filenameHint: arg_filenameHint, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + @override + FileChooserParams pigeon_copy() { + return FileChooserParams.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + isCaptureEnabled: isCaptureEnabled, + acceptTypes: acceptTypes, + mode: mode, + filenameHint: filenameHint, + ); + } +} + +/// This class defines a permission request and is used when web content +/// requests access to protected resources. +/// +/// See https://developer.android.com/reference/android/webkit/PermissionRequest. +class PermissionRequest extends PigeonInternalProxyApiBaseClass { + /// Constructs [PermissionRequest] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + PermissionRequest.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.resources, + }); + + late final _PigeonInternalProxyApiBaseCodec + _pigeonVar_codecPermissionRequest = _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager, + ); + + final List resources; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + PermissionRequest Function(List resources)? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.PermissionRequest.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.PermissionRequest.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.PermissionRequest.pigeon_newInstance was null, expected non-null int.', + ); + final List? arg_resources = (args[1] as List?) + ?.cast(); + assert( + arg_resources != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.PermissionRequest.pigeon_newInstance was null, expected non-null List.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call(arg_resources!) ?? + PermissionRequest.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + resources: arg_resources!, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + /// Call this method to grant origin the permission to access the given + /// resources. + Future grant(List resources) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecPermissionRequest; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.PermissionRequest.grant'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, resources], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Call this method to deny the request. + Future deny() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecPermissionRequest; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.PermissionRequest.deny'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + @override + PermissionRequest pigeon_copy() { + return PermissionRequest.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + resources: resources, + ); + } +} + +/// A callback interface used by the host application to notify the current page +/// that its custom view has been dismissed. +/// +/// See https://developer.android.com/reference/android/webkit/WebChromeClient.CustomViewCallback. +class CustomViewCallback extends PigeonInternalProxyApiBaseClass { + /// Constructs [CustomViewCallback] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + CustomViewCallback.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec + _pigeonVar_codecCustomViewCallback = _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager, + ); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + CustomViewCallback Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.CustomViewCallback.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.CustomViewCallback.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.CustomViewCallback.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + CustomViewCallback.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + /// Invoked when the host application dismisses the custom view. + Future onCustomViewHidden() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecCustomViewCallback; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.CustomViewCallback.onCustomViewHidden'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + @override + CustomViewCallback pigeon_copy() { + return CustomViewCallback.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// This class represents the basic building block for user interface +/// components. +/// +/// See https://developer.android.com/reference/android/view/View. +class View extends PigeonInternalProxyApiBaseClass { + /// Constructs [View] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + View.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec _pigeonVar_codecView = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + View Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.View.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.View.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.View.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + View.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + /// Set the scrolled position of your view. + Future scrollTo(int x, int y) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.View.scrollTo'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, x, y], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Move the scrolled position of your view. + Future scrollBy(int x, int y) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.View.scrollBy'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, x, y], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Return the scrolled position of this view. + Future getScrollPosition() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.View.getScrollPosition'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as WebViewPoint?)!; + } + } + + /// Define whether the vertical scrollbar should be drawn or not. + /// + /// The scrollbar is not drawn by default. + Future setVerticalScrollBarEnabled(bool enabled) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.View.setVerticalScrollBarEnabled'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, enabled], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Define whether the horizontal scrollbar should be drawn or not. + /// + /// The scrollbar is not drawn by default. + Future setHorizontalScrollBarEnabled(bool enabled) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.View.setHorizontalScrollBarEnabled'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, enabled], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Set the over-scroll mode for this view. + Future setOverScrollMode(OverScrollMode mode) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecView; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.View.setOverScrollMode'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, mode], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + @override + View pigeon_copy() { + return View.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// A callback interface used by the host application to set the Geolocation +/// permission state for an origin. +/// +/// See https://developer.android.com/reference/android/webkit/GeolocationPermissions.Callback. +class GeolocationPermissionsCallback extends PigeonInternalProxyApiBaseClass { + /// Constructs [GeolocationPermissionsCallback] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + GeolocationPermissionsCallback.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec + _pigeonVar_codecGeolocationPermissionsCallback = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + GeolocationPermissionsCallback Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.GeolocationPermissionsCallback.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.GeolocationPermissionsCallback.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.GeolocationPermissionsCallback.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + GeolocationPermissionsCallback.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + /// Sets the Geolocation permission state for the supplied origin. + Future invoke(String origin, bool allow, bool retain) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecGeolocationPermissionsCallback; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.GeolocationPermissionsCallback.invoke'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, origin, allow, retain], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + @override + GeolocationPermissionsCallback pigeon_copy() { + return GeolocationPermissionsCallback.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// Represents a request for HTTP authentication. +/// +/// See https://developer.android.com/reference/android/webkit/HttpAuthHandler. +class HttpAuthHandler extends PigeonInternalProxyApiBaseClass { + /// Constructs [HttpAuthHandler] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + HttpAuthHandler.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec _pigeonVar_codecHttpAuthHandler = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + HttpAuthHandler Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.HttpAuthHandler.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.HttpAuthHandler.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.HttpAuthHandler.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + HttpAuthHandler.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + /// Gets whether the credentials stored for the current host (i.e. the host + /// for which `WebViewClient.onReceivedHttpAuthRequest` was called) are + /// suitable for use. + Future useHttpAuthUsernamePassword() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecHttpAuthHandler; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.HttpAuthHandler.useHttpAuthUsernamePassword'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } + + /// Instructs the WebView to cancel the authentication request.. + Future cancel() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecHttpAuthHandler; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.HttpAuthHandler.cancel'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Instructs the WebView to proceed with the authentication with the given + /// credentials. + Future proceed(String username, String password) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecHttpAuthHandler; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.HttpAuthHandler.proceed'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, username, password], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + @override + HttpAuthHandler pigeon_copy() { + return HttpAuthHandler.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// Defines a message containing a description and arbitrary data object that +/// can be sent to a `Handler`. +/// +/// See https://developer.android.com/reference/android/os/Message. +class AndroidMessage extends PigeonInternalProxyApiBaseClass { + /// Constructs [AndroidMessage] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + AndroidMessage.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec _pigeonVar_codecAndroidMessage = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + AndroidMessage Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.AndroidMessage.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.AndroidMessage.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.AndroidMessage.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + AndroidMessage.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + /// Sends this message to the Android native `Handler` specified by + /// getTarget(). + /// + /// Throws a null pointer exception if this field has not been set. + Future sendToTarget() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecAndroidMessage; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.AndroidMessage.sendToTarget'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + @override + AndroidMessage pigeon_copy() { + return AndroidMessage.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// Defines a message containing a description and arbitrary data object that +/// can be sent to a `Handler`. +/// +/// See https://developer.android.com/reference/android/webkit/ClientCertRequest. +class ClientCertRequest extends PigeonInternalProxyApiBaseClass { + /// Constructs [ClientCertRequest] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + ClientCertRequest.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec + _pigeonVar_codecClientCertRequest = _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager, + ); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + ClientCertRequest Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.ClientCertRequest.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.ClientCertRequest.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.ClientCertRequest.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + ClientCertRequest.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + /// Cancel this request. + Future cancel() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecClientCertRequest; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.ClientCertRequest.cancel'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Ignore the request for now. + Future ignore() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecClientCertRequest; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.ClientCertRequest.ignore'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Proceed with the specified private key and client certificate chain. + Future proceed( + PrivateKey privateKey, + List chain, + ) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecClientCertRequest; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.ClientCertRequest.proceed'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, privateKey, chain], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + @override + ClientCertRequest pigeon_copy() { + return ClientCertRequest.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// A private key. +/// +/// The purpose of this interface is to group (and provide type safety for) all +/// private key interfaces. +/// +/// See https://developer.android.com/reference/java/security/PrivateKey. +class PrivateKey extends PigeonInternalProxyApiBaseClass { + /// Constructs [PrivateKey] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + PrivateKey.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + PrivateKey Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.PrivateKey.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.PrivateKey.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.PrivateKey.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + PrivateKey.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + @override + PrivateKey pigeon_copy() { + return PrivateKey.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// Abstract class for X.509 certificates. +/// +/// This provides a standard way to access all the attributes of an X.509 +/// certificate. +/// +/// See https://developer.android.com/reference/java/security/cert/X509Certificate. +class X509Certificate extends Certificate { + /// Constructs [X509Certificate] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + X509Certificate.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }) : super.pigeon_detached(); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + X509Certificate Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.X509Certificate.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.X509Certificate.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.X509Certificate.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + X509Certificate.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + @override + X509Certificate pigeon_copy() { + return X509Certificate.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// Represents a request for handling an SSL error. +/// +/// See https://developer.android.com/reference/android/webkit/SslErrorHandler. +class SslErrorHandler extends PigeonInternalProxyApiBaseClass { + /// Constructs [SslErrorHandler] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + SslErrorHandler.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec _pigeonVar_codecSslErrorHandler = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + SslErrorHandler Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.SslErrorHandler.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.SslErrorHandler.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.SslErrorHandler.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + SslErrorHandler.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + /// Instructs the WebView that encountered the SSL certificate error to + /// terminate communication with the server. + Future cancel() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecSslErrorHandler; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.SslErrorHandler.cancel'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Instructs the WebView that encountered the SSL certificate error to ignore + /// the error and continue communicating with the server. + Future proceed() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecSslErrorHandler; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.SslErrorHandler.proceed'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + @override + SslErrorHandler pigeon_copy() { + return SslErrorHandler.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// This class represents a set of one or more SSL errors and the associated SSL +/// certificate. +/// +/// See https://developer.android.com/reference/android/net/http/SslError. +class SslError extends PigeonInternalProxyApiBaseClass { + /// Constructs [SslError] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + SslError.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + required this.certificate, + required this.url, + }); + + late final _PigeonInternalProxyApiBaseCodec _pigeonVar_codecSslError = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + /// Gets the SSL certificate associated with this object. + final SslCertificate certificate; + + /// Gets the URL associated with this object. + final String url; + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + SslError Function(SslCertificate certificate, String url)? + pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.SslError.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.SslError.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.SslError.pigeon_newInstance was null, expected non-null int.', + ); + final SslCertificate? arg_certificate = (args[1] as SslCertificate?); + assert( + arg_certificate != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.SslError.pigeon_newInstance was null, expected non-null SslCertificate.', + ); + final String? arg_url = (args[2] as String?); + assert( + arg_url != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.SslError.pigeon_newInstance was null, expected non-null String.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call(arg_certificate!, arg_url!) ?? + SslError.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + certificate: arg_certificate!, + url: arg_url!, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + /// Gets the most severe SSL error in this object's set of errors. + Future getPrimaryError() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecSslError; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.SslError.getPrimaryError'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as SslErrorType?)!; + } + } + + /// Determines whether this object includes the supplied error. + Future hasError(SslErrorType error) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecSslError; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.SslError.hasError'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this, error], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } + + @override + SslError pigeon_copy() { + return SslError.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + certificate: certificate, + url: url, + ); + } +} + +/// A distinguished name helper class. +/// +/// A 3-tuple of: +/// the most specific common name (CN) +/// the most specific organization (O) +/// the most specific organizational unit (OU) +class SslCertificateDName extends PigeonInternalProxyApiBaseClass { + /// Constructs [SslCertificateDName] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + SslCertificateDName.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec + _pigeonVar_codecSslCertificateDName = _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager, + ); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + SslCertificateDName Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.SslCertificateDName.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.SslCertificateDName.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.SslCertificateDName.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + SslCertificateDName.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + /// The most specific Common-name (CN) component of this name. + Future getCName() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecSslCertificateDName; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.SslCertificateDName.getCName'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as String?)!; + } + } + + /// The distinguished name (normally includes CN, O, and OU names). + Future getDName() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecSslCertificateDName; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.SslCertificateDName.getDName'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as String?)!; + } + } + + /// The most specific Organization (O) component of this name. + Future getOName() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecSslCertificateDName; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.SslCertificateDName.getOName'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as String?)!; + } + } + + /// The most specific Organizational Unit (OU) component of this name. + Future getUName() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecSslCertificateDName; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.SslCertificateDName.getUName'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as String?)!; + } + } + + @override + SslCertificateDName pigeon_copy() { + return SslCertificateDName.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// SSL certificate info (certificate details) class. +/// +/// See https://developer.android.com/reference/android/net/http/SslCertificate. +class SslCertificate extends PigeonInternalProxyApiBaseClass { + /// Constructs [SslCertificate] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + SslCertificate.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec _pigeonVar_codecSslCertificate = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + SslCertificate Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.SslCertificate.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.SslCertificate.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.SslCertificate.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + SslCertificate.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + /// Issued-by distinguished name or null if none has been set. + Future getIssuedBy() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecSslCertificate; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.SslCertificate.getIssuedBy'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as SslCertificateDName?); + } + } + + /// Issued-to distinguished name or null if none has been set. + Future getIssuedTo() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecSslCertificate; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.SslCertificate.getIssuedTo'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as SslCertificateDName?); + } + } + + /// Not-after date from the certificate validity period or null if none has been + /// set. + Future getValidNotAfterMsSinceEpoch() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecSslCertificate; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.SslCertificate.getValidNotAfterMsSinceEpoch'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as int?); + } + } + + /// Not-before date from the certificate validity period or null if none has + /// been set. + Future getValidNotBeforeMsSinceEpoch() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecSslCertificate; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.SslCertificate.getValidNotBeforeMsSinceEpoch'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as int?); + } + } + + /// The X509Certificate used to create this SslCertificate or null if no + /// certificate was provided. + /// + /// Always returns null on Android versions below Q. + Future getX509Certificate() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecSslCertificate; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.SslCertificate.getX509Certificate'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return (pigeonVar_replyList[0] as X509Certificate?); + } + } + + @override + SslCertificate pigeon_copy() { + return SslCertificate.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// Abstract class for managing a variety of identity certificates. +/// +/// See https://developer.android.com/reference/java/security/cert/Certificate. +class Certificate extends PigeonInternalProxyApiBaseClass { + /// Constructs [Certificate] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + Certificate.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec _pigeonVar_codecCertificate = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + Certificate Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.Certificate.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.Certificate.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.Certificate.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + Certificate.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + /// The encoded form of this certificate. + Future getEncoded() async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecCertificate; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.Certificate.getEncoded'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [this], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as Uint8List?)!; + } + } + + @override + Certificate pigeon_copy() { + return Certificate.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// Compatibility version of `WebSettings`. +/// +/// See https://developer.android.com/reference/kotlin/androidx/webkit/WebSettingsCompat. +class WebSettingsCompat extends PigeonInternalProxyApiBaseClass { + /// Constructs [WebSettingsCompat] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + WebSettingsCompat.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec + _pigeonVar_codecWebSettingsCompat = _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager, + ); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + WebSettingsCompat Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebSettingsCompat.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebSettingsCompat.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebSettingsCompat.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + WebSettingsCompat.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + static Future setPaymentRequestEnabled( + WebSettings webSettings, + bool enabled, { + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + }) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebSettingsCompat.setPaymentRequestEnabled'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [webSettings, enabled], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + @override + WebSettingsCompat pigeon_copy() { + return WebSettingsCompat.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} + +/// Utility class for checking which WebView Support Library features are supported on the device. +/// +/// See https://developer.android.com/reference/kotlin/androidx/webkit/WebViewFeature. +class WebViewFeature extends PigeonInternalProxyApiBaseClass { + /// Constructs [WebViewFeature] without creating the associated native object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies for an [PigeonInstanceManager]. + @protected + WebViewFeature.pigeon_detached({ + super.pigeon_binaryMessenger, + super.pigeon_instanceManager, + }); + + late final _PigeonInternalProxyApiBaseCodec _pigeonVar_codecWebViewFeature = + _PigeonInternalProxyApiBaseCodec(pigeon_instanceManager); + + static void pigeon_setUpMessageHandlers({ + bool pigeon_clearHandlers = false, + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + WebViewFeature Function()? pigeon_newInstance, + }) { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? binaryMessenger = pigeon_binaryMessenger; + { + final BasicMessageChannel + pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebViewFeature.pigeon_newInstance', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (pigeon_clearHandlers) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewFeature.pigeon_newInstance was null.', + ); + final List args = (message as List?)!; + final int? arg_pigeon_instanceIdentifier = (args[0] as int?); + assert( + arg_pigeon_instanceIdentifier != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebViewFeature.pigeon_newInstance was null, expected non-null int.', + ); + try { + (pigeon_instanceManager ?? PigeonInstanceManager.instance) + .addHostCreatedInstance( + pigeon_newInstance?.call() ?? + WebViewFeature.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ), + arg_pigeon_instanceIdentifier!, + ); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } + + static Future isFeatureSupported( + String feature, { + BinaryMessenger? pigeon_binaryMessenger, + PigeonInstanceManager? pigeon_instanceManager, + }) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _PigeonInternalProxyApiBaseCodec( + pigeon_instanceManager ?? PigeonInstanceManager.instance, + ); + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebViewFeature.isFeatureSupported'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [feature], + ); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (pigeonVar_replyList[0] as bool?)!; + } + } + + @override + WebViewFeature pigeon_copy() { + return WebViewFeature.pigeon_detached( + pigeon_binaryMessenger: pigeon_binaryMessenger, + pigeon_instanceManager: pigeon_instanceManager, + ); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/lib/src/android_webkit_constants.dart b/local_packages/webview_flutter_android-4.10.5/lib/src/android_webkit_constants.dart new file mode 100644 index 0000000..adee91f --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/lib/src/android_webkit_constants.dart @@ -0,0 +1,130 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'android_webkit.g.dart'; + +/// Class constants for [PermissionRequest]. +/// +/// Since the Dart [PermissionRequest] is generated, the constants for the class +/// are added here. +class PermissionRequestConstants { + /// Resource belongs to audio capture device, like microphone. + /// + /// See https://developer.android.com/reference/android/webkit/PermissionRequest#RESOURCE_AUDIO_CAPTURE. + static const String audioCapture = 'android.webkit.resource.AUDIO_CAPTURE'; + + /// Resource will allow sysex messages to be sent to or received from MIDI + /// devices. + /// + /// See https://developer.android.com/reference/android/webkit/PermissionRequest#RESOURCE_MIDI_SYSEX. + static const String midiSysex = 'android.webkit.resource.MIDI_SYSEX'; + + /// Resource belongs to video capture device, like camera. + /// + /// See https://developer.android.com/reference/android/webkit/PermissionRequest#RESOURCE_VIDEO_CAPTURE. + static const String videoCapture = 'android.webkit.resource.VIDEO_CAPTURE'; + + /// Resource belongs to protected media identifier. + /// + /// See https://developer.android.com/reference/android/webkit/PermissionRequest#RESOURCE_VIDEO_CAPTURE. + static const String protectedMediaId = + 'android.webkit.resource.PROTECTED_MEDIA_ID'; +} + +/// Class constants for [WebViewClient]. +/// +/// Since the Dart [WebViewClient] is generated, the constants for the class +/// are added here. +class WebViewClientConstants { + /// User authentication failed on server. + /// + /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_AUTHENTICATION + static const int errorAuthentication = -4; + + /// Malformed URL. + /// + /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_BAD_URL + static const int errorBadUrl = -12; + + /// Failed to connect to the server. + /// + /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_CONNECT + static const int errorConnect = -6; + + /// Failed to perform SSL handshake. + /// + /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_FAILED_SSL_HANDSHAKE + static const int errorFailedSslHandshake = -11; + + /// Generic file error. + /// + /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_FILE + static const int errorFile = -13; + + /// File not found. + /// + /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_FILE_NOT_FOUND + static const int errorFileNotFound = -14; + + /// Server or proxy hostname lookup failed. + /// + /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_HOST_LOOKUP + static const int errorHostLookup = -2; + + /// Failed to read or write to the server. + /// + /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_IO + static const int errorIO = -7; + + /// User authentication failed on proxy. + /// + /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_PROXY_AUTHENTICATION + static const int errorProxyAuthentication = -5; + + /// Too many redirects. + /// + /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_REDIRECT_LOOP + static const int errorRedirectLoop = -9; + + /// Connection timed out. + /// + /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_TIMEOUT + static const int errorTimeout = -8; + + /// Too many requests during this load. + /// + /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_TOO_MANY_REQUESTS + static const int errorTooManyRequests = -15; + + /// Generic error. + /// + /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_UNKNOWN + static const int errorUnknown = -1; + + /// Resource load was canceled by Safe Browsing. + /// + /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_UNSAFE_RESOURCE + static const int errorUnsafeResource = -16; + + /// Unsupported authentication scheme (not basic or digest). + /// + /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_UNSUPPORTED_AUTH_SCHEME + static const int errorUnsupportedAuthScheme = -3; + + /// Unsupported URI scheme. + /// + /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_UNSUPPORTED_SCHEME + static const int errorUnsupportedScheme = -10; +} + +/// Class constants for [WebViewFeature]. +/// +/// Since the Dart [WebViewFeature] is generated, the constants for the class +/// are added here. +class WebViewFeatureConstants { + /// This feature covers [WebSettingsCompat.setPaymentRequestEnabled]. + /// + /// See https://developer.android.com/reference/androidx/webkit/WebViewFeature#PAYMENT_REQUEST. + static const String paymentRequest = 'PAYMENT_REQUEST'; +} diff --git a/local_packages/webview_flutter_android-4.10.5/lib/src/android_webview_controller.dart b/local_packages/webview_flutter_android-4.10.5/lib/src/android_webview_controller.dart new file mode 100644 index 0000000..38952bf --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/lib/src/android_webview_controller.dart @@ -0,0 +1,1834 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'android_proxy.dart'; +import 'android_ssl_auth_error.dart'; +import 'android_webkit.g.dart' as android_webview; +import 'android_webkit_constants.dart'; +import 'platform_views_service_proxy.dart'; +import 'weak_reference_utils.dart'; + +/// Object specifying parameters for loading a local file in a +/// [AndroidWebViewController]. +@immutable +base class AndroidLoadFileParams extends LoadFileParams { + /// Constructs a [AndroidLoadFileParams], the subclass of a [LoadFileParams]. + AndroidLoadFileParams({ + required String absoluteFilePath, + this.headers = const {}, + }) : super( + absoluteFilePath: absoluteFilePath.startsWith('file://') + ? absoluteFilePath + : Uri.file(absoluteFilePath).toString(), + ); + + /// Constructs a [AndroidLoadFileParams] using a [LoadFileParams]. + factory AndroidLoadFileParams.fromLoadFileParams( + LoadFileParams params, { + Map headers = const {}, + }) { + return AndroidLoadFileParams( + absoluteFilePath: params.absoluteFilePath, + headers: headers, + ); + } + + /// Additional HTTP headers to be included when loading the local file. + /// + /// If not provided at initialization time, doesn't add any additional headers. + /// + /// On Android, WebView supports adding headers when loading local or remote + /// content. This can be useful for scenarios like authentication, + /// content-type overrides, or custom request context. + final Map headers; +} + +/// Object specifying creation parameters for creating a [AndroidWebViewController]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformWebViewControllerCreationParams] for +/// more information. +@immutable +class AndroidWebViewControllerCreationParams + extends PlatformWebViewControllerCreationParams { + /// Creates a new [AndroidWebViewControllerCreationParams] instance. + AndroidWebViewControllerCreationParams({ + @visibleForTesting this.androidWebViewProxy = const AndroidWebViewProxy(), + @visibleForTesting android_webview.WebStorage? androidWebStorage, + }) : androidWebStorage = + androidWebStorage ?? android_webview.WebStorage.instance, + super(); + + /// Creates a [AndroidWebViewControllerCreationParams] instance based on [PlatformWebViewControllerCreationParams]. + factory AndroidWebViewControllerCreationParams.fromPlatformWebViewControllerCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformWebViewControllerCreationParams params, { + @visibleForTesting + AndroidWebViewProxy androidWebViewProxy = const AndroidWebViewProxy(), + @visibleForTesting android_webview.WebStorage? androidWebStorage, + }) { + return AndroidWebViewControllerCreationParams( + androidWebViewProxy: androidWebViewProxy, + androidWebStorage: + androidWebStorage ?? android_webview.WebStorage.instance, + ); + } + + /// Handles constructing objects and calling static methods for the Android WebView + /// native library. + @visibleForTesting + final AndroidWebViewProxy androidWebViewProxy; + + /// Manages the JavaScript storage APIs provided by the [android_webview.WebView]. + @visibleForTesting + final android_webview.WebStorage androidWebStorage; +} + +/// Android-specific resources that can require permissions. +class AndroidWebViewPermissionResourceType + extends WebViewPermissionResourceType { + const AndroidWebViewPermissionResourceType._(super.name); + + /// A resource that will allow sysex messages to be sent to or received from + /// MIDI devices. + static const AndroidWebViewPermissionResourceType midiSysex = + AndroidWebViewPermissionResourceType._('midiSysex'); + + /// A resource that belongs to a protected media identifier. + static const AndroidWebViewPermissionResourceType protectedMediaId = + AndroidWebViewPermissionResourceType._('protectedMediaId'); +} + +/// Implementation of the [PlatformWebViewController] with the Android WebView API. +class AndroidWebViewController extends PlatformWebViewController { + /// Creates a new [AndroidWebViewController]. + AndroidWebViewController(PlatformWebViewControllerCreationParams params) + : super.implementation( + params is AndroidWebViewControllerCreationParams + ? params + : AndroidWebViewControllerCreationParams.fromPlatformWebViewControllerCreationParams( + params, + ), + ) { + _webView.settings.setDomStorageEnabled(true); + _webView.settings.setJavaScriptCanOpenWindowsAutomatically(true); + _webView.settings.setSupportMultipleWindows(true); + _webView.settings.setLoadWithOverviewMode(true); + _webView.settings.setUseWideViewPort(false); + _webView.settings.setDisplayZoomControls(false); + _webView.settings.setBuiltInZoomControls(true); + + _webView.setWebChromeClient(_webChromeClient); + } + + AndroidWebViewControllerCreationParams get _androidWebViewParams => + params as AndroidWebViewControllerCreationParams; + + /// The native [android_webview.WebView] being controlled. + late final android_webview.WebView _webView = _androidWebViewParams + .androidWebViewProxy + .newWebView( + onScrollChanged: withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return (_, int left, int top, int oldLeft, int oldTop) async { + final void Function(ScrollPositionChange)? callback = + weakReference.target?._onScrollPositionChangedCallback; + callback?.call( + ScrollPositionChange(left.toDouble(), top.toDouble()), + ); + }; + }), + ); + + late final android_webview.WebChromeClient _webChromeClient = + _androidWebViewParams.androidWebViewProxy.newWebChromeClient( + onProgressChanged: withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return (_, android_webview.WebView webView, int progress) { + if (weakReference.target?._currentNavigationDelegate?._onProgress != + null) { + weakReference.target!._currentNavigationDelegate!._onProgress!( + progress, + ); + } + }; + }), + onGeolocationPermissionsShowPrompt: withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return ( + _, + String origin, + android_webview.GeolocationPermissionsCallback callback, + ) async { + final OnGeolocationPermissionsShowPrompt? onShowPrompt = + weakReference.target?._onGeolocationPermissionsShowPrompt; + if (onShowPrompt != null) { + final GeolocationPermissionsResponse response = + await onShowPrompt( + GeolocationPermissionsRequestParams(origin: origin), + ); + return callback.invoke(origin, response.allow, response.retain); + } else { + // default don't allow + return callback.invoke(origin, false, false); + } + }; + }), + onGeolocationPermissionsHidePrompt: withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return (android_webview.WebChromeClient instance) { + final OnGeolocationPermissionsHidePrompt? onHidePrompt = + weakReference.target?._onGeolocationPermissionsHidePrompt; + if (onHidePrompt != null) { + onHidePrompt(); + } + }; + }), + onShowCustomView: withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return ( + _, + android_webview.View view, + android_webview.CustomViewCallback callback, + ) { + final AndroidWebViewController? webViewController = + weakReference.target; + if (webViewController == null) { + callback.onCustomViewHidden(); + return; + } + final OnShowCustomWidgetCallback? onShowCallback = + webViewController._onShowCustomWidgetCallback; + if (onShowCallback == null) { + callback.onCustomViewHidden(); + return; + } + onShowCallback( + AndroidCustomViewWidget.private( + controller: webViewController, + customView: view, + // ignore: invalid_use_of_protected_member + instanceManager: view.pigeon_instanceManager, + ), + () => callback.onCustomViewHidden(), + ); + }; + }), + onHideCustomView: withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return (android_webview.WebChromeClient instance) { + final OnHideCustomWidgetCallback? onHideCustomViewCallback = + weakReference.target?._onHideCustomWidgetCallback; + if (onHideCustomViewCallback != null) { + onHideCustomViewCallback(); + } + }; + }), + onShowFileChooser: withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return ( + _, + android_webview.WebView webView, + android_webview.FileChooserParams params, + ) async { + if (weakReference.target?._onShowFileSelectorCallback != null) { + return weakReference.target!._onShowFileSelectorCallback!( + FileSelectorParams._fromFileChooserParams(params), + ); + } + return []; + }; + }), + onConsoleMessage: withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return ( + android_webview.WebChromeClient webChromeClient, + android_webview.ConsoleMessage consoleMessage, + ) async { + final void Function(JavaScriptConsoleMessage)? callback = + weakReference.target?._onConsoleLogCallback; + if (callback != null) { + JavaScriptLogLevel logLevel; + switch (consoleMessage.level) { + // Android maps `console.debug` to `MessageLevel.TIP`, it seems + // `MessageLevel.DEBUG` if not being used. + case android_webview.ConsoleMessageLevel.debug: + case android_webview.ConsoleMessageLevel.tip: + logLevel = JavaScriptLogLevel.debug; + case android_webview.ConsoleMessageLevel.error: + logLevel = JavaScriptLogLevel.error; + case android_webview.ConsoleMessageLevel.warning: + logLevel = JavaScriptLogLevel.warning; + case android_webview.ConsoleMessageLevel.unknown: + case android_webview.ConsoleMessageLevel.log: + logLevel = JavaScriptLogLevel.log; + } + + callback( + JavaScriptConsoleMessage( + level: logLevel, + message: consoleMessage.message, + ), + ); + } + }; + }), + onPermissionRequest: withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return (_, android_webview.PermissionRequest request) async { + final void Function(PlatformWebViewPermissionRequest)? callback = + weakReference.target?._onPermissionRequestCallback; + if (callback == null) { + return request.deny(); + } else { + final Set types = request + .resources + .nonNulls + .map((String type) { + switch (type) { + case PermissionRequestConstants.videoCapture: + return WebViewPermissionResourceType.camera; + case PermissionRequestConstants.audioCapture: + return WebViewPermissionResourceType.microphone; + case PermissionRequestConstants.midiSysex: + return AndroidWebViewPermissionResourceType.midiSysex; + case PermissionRequestConstants.protectedMediaId: + return AndroidWebViewPermissionResourceType + .protectedMediaId; + } + + // Type not supported. + return null; + }) + .whereType() + .toSet(); + + // If the request didn't contain any permissions recognized by the + // implementation, deny by default. + if (types.isEmpty) { + return request.deny(); + } + + callback( + AndroidWebViewPermissionRequest._( + types: types, + request: request, + ), + ); + } + }; + }), + onJsAlert: withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return (_, __, String url, String message) async { + final Future Function(JavaScriptAlertDialogRequest)? + callback = weakReference.target?._onJavaScriptAlert; + if (callback != null) { + final JavaScriptAlertDialogRequest request = + JavaScriptAlertDialogRequest(message: message, url: url); + + await callback.call(request); + } + return; + }; + }), + onJsConfirm: withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return (_, __, String url, String message) async { + final Future Function(JavaScriptConfirmDialogRequest)? + callback = weakReference.target?._onJavaScriptConfirm; + if (callback != null) { + final JavaScriptConfirmDialogRequest request = + JavaScriptConfirmDialogRequest(message: message, url: url); + final bool result = await callback.call(request); + return result; + } + return false; + }; + }), + onJsPrompt: withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return ( + _, + __, + String url, + String message, + String defaultValue, + ) async { + final Future Function(JavaScriptTextInputDialogRequest)? + callback = weakReference.target?._onJavaScriptPrompt; + if (callback != null) { + final JavaScriptTextInputDialogRequest request = + JavaScriptTextInputDialogRequest( + message: message, + url: url, + defaultText: defaultValue, + ); + final String result = await callback.call(request); + return result; + } + return ''; + }; + }), + ); + + /// The native [android_webview.FlutterAssetManager] allows managing assets. + late final android_webview.FlutterAssetManager _flutterAssetManager = + _androidWebViewParams.androidWebViewProxy.instanceFlutterAssetManager(); + + final Map _javaScriptChannelParams = + {}; + + AndroidNavigationDelegate? _currentNavigationDelegate; + + Future> Function(FileSelectorParams)? + _onShowFileSelectorCallback; + + OnGeolocationPermissionsShowPrompt? _onGeolocationPermissionsShowPrompt; + + OnGeolocationPermissionsHidePrompt? _onGeolocationPermissionsHidePrompt; + + OnShowCustomWidgetCallback? _onShowCustomWidgetCallback; + + OnHideCustomWidgetCallback? _onHideCustomWidgetCallback; + + void Function(PlatformWebViewPermissionRequest)? _onPermissionRequestCallback; + + void Function(JavaScriptConsoleMessage consoleMessage)? _onConsoleLogCallback; + + Future Function(JavaScriptAlertDialogRequest request)? + _onJavaScriptAlert; + Future Function(JavaScriptConfirmDialogRequest request)? + _onJavaScriptConfirm; + Future Function(JavaScriptTextInputDialogRequest request)? + _onJavaScriptPrompt; + + void Function(ScrollPositionChange scrollPositionChange)? + _onScrollPositionChangedCallback; + + /// Sets the file access permission for the web view. + /// + /// The default value is true for apps targeting API 29 and below, and false + /// when targeting API 30 and above. + Future setAllowFileAccess(bool allow) => + _webView.settings.setAllowFileAccess(allow); + + /// Whether to enable the platform's webview content debugging tools. + /// + /// Defaults to false. + static Future enableDebugging( + bool enabled, { + @visibleForTesting + AndroidWebViewProxy webViewProxy = const AndroidWebViewProxy(), + }) { + return webViewProxy.setWebContentsDebuggingEnabledWebView(enabled); + } + + /// Identifier used to retrieve the underlying native `WebView`. + /// + /// This is typically used by other plugins to retrieve the native `WebView` + /// from an `InstanceManager`. + /// + /// See Java method `WebViewFlutterPlugin.getWebView`. + int get webViewIdentifier => + // ignore: invalid_use_of_protected_member + _webView.pigeon_instanceManager.getIdentifier(_webView)!; + + @override + Future loadFile(String absoluteFilePath) { + return loadFileWithParams( + AndroidLoadFileParams(absoluteFilePath: absoluteFilePath), + ); + } + + @override + Future loadFileWithParams(LoadFileParams params) async { + switch (params) { + case final AndroidLoadFileParams params: + await Future.wait(>[ + _webView.settings.setAllowFileAccess(true), + _webView.loadUrl(params.absoluteFilePath, params.headers), + ]); + + default: + await loadFileWithParams( + AndroidLoadFileParams.fromLoadFileParams(params), + ); + } + } + + @override + Future loadFlutterAsset(String key) async { + final String assetFilePath = await _flutterAssetManager + .getAssetFilePathByName(key); + final List pathElements = assetFilePath.split('/'); + final String fileName = pathElements.removeLast(); + final List paths = await _flutterAssetManager.list( + pathElements.join('/'), + ); + + if (!paths.contains(fileName)) { + throw ArgumentError('Asset for key "$key" not found.', 'key'); + } + + return _webView.loadUrl( + Uri.file('/android_asset/$assetFilePath').toString(), + {}, + ); + } + + @override + Future loadHtmlString(String html, {String? baseUrl}) { + return _webView.loadDataWithBaseUrl(baseUrl, html, 'text/html', null, null); + } + + @override + Future loadRequest(LoadRequestParams params) { + if (!params.uri.hasScheme) { + throw ArgumentError('WebViewRequest#uri is required to have a scheme.'); + } + switch (params.method) { + case LoadRequestMethod.get: + return _webView.loadUrl(params.uri.toString(), params.headers); + case LoadRequestMethod.post: + return _webView.postUrl( + params.uri.toString(), + params.body ?? Uint8List(0), + ); + } + // The enum comes from a different package, which could get a new value at + // any time, so a fallback case is necessary. Since there is no reasonable + // default behavior, throw to alert the client that they need an updated + // version. This is deliberately outside the switch rather than a `default` + // so that the linter will flag the switch as needing an update. + // ignore: dead_code + throw UnimplementedError( + 'This version of `AndroidWebViewController` currently has no ' + 'implementation for HTTP method ${params.method.serialize()} in ' + 'loadRequest.', + ); + } + + @override + Future currentUrl() => _webView.getUrl(); + + @override + Future canGoBack() => _webView.canGoBack(); + + @override + Future canGoForward() => _webView.canGoForward(); + + @override + Future goBack() => _webView.goBack(); + + @override + Future goForward() => _webView.goForward(); + + @override + Future reload() => _webView.reload(); + + @override + Future clearCache() => _webView.clearCache(true); + + @override + Future clearLocalStorage() => + _androidWebViewParams.androidWebStorage.deleteAllData(); + + @override + Future setPlatformNavigationDelegate( + covariant AndroidNavigationDelegate handler, + ) async { + _currentNavigationDelegate = handler; + await Future.wait(>[ + handler.setOnLoadRequest(loadRequest), + _webView.setWebViewClient(handler.androidWebViewClient), + _webView.setDownloadListener(handler.androidDownloadListener), + ]); + } + + @override + Future runJavaScript(String javaScript) { + return _webView.evaluateJavascript(javaScript); + } + + @override + Future runJavaScriptReturningResult(String javaScript) async { + final String? result = await _webView.evaluateJavascript(javaScript); + + if (result == null) { + return ''; + } else if (result == 'true') { + return true; + } else if (result == 'false') { + return false; + } + + return num.tryParse(result) ?? result; + } + + @override + Future addJavaScriptChannel( + JavaScriptChannelParams javaScriptChannelParams, + ) { + final AndroidJavaScriptChannelParams androidJavaScriptParams = + javaScriptChannelParams is AndroidJavaScriptChannelParams + ? javaScriptChannelParams + : AndroidJavaScriptChannelParams.fromJavaScriptChannelParams( + javaScriptChannelParams, + ); + + // When JavaScript channel with the same name exists make sure to remove it + // before registering the new channel. + if (_javaScriptChannelParams.containsKey(androidJavaScriptParams.name)) { + _webView.removeJavaScriptChannel(androidJavaScriptParams.name); + } + + _javaScriptChannelParams[androidJavaScriptParams.name] = + androidJavaScriptParams; + + return _webView.addJavaScriptChannel( + androidJavaScriptParams._javaScriptChannel, + ); + } + + @override + Future removeJavaScriptChannel(String javaScriptChannelName) async { + final AndroidJavaScriptChannelParams? javaScriptChannelParams = + _javaScriptChannelParams[javaScriptChannelName]; + if (javaScriptChannelParams == null) { + return; + } + + _javaScriptChannelParams.remove(javaScriptChannelName); + return _webView.removeJavaScriptChannel(javaScriptChannelParams.name); + } + + @override + Future getTitle() => _webView.getTitle(); + + @override + Future scrollTo(int x, int y) => _webView.scrollTo(x, y); + + @override + Future scrollBy(int x, int y) => _webView.scrollBy(x, y); + + @override + Future getScrollPosition() async { + final android_webview.WebViewPoint point = await _webView + .getScrollPosition(); + return Offset(point.x.toDouble(), point.y.toDouble()); + } + + @override + Future enableZoom(bool enabled) => + _webView.settings.setSupportZoom(enabled); + + @override + Future setBackgroundColor(Color color) => + _webView.setBackgroundColor(color.value); + + @override + Future setJavaScriptMode(JavaScriptMode javaScriptMode) => _webView + .settings + .setJavaScriptEnabled(javaScriptMode == JavaScriptMode.unrestricted); + + @override + Future setUserAgent(String? userAgent) => + _webView.settings.setUserAgentString(userAgent); + + @override + Future setOnScrollPositionChange( + void Function(ScrollPositionChange scrollPositionChange)? + onScrollPositionChange, + ) async { + _onScrollPositionChangedCallback = onScrollPositionChange; + } + + /// Sets the restrictions that apply on automatic media playback. + Future setMediaPlaybackRequiresUserGesture(bool require) { + return _webView.settings.setMediaPlaybackRequiresUserGesture(require); + } + + /// Sets the text zoom of the page in percent. + /// + /// The default is 100. + Future setTextZoom(int textZoom) => + _webView.settings.setTextZoom(textZoom); + + /// Sets whether the WebView should enable support for the "viewport" HTML + /// meta tag or should use a wide viewport. + /// + /// The default is false. + Future setUseWideViewPort(bool use) => + _webView.settings.setUseWideViewPort(use); + + /// Enables or disables content URL access. + /// + /// The default is true. + Future setAllowContentAccess(bool enabled) => + _webView.settings.setAllowContentAccess(enabled); + + /// Sets whether Geolocation is enabled. + /// + /// The default is true. + Future setGeolocationEnabled(bool enabled) => + _webView.settings.setGeolocationEnabled(enabled); + + /// Sets the callback that is invoked when the client should show a file + /// selector. + Future setOnShowFileSelector( + Future> Function(FileSelectorParams params)? + onShowFileSelector, + ) { + _onShowFileSelectorCallback = onShowFileSelector; + return _webChromeClient.setSynchronousReturnValueForOnShowFileChooser( + onShowFileSelector != null, + ); + } + + /// Sets a callback that notifies the host application that web content is + /// requesting permission to access the specified resources. + @override + Future setOnPlatformPermissionRequest( + void Function(PlatformWebViewPermissionRequest request) onPermissionRequest, + ) async { + _onPermissionRequestCallback = onPermissionRequest; + } + + /// Sets the callback that is invoked when the client request handle geolocation permissions. + /// + /// Param [onShowPrompt] notifies the host application that web content from the specified origin is attempting to use the Geolocation API, + /// but no permission state is currently set for that origin. + /// + /// The host application should invoke the specified callback with the desired permission state. + /// See GeolocationPermissions for details. + /// + /// This method is only called for requests originating from secure origins such as https. + /// On non-secure origins geolocation requests are automatically denied. + /// + /// Param [onHidePrompt] notifies the host application that a request for Geolocation permissions, + /// made with a previous call to onGeolocationPermissionsShowPrompt() has been canceled. + /// Any related UI should therefore be hidden. + /// + /// See https://developer.android.com/reference/android/webkit/WebChromeClient#onGeolocationPermissionsShowPrompt(java.lang.String,%20android.webkit.GeolocationPermissions.Callback) + /// + /// See https://developer.android.com/reference/android/webkit/WebChromeClient#onGeolocationPermissionsHidePrompt() + Future setGeolocationPermissionsPromptCallbacks({ + OnGeolocationPermissionsShowPrompt? onShowPrompt, + OnGeolocationPermissionsHidePrompt? onHidePrompt, + }) async { + _onGeolocationPermissionsShowPrompt = onShowPrompt; + _onGeolocationPermissionsHidePrompt = onHidePrompt; + } + + /// Sets the callbacks that are invoked when the host application wants to + /// show or hide a custom widget. + /// + /// The most common use case these methods are invoked a video element wants + /// to be displayed in fullscreen. + /// + /// The [onShowCustomWidget] notifies the host application that web content + /// from the specified origin wants to be displayed in a custom widget. After + /// this call, web content will no longer be rendered in the `WebViewWidget`, + /// but will instead be rendered in the custom widget. The application may + /// explicitly exit fullscreen mode by invoking `onCustomWidgetHidden` in the + /// [onShowCustomWidget] callback (ex. when the user presses the back + /// button). However, this is generally not necessary as the web page will + /// often show its own UI to close out of fullscreen. Regardless of how the + /// WebView exits fullscreen mode, WebView will invoke [onHideCustomWidget], + /// signaling for the application to remove the custom widget. If this value + /// is `null` when passed to an `AndroidWebViewWidget`, a default handler + /// will be set. + /// + /// The [onHideCustomWidget] notifies the host application that the custom + /// widget must be hidden. After this call, web content will render in the + /// original `WebViewWidget` again. + Future setCustomWidgetCallbacks({ + required OnShowCustomWidgetCallback? onShowCustomWidget, + required OnHideCustomWidgetCallback? onHideCustomWidget, + }) async { + _onShowCustomWidgetCallback = onShowCustomWidget; + _onHideCustomWidgetCallback = onHideCustomWidget; + } + + /// Sets a callback that notifies the host application of any log messages + /// written to the JavaScript console. + @override + Future setOnConsoleMessage( + void Function(JavaScriptConsoleMessage consoleMessage) onConsoleMessage, + ) async { + _onConsoleLogCallback = onConsoleMessage; + + return _webChromeClient.setSynchronousReturnValueForOnConsoleMessage( + _onConsoleLogCallback != null, + ); + } + + @override + Future getUserAgent() => _webView.settings.getUserAgentString(); + + @override + Future setOnJavaScriptAlertDialog( + Future Function(JavaScriptAlertDialogRequest request) + onJavaScriptAlertDialog, + ) async { + _onJavaScriptAlert = onJavaScriptAlertDialog; + return _webChromeClient.setSynchronousReturnValueForOnJsAlert(true); + } + + @override + Future setOnJavaScriptConfirmDialog( + Future Function(JavaScriptConfirmDialogRequest request) + onJavaScriptConfirmDialog, + ) async { + _onJavaScriptConfirm = onJavaScriptConfirmDialog; + return _webChromeClient.setSynchronousReturnValueForOnJsConfirm(true); + } + + @override + Future setOnJavaScriptTextInputDialog( + Future Function(JavaScriptTextInputDialogRequest request) + onJavaScriptTextInputDialog, + ) async { + _onJavaScriptPrompt = onJavaScriptTextInputDialog; + return _webChromeClient.setSynchronousReturnValueForOnJsPrompt(true); + } + + @override + Future setVerticalScrollBarEnabled(bool enabled) => + _webView.setVerticalScrollBarEnabled(enabled); + + @override + Future setHorizontalScrollBarEnabled(bool enabled) => + _webView.setHorizontalScrollBarEnabled(enabled); + + @override + bool supportsSetScrollBarsEnabled() => true; + + @override + Future setOverScrollMode(WebViewOverScrollMode mode) { + return switch (mode) { + WebViewOverScrollMode.always => _webView.setOverScrollMode( + android_webview.OverScrollMode.always, + ), + WebViewOverScrollMode.ifContentScrolls => _webView.setOverScrollMode( + android_webview.OverScrollMode.ifContentScrolls, + ), + WebViewOverScrollMode.never => _webView.setOverScrollMode( + android_webview.OverScrollMode.never, + ), + // This prevents future additions from causing a breaking change. + // ignore: unreachable_switch_case + _ => throw UnsupportedError('Android does not support $mode.'), + }; + } + + /// Configures the WebView's behavior when handling mixed content. + Future setMixedContentMode(MixedContentMode mode) { + final android_webview.MixedContentMode androidMode = switch (mode) { + MixedContentMode.alwaysAllow => + android_webview.MixedContentMode.alwaysAllow, + MixedContentMode.compatibilityMode => + android_webview.MixedContentMode.compatibilityMode, + MixedContentMode.neverAllow => + android_webview.MixedContentMode.neverAllow, + }; + return _webView.settings.setMixedContentMode(androidMode); + } + + /// Checks if a WebView feature is supported on the current device. + /// + /// This method uses [android_webview.WebViewFeature.isFeatureSupported] to check + /// if the specified WebView feature is available on the current device and WebView version. + /// + /// See [WebViewFeatureType] for available feature constants. + Future isWebViewFeatureSupported(WebViewFeatureType featureType) { + final String feature = switch (featureType) { + WebViewFeatureType.paymentRequest => + WebViewFeatureConstants.paymentRequest, + }; + return _androidWebViewParams.androidWebViewProxy.isWebViewFeatureSupported( + feature, + ); + } + + /// Sets whether the WebView should enable the Payment Request API. + /// + /// This method uses [android_webview.WebSettingsCompat.setPaymentRequestEnabled] + /// to enable or disable the Payment Request API for the WebView. + /// + /// Before calling this method, you should check if the feature is supported using + /// [isWebViewFeatureSupported] with [WebViewFeatureType.paymentRequest]. + /// + /// This feature requires adding queries to the AndroidManifest.xml to allow WebView to query the device for the user's payment applications: + /// See https://developer.android.com/reference/androidx/webkit/WebSettingsCompat#setPaymentRequestEnabled(android.webkit.WebSettings,boolean). + Future setPaymentRequestEnabled(bool enabled) { + return _androidWebViewParams.androidWebViewProxy.setPaymentRequestEnabled( + _webView.settings, + enabled, + ); + } +} + +/// Android implementation of [PlatformWebViewPermissionRequest]. +class AndroidWebViewPermissionRequest extends PlatformWebViewPermissionRequest { + const AndroidWebViewPermissionRequest._({ + required super.types, + required android_webview.PermissionRequest request, + }) : _request = request; + + final android_webview.PermissionRequest _request; + + @override + Future grant() { + return _request.grant( + types.map((WebViewPermissionResourceType type) { + switch (type) { + case WebViewPermissionResourceType.camera: + return PermissionRequestConstants.videoCapture; + case WebViewPermissionResourceType.microphone: + return PermissionRequestConstants.audioCapture; + case AndroidWebViewPermissionResourceType.midiSysex: + return PermissionRequestConstants.midiSysex; + case AndroidWebViewPermissionResourceType.protectedMediaId: + return PermissionRequestConstants.protectedMediaId; + } + + throw UnsupportedError( + 'Resource of type `${type.name}` is not supported.', + ); + }).toList(), + ); + } + + @override + Future deny() { + return _request.deny(); + } +} + +/// Signature for the `setGeolocationPermissionsPromptCallbacks` callback responsible for request the Geolocation API. +typedef OnGeolocationPermissionsShowPrompt = + Future Function( + GeolocationPermissionsRequestParams request, + ); + +/// Signature for the `setGeolocationPermissionsPromptCallbacks` callback responsible for request the Geolocation API is cancel. +typedef OnGeolocationPermissionsHidePrompt = void Function(); + +/// Signature for the `setCustomWidgetCallbacks` callback responsible for showing the custom view. +typedef OnShowCustomWidgetCallback = + void Function(Widget widget, void Function() onCustomWidgetHidden); + +/// Signature for the `setCustomWidgetCallbacks` callback responsible for hiding the custom view. +typedef OnHideCustomWidgetCallback = void Function(); + +/// A request params used by the host application to set the Geolocation permission state for an origin. +@immutable +class GeolocationPermissionsRequestParams { + /// [origin]: The origin for which permissions are set. + const GeolocationPermissionsRequestParams({required this.origin}); + + /// [origin]: The origin for which permissions are set. + final String origin; +} + +/// A response used by the host application to set the Geolocation permission state for an origin. +@immutable +class GeolocationPermissionsResponse { + /// [allow]: Whether or not the origin should be allowed to use the Geolocation API. + /// + /// [retain]: Whether the permission should be retained beyond the lifetime of + /// a page currently being displayed by a WebView. + const GeolocationPermissionsResponse({ + required this.allow, + required this.retain, + }); + + /// Whether or not the origin should be allowed to use the Geolocation API. + final bool allow; + + /// Whether the permission should be retained beyond the lifetime of + /// a page currently being displayed by a WebView. + final bool retain; +} + +/// Mode of how to select files for a file chooser. +enum FileSelectorMode { + /// Open single file and requires that the file exists before allowing the + /// user to pick it. + open, + + /// Similar to [open] but allows multiple files to be selected. + openMultiple, + + /// Allows picking a nonexistent file and saving it. + save, +} + +/// Mode for controlling mixed content handling. + +/// See [AndroidWebViewController.setMixedContentMode]. +enum MixedContentMode { + /// The WebView will allow a secure origin to load content from any other + /// origin, even if that origin is insecure. + /// + /// This is the least secure mode of operation, and where possible apps should + /// not set this mode. + alwaysAllow, + + /// The WebView will attempt to be compatible with the approach of a modern + /// web browser with regard to mixed content. + /// + /// The types of content are allowed or blocked may change release to release + /// of the underlying Android WebView, and are not explicitly defined. This + /// mode is intended to be used by apps that are not in control of the content + /// that they render but desire to operate in a reasonably secure environment. + compatibilityMode, + + /// The WebView will not allow a secure origin to load content from an + /// insecure origin. + /// + /// This is the preferred and most secure mode of operation, and apps are + /// strongly advised to use this mode. + /// + /// This is the default mode. + neverAllow, +} + +/// WebView support library feature types used to query for support on the device. +/// +/// See https://developer.android.com/reference/androidx/webkit/WebViewFeature#constants_1. +enum WebViewFeatureType { + /// Feature for isFeatureSupported. + /// + /// This feature covers [WebSettingsCompat.setPaymentRequestEnabled]. + paymentRequest, +} + +/// Parameters received when the `WebView` should show a file selector. +@immutable +class FileSelectorParams { + /// Constructs a [FileSelectorParams]. + const FileSelectorParams({ + required this.isCaptureEnabled, + required this.acceptTypes, + this.filenameHint, + required this.mode, + }); + + factory FileSelectorParams._fromFileChooserParams( + android_webview.FileChooserParams params, + ) { + final FileSelectorMode mode; + switch (params.mode) { + case android_webview.FileChooserMode.open: + mode = FileSelectorMode.open; + case android_webview.FileChooserMode.openMultiple: + mode = FileSelectorMode.openMultiple; + case android_webview.FileChooserMode.save: + mode = FileSelectorMode.save; + case android_webview.FileChooserMode.unknown: + throw UnsupportedError( + 'FileSelectorParams could not be instantiated because it received an unsupported mode.', + ); + } + + return FileSelectorParams( + isCaptureEnabled: params.isCaptureEnabled, + acceptTypes: params.acceptTypes.nonNulls.toList(), + mode: mode, + filenameHint: params.filenameHint, + ); + } + + /// Preference for a live media captured value (e.g. Camera, Microphone). + final bool isCaptureEnabled; + + /// A list of acceptable MIME types. + final List acceptTypes; + + /// The file name of a default selection if specified, or null. + final String? filenameHint; + + /// Mode of how to select files for a file selector. + final FileSelectorMode mode; +} + +/// An implementation of [JavaScriptChannelParams] with the Android WebView API. +/// +/// See [AndroidWebViewController.addJavaScriptChannel]. +@immutable +class AndroidJavaScriptChannelParams extends JavaScriptChannelParams { + /// Constructs a [AndroidJavaScriptChannelParams]. + AndroidJavaScriptChannelParams({ + required super.name, + required super.onMessageReceived, + @visibleForTesting + AndroidWebViewProxy webViewProxy = const AndroidWebViewProxy(), + }) : assert(name.isNotEmpty), + _javaScriptChannel = webViewProxy.newJavaScriptChannel( + channelName: name, + postMessage: withWeakReferenceTo(onMessageReceived, ( + WeakReference weakReference, + ) { + return (_, String message) { + if (weakReference.target != null) { + weakReference.target!(JavaScriptMessage(message: message)); + } + }; + }), + ); + + /// Constructs a [AndroidJavaScriptChannelParams] using a + /// [JavaScriptChannelParams]. + AndroidJavaScriptChannelParams.fromJavaScriptChannelParams( + JavaScriptChannelParams params, { + @visibleForTesting + AndroidWebViewProxy webViewProxy = const AndroidWebViewProxy(), + }) : this( + name: params.name, + onMessageReceived: params.onMessageReceived, + webViewProxy: webViewProxy, + ); + + final android_webview.JavaScriptChannel _javaScriptChannel; +} + +/// Object specifying creation parameters for creating a [AndroidWebViewWidget]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformWebViewWidgetCreationParams] for +/// more information. +@immutable +class AndroidWebViewWidgetCreationParams + extends PlatformWebViewWidgetCreationParams { + /// Creates [AndroidWebWidgetCreationParams]. + AndroidWebViewWidgetCreationParams({ + super.key, + required super.controller, + super.layoutDirection, + super.gestureRecognizers, + this.displayWithHybridComposition = false, + @visibleForTesting android_webview.PigeonInstanceManager? instanceManager, + @visibleForTesting + this.platformViewsServiceProxy = const PlatformViewsServiceProxy(), + }) : instanceManager = + instanceManager ?? android_webview.PigeonInstanceManager.instance; + + /// Constructs a [WebKitWebViewWidgetCreationParams] using a + /// [PlatformWebViewWidgetCreationParams]. + AndroidWebViewWidgetCreationParams.fromPlatformWebViewWidgetCreationParams( + PlatformWebViewWidgetCreationParams params, { + bool displayWithHybridComposition = false, + @visibleForTesting android_webview.PigeonInstanceManager? instanceManager, + @visibleForTesting + PlatformViewsServiceProxy platformViewsServiceProxy = + const PlatformViewsServiceProxy(), + }) : this( + key: params.key, + controller: params.controller, + layoutDirection: params.layoutDirection, + gestureRecognizers: params.gestureRecognizers, + displayWithHybridComposition: displayWithHybridComposition, + instanceManager: instanceManager, + platformViewsServiceProxy: platformViewsServiceProxy, + ); + + /// Maintains instances used to communicate with the native objects they + /// represent. + /// + /// This field is exposed for testing purposes only and should not be used + /// outside of tests. + @visibleForTesting + final android_webview.PigeonInstanceManager instanceManager; + + /// Proxy that provides access to the platform views service. + /// + /// This service allows creating and controlling platform-specific views. + @visibleForTesting + final PlatformViewsServiceProxy platformViewsServiceProxy; + + /// Whether the [WebView] will be displayed using the Hybrid Composition + /// PlatformView implementation. + /// + /// For most use cases, this flag should be set to false. Hybrid Composition + /// can have performance costs but doesn't have the limitation of rendering to + /// an Android SurfaceTexture. See + /// * https://docs.flutter.dev/platform-integration/android/platform-views#performance + /// * https://github.com/flutter/flutter/issues/104889 + /// * https://github.com/flutter/flutter/issues/116954 + /// + /// Defaults to false. + final bool displayWithHybridComposition; + + @override + int get hashCode => Object.hash( + controller, + layoutDirection, + displayWithHybridComposition, + platformViewsServiceProxy, + instanceManager, + ); + + @override + bool operator ==(Object other) { + return other is AndroidWebViewWidgetCreationParams && + controller == other.controller && + layoutDirection == other.layoutDirection && + displayWithHybridComposition == other.displayWithHybridComposition && + platformViewsServiceProxy == other.platformViewsServiceProxy && + instanceManager == other.instanceManager; + } +} + +/// An implementation of [PlatformWebViewWidget] with the Android WebView API. +class AndroidWebViewWidget extends PlatformWebViewWidget { + /// Constructs a [WebKitWebViewWidget]. + AndroidWebViewWidget(PlatformWebViewWidgetCreationParams params) + : super.implementation( + params is AndroidWebViewWidgetCreationParams + ? params + : AndroidWebViewWidgetCreationParams.fromPlatformWebViewWidgetCreationParams( + params, + ), + ); + + AndroidWebViewWidgetCreationParams get _androidParams => + params as AndroidWebViewWidgetCreationParams; + + @override + Widget build(BuildContext context) { + _trySetDefaultOnShowCustomWidgetCallbacks(context); + return PlatformViewLink( + // Setting a default key using `params` ensures the `PlatformViewLink` + // recreates the PlatformView when changes are made. + key: + _androidParams.key ?? + ValueKey( + params as AndroidWebViewWidgetCreationParams, + ), + viewType: 'plugins.flutter.io/webview', + surfaceFactory: + (BuildContext context, PlatformViewController controller) { + return AndroidViewSurface( + controller: controller as AndroidViewController, + gestureRecognizers: _androidParams.gestureRecognizers, + hitTestBehavior: PlatformViewHitTestBehavior.opaque, + ); + }, + onCreatePlatformView: (PlatformViewCreationParams params) { + return _initAndroidView( + params, + displayWithHybridComposition: + _androidParams.displayWithHybridComposition, + platformViewsServiceProxy: _androidParams.platformViewsServiceProxy, + view: (_androidParams.controller as AndroidWebViewController) + ._webView, + instanceManager: _androidParams.instanceManager, + layoutDirection: _androidParams.layoutDirection, + ) + ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) + ..create(); + }, + ); + } + + // Attempt to handle custom views with a default implementation if it has not + // been set. + void _trySetDefaultOnShowCustomWidgetCallbacks(BuildContext context) { + final AndroidWebViewController controller = + _androidParams.controller as AndroidWebViewController; + + if (controller._onShowCustomWidgetCallback == null) { + controller.setCustomWidgetCallbacks( + onShowCustomWidget: + (Widget widget, OnHideCustomWidgetCallback callback) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => widget, + fullscreenDialog: true, + ), + ); + }, + onHideCustomWidget: () { + Navigator.of(context).pop(); + }, + ); + } + } +} + +/// Represents a Flutter implementation of the Android [View](https://developer.android.com/reference/android/view/View) +/// that is created by the host platform when web content needs to be displayed +/// in fullscreen mode. +/// +/// The [AndroidCustomViewWidget] cannot be manually instantiated and is +/// provided to the host application through the callbacks specified using the +/// [AndroidWebViewController.setCustomWidgetCallbacks] method. +/// +/// The [AndroidCustomViewWidget] is initialized internally and should only be +/// exposed as a [Widget] externally. The type [AndroidCustomViewWidget] is +/// visible for testing purposes only and should never be called externally. +@visibleForTesting +class AndroidCustomViewWidget extends StatelessWidget { + /// Creates a [AndroidCustomViewWidget]. + /// + /// The [AndroidCustomViewWidget] should only be instantiated internally. + /// This constructor is visible for testing purposes only and should + /// never be called externally. + @visibleForTesting + AndroidCustomViewWidget.private({ + super.key, + required this.controller, + required this.customView, + @visibleForTesting android_webview.PigeonInstanceManager? instanceManager, + @visibleForTesting + this.platformViewsServiceProxy = const PlatformViewsServiceProxy(), + }) : instanceManager = + instanceManager ?? android_webview.PigeonInstanceManager.instance; + + /// The reference to the Android native view that should be shown. + final android_webview.View customView; + + /// The [PlatformWebViewController] that allows controlling the native web + /// view. + final PlatformWebViewController controller; + + /// Maintains instances used to communicate with the native objects they + /// represent. + /// + /// This field is exposed for testing purposes only and should not be used + /// outside of tests. + @visibleForTesting + final android_webview.PigeonInstanceManager instanceManager; + + /// Proxy that provides access to the platform views service. + /// + /// This service allows creating and controlling platform-specific views. + @visibleForTesting + final PlatformViewsServiceProxy platformViewsServiceProxy; + + @override + Widget build(BuildContext context) { + return PlatformViewLink( + key: key, + viewType: 'plugins.flutter.io/webview', + surfaceFactory: + (BuildContext context, PlatformViewController controller) { + return AndroidViewSurface( + controller: controller as AndroidViewController, + hitTestBehavior: PlatformViewHitTestBehavior.opaque, + gestureRecognizers: + const >{}, + ); + }, + onCreatePlatformView: (PlatformViewCreationParams params) { + return _initAndroidView( + params, + displayWithHybridComposition: false, + platformViewsServiceProxy: platformViewsServiceProxy, + view: customView, + instanceManager: instanceManager, + ) + ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) + ..create(); + }, + ); + } +} + +AndroidViewController _initAndroidView( + PlatformViewCreationParams params, { + required bool displayWithHybridComposition, + required PlatformViewsServiceProxy platformViewsServiceProxy, + required android_webview.View view, + required android_webview.PigeonInstanceManager instanceManager, + TextDirection layoutDirection = TextDirection.ltr, +}) { + final int identifier = instanceManager.getIdentifier(view)!; + + if (displayWithHybridComposition) { + return platformViewsServiceProxy.initExpensiveAndroidView( + id: params.id, + viewType: 'plugins.flutter.io/webview', + layoutDirection: layoutDirection, + creationParams: identifier, + creationParamsCodec: const StandardMessageCodec(), + ); + } else { + return platformViewsServiceProxy.initSurfaceAndroidView( + id: params.id, + viewType: 'plugins.flutter.io/webview', + layoutDirection: layoutDirection, + creationParams: identifier, + creationParamsCodec: const StandardMessageCodec(), + ); + } +} + +/// Signature for the `loadRequest` callback responsible for loading the [url] +/// after a navigation request has been approved. +typedef LoadRequestCallback = Future Function(LoadRequestParams params); + +/// Error returned in `WebView.onWebResourceError` when a web resource loading error has occurred. +@immutable +class AndroidWebResourceError extends WebResourceError { + /// Creates a new [AndroidWebResourceError]. + AndroidWebResourceError._({ + required super.errorCode, + required super.description, + super.isForMainFrame, + super.url, + }) : failingUrl = url, + super(errorType: _errorCodeToErrorType(errorCode)); + + /// Gets the URL for which the failing resource request was made. + @Deprecated('Please use `url`.') + final String? failingUrl; + + static WebResourceErrorType? _errorCodeToErrorType(int errorCode) { + switch (errorCode) { + case WebViewClientConstants.errorAuthentication: + return WebResourceErrorType.authentication; + case WebViewClientConstants.errorBadUrl: + return WebResourceErrorType.badUrl; + case WebViewClientConstants.errorConnect: + return WebResourceErrorType.connect; + case WebViewClientConstants.errorFailedSslHandshake: + return WebResourceErrorType.failedSslHandshake; + case WebViewClientConstants.errorFile: + return WebResourceErrorType.file; + case WebViewClientConstants.errorFileNotFound: + return WebResourceErrorType.fileNotFound; + case WebViewClientConstants.errorHostLookup: + return WebResourceErrorType.hostLookup; + case WebViewClientConstants.errorIO: + return WebResourceErrorType.io; + case WebViewClientConstants.errorProxyAuthentication: + return WebResourceErrorType.proxyAuthentication; + case WebViewClientConstants.errorRedirectLoop: + return WebResourceErrorType.redirectLoop; + case WebViewClientConstants.errorTimeout: + return WebResourceErrorType.timeout; + case WebViewClientConstants.errorTooManyRequests: + return WebResourceErrorType.tooManyRequests; + case WebViewClientConstants.errorUnknown: + return WebResourceErrorType.unknown; + case WebViewClientConstants.errorUnsafeResource: + return WebResourceErrorType.unsafeResource; + case WebViewClientConstants.errorUnsupportedAuthScheme: + return WebResourceErrorType.unsupportedAuthScheme; + case WebViewClientConstants.errorUnsupportedScheme: + return WebResourceErrorType.unsupportedScheme; + } + + throw ArgumentError( + 'Could not find a WebResourceErrorType for errorCode: $errorCode', + ); + } +} + +/// Object specifying creation parameters for creating a [AndroidNavigationDelegate]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformNavigationDelegateCreationParams] for +/// more information. +@immutable +class AndroidNavigationDelegateCreationParams + extends PlatformNavigationDelegateCreationParams { + /// Creates a new [AndroidNavigationDelegateCreationParams] instance. + const AndroidNavigationDelegateCreationParams._({ + @visibleForTesting this.androidWebViewProxy = const AndroidWebViewProxy(), + }) : super(); + + /// Creates a [AndroidNavigationDelegateCreationParams] instance based on [PlatformNavigationDelegateCreationParams]. + factory AndroidNavigationDelegateCreationParams.fromPlatformNavigationDelegateCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformNavigationDelegateCreationParams params, { + @visibleForTesting + AndroidWebViewProxy androidWebViewProxy = const AndroidWebViewProxy(), + }) { + return AndroidNavigationDelegateCreationParams._( + androidWebViewProxy: androidWebViewProxy, + ); + } + + /// Handles constructing objects and calling static methods for the Android WebView + /// native library. + @visibleForTesting + final AndroidWebViewProxy androidWebViewProxy; +} + +/// Android details of the change to a web view's url. +class AndroidUrlChange extends UrlChange { + /// Constructs an [AndroidUrlChange]. + const AndroidUrlChange({required super.url, required this.isReload}); + + /// Whether the url is being reloaded. + final bool isReload; +} + +/// A place to register callback methods responsible to handle navigation events +/// triggered by the [android_webview.WebView]. +class AndroidNavigationDelegate extends PlatformNavigationDelegate { + /// Creates a new [AndroidNavigationDelegate]. + AndroidNavigationDelegate(PlatformNavigationDelegateCreationParams params) + : super.implementation( + params is AndroidNavigationDelegateCreationParams + ? params + : AndroidNavigationDelegateCreationParams.fromPlatformNavigationDelegateCreationParams( + params, + ), + ) { + final WeakReference weakThis = + WeakReference(this); + + _webViewClient = (this.params as AndroidNavigationDelegateCreationParams) + .androidWebViewProxy + .newWebViewClient( + onPageFinished: (_, android_webview.WebView webView, String url) { + final PageEventCallback? callback = + weakThis.target?._onPageFinished; + if (callback != null) { + callback(url); + } + }, + onPageStarted: (_, android_webview.WebView webView, String url) { + final PageEventCallback? callback = weakThis.target?._onPageStarted; + if (callback != null) { + callback(url); + } + }, + onReceivedHttpError: + ( + _, + android_webview.WebView webView, + android_webview.WebResourceRequest request, + android_webview.WebResourceResponse response, + ) { + if (weakThis.target?._onHttpError != null) { + weakThis.target!._onHttpError!( + HttpResponseError( + request: WebResourceRequest(uri: Uri.parse(request.url)), + response: WebResourceResponse( + uri: null, + statusCode: response.statusCode, + ), + ), + ); + } + }, + onReceivedRequestError: + ( + _, + android_webview.WebView webView, + android_webview.WebResourceRequest request, + android_webview.WebResourceError error, + ) { + final WebResourceErrorCallback? callback = + weakThis.target?._onWebResourceError; + if (callback != null) { + callback( + AndroidWebResourceError._( + errorCode: error.errorCode, + description: error.description, + url: request.url, + isForMainFrame: request.isForMainFrame, + ), + ); + } + }, + onReceivedRequestErrorCompat: + ( + _, + android_webview.WebView webView, + android_webview.WebResourceRequest request, + android_webview.WebResourceErrorCompat error, + ) { + final WebResourceErrorCallback? callback = + weakThis.target?._onWebResourceError; + if (callback != null) { + callback( + AndroidWebResourceError._( + errorCode: error.errorCode, + description: error.description, + url: request.url, + isForMainFrame: request.isForMainFrame, + ), + ); + } + }, + requestLoading: + ( + _, + android_webview.WebView webView, + android_webview.WebResourceRequest request, + ) { + weakThis.target?._handleNavigation( + request.url, + headers: + request.requestHeaders?.map(( + String? key, + String? value, + ) { + return MapEntry(key!, value!); + }) ?? + {}, + isForMainFrame: request.isForMainFrame, + ); + }, + urlLoading: (_, android_webview.WebView webView, String url) { + weakThis.target?._handleNavigation(url, isForMainFrame: true); + }, + doUpdateVisitedHistory: + (_, android_webview.WebView webView, String url, bool isReload) { + final UrlChangeCallback? callback = + weakThis.target?._onUrlChange; + if (callback != null) { + callback(AndroidUrlChange(url: url, isReload: isReload)); + } + }, + onReceivedHttpAuthRequest: + ( + _, + android_webview.WebView webView, + android_webview.HttpAuthHandler httpAuthHandler, + String host, + String realm, + ) { + final void Function(HttpAuthRequest)? callback = + weakThis.target?._onHttpAuthRequest; + if (callback != null) { + callback( + HttpAuthRequest( + onProceed: (WebViewCredential credential) { + httpAuthHandler.proceed( + credential.user, + credential.password, + ); + }, + onCancel: () { + httpAuthHandler.cancel(); + }, + host: host, + realm: realm, + ), + ); + } else { + httpAuthHandler.cancel(); + } + }, + onFormResubmission: + (_, __, android_webview.AndroidMessage dontResend, ___) { + dontResend.sendToTarget(); + }, + onReceivedClientCertRequest: + (_, __, android_webview.ClientCertRequest request) { + request.cancel(); + }, + onReceivedSslError: + ( + _, + __, + android_webview.SslErrorHandler handler, + android_webview.SslError error, + ) async { + final void Function(PlatformSslAuthError)? callback = + weakThis.target?._onSslAuthError; + + if (callback != null) { + final AndroidSslAuthError authError = + await AndroidSslAuthError.fromNativeCallback( + error: error, + handler: handler, + ); + + callback(authError); + } else { + await handler.cancel(); + } + }, + ); + + _downloadListener = (this.params as AndroidNavigationDelegateCreationParams) + .androidWebViewProxy + .newDownloadListener( + onDownloadStart: + ( + _, + String url, + String userAgent, + String contentDisposition, + String mimetype, + int contentLength, + ) { + if (weakThis.target != null) { + weakThis.target?._handleNavigation(url, isForMainFrame: true); + } + }, + ); + } + + AndroidNavigationDelegateCreationParams get _androidParams => + params as AndroidNavigationDelegateCreationParams; + + late final android_webview.WebChromeClient _webChromeClient = _androidParams + .androidWebViewProxy + .newWebChromeClient( + onJsConfirm: (_, __, ___, ____) async => false, + onShowFileChooser: (_, __, ___) async => [], + ); + + /// Gets the native [android_webview.WebChromeClient] that is bridged by this [AndroidNavigationDelegate]. + /// + /// Used by the [AndroidWebViewController] to set the `android_webview.WebView.setWebChromeClient`. + @Deprecated( + 'This value is not used by `AndroidWebViewController` and has no effect on the `WebView`.', + ) + android_webview.WebChromeClient get androidWebChromeClient => + _webChromeClient; + + late final android_webview.WebViewClient _webViewClient; + + /// Gets the native [android_webview.WebViewClient] that is bridged by this [AndroidNavigationDelegate]. + /// + /// Used by the [AndroidWebViewController] to set the `android_webview.WebView.setWebViewClient`. + android_webview.WebViewClient get androidWebViewClient => _webViewClient; + + late final android_webview.DownloadListener _downloadListener; + + /// Gets the native [android_webview.DownloadListener] that is bridged by this [AndroidNavigationDelegate]. + /// + /// Used by the [AndroidWebViewController] to set the `android_webview.WebView.setDownloadListener`. + android_webview.DownloadListener get androidDownloadListener => + _downloadListener; + + PageEventCallback? _onPageFinished; + PageEventCallback? _onPageStarted; + HttpResponseErrorCallback? _onHttpError; + ProgressCallback? _onProgress; + WebResourceErrorCallback? _onWebResourceError; + NavigationRequestCallback? _onNavigationRequest; + LoadRequestCallback? _onLoadRequest; + UrlChangeCallback? _onUrlChange; + HttpAuthRequestCallback? _onHttpAuthRequest; + SslAuthErrorCallback? _onSslAuthError; + + void _handleNavigation( + String url, { + required bool isForMainFrame, + Map headers = const {}, + }) { + final LoadRequestCallback? onLoadRequest = _onLoadRequest; + final NavigationRequestCallback? onNavigationRequest = _onNavigationRequest; + + // The client is only allowed to stop navigations that target the main frame because + // overridden URLs are passed to `loadUrl` and `loadUrl` cannot load a subframe. + if (!isForMainFrame || + onNavigationRequest == null || + onLoadRequest == null) { + return; + } + + final FutureOr returnValue = onNavigationRequest( + NavigationRequest(url: url, isMainFrame: isForMainFrame), + ); + + if (returnValue is NavigationDecision && + returnValue == NavigationDecision.navigate) { + onLoadRequest(LoadRequestParams(uri: Uri.parse(url), headers: headers)); + } else if (returnValue is Future) { + returnValue.then((NavigationDecision shouldLoadUrl) { + if (shouldLoadUrl == NavigationDecision.navigate) { + onLoadRequest( + LoadRequestParams(uri: Uri.parse(url), headers: headers), + ); + } + }); + } + } + + /// Invoked when loading the url after a navigation request is approved. + Future setOnLoadRequest(LoadRequestCallback onLoadRequest) async { + _onLoadRequest = onLoadRequest; + } + + @override + Future setOnNavigationRequest( + NavigationRequestCallback onNavigationRequest, + ) async { + _onNavigationRequest = onNavigationRequest; + return _webViewClient.setSynchronousReturnValueForShouldOverrideUrlLoading( + true, + ); + } + + @override + Future setOnPageStarted(PageEventCallback onPageStarted) async { + _onPageStarted = onPageStarted; + } + + @override + Future setOnPageFinished(PageEventCallback onPageFinished) async { + _onPageFinished = onPageFinished; + } + + @override + Future setOnHttpError(HttpResponseErrorCallback onHttpError) async { + _onHttpError = onHttpError; + } + + @override + Future setOnProgress(ProgressCallback onProgress) async { + _onProgress = onProgress; + } + + @override + Future setOnWebResourceError( + WebResourceErrorCallback onWebResourceError, + ) async { + _onWebResourceError = onWebResourceError; + } + + @override + Future setOnUrlChange(UrlChangeCallback onUrlChange) async { + _onUrlChange = onUrlChange; + } + + @override + Future setOnHttpAuthRequest( + HttpAuthRequestCallback onHttpAuthRequest, + ) async { + _onHttpAuthRequest = onHttpAuthRequest; + } + + @override + Future setOnSSlAuthError(SslAuthErrorCallback onSslAuthError) async { + _onSslAuthError = onSslAuthError; + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/lib/src/android_webview_cookie_manager.dart b/local_packages/webview_flutter_android-4.10.5/lib/src/android_webview_cookie_manager.dart new file mode 100644 index 0000000..88f5ad0 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/lib/src/android_webview_cookie_manager.dart @@ -0,0 +1,91 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'android_webkit.g.dart'; +import 'android_webview_controller.dart'; + +/// Object specifying creation parameters for creating a [AndroidWebViewCookieManager]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformWebViewCookieManagerCreationParams] for +/// more information. +@immutable +class AndroidWebViewCookieManagerCreationParams + extends PlatformWebViewCookieManagerCreationParams { + /// Creates a new [AndroidWebViewCookieManagerCreationParams] instance. + const AndroidWebViewCookieManagerCreationParams._( + // This parameter prevents breaking changes later. + // ignore: avoid_unused_constructor_parameters + PlatformWebViewCookieManagerCreationParams params, + ) : super(); + + /// Creates a [AndroidWebViewCookieManagerCreationParams] instance based on [PlatformWebViewCookieManagerCreationParams]. + factory AndroidWebViewCookieManagerCreationParams.fromPlatformWebViewCookieManagerCreationParams( + PlatformWebViewCookieManagerCreationParams params, + ) { + return AndroidWebViewCookieManagerCreationParams._(params); + } +} + +/// Handles all cookie operations for the Android platform. +class AndroidWebViewCookieManager extends PlatformWebViewCookieManager { + /// Creates a new [AndroidWebViewCookieManager]. + AndroidWebViewCookieManager( + PlatformWebViewCookieManagerCreationParams params, { + CookieManager? cookieManager, + }) : _cookieManager = cookieManager ?? CookieManager.instance, + super.implementation( + params is AndroidWebViewCookieManagerCreationParams + ? params + : AndroidWebViewCookieManagerCreationParams.fromPlatformWebViewCookieManagerCreationParams( + params, + ), + ); + + final CookieManager _cookieManager; + + @override + Future clearCookies() { + return _cookieManager.removeAllCookies(); + } + + @override + Future setCookie(WebViewCookie cookie) { + if (!_isValidPath(cookie.path)) { + throw ArgumentError( + 'The path property for the provided cookie was not given a legal value.', + ); + } + return _cookieManager.setCookie( + cookie.domain, + '${Uri.encodeComponent(cookie.name)}=${Uri.encodeComponent(cookie.value)}; path=${cookie.path}', + ); + } + + bool _isValidPath(String path) { + // Permitted ranges based on RFC6265bis: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 + for (final int char in path.codeUnits) { + if ((char < 0x20 || char > 0x3A) && (char < 0x3C || char > 0x7E)) { + return false; + } + } + return true; + } + + /// Sets whether the WebView should allow third party cookies to be set. + /// + /// Defaults to false. + Future setAcceptThirdPartyCookies( + AndroidWebViewController controller, + bool accept, + ) { + // ignore: invalid_use_of_protected_member + final WebView webView = _cookieManager.pigeon_instanceManager + .getInstanceWithWeakReference(controller.webViewIdentifier)!; + return _cookieManager.setAcceptThirdPartyCookies(webView, accept); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/lib/src/android_webview_platform.dart b/local_packages/webview_flutter_android-4.10.5/lib/src/android_webview_platform.dart new file mode 100644 index 0000000..2dfced7 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/lib/src/android_webview_platform.dart @@ -0,0 +1,44 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'android_webview_controller.dart'; +import 'android_webview_cookie_manager.dart'; + +/// Implementation of [WebViewPlatform] using the WebKit API. +class AndroidWebViewPlatform extends WebViewPlatform { + /// Registers this class as the default instance of [WebViewPlatform]. + static void registerWith() { + WebViewPlatform.instance = AndroidWebViewPlatform(); + } + + @override + AndroidWebViewController createPlatformWebViewController( + PlatformWebViewControllerCreationParams params, + ) { + return AndroidWebViewController(params); + } + + @override + AndroidNavigationDelegate createPlatformNavigationDelegate( + PlatformNavigationDelegateCreationParams params, + ) { + return AndroidNavigationDelegate(params); + } + + @override + AndroidWebViewWidget createPlatformWebViewWidget( + PlatformWebViewWidgetCreationParams params, + ) { + return AndroidWebViewWidget(params); + } + + @override + AndroidWebViewCookieManager createPlatformCookieManager( + PlatformWebViewCookieManagerCreationParams params, + ) { + return AndroidWebViewCookieManager(params); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/lib/src/legacy/webview_android.dart b/local_packages/webview_flutter_android-4.10.5/lib/src/legacy/webview_android.dart new file mode 100644 index 0000000..5313cf7 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/lib/src/legacy/webview_android.dart @@ -0,0 +1,83 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +// ignore: implementation_imports +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; + +import '../android_webkit.g.dart'; +import 'webview_android_widget.dart'; + +/// Builds an Android webview. +/// +/// This is used as the default implementation for [WebView.platform] on Android. It uses +/// an [AndroidView] to embed the webview in the widget hierarchy, and uses a method channel to +/// communicate with the platform code. +class AndroidWebView implements WebViewPlatform { + /// Constructs an [AndroidWebView]. + AndroidWebView({@visibleForTesting PigeonInstanceManager? instanceManager}) + : instanceManager = instanceManager ?? PigeonInstanceManager.instance; + + /// Maintains instances used to communicate with the native objects they + /// represent. + @protected + final PigeonInstanceManager instanceManager; + + @override + Widget build({ + required BuildContext context, + required CreationParams creationParams, + required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, + required JavascriptChannelRegistry javascriptChannelRegistry, + WebViewPlatformCreatedCallback? onWebViewPlatformCreated, + Set>? gestureRecognizers, + }) { + return WebViewAndroidWidget( + creationParams: creationParams, + callbacksHandler: webViewPlatformCallbacksHandler, + javascriptChannelRegistry: javascriptChannelRegistry, + onBuildWidget: (WebViewAndroidPlatformController controller) { + return GestureDetector( + // We prevent text selection by intercepting the long press event. + // This is a temporary stop gap due to issues with text selection on Android: + // https://github.com/flutter/flutter/issues/24585 - the text selection + // dialog is not responding to touch events. + // https://github.com/flutter/flutter/issues/24584 - the text selection + // handles are not showing. + // TODO(amirh): remove this when the issues above are fixed. + onLongPress: () {}, + excludeFromSemantics: true, + child: AndroidView( + viewType: 'plugins.flutter.io/webview', + onPlatformViewCreated: (int id) { + if (onWebViewPlatformCreated != null) { + onWebViewPlatformCreated(controller); + } + }, + gestureRecognizers: gestureRecognizers, + layoutDirection: + Directionality.maybeOf(context) ?? TextDirection.rtl, + creationParams: instanceManager.getIdentifier(controller.webView), + creationParamsCodec: const StandardMessageCodec(), + ), + ); + }, + ); + } + + @override + Future clearCookies() { + if (WebViewCookieManagerPlatform.instance == null) { + throw Exception( + 'Could not clear cookies as no implementation for WebViewCookieManagerPlatform has been registered.', + ); + } + return WebViewCookieManagerPlatform.instance!.clearCookies(); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/lib/src/legacy/webview_android_cookie_manager.dart b/local_packages/webview_flutter_android-4.10.5/lib/src/legacy/webview_android_cookie_manager.dart new file mode 100644 index 0000000..ee40c55 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/lib/src/legacy/webview_android_cookie_manager.dart @@ -0,0 +1,45 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; +// ignore: implementation_imports +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; + +import '../android_webkit.g.dart' as android_webview; + +/// Handles all cookie operations for the current platform. +class WebViewAndroidCookieManager extends WebViewCookieManagerPlatform { + /// Constructs a [WebViewAndroidCookieManager]. + WebViewAndroidCookieManager({ + @visibleForTesting android_webview.CookieManager? cookieManager, + }) : _cookieManager = cookieManager ?? android_webview.CookieManager.instance; + + final android_webview.CookieManager _cookieManager; + + @override + Future clearCookies() => _cookieManager.removeAllCookies(); + + @override + Future setCookie(WebViewCookie cookie) { + if (!_isValidPath(cookie.path)) { + throw ArgumentError( + 'The path property for the provided cookie was not given a legal value.', + ); + } + return _cookieManager.setCookie( + cookie.domain, + '${Uri.encodeComponent(cookie.name)}=${Uri.encodeComponent(cookie.value)}; path=${cookie.path}', + ); + } + + bool _isValidPath(String path) { + // Permitted ranges based on RFC6265bis: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 + for (final int char in path.codeUnits) { + if ((char < 0x20 || char > 0x3A) && (char < 0x3C || char > 0x7E)) { + return false; + } + } + return true; + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/lib/src/legacy/webview_android_widget.dart b/local_packages/webview_flutter_android-4.10.5/lib/src/legacy/webview_android_widget.dart new file mode 100644 index 0000000..ba7d73a --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/lib/src/legacy/webview_android_widget.dart @@ -0,0 +1,679 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:flutter/widgets.dart'; +// ignore: implementation_imports +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; + +import '../android_webkit.g.dart' as android_webview; +import '../android_webkit_constants.dart'; +import '../weak_reference_utils.dart'; +import 'webview_android_cookie_manager.dart'; + +/// Creates a [Widget] with a [android_webview.WebView]. +class WebViewAndroidWidget extends StatefulWidget { + /// Constructs a [WebViewAndroidWidget]. + WebViewAndroidWidget({ + super.key, + required this.creationParams, + required this.callbacksHandler, + required this.javascriptChannelRegistry, + required this.onBuildWidget, + @visibleForTesting this.webViewProxy = const WebViewProxy(), + @visibleForTesting android_webview.FlutterAssetManager? flutterAssetManager, + @visibleForTesting this.webStorage, + }) : flutterAssetManager = + flutterAssetManager ?? android_webview.FlutterAssetManager.instance; + + /// Initial parameters used to setup the WebView. + final CreationParams creationParams; + + /// Handles callbacks that are made by [android_webview.WebViewClient], [android_webview.DownloadListener], and [android_webview.WebChromeClient]. + final WebViewPlatformCallbacksHandler callbacksHandler; + + /// Manages named JavaScript channels and forwarding incoming messages on the correct channel. + final JavascriptChannelRegistry javascriptChannelRegistry; + + /// Handles constructing [android_webview.WebView]s and calling static methods. + /// + /// This should only be changed for testing purposes. + final WebViewProxy webViewProxy; + + /// Manages access to Flutter assets that are part of the Android App bundle. + /// + /// This should only be changed for testing purposes. + final android_webview.FlutterAssetManager flutterAssetManager; + + /// Callback to build a widget once [android_webview.WebView] has been initialized. + final Widget Function(WebViewAndroidPlatformController controller) + onBuildWidget; + + /// Manages the JavaScript storage APIs. + final android_webview.WebStorage? webStorage; + + @override + State createState() => _WebViewAndroidWidgetState(); +} + +class _WebViewAndroidWidgetState extends State { + late final WebViewAndroidPlatformController controller; + + @override + void initState() { + super.initState(); + controller = WebViewAndroidPlatformController( + creationParams: widget.creationParams, + callbacksHandler: widget.callbacksHandler, + javascriptChannelRegistry: widget.javascriptChannelRegistry, + webViewProxy: widget.webViewProxy, + flutterAssetManager: widget.flutterAssetManager, + webStorage: widget.webStorage, + ); + } + + @override + Widget build(BuildContext context) { + return widget.onBuildWidget(controller); + } +} + +/// Implementation of [WebViewPlatformController] with the Android WebView api. +class WebViewAndroidPlatformController extends WebViewPlatformController { + /// Construct a [WebViewAndroidPlatformController]. + WebViewAndroidPlatformController({ + required CreationParams creationParams, + required this.callbacksHandler, + required this.javascriptChannelRegistry, + @visibleForTesting this.webViewProxy = const WebViewProxy(), + @visibleForTesting android_webview.FlutterAssetManager? flutterAssetManager, + @visibleForTesting android_webview.WebStorage? webStorage, + }) : webStorage = webStorage ?? android_webview.WebStorage.instance, + flutterAssetManager = + flutterAssetManager ?? android_webview.FlutterAssetManager.instance, + assert(creationParams.webSettings?.hasNavigationDelegate != null), + super(callbacksHandler) { + webView = webViewProxy.createWebView(); + + webView.settings.setDomStorageEnabled(true); + webView.settings.setJavaScriptCanOpenWindowsAutomatically(true); + webView.settings.setSupportMultipleWindows(true); + webView.settings.setLoadWithOverviewMode(true); + webView.settings.setUseWideViewPort(true); + webView.settings.setDisplayZoomControls(false); + webView.settings.setBuiltInZoomControls(true); + + _setCreationParams(creationParams); + webView.setDownloadListener(downloadListener); + webView.setWebChromeClient(webChromeClient); + webView.setWebViewClient(webViewClient); + + final String? initialUrl = creationParams.initialUrl; + if (initialUrl != null) { + loadUrl(initialUrl, {}); + } + } + + final Map _javaScriptChannels = + {}; + + late final android_webview.WebViewClient _webViewClient = webViewProxy + .createWebViewClient( + onPageStarted: withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return (_, __, String url) { + weakReference.target?.callbacksHandler.onPageStarted(url); + }; + }), + onPageFinished: withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return (_, __, String url) { + weakReference.target?.callbacksHandler.onPageFinished(url); + }; + }), + onReceivedRequestError: withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return ( + _, + __, + android_webview.WebResourceRequest request, + android_webview.WebResourceError error, + ) { + if (request.isForMainFrame) { + weakReference.target?.callbacksHandler.onWebResourceError( + WebResourceError( + errorCode: error.errorCode, + description: error.description, + failingUrl: request.url, + errorType: _errorCodeToErrorType(error.errorCode), + ), + ); + } + }; + }), + urlLoading: withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return (_, __, String url) { + weakReference.target?._handleNavigationRequest( + url: url, + isForMainFrame: true, + ); + }; + }), + onFormResubmission: + (_, __, android_webview.AndroidMessage dontResend, ___) { + dontResend.sendToTarget(); + }, + onReceivedClientCertRequest: + (_, __, android_webview.ClientCertRequest request) { + request.cancel(); + }, + onReceivedSslError: + (_, __, android_webview.SslErrorHandler handler, ___) { + handler.cancel(); + }, + requestLoading: withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return (_, __, android_webview.WebResourceRequest request) { + weakReference.target?._handleNavigationRequest( + url: request.url, + isForMainFrame: request.isForMainFrame, + ); + }; + }), + ); + + bool _hasNavigationDelegate = false; + bool _hasProgressTracking = false; + + /// Represents the WebView maintained by platform code. + late final android_webview.WebView webView; + + /// Handles callbacks that are made by [android_webview.WebViewClient], [android_webview.DownloadListener], and [android_webview.WebChromeClient]. + final WebViewPlatformCallbacksHandler callbacksHandler; + + /// Manages named JavaScript channels and forwarding incoming messages on the correct channel. + final JavascriptChannelRegistry javascriptChannelRegistry; + + /// Handles constructing [android_webview.WebView]s and calling static methods. + /// + /// This should only be changed for testing purposes. + final WebViewProxy webViewProxy; + + /// Manages access to Flutter assets that are part of the Android App bundle. + /// + /// This should only be changed for testing purposes. + final android_webview.FlutterAssetManager flutterAssetManager; + + /// Receives callbacks when content should be downloaded instead. + @visibleForTesting + late final android_webview.DownloadListener downloadListener = + android_webview.DownloadListener( + onDownloadStart: withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return ( + _, + String url, + String userAgent, + String contentDisposition, + String mimetype, + int contentLength, + ) { + weakReference.target?._handleNavigationRequest( + url: url, + isForMainFrame: true, + ); + }; + }), + ); + + /// Handles JavaScript dialogs, favicons, titles, new windows, and the progress for [android_webview.WebView]. + @visibleForTesting + late final android_webview.WebChromeClient webChromeClient = + android_webview.WebChromeClient( + onProgressChanged: withWeakReferenceTo(this, ( + WeakReference weakReference, + ) { + return (_, __, int progress) { + final WebViewAndroidPlatformController? controller = + weakReference.target; + if (controller != null && controller._hasProgressTracking) { + controller.callbacksHandler.onProgress(progress); + } + }; + }), + onJsConfirm: (_, __, ___, ____) async => false, + onShowFileChooser: (_, __, ___) async => [], + ); + + /// Manages the JavaScript storage APIs. + final android_webview.WebStorage webStorage; + + /// Receive various notifications and requests for [android_webview.WebView]. + @visibleForTesting + android_webview.WebViewClient get webViewClient => _webViewClient; + + @override + Future loadHtmlString(String html, {String? baseUrl}) { + return webView.loadDataWithBaseUrl(baseUrl, html, 'text/html', null, null); + } + + @override + Future loadFile(String absoluteFilePath) { + final String url = absoluteFilePath.startsWith('file://') + ? absoluteFilePath + : 'file://$absoluteFilePath'; + + webView.settings.setAllowFileAccess(true); + return webView.loadUrl(url, {}); + } + + @override + Future loadFlutterAsset(String key) async { + final String assetFilePath = await flutterAssetManager + .getAssetFilePathByName(key); + final List pathElements = assetFilePath.split('/'); + final String fileName = pathElements.removeLast(); + final List paths = await flutterAssetManager.list( + pathElements.join('/'), + ); + + if (!paths.contains(fileName)) { + throw ArgumentError('Asset for key "$key" not found.', 'key'); + } + + return webView.loadUrl( + 'file:///android_asset/$assetFilePath', + {}, + ); + } + + @override + Future loadUrl(String url, Map? headers) { + return webView.loadUrl(url, headers ?? {}); + } + + /// When making a POST request, headers are ignored. As a workaround, make + /// the request manually and load the response data using [loadHTMLString]. + @override + Future loadRequest(WebViewRequest request) async { + if (!request.uri.hasScheme) { + throw ArgumentError('WebViewRequest#uri is required to have a scheme.'); + } + switch (request.method) { + case WebViewRequestMethod.get: + return webView.loadUrl(request.uri.toString(), request.headers); + case WebViewRequestMethod.post: + return webView.postUrl( + request.uri.toString(), + request.body ?? Uint8List(0), + ); + } + // The enum comes from a different package, which could get a new value at + // any time, so a fallback case is necessary. Since there is no reasonable + // default behavior, throw to alert the client that they need an updated + // version. This is deliberately outside the switch rather than a `default` + // so that the linter will flag the switch as needing an update. + // ignore: dead_code + throw UnimplementedError( + 'This version of webview_android_widget currently has no ' + 'implementation for HTTP method ${request.method.serialize()} in ' + 'loadRequest.', + ); + } + + @override + Future currentUrl() => webView.getUrl(); + + @override + Future canGoBack() => webView.canGoBack(); + + @override + Future canGoForward() => webView.canGoForward(); + + @override + Future goBack() => webView.goBack(); + + @override + Future goForward() => webView.goForward(); + + @override + Future reload() => webView.reload(); + + @override + Future clearCache() { + webView.clearCache(true); + return webStorage.deleteAllData(); + } + + @override + Future updateSettings(WebSettings setting) async { + _hasProgressTracking = setting.hasProgressTracking ?? _hasProgressTracking; + await Future.wait(>[ + _setUserAgent(setting.userAgent), + if (setting.hasNavigationDelegate != null) + _setHasNavigationDelegate(setting.hasNavigationDelegate!), + if (setting.javascriptMode != null) + _setJavaScriptMode(setting.javascriptMode!), + if (setting.debuggingEnabled != null) + _setDebuggingEnabled(setting.debuggingEnabled!), + if (setting.zoomEnabled != null) _setZoomEnabled(setting.zoomEnabled!), + ]); + } + + @override + Future evaluateJavascript(String javascript) async { + return runJavascriptReturningResult(javascript); + } + + @override + Future runJavascript(String javascript) async { + await webView.evaluateJavascript(javascript); + } + + @override + Future runJavascriptReturningResult(String javascript) async { + return await webView.evaluateJavascript(javascript) ?? ''; + } + + @override + Future addJavascriptChannels(Set javascriptChannelNames) { + return Future.wait( + javascriptChannelNames + .where((String channelName) { + return !_javaScriptChannels.containsKey(channelName); + }) + .map>((String channelName) { + final WebViewAndroidJavaScriptChannel javaScriptChannel = + WebViewAndroidJavaScriptChannel( + channelName: channelName, + javascriptChannelRegistry: javascriptChannelRegistry, + ); + _javaScriptChannels[channelName] = javaScriptChannel; + return webView.addJavaScriptChannel(javaScriptChannel); + }), + ); + } + + @override + Future removeJavascriptChannels(Set javascriptChannelNames) { + return Future.wait( + javascriptChannelNames + .where((String channelName) { + return _javaScriptChannels.containsKey(channelName); + }) + .map>((String channelName) { + _javaScriptChannels.remove(channelName); + return webView.removeJavaScriptChannel(channelName); + }), + ); + } + + @override + Future getTitle() => webView.getTitle(); + + @override + Future scrollTo(int x, int y) => webView.scrollTo(x, y); + + @override + Future scrollBy(int x, int y) => webView.scrollBy(x, y); + + @override + Future getScrollX() async => (await webView.getScrollPosition()).x; + + @override + Future getScrollY() async => (await webView.getScrollPosition()).y; + + void _setCreationParams(CreationParams creationParams) { + final WebSettings? webSettings = creationParams.webSettings; + if (webSettings != null) { + updateSettings(webSettings); + } + + final String? userAgent = creationParams.userAgent; + if (userAgent != null) { + webView.settings.setUserAgentString(userAgent); + } + + webView.settings.setMediaPlaybackRequiresUserGesture( + creationParams.autoMediaPlaybackPolicy != + AutoMediaPlaybackPolicy.always_allow, + ); + + final Color? backgroundColor = creationParams.backgroundColor; + if (backgroundColor != null) { + webView.setBackgroundColor(backgroundColor.value); + } + + addJavascriptChannels(creationParams.javascriptChannelNames); + + // TODO(BeMacized): Remove once platform implementations + // are able to register themselves (Flutter >=2.8), + // https://github.com/flutter/flutter/issues/94224 + WebViewCookieManagerPlatform.instance ??= WebViewAndroidCookieManager(); + + creationParams.cookies.forEach( + WebViewCookieManagerPlatform.instance!.setCookie, + ); + } + + Future _setHasNavigationDelegate(bool hasNavigationDelegate) { + _hasNavigationDelegate = hasNavigationDelegate; + return _webViewClient.setSynchronousReturnValueForShouldOverrideUrlLoading( + hasNavigationDelegate, + ); + } + + Future _setJavaScriptMode(JavascriptMode mode) { + switch (mode) { + case JavascriptMode.disabled: + return webView.settings.setJavaScriptEnabled(false); + case JavascriptMode.unrestricted: + return webView.settings.setJavaScriptEnabled(true); + } + } + + Future _setDebuggingEnabled(bool debuggingEnabled) { + return webViewProxy.setWebContentsDebuggingEnabled(debuggingEnabled); + } + + Future _setUserAgent(WebSetting userAgent) { + if (userAgent.isPresent) { + // If the string is empty, the system default value will be used. + return webView.settings.setUserAgentString(userAgent.value ?? ''); + } + + return Future.value(); + } + + Future _setZoomEnabled(bool zoomEnabled) { + return webView.settings.setSupportZoom(zoomEnabled); + } + + static WebResourceErrorType _errorCodeToErrorType(int errorCode) { + switch (errorCode) { + case WebViewClientConstants.errorAuthentication: + return WebResourceErrorType.authentication; + case WebViewClientConstants.errorBadUrl: + return WebResourceErrorType.badUrl; + case WebViewClientConstants.errorConnect: + return WebResourceErrorType.connect; + case WebViewClientConstants.errorFailedSslHandshake: + return WebResourceErrorType.failedSslHandshake; + case WebViewClientConstants.errorFile: + return WebResourceErrorType.file; + case WebViewClientConstants.errorFileNotFound: + return WebResourceErrorType.fileNotFound; + case WebViewClientConstants.errorHostLookup: + return WebResourceErrorType.hostLookup; + case WebViewClientConstants.errorIO: + return WebResourceErrorType.io; + case WebViewClientConstants.errorProxyAuthentication: + return WebResourceErrorType.proxyAuthentication; + case WebViewClientConstants.errorRedirectLoop: + return WebResourceErrorType.redirectLoop; + case WebViewClientConstants.errorTimeout: + return WebResourceErrorType.timeout; + case WebViewClientConstants.errorTooManyRequests: + return WebResourceErrorType.tooManyRequests; + case WebViewClientConstants.errorUnknown: + return WebResourceErrorType.unknown; + case WebViewClientConstants.errorUnsafeResource: + return WebResourceErrorType.unsafeResource; + case WebViewClientConstants.errorUnsupportedAuthScheme: + return WebResourceErrorType.unsupportedAuthScheme; + case WebViewClientConstants.errorUnsupportedScheme: + return WebResourceErrorType.unsupportedScheme; + } + + throw ArgumentError( + 'Could not find a WebResourceErrorType for errorCode: $errorCode', + ); + } + + void _handleNavigationRequest({ + required String url, + required bool isForMainFrame, + }) { + if (!_hasNavigationDelegate) { + return; + } + + final FutureOr returnValue = callbacksHandler.onNavigationRequest( + url: url, + isForMainFrame: isForMainFrame, + ); + + if (returnValue is bool && returnValue) { + loadUrl(url, {}); + } else if (returnValue is Future) { + returnValue.then((bool shouldLoadUrl) { + if (shouldLoadUrl) { + loadUrl(url, {}); + } + }); + } + } +} + +/// Exposes a channel to receive calls from javaScript. +class WebViewAndroidJavaScriptChannel + extends android_webview.JavaScriptChannel { + /// Creates a [WebViewAndroidJavaScriptChannel]. + WebViewAndroidJavaScriptChannel({ + required super.channelName, + required this.javascriptChannelRegistry, + }) : super( + postMessage: withWeakReferenceTo(javascriptChannelRegistry, ( + WeakReference weakReference, + ) { + return (_, String message) { + weakReference.target?.onJavascriptChannelMessage( + channelName, + message, + ); + }; + }), + ); + + /// Manages named JavaScript channels and forwarding incoming messages on the correct channel. + final JavascriptChannelRegistry javascriptChannelRegistry; +} + +/// Handles constructing [android_webview.WebView]s and calling static methods. +/// +/// This should only be used for testing purposes. +@visibleForTesting +class WebViewProxy { + /// Creates a [WebViewProxy]. + const WebViewProxy(); + + /// Constructs a [android_webview.WebView]. + android_webview.WebView createWebView() { + return android_webview.WebView(); + } + + /// Constructs a [android_webview.WebViewClient]. + android_webview.WebViewClient createWebViewClient({ + void Function( + android_webview.WebViewClient, + android_webview.WebView webView, + String url, + )? + onPageStarted, + void Function( + android_webview.WebViewClient, + android_webview.WebView webView, + String url, + )? + onPageFinished, + void Function( + android_webview.WebViewClient, + android_webview.WebView webView, + android_webview.WebResourceRequest request, + android_webview.WebResourceError error, + )? + onReceivedRequestError, + void Function( + android_webview.WebViewClient, + android_webview.WebView webView, + android_webview.WebResourceRequest request, + )? + requestLoading, + void Function( + android_webview.WebViewClient, + android_webview.WebView, + android_webview.AndroidMessage, + android_webview.AndroidMessage, + )? + onFormResubmission, + void Function( + android_webview.WebViewClient, + android_webview.WebView, + android_webview.ClientCertRequest, + )? + onReceivedClientCertRequest, + void Function( + android_webview.WebViewClient, + android_webview.WebView, + android_webview.SslErrorHandler, + android_webview.SslError, + )? + onReceivedSslError, + void Function( + android_webview.WebViewClient, + android_webview.WebView webView, + String url, + )? + urlLoading, + }) { + return android_webview.WebViewClient( + onPageStarted: onPageStarted, + onPageFinished: onPageFinished, + onReceivedRequestError: onReceivedRequestError, + requestLoading: requestLoading, + urlLoading: urlLoading, + ); + } + + /// Enables debugging of web contents (HTML / CSS / JavaScript) loaded into any WebViews of this application. + /// + /// This flag can be enabled in order to facilitate debugging of web layouts + /// and JavaScript code running inside WebViews. Please refer to + /// [android_webview.WebView] documentation for the debugging guide. The + /// default is false. + /// + /// See [android_webview.WebView].setWebContentsDebuggingEnabled. + Future setWebContentsDebuggingEnabled(bool enabled) { + return android_webview.WebView.setWebContentsDebuggingEnabled(enabled); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/lib/src/legacy/webview_surface_android.dart b/local_packages/webview_flutter_android-4.10.5/lib/src/legacy/webview_surface_android.dart new file mode 100644 index 0000000..2fa6015 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/lib/src/legacy/webview_surface_android.dart @@ -0,0 +1,119 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +// ignore: implementation_imports +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; + +import '../android_webkit.g.dart'; +import 'webview_android.dart'; +import 'webview_android_widget.dart'; + +/// Android [WebViewPlatform] that uses [AndroidViewSurface] to build the +/// [WebView] widget. +/// +/// To use this, set [WebView.platform] to an instance of this class. +/// +/// This implementation uses [AndroidViewSurface] to render the [WebView] on +/// Android. It solves multiple issues related to accessibility and interaction +/// with the [WebView] at the cost of some performance on Android versions below +/// 10. +/// +/// To support transparent backgrounds on all Android devices, this +/// implementation uses hybrid composition when the opacity of +/// `CreationParams.backgroundColor` is less than 1.0. See +/// https://github.com/flutter/flutter/wiki/Hybrid-Composition for more +/// information. +class SurfaceAndroidWebView extends AndroidWebView { + /// Constructs a [SurfaceAndroidWebView]. + SurfaceAndroidWebView({@visibleForTesting super.instanceManager}); + + @override + Widget build({ + required BuildContext context, + required CreationParams creationParams, + required JavascriptChannelRegistry javascriptChannelRegistry, + WebViewPlatformCreatedCallback? onWebViewPlatformCreated, + Set>? gestureRecognizers, + required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, + }) { + return WebViewAndroidWidget( + creationParams: creationParams, + callbacksHandler: webViewPlatformCallbacksHandler, + javascriptChannelRegistry: javascriptChannelRegistry, + onBuildWidget: (WebViewAndroidPlatformController controller) { + return PlatformViewLink( + viewType: 'plugins.flutter.io/webview', + surfaceFactory: + (BuildContext context, PlatformViewController controller) { + return AndroidViewSurface( + controller: controller as AndroidViewController, + gestureRecognizers: + gestureRecognizers ?? + const >{}, + hitTestBehavior: PlatformViewHitTestBehavior.opaque, + ); + }, + onCreatePlatformView: (PlatformViewCreationParams params) { + final Color? backgroundColor = creationParams.backgroundColor; + return _createViewController( + // On some Android devices, transparent backgrounds can cause + // rendering issues on the non hybrid composition + // AndroidViewSurface. This switches the WebView to Hybrid + // Composition when the background color is not 100% opaque. + hybridComposition: + backgroundColor != null && backgroundColor.opacity < 1.0, + id: params.id, + viewType: 'plugins.flutter.io/webview', + // WebView content is not affected by the Android view's layout direction, + // we explicitly set it here so that the widget doesn't require an ambient + // directionality. + layoutDirection: + Directionality.maybeOf(context) ?? TextDirection.ltr, + webViewIdentifier: instanceManager.getIdentifier( + controller.webView, + )!, + ) + ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) + ..addOnPlatformViewCreatedListener((int id) { + if (onWebViewPlatformCreated != null) { + onWebViewPlatformCreated(controller); + } + }) + ..create(); + }, + ); + }, + ); + } + + AndroidViewController _createViewController({ + required bool hybridComposition, + required int id, + required String viewType, + required TextDirection layoutDirection, + required int webViewIdentifier, + }) { + if (hybridComposition) { + return PlatformViewsService.initExpensiveAndroidView( + id: id, + viewType: viewType, + layoutDirection: layoutDirection, + creationParams: webViewIdentifier, + creationParamsCodec: const StandardMessageCodec(), + ); + } + return PlatformViewsService.initSurfaceAndroidView( + id: id, + viewType: viewType, + layoutDirection: layoutDirection, + creationParams: webViewIdentifier, + creationParamsCodec: const StandardMessageCodec(), + ); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/lib/src/platform_views_service_proxy.dart b/local_packages/webview_flutter_android-4.10.5/lib/src/platform_views_service_proxy.dart new file mode 100644 index 0000000..89cd8e8 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/lib/src/platform_views_service_proxy.dart @@ -0,0 +1,53 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +/// Proxy that provides access to the platform views service. +/// +/// This service allows creating and controlling platform-specific views. +@immutable +class PlatformViewsServiceProxy { + /// Constructs a [PlatformViewsServiceProxy]. + const PlatformViewsServiceProxy(); + + /// Proxy method for [PlatformViewsService.initExpensiveAndroidView]. + ExpensiveAndroidViewController initExpensiveAndroidView({ + required int id, + required String viewType, + required TextDirection layoutDirection, + dynamic creationParams, + MessageCodec? creationParamsCodec, + VoidCallback? onFocus, + }) { + return PlatformViewsService.initExpensiveAndroidView( + id: id, + viewType: viewType, + layoutDirection: layoutDirection, + creationParams: creationParams, + creationParamsCodec: creationParamsCodec, + onFocus: onFocus, + ); + } + + /// Proxy method for [PlatformViewsService.initSurfaceAndroidView]. + SurfaceAndroidViewController initSurfaceAndroidView({ + required int id, + required String viewType, + required TextDirection layoutDirection, + dynamic creationParams, + MessageCodec? creationParamsCodec, + VoidCallback? onFocus, + }) { + return PlatformViewsService.initSurfaceAndroidView( + id: id, + viewType: viewType, + layoutDirection: layoutDirection, + creationParams: creationParams, + creationParamsCodec: creationParamsCodec, + onFocus: onFocus, + ); + } +} diff --git a/local_packages/webview_flutter_android-4.10.5/lib/src/weak_reference_utils.dart b/local_packages/webview_flutter_android-4.10.5/lib/src/weak_reference_utils.dart new file mode 100644 index 0000000..1d36eab --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/lib/src/weak_reference_utils.dart @@ -0,0 +1,34 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// Helper method for creating callbacks methods with a weak reference. +/// +/// Example: +/// ```dart +/// final JavascriptChannelRegistry javascriptChannelRegistry = ... +/// +/// final WKScriptMessageHandler handler = WKScriptMessageHandler( +/// didReceiveScriptMessage: withWeakReferenceTo( +/// javascriptChannelRegistry, +/// (WeakReference weakReference) { +/// return ( +/// WKUserContentController userContentController, +/// WKScriptMessage message, +/// ) { +/// weakReference.target?.onJavascriptChannelMessage( +/// message.name, +/// message.body!.toString(), +/// ); +/// }; +/// }, +/// ), +/// ); +/// ``` +S withWeakReferenceTo( + T reference, + S Function(WeakReference weakReference) onCreate, +) { + final WeakReference weakReference = WeakReference(reference); + return onCreate(weakReference); +} diff --git a/local_packages/webview_flutter_android-4.10.5/lib/src/webview_flutter_android_legacy.dart b/local_packages/webview_flutter_android-4.10.5/lib/src/webview_flutter_android_legacy.dart new file mode 100644 index 0000000..b6ee67b --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/lib/src/webview_flutter_android_legacy.dart @@ -0,0 +1,7 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'legacy/webview_android.dart'; +export 'legacy/webview_android_cookie_manager.dart'; +export 'legacy/webview_surface_android.dart'; diff --git a/local_packages/webview_flutter_android-4.10.5/lib/webview_flutter_android.dart b/local_packages/webview_flutter_android-4.10.5/lib/webview_flutter_android.dart new file mode 100644 index 0000000..3904d57 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/lib/webview_flutter_android.dart @@ -0,0 +1,8 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'src/android_ssl_auth_error.dart'; +export 'src/android_webview_controller.dart'; +export 'src/android_webview_cookie_manager.dart'; +export 'src/android_webview_platform.dart'; diff --git a/local_packages/webview_flutter_android-4.10.5/pigeons/android_webkit.dart b/local_packages/webview_flutter_android-4.10.5/pigeons/android_webkit.dart new file mode 100644 index 0000000..a6c73ab --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/pigeons/android_webkit.dart @@ -0,0 +1,1083 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon( + PigeonOptions( + copyrightHeader: 'pigeons/copyright.txt', + dartOut: 'lib/src/android_webkit.g.dart', + kotlinOut: + 'android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt', + kotlinOptions: KotlinOptions( + package: 'io.flutter.plugins.webviewflutter', + errorClassName: 'AndroidWebKitError', + ), + ), +) +/// Mode of how to select files for a file chooser. +/// +/// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams. +enum FileChooserMode { + /// Open single file and requires that the file exists before allowing the + /// user to pick it. + /// + /// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_OPEN. + open, + + /// Similar to [open] but allows multiple files to be selected. + /// + /// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_OPEN_MULTIPLE. + openMultiple, + + /// Allows picking a nonexistent file and saving it. + /// + /// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_SAVE. + save, + + /// Indicates a `FileChooserMode` with an unknown mode. + /// + /// This does not represent an actual value provided by the platform and only + /// indicates a value was provided that isn't currently supported. + unknown, +} + +/// Indicates the type of message logged to the console. +/// +/// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel. +enum ConsoleMessageLevel { + /// Indicates a message is logged for debugging. + /// + /// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#DEBUG. + debug, + + /// Indicates a message is provided as an error. + /// + /// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#ERROR. + error, + + /// Indicates a message is provided as a basic log message. + /// + /// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#LOG. + log, + + /// Indicates a message is provided as a tip. + /// + /// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#TIP. + tip, + + /// Indicates a message is provided as a warning. + /// + /// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#WARNING. + warning, + + /// Indicates a message with an unknown level. + /// + /// This does not represent an actual value provided by the platform and only + /// indicates a value was provided that isn't currently supported. + unknown, +} + +/// The over-scroll mode for a view. +/// +/// See https://developer.android.com/reference/android/view/View#OVER_SCROLL_ALWAYS. +enum OverScrollMode { + /// Always allow a user to over-scroll this view, provided it is a view that + /// can scroll. + always, + + /// Allow a user to over-scroll this view only if the content is large enough + /// to meaningfully scroll, provided it is a view that can scroll. + ifContentScrolls, + + /// Never allow a user to over-scroll this view. + never, + + /// The type is not recognized by this wrapper. + unknown, +} + +/// Type of error for a SslCertificate. +/// +/// See https://developer.android.com/reference/android/net/http/SslError#SSL_DATE_INVALID. +enum SslErrorType { + /// The date of the certificate is invalid. + dateInvalid, + + /// The certificate has expired. + expired, + + /// Hostname mismatch. + idMismatch, + + /// A generic error occurred. + invalid, + + /// The certificate is not yet valid. + notYetValid, + + /// The certificate authority is not trusted. + untrusted, + + /// The type is not recognized by this wrapper. + unknown, +} + +/// Options for mixed content mode support. +/// +/// See https://developer.android.com/reference/android/webkit/WebSettings#MIXED_CONTENT_ALWAYS_ALLOW +enum MixedContentMode { + /// The WebView will allow a secure origin to load content from any other + /// origin, even if that origin is insecure. + alwaysAllow, + + /// The WebView will attempt to be compatible with the approach of a modern + /// web browser with regard to mixed content. + compatibilityMode, + + /// The WebView will not allow a secure origin to load content from an + /// insecure origin. + neverAllow, +} + +/// Encompasses parameters to the `WebViewClient.shouldInterceptRequest` method. +/// +/// See https://developer.android.com/reference/android/webkit/WebResourceRequest. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'android.webkit.WebResourceRequest', + ), +) +abstract class WebResourceRequest { + /// The URL for which the resource request was made. + late String url; + + /// Whether the request was made in order to fetch the main frame's document. + late bool isForMainFrame; + + /// Whether the request was a result of a server-side redirect. + late bool isRedirect; + + /// Whether a gesture (such as a click) was associated with the request. + late bool hasGesture; + + /// The method associated with the request, for example "GET". + late String method; + + /// The headers associated with the request. + late Map? requestHeaders; +} + +/// Encapsulates a resource response. +/// +/// See https://developer.android.com/reference/android/webkit/WebResourceResponse. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'android.webkit.WebResourceResponse', + ), +) +abstract class WebResourceResponse { + /// The resource response's status code. + late int statusCode; +} + +/// Encapsulates information about errors that occurred during loading of web +/// resources. +/// +/// See https://developer.android.com/reference/android/webkit/WebResourceError. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'android.webkit.WebResourceError', + ), +) +abstract class WebResourceError { + /// The error code of the error. + late int errorCode; + + /// The string describing the error. + late String description; +} + +/// Encapsulates information about errors that occurred during loading of web +/// resources. +/// +/// See https://developer.android.com/reference/androidx/webkit/WebResourceErrorCompat. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'androidx.webkit.WebResourceErrorCompat', + ), +) +abstract class WebResourceErrorCompat { + /// The error code of the error. + late int errorCode; + + /// The string describing the error. + late String description; +} + +/// Represents a position on a web page. +/// +/// This is a custom class created for convenience of the wrapper. +@ProxyApi() +abstract class WebViewPoint { + late int x; + late int y; +} + +/// Represents a JavaScript console message from WebCore. +/// +/// See https://developer.android.com/reference/android/webkit/ConsoleMessage +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'android.webkit.ConsoleMessage', + ), +) +abstract class ConsoleMessage { + late int lineNumber; + late String message; + late ConsoleMessageLevel level; + late String sourceId; +} + +/// Manages the cookies used by an application's `WebView` instances. +/// +/// See https://developer.android.com/reference/android/webkit/CookieManager. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'android.webkit.CookieManager', + ), +) +abstract class CookieManager { + @static + late CookieManager instance; + + /// Sets a single cookie (key-value pair) for the given URL. + void setCookie(String url, String value); + + /// Removes all cookies. + @async + bool removeAllCookies(); + + /// Sets whether the `WebView` should allow third party cookies to be set. + void setAcceptThirdPartyCookies(WebView webView, bool accept); +} + +/// A View that displays web pages. +/// +/// See https://developer.android.com/reference/android/webkit/WebView. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions(fullClassName: 'android.webkit.WebView'), +) +abstract class WebView extends View { + WebView(); + + /// This is called in response to an internal scroll in this view (i.e., the + /// view scrolled its own contents). + late void Function(int left, int top, int oldLeft, int oldTop)? + onScrollChanged; + + /// The WebSettings object used to control the settings for this WebView. + @attached + late WebSettings settings; + + /// Loads the given data into this WebView using a 'data' scheme URL. + void loadData(String data, String? mimeType, String? encoding); + + /// Loads the given data into this WebView, using baseUrl as the base URL for + /// the content. + void loadDataWithBaseUrl( + String? baseUrl, + String data, + String? mimeType, + String? encoding, + String? historyUrl, + ); + + /// Loads the given URL. + void loadUrl(String url, Map headers); + + /// Loads the URL with postData using "POST" method into this WebView. + void postUrl(String url, Uint8List data); + + /// Gets the URL for the current page. + String? getUrl(); + + /// Gets whether this WebView has a back history item. + bool canGoBack(); + + /// Gets whether this WebView has a forward history item. + bool canGoForward(); + + /// Goes back in the history of this WebView. + void goBack(); + + /// Goes forward in the history of this WebView. + void goForward(); + + /// Reloads the current URL. + void reload(); + + /// Clears the resource cache. + void clearCache(bool includeDiskFiles); + + /// Asynchronously evaluates JavaScript in the context of the currently + /// displayed page. + @async + String? evaluateJavascript(String javascriptString); + + /// Gets the title for the current page. + String? getTitle(); + + /// Enables debugging of web contents (HTML / CSS / JavaScript) loaded into + /// any WebViews of this application. + @static + void setWebContentsDebuggingEnabled(bool enabled); + + /// Sets the WebViewClient that will receive various notifications and + /// requests. + void setWebViewClient(WebViewClient? client); + + /// Injects the supplied Java object into this WebView. + void addJavaScriptChannel(JavaScriptChannel channel); + + /// Removes a previously injected Java object from this WebView. + void removeJavaScriptChannel(String name); + + /// Registers the interface to be used when content can not be handled by the + /// rendering engine, and should be downloaded instead. + void setDownloadListener(DownloadListener? listener); + + /// Sets the chrome handler. + void setWebChromeClient(WebChromeClient? client); + + /// Sets the background color for this view. + void setBackgroundColor(int color); + + /// Destroys the internal state of this WebView. + void destroy(); +} + +/// Manages settings state for a `WebView`. +/// +/// See https://developer.android.com/reference/android/webkit/WebSettings. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'android.webkit.WebSettings', + ), +) +abstract class WebSettings { + /// Sets whether the DOM storage API is enabled. + void setDomStorageEnabled(bool flag); + + /// Tells JavaScript to open windows automatically. + void setJavaScriptCanOpenWindowsAutomatically(bool flag); + + /// Sets whether the WebView whether supports multiple windows. + void setSupportMultipleWindows(bool support); + + /// Tells the WebView to enable JavaScript execution. + void setJavaScriptEnabled(bool flag); + + /// Sets the WebView's user-agent string. + void setUserAgentString(String? userAgentString); + + /// Sets whether the WebView requires a user gesture to play media. + void setMediaPlaybackRequiresUserGesture(bool require); + + /// Sets whether the WebView should support zooming using its on-screen zoom + /// controls and gestures. + void setSupportZoom(bool support); + + /// Sets whether the WebView loads pages in overview mode, that is, zooms out + /// the content to fit on screen by width. + void setLoadWithOverviewMode(bool overview); + + /// Sets whether the WebView should enable support for the "viewport" HTML + /// meta tag or should use a wide viewport. + void setUseWideViewPort(bool use); + + /// Sets whether the WebView should display on-screen zoom controls when using + /// the built-in zoom mechanisms. + void setDisplayZoomControls(bool enabled); + + /// Sets whether the WebView should display on-screen zoom controls when using + /// the built-in zoom mechanisms. + void setBuiltInZoomControls(bool enabled); + + /// Enables or disables file access within WebView. + void setAllowFileAccess(bool enabled); + + /// Enables or disables content URL access within WebView. + void setAllowContentAccess(bool enabled); + + /// Sets whether Geolocation is enabled within WebView. + void setGeolocationEnabled(bool enabled); + + /// Sets the text zoom of the page in percent. + void setTextZoom(int textZoom); + + /// Gets the WebView's user-agent string. + String getUserAgentString(); + + /// Configures the WebView's behavior when handling mixed content. + void setMixedContentMode(MixedContentMode mode); +} + +/// A JavaScript interface for exposing Javascript callbacks to Dart. +/// +/// This is a custom class for the wrapper that is annotated with +/// [JavascriptInterface](https://developer.android.com/reference/android/webkit/JavascriptInterface). +@ProxyApi() +abstract class JavaScriptChannel { + // ignore: avoid_unused_constructor_parameters + JavaScriptChannel(); + + late final String channelName; + + /// Handles callbacks messages from JavaScript. + late void Function(String message) postMessage; +} + +/// Receives various notifications and requests from a `WebView`. +/// +/// See https://developer.android.com/reference/android/webkit/WebViewClient. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'android.webkit.WebViewClient', + ), +) +abstract class WebViewClient { + WebViewClient(); + + /// Notify the host application that a page has started loading. + late void Function(WebView webView, String url)? onPageStarted; + + /// Notify the host application that a page has finished loading. + late void Function(WebView webView, String url)? onPageFinished; + + /// Notify the host application that an HTTP error has been received from the + /// server while loading a resource. + late void Function( + WebView webView, + WebResourceRequest request, + WebResourceResponse response, + )? + onReceivedHttpError; + + /// Report web resource loading error to the host application. + late void Function( + WebView webView, + WebResourceRequest request, + WebResourceError error, + )? + onReceivedRequestError; + + /// Report web resource loading error to the host application. + late void Function( + WebView webView, + WebResourceRequest request, + WebResourceErrorCompat error, + )? + onReceivedRequestErrorCompat; + + /// Give the host application a chance to take control when a URL is about to + /// be loaded in the current WebView. + late void Function(WebView webView, WebResourceRequest request)? + requestLoading; + + /// Give the host application a chance to take control when a URL is about to + /// be loaded in the current WebView. + late void Function(WebView webView, String url)? urlLoading; + + /// Notify the host application to update its visited links database. + late void Function(WebView webView, String url, bool isReload)? + doUpdateVisitedHistory; + + /// Notifies the host application that the WebView received an HTTP + /// authentication request. + late void Function( + WebView webView, + HttpAuthHandler handler, + String host, + String realm, + )? + onReceivedHttpAuthRequest; + + /// Ask the host application if the browser should resend data as the + /// requested page was a result of a POST. + void Function(WebView view, AndroidMessage dontResend, AndroidMessage resend)? + onFormResubmission; + + /// Notify the host application that the WebView will load the resource + /// specified by the given url. + void Function(WebView view, String url)? onLoadResource; + + /// Notify the host application that WebView content left over from previous + /// page navigations will no longer be drawn. + void Function(WebView view, String url)? onPageCommitVisible; + + /// Notify the host application to handle a SSL client certificate request. + void Function(WebView view, ClientCertRequest request)? + onReceivedClientCertRequest; + + /// Notify the host application that a request to automatically log in the + /// user has been processed. + void Function(WebView view, String realm, String? account, String args)? + onReceivedLoginRequest; + + /// Notifies the host application that an SSL error occurred while loading a + /// resource. + void Function(WebView view, SslErrorHandler handler, SslError error)? + onReceivedSslError; + + /// Notify the host application that the scale applied to the WebView has + /// changed. + void Function(WebView view, double oldScale, double newScale)? onScaleChanged; + + /// Sets the required synchronous return value for the Java method, + /// `WebViewClient.shouldOverrideUrlLoading(...)`. + /// + /// The Java method, `WebViewClient.shouldOverrideUrlLoading(...)`, requires + /// a boolean to be returned and this method sets the returned value for all + /// calls to the Java method. + /// + /// Setting this to true causes the current [WebView] to abort loading any URL + /// received by [requestLoading] or [urlLoading], while setting this to false + /// causes the [WebView] to continue loading a URL as usual. + /// + /// Defaults to false. + void setSynchronousReturnValueForShouldOverrideUrlLoading(bool value); +} + +/// Handles notifications that a file should be downloaded. +/// +/// See https://developer.android.com/reference/android/webkit/DownloadListener. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'android.webkit.DownloadListener', + ), +) +abstract class DownloadListener { + DownloadListener(); + + /// Notify the host application that a file should be downloaded. + late void Function( + String url, + String userAgent, + String contentDisposition, + String mimetype, + int contentLength, + ) + onDownloadStart; +} + +/// Handles notification of JavaScript dialogs, favicons, titles, and the +/// progress. +/// +/// See https://developer.android.com/reference/android/webkit/WebChromeClient. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: + 'io.flutter.plugins.webviewflutter.WebChromeClientProxyApi.WebChromeClientImpl', + ), +) +abstract class WebChromeClient { + WebChromeClient(); + + /// Tell the host application the current progress of loading a page. + late void Function(WebView webView, int progress)? onProgressChanged; + + /// Tell the client to show a file chooser. + @async + late List Function(WebView webView, FileChooserParams params) + onShowFileChooser; + + /// Notify the host application that web content is requesting permission to + /// access the specified resources and the permission currently isn't granted + /// or denied. + late void Function(PermissionRequest request)? onPermissionRequest; + + /// Callback to Dart function `WebChromeClient.onShowCustomView`. + late void Function(View view, CustomViewCallback callback)? onShowCustomView; + + /// Notify the host application that the current page has entered full screen + /// mode. + late void Function()? onHideCustomView; + + /// Notify the host application that web content from the specified origin is + /// attempting to use the Geolocation API, but no permission state is + /// currently set for that origin. + late void Function(String origin, GeolocationPermissionsCallback callback)? + onGeolocationPermissionsShowPrompt; + + /// Notify the host application that a request for Geolocation permissions, + /// made with a previous call to `onGeolocationPermissionsShowPrompt` has been + /// canceled. + late void Function()? onGeolocationPermissionsHidePrompt; + + /// Report a JavaScript console message to the host application. + late void Function(ConsoleMessage message)? onConsoleMessage; + + /// Notify the host application that the web page wants to display a + /// JavaScript `alert()` dialog. + @async + late void Function(WebView webView, String url, String message)? onJsAlert; + + /// Notify the host application that the web page wants to display a + /// JavaScript `confirm()` dialog. + @async + late bool Function(WebView webView, String url, String message) onJsConfirm; + + /// Notify the host application that the web page wants to display a + /// JavaScript `prompt()` dialog. + @async + late String? Function( + WebView webView, + String url, + String message, + String defaultValue, + )? + onJsPrompt; + + /// Sets the required synchronous return value for the Java method, + /// `WebChromeClient.onShowFileChooser(...)`. + /// + /// The Java method, `WebChromeClient.onShowFileChooser(...)`, requires + /// a boolean to be returned and this method sets the returned value for all + /// calls to the Java method. + /// + /// Setting this to true indicates that all file chooser requests should be + /// handled by `onShowFileChooser` and the returned list of Strings will be + /// returned to the WebView. Otherwise, the client will use the default + /// handling and the returned value in `onShowFileChooser` will be ignored. + /// + /// Requires `onShowFileChooser` to be nonnull. + /// + /// Defaults to false. + void setSynchronousReturnValueForOnShowFileChooser(bool value); + + /// Sets the required synchronous return value for the Java method, + /// `WebChromeClient.onConsoleMessage(...)`. + /// + /// The Java method, `WebChromeClient.onConsoleMessage(...)`, requires + /// a boolean to be returned and this method sets the returned value for all + /// calls to the Java method. + /// + /// Setting this to true indicates that the client is handling all console + /// messages. + /// + /// Requires `onConsoleMessage` to be nonnull. + /// + /// Defaults to false. + void setSynchronousReturnValueForOnConsoleMessage(bool value); + + /// Sets the required synchronous return value for the Java method, + /// `WebChromeClient.onJsAlert(...)`. + /// + /// The Java method, `WebChromeClient.onJsAlert(...)`, requires a boolean to + /// be returned and this method sets the returned value for all calls to the + /// Java method. + /// + /// Setting this to true indicates that the client is handling all console + /// messages. + /// + /// Requires `onJsAlert` to be nonnull. + /// + /// Defaults to false. + void setSynchronousReturnValueForOnJsAlert(bool value); + + /// Sets the required synchronous return value for the Java method, + /// `WebChromeClient.onJsConfirm(...)`. + /// + /// The Java method, `WebChromeClient.onJsConfirm(...)`, requires a boolean to + /// be returned and this method sets the returned value for all calls to the + /// Java method. + /// + /// Setting this to true indicates that the client is handling all console + /// messages. + /// + /// Requires `onJsConfirm` to be nonnull. + /// + /// Defaults to false. + void setSynchronousReturnValueForOnJsConfirm(bool value); + + /// Sets the required synchronous return value for the Java method, + /// `WebChromeClient.onJsPrompt(...)`. + /// + /// The Java method, `WebChromeClient.onJsPrompt(...)`, requires a boolean to + /// be returned and this method sets the returned value for all calls to the + /// Java method. + /// + /// Setting this to true indicates that the client is handling all console + /// messages. + /// + /// Requires `onJsPrompt` to be nonnull. + /// + /// Defaults to false. + void setSynchronousReturnValueForOnJsPrompt(bool value); +} + +/// Provides access to the assets registered as part of the App bundle. +/// +/// Convenience class for accessing Flutter asset resources. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'io.flutter.plugins.webviewflutter.FlutterAssetManager', + ), +) +abstract class FlutterAssetManager { + /// The global instance of the `FlutterAssetManager`. + @static + late FlutterAssetManager instance; + + /// Returns a String array of all the assets at the given path. + /// + /// Throws an IOException in case I/O operations were interrupted. + List list(String path); + + /// Gets the relative file path to the Flutter asset with the given name, including the file's + /// extension, e.g., "myImage.jpg". + /// + /// The returned file path is relative to the Android app's standard asset's + /// directory. Therefore, the returned path is appropriate to pass to + /// Android's AssetManager, but the path is not appropriate to load as an + /// absolute path. + String getAssetFilePathByName(String name); +} + +/// This class is used to manage the JavaScript storage APIs provided by the +/// WebView. +/// +/// See https://developer.android.com/reference/android/webkit/WebStorage. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'android.webkit.WebStorage', + ), +) +abstract class WebStorage { + @static + late WebStorage instance; + + /// Clears all storage currently being used by the JavaScript storage APIs. + void deleteAllData(); +} + +/// Parameters used in the `WebChromeClient.onShowFileChooser` method. +/// +/// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'android.webkit.WebChromeClient.FileChooserParams', + ), +) +abstract class FileChooserParams { + /// Preference for a live media captured value (e.g. Camera, Microphone). + late bool isCaptureEnabled; + + /// An array of acceptable MIME types. + late List acceptTypes; + + /// File chooser mode. + late FileChooserMode mode; + + /// File name of a default selection if specified, or null. + late String? filenameHint; +} + +/// This class defines a permission request and is used when web content +/// requests access to protected resources. +/// +/// See https://developer.android.com/reference/android/webkit/PermissionRequest. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'android.webkit.PermissionRequest', + ), +) +abstract class PermissionRequest { + late List resources; + + /// Call this method to grant origin the permission to access the given + /// resources. + void grant(List resources); + + /// Call this method to deny the request. + void deny(); +} + +/// A callback interface used by the host application to notify the current page +/// that its custom view has been dismissed. +/// +/// See https://developer.android.com/reference/android/webkit/WebChromeClient.CustomViewCallback. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'android.webkit.WebChromeClient.CustomViewCallback', + ), +) +abstract class CustomViewCallback { + /// Invoked when the host application dismisses the custom view. + void onCustomViewHidden(); +} + +/// This class represents the basic building block for user interface +/// components. +/// +/// See https://developer.android.com/reference/android/view/View. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions(fullClassName: 'android.view.View'), +) +abstract class View { + /// Set the scrolled position of your view. + void scrollTo(int x, int y); + + /// Move the scrolled position of your view. + void scrollBy(int x, int y); + + /// Return the scrolled position of this view. + WebViewPoint getScrollPosition(); + + /// Define whether the vertical scrollbar should be drawn or not. + /// + /// The scrollbar is not drawn by default. + void setVerticalScrollBarEnabled(bool enabled); + + /// Define whether the horizontal scrollbar should be drawn or not. + /// + /// The scrollbar is not drawn by default. + void setHorizontalScrollBarEnabled(bool enabled); + + /// Set the over-scroll mode for this view. + void setOverScrollMode(OverScrollMode mode); +} + +/// A callback interface used by the host application to set the Geolocation +/// permission state for an origin. +/// +/// See https://developer.android.com/reference/android/webkit/GeolocationPermissions.Callback. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'android.webkit.GeolocationPermissions.Callback', + ), +) +abstract class GeolocationPermissionsCallback { + /// Sets the Geolocation permission state for the supplied origin. + void invoke(String origin, bool allow, bool retain); +} + +/// Represents a request for HTTP authentication. +/// +/// See https://developer.android.com/reference/android/webkit/HttpAuthHandler. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'android.webkit.HttpAuthHandler', + ), +) +abstract class HttpAuthHandler { + /// Gets whether the credentials stored for the current host (i.e. the host + /// for which `WebViewClient.onReceivedHttpAuthRequest` was called) are + /// suitable for use. + bool useHttpAuthUsernamePassword(); + + /// Instructs the WebView to cancel the authentication request.. + void cancel(); + + /// Instructs the WebView to proceed with the authentication with the given + /// credentials. + void proceed(String username, String password); +} + +/// Defines a message containing a description and arbitrary data object that +/// can be sent to a `Handler`. +/// +/// See https://developer.android.com/reference/android/os/Message. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions(fullClassName: 'android.os.Message'), +) +abstract class AndroidMessage { + /// Sends this message to the Android native `Handler` specified by + /// getTarget(). + /// + /// Throws a null pointer exception if this field has not been set. + void sendToTarget(); +} + +/// Defines a message containing a description and arbitrary data object that +/// can be sent to a `Handler`. +/// +/// See https://developer.android.com/reference/android/webkit/ClientCertRequest. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'android.webkit.ClientCertRequest', + ), +) +abstract class ClientCertRequest { + /// Cancel this request. + void cancel(); + + /// Ignore the request for now. + void ignore(); + + /// Proceed with the specified private key and client certificate chain. + void proceed(PrivateKey privateKey, List chain); +} + +/// A private key. +/// +/// The purpose of this interface is to group (and provide type safety for) all +/// private key interfaces. +/// +/// See https://developer.android.com/reference/java/security/PrivateKey. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'java.security.PrivateKey', + ), +) +abstract class PrivateKey {} + +/// Abstract class for X.509 certificates. +/// +/// This provides a standard way to access all the attributes of an X.509 +/// certificate. +/// +/// See https://developer.android.com/reference/java/security/cert/X509Certificate. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'java.security.cert.X509Certificate', + ), +) +abstract class X509Certificate extends Certificate {} + +/// Represents a request for handling an SSL error. +/// +/// See https://developer.android.com/reference/android/webkit/SslErrorHandler. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'android.webkit.SslErrorHandler', + ), +) +abstract class SslErrorHandler { + /// Instructs the WebView that encountered the SSL certificate error to + /// terminate communication with the server. + void cancel(); + + /// Instructs the WebView that encountered the SSL certificate error to ignore + /// the error and continue communicating with the server. + void proceed(); +} + +/// This class represents a set of one or more SSL errors and the associated SSL +/// certificate. +/// +/// See https://developer.android.com/reference/android/net/http/SslError. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'android.net.http.SslError', + ), +) +abstract class SslError { + /// Gets the SSL certificate associated with this object. + late SslCertificate certificate; + + /// Gets the URL associated with this object. + late String url; + + /// Gets the most severe SSL error in this object's set of errors. + SslErrorType getPrimaryError(); + + /// Determines whether this object includes the supplied error. + bool hasError(SslErrorType error); +} + +/// A distinguished name helper class. +/// +/// A 3-tuple of: +/// the most specific common name (CN) +/// the most specific organization (O) +/// the most specific organizational unit (OU) +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'android.net.http.SslCertificate.DName', + ), +) +abstract class SslCertificateDName { + /// The most specific Common-name (CN) component of this name. + String getCName(); + + /// The distinguished name (normally includes CN, O, and OU names). + String getDName(); + + /// The most specific Organization (O) component of this name. + String getOName(); + + /// The most specific Organizational Unit (OU) component of this name. + String getUName(); +} + +/// SSL certificate info (certificate details) class. +/// +/// See https://developer.android.com/reference/android/net/http/SslCertificate. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'android.net.http.SslCertificate', + ), +) +abstract class SslCertificate { + /// Issued-by distinguished name or null if none has been set. + SslCertificateDName? getIssuedBy(); + + /// Issued-to distinguished name or null if none has been set. + SslCertificateDName? getIssuedTo(); + + /// Not-after date from the certificate validity period or null if none has been + /// set. + int? getValidNotAfterMsSinceEpoch(); + + /// Not-before date from the certificate validity period or null if none has + /// been set. + int? getValidNotBeforeMsSinceEpoch(); + + /// The X509Certificate used to create this SslCertificate or null if no + /// certificate was provided. + /// + /// Always returns null on Android versions below Q. + X509Certificate? getX509Certificate(); +} + +/// Abstract class for managing a variety of identity certificates. +/// +/// See https://developer.android.com/reference/java/security/cert/Certificate. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'java.security.cert.Certificate', + ), +) +abstract class Certificate { + /// The encoded form of this certificate. + Uint8List getEncoded(); +} + +/// Compatibility version of `WebSettings`. +/// +/// See https://developer.android.com/reference/kotlin/androidx/webkit/WebSettingsCompat. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'androidx.webkit.WebSettingsCompat', + ), +) +abstract class WebSettingsCompat { + @static + void setPaymentRequestEnabled(WebSettings webSettings, bool enabled); +} + +/// Utility class for checking which WebView Support Library features are supported on the device. +/// +/// See https://developer.android.com/reference/kotlin/androidx/webkit/WebViewFeature. +@ProxyApi( + kotlinOptions: KotlinProxyApiOptions( + fullClassName: 'androidx.webkit.WebViewFeature', + ), +) +abstract class WebViewFeature { + @static + bool isFeatureSupported(String feature); +} diff --git a/local_packages/webview_flutter_android-4.10.5/pigeons/copyright.txt b/local_packages/webview_flutter_android-4.10.5/pigeons/copyright.txt new file mode 100644 index 0000000..07e5f85 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/pigeons/copyright.txt @@ -0,0 +1,3 @@ +Copyright 2013 The Flutter Authors +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. diff --git a/local_packages/webview_flutter_android-4.10.5/pubspec.yaml b/local_packages/webview_flutter_android-4.10.5/pubspec.yaml new file mode 100644 index 0000000..0f745a0 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/pubspec.yaml @@ -0,0 +1,36 @@ +name: webview_flutter_android +description: A Flutter plugin that provides a WebView widget on Android. +repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter_android +issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 +version: 4.10.5 + +environment: + sdk: ^3.7.2 + flutter: ">=3.27.0" + +flutter: + plugin: + implements: webview_flutter + platforms: + android: + package: io.flutter.plugins.webviewflutter + pluginClass: WebViewFlutterPlugin + dartPluginClass: AndroidWebViewPlatform + +dependencies: + flutter: + sdk: flutter + meta: ^1.10.0 + webview_flutter_platform_interface: ^2.14.0 + +dev_dependencies: + build_runner: ^2.1.4 + flutter_test: + sdk: flutter + mockito: ^5.4.4 + pigeon: ^25.3.2 + +topics: + - html + - webview + - webview-flutter diff --git a/local_packages/webview_flutter_android-4.10.5/test/android_navigation_delegate_test.dart b/local_packages/webview_flutter_android-4.10.5/test/android_navigation_delegate_test.dart new file mode 100644 index 0000000..c8ba5a2 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/test/android_navigation_delegate_test.dart @@ -0,0 +1,820 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:webview_flutter_android/src/android_proxy.dart'; +import 'package:webview_flutter_android/src/android_webkit.g.dart' + as android_webview; +import 'package:webview_flutter_android/src/android_webkit_constants.dart'; +import 'package:webview_flutter_android/webview_flutter_android.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'android_navigation_delegate_test.mocks.dart'; + +@GenerateMocks([ + android_webview.HttpAuthHandler, + android_webview.DownloadListener, + android_webview.SslCertificate, + android_webview.SslError, + android_webview.SslErrorHandler, + android_webview.X509Certificate, +]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('AndroidNavigationDelegate', () { + test('onPageFinished', () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + late final String callbackUrl; + androidNavigationDelegate.setOnPageFinished( + (String url) => callbackUrl = url, + ); + + CapturingWebViewClient.lastCreatedDelegate.onPageFinished!( + CapturingWebViewClient(), + TestWebView(), + 'https://www.google.com', + ); + + expect(callbackUrl, 'https://www.google.com'); + }); + + test('onPageStarted', () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + late final String callbackUrl; + androidNavigationDelegate.setOnPageStarted( + (String url) => callbackUrl = url, + ); + + CapturingWebViewClient.lastCreatedDelegate.onPageStarted!( + CapturingWebViewClient(), + TestWebView(), + 'https://www.google.com', + ); + + expect(callbackUrl, 'https://www.google.com'); + }); + + test('onHttpError from onReceivedHttpError', () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + late final HttpResponseError callbackError; + androidNavigationDelegate.setOnHttpError( + (HttpResponseError httpError) => callbackError = httpError, + ); + + CapturingWebViewClient.lastCreatedDelegate.onReceivedHttpError!( + CapturingWebViewClient(), + TestWebView(), + android_webview.WebResourceRequest.pigeon_detached( + url: 'https://www.google.com', + isForMainFrame: false, + isRedirect: true, + hasGesture: true, + method: 'GET', + requestHeaders: const {'X-Mock': 'mocking'}, + pigeon_instanceManager: TestInstanceManager(), + ), + android_webview.WebResourceResponse.pigeon_detached( + statusCode: 401, + pigeon_instanceManager: TestInstanceManager(), + ), + ); + + expect(callbackError.response?.statusCode, 401); + }); + + test('onWebResourceError from onReceivedRequestError', () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + late final WebResourceError callbackError; + androidNavigationDelegate.setOnWebResourceError( + (WebResourceError error) => callbackError = error, + ); + + CapturingWebViewClient.lastCreatedDelegate.onReceivedRequestError!( + CapturingWebViewClient(), + TestWebView(), + android_webview.WebResourceRequest.pigeon_detached( + url: 'https://www.google.com', + isForMainFrame: false, + isRedirect: true, + hasGesture: true, + method: 'GET', + requestHeaders: const {'X-Mock': 'mocking'}, + pigeon_instanceManager: TestInstanceManager(), + ), + android_webview.WebResourceError.pigeon_detached( + errorCode: WebViewClientConstants.errorFileNotFound, + description: 'Page not found.', + pigeon_instanceManager: TestInstanceManager(), + ), + ); + + expect(callbackError.errorCode, WebViewClientConstants.errorFileNotFound); + expect(callbackError.description, 'Page not found.'); + expect(callbackError.errorType, WebResourceErrorType.fileNotFound); + expect(callbackError.isForMainFrame, false); + }); + + test( + 'onNavigationRequest from requestLoading should not be called when loadUrlCallback is not specified', + () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + NavigationRequest? callbackNavigationRequest; + androidNavigationDelegate.setOnNavigationRequest(( + NavigationRequest navigationRequest, + ) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.prevent; + }); + + CapturingWebViewClient.lastCreatedDelegate.requestLoading!( + CapturingWebViewClient(), + TestWebView(), + android_webview.WebResourceRequest.pigeon_detached( + url: 'https://www.google.com', + isForMainFrame: true, + isRedirect: true, + hasGesture: true, + method: 'GET', + requestHeaders: const {'X-Mock': 'mocking'}, + pigeon_instanceManager: TestInstanceManager(), + ), + ); + + expect(callbackNavigationRequest, isNull); + }, + ); + + test( + 'onNavigationRequest from requestLoading should be called when request is for main frame', + () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + NavigationRequest? callbackNavigationRequest; + androidNavigationDelegate.setOnNavigationRequest(( + NavigationRequest navigationRequest, + ) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.prevent; + }); + + androidNavigationDelegate.setOnLoadRequest((_) async {}); + + CapturingWebViewClient.lastCreatedDelegate.requestLoading!( + CapturingWebViewClient(), + TestWebView(), + android_webview.WebResourceRequest.pigeon_detached( + url: 'https://www.google.com', + isForMainFrame: true, + isRedirect: true, + hasGesture: true, + method: 'GET', + requestHeaders: const {'X-Mock': 'mocking'}, + pigeon_instanceManager: TestInstanceManager(), + ), + ); + + expect(callbackNavigationRequest, isNotNull); + }, + ); + + test( + 'onNavigationRequest from requestLoading should not be called when request is not for main frame', + () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + NavigationRequest? callbackNavigationRequest; + androidNavigationDelegate.setOnNavigationRequest(( + NavigationRequest navigationRequest, + ) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.prevent; + }); + + androidNavigationDelegate.setOnLoadRequest((_) async {}); + + CapturingWebViewClient.lastCreatedDelegate.requestLoading!( + CapturingWebViewClient(), + TestWebView(), + android_webview.WebResourceRequest.pigeon_detached( + url: 'https://www.google.com', + isForMainFrame: false, + isRedirect: true, + hasGesture: true, + method: 'GET', + requestHeaders: const {'X-Mock': 'mocking'}, + pigeon_instanceManager: TestInstanceManager(), + ), + ); + + expect(callbackNavigationRequest, isNull); + }, + ); + + test( + 'onLoadRequest from requestLoading should not be called when navigationRequestCallback is not specified', + () { + final Completer completer = Completer(); + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + androidNavigationDelegate.setOnLoadRequest((_) { + completer.complete(); + return completer.future; + }); + + CapturingWebViewClient.lastCreatedDelegate.requestLoading!( + CapturingWebViewClient(), + TestWebView(), + android_webview.WebResourceRequest.pigeon_detached( + url: 'https://www.google.com', + isForMainFrame: true, + isRedirect: true, + hasGesture: true, + method: 'GET', + requestHeaders: const {'X-Mock': 'mocking'}, + pigeon_instanceManager: TestInstanceManager(), + ), + ); + + expect(completer.isCompleted, false); + }, + ); + + test( + 'onLoadRequest from requestLoading should not be called when onNavigationRequestCallback returns NavigationDecision.prevent', + () { + final Completer completer = Completer(); + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + androidNavigationDelegate.setOnLoadRequest((_) { + completer.complete(); + return completer.future; + }); + + late final NavigationRequest callbackNavigationRequest; + androidNavigationDelegate.setOnNavigationRequest(( + NavigationRequest navigationRequest, + ) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.prevent; + }); + + CapturingWebViewClient.lastCreatedDelegate.requestLoading!( + CapturingWebViewClient(), + TestWebView(), + android_webview.WebResourceRequest.pigeon_detached( + url: 'https://www.google.com', + isForMainFrame: true, + isRedirect: true, + hasGesture: true, + method: 'GET', + requestHeaders: const {'X-Mock': 'mocking'}, + pigeon_instanceManager: TestInstanceManager(), + ), + ); + + expect(callbackNavigationRequest.isMainFrame, true); + expect(callbackNavigationRequest.url, 'https://www.google.com'); + expect(completer.isCompleted, false); + }, + ); + + test( + 'onLoadRequest from requestLoading should complete when onNavigationRequestCallback returns NavigationDecision.navigate', + () { + final Completer completer = Completer(); + late final LoadRequestParams loadRequestParams; + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + androidNavigationDelegate.setOnLoadRequest((LoadRequestParams params) { + loadRequestParams = params; + completer.complete(); + return completer.future; + }); + + late final NavigationRequest callbackNavigationRequest; + androidNavigationDelegate.setOnNavigationRequest(( + NavigationRequest navigationRequest, + ) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.navigate; + }); + + CapturingWebViewClient.lastCreatedDelegate.requestLoading!( + CapturingWebViewClient(), + TestWebView(), + android_webview.WebResourceRequest.pigeon_detached( + url: 'https://www.google.com', + isForMainFrame: true, + isRedirect: true, + hasGesture: true, + method: 'GET', + requestHeaders: const {'X-Mock': 'mocking'}, + pigeon_instanceManager: TestInstanceManager(), + ), + ); + + expect(loadRequestParams.uri.toString(), 'https://www.google.com'); + expect(loadRequestParams.headers, { + 'X-Mock': 'mocking', + }); + expect(callbackNavigationRequest.isMainFrame, true); + expect(callbackNavigationRequest.url, 'https://www.google.com'); + expect(completer.isCompleted, true); + }, + ); + + test( + 'onNavigationRequest from urlLoading should not be called when loadUrlCallback is not specified', + () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + NavigationRequest? callbackNavigationRequest; + androidNavigationDelegate.setOnNavigationRequest(( + NavigationRequest navigationRequest, + ) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.prevent; + }); + + CapturingWebViewClient.lastCreatedDelegate.urlLoading!( + CapturingWebViewClient(), + TestWebView(), + 'https://www.google.com', + ); + + expect(callbackNavigationRequest, isNull); + }, + ); + + test( + 'onLoadRequest from urlLoading should not be called when navigationRequestCallback is not specified', + () { + final Completer completer = Completer(); + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + androidNavigationDelegate.setOnLoadRequest((_) { + completer.complete(); + return completer.future; + }); + + CapturingWebViewClient.lastCreatedDelegate.urlLoading!( + CapturingWebViewClient(), + TestWebView(), + 'https://www.google.com', + ); + + expect(completer.isCompleted, false); + }, + ); + + test( + 'onLoadRequest from urlLoading should not be called when onNavigationRequestCallback returns NavigationDecision.prevent', + () { + final Completer completer = Completer(); + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + androidNavigationDelegate.setOnLoadRequest((_) { + completer.complete(); + return completer.future; + }); + + late final NavigationRequest callbackNavigationRequest; + androidNavigationDelegate.setOnNavigationRequest(( + NavigationRequest navigationRequest, + ) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.prevent; + }); + + CapturingWebViewClient.lastCreatedDelegate.urlLoading!( + CapturingWebViewClient(), + TestWebView(), + 'https://www.google.com', + ); + + expect(callbackNavigationRequest.isMainFrame, true); + expect(callbackNavigationRequest.url, 'https://www.google.com'); + expect(completer.isCompleted, false); + }, + ); + + test( + 'onLoadRequest from urlLoading should complete when onNavigationRequestCallback returns NavigationDecision.navigate', + () { + final Completer completer = Completer(); + late final LoadRequestParams loadRequestParams; + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + androidNavigationDelegate.setOnLoadRequest((LoadRequestParams params) { + loadRequestParams = params; + completer.complete(); + return completer.future; + }); + + late final NavigationRequest callbackNavigationRequest; + androidNavigationDelegate.setOnNavigationRequest(( + NavigationRequest navigationRequest, + ) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.navigate; + }); + + CapturingWebViewClient.lastCreatedDelegate.urlLoading!( + CapturingWebViewClient(), + TestWebView(), + 'https://www.google.com', + ); + + expect(loadRequestParams.uri.toString(), 'https://www.google.com'); + expect(loadRequestParams.headers, {}); + expect(callbackNavigationRequest.isMainFrame, true); + expect(callbackNavigationRequest.url, 'https://www.google.com'); + expect(completer.isCompleted, true); + }, + ); + + test('setOnNavigationRequest should override URL loading', () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + androidNavigationDelegate.setOnNavigationRequest( + (NavigationRequest request) => NavigationDecision.navigate, + ); + + expect( + CapturingWebViewClient + .lastCreatedDelegate + .synchronousReturnValueForShouldOverrideUrlLoading, + isTrue, + ); + }); + + test( + 'onLoadRequest from onDownloadStart should not be called when navigationRequestCallback is not specified', + () { + final Completer completer = Completer(); + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + androidNavigationDelegate.setOnLoadRequest((_) { + completer.complete(); + return completer.future; + }); + + CapturingDownloadListener.lastCreatedListener.onDownloadStart( + MockDownloadListener(), + '', + '', + '', + '', + 0, + ); + + expect(completer.isCompleted, false); + }, + ); + + test( + 'onLoadRequest from onDownloadStart should not be called when onNavigationRequestCallback returns NavigationDecision.prevent', + () { + final Completer completer = Completer(); + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + androidNavigationDelegate.setOnLoadRequest((_) { + completer.complete(); + return completer.future; + }); + + late final NavigationRequest callbackNavigationRequest; + androidNavigationDelegate.setOnNavigationRequest(( + NavigationRequest navigationRequest, + ) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.prevent; + }); + + CapturingDownloadListener.lastCreatedListener.onDownloadStart( + MockDownloadListener(), + 'https://www.google.com', + '', + '', + '', + 0, + ); + + expect(callbackNavigationRequest.isMainFrame, true); + expect(callbackNavigationRequest.url, 'https://www.google.com'); + expect(completer.isCompleted, false); + }, + ); + + test( + 'onLoadRequest from onDownloadStart should complete when onNavigationRequestCallback returns NavigationDecision.navigate', + () { + final Completer completer = Completer(); + late final LoadRequestParams loadRequestParams; + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + androidNavigationDelegate.setOnLoadRequest((LoadRequestParams params) { + loadRequestParams = params; + completer.complete(); + return completer.future; + }); + + late final NavigationRequest callbackNavigationRequest; + androidNavigationDelegate.setOnNavigationRequest(( + NavigationRequest navigationRequest, + ) { + callbackNavigationRequest = navigationRequest; + return NavigationDecision.navigate; + }); + + CapturingDownloadListener.lastCreatedListener.onDownloadStart( + MockDownloadListener(), + 'https://www.google.com', + '', + '', + '', + 0, + ); + + expect(loadRequestParams.uri.toString(), 'https://www.google.com'); + expect(loadRequestParams.headers, {}); + expect(callbackNavigationRequest.isMainFrame, true); + expect(callbackNavigationRequest.url, 'https://www.google.com'); + expect(completer.isCompleted, true); + }, + ); + + test('onUrlChange', () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + late final AndroidUrlChange urlChange; + androidNavigationDelegate.setOnUrlChange((UrlChange change) { + urlChange = change as AndroidUrlChange; + }); + + CapturingWebViewClient.lastCreatedDelegate.doUpdateVisitedHistory!( + CapturingWebViewClient(), + TestWebView(), + 'https://www.google.com', + false, + ); + + expect(urlChange.url, 'https://www.google.com'); + expect(urlChange.isReload, isFalse); + }); + + test('onReceivedHttpAuthRequest emits host and realm', () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + String? callbackHost; + String? callbackRealm; + androidNavigationDelegate.setOnHttpAuthRequest((HttpAuthRequest request) { + callbackHost = request.host; + callbackRealm = request.realm; + }); + + const String expectedHost = 'expectedHost'; + const String expectedRealm = 'expectedRealm'; + + CapturingWebViewClient.lastCreatedDelegate.onReceivedHttpAuthRequest!( + CapturingWebViewClient(), + TestWebView(), + android_webview.HttpAuthHandler.pigeon_detached( + pigeon_instanceManager: TestInstanceManager(), + ), + expectedHost, + expectedRealm, + ); + + expect(callbackHost, expectedHost); + expect(callbackRealm, expectedRealm); + }); + + test('onReceivedHttpAuthRequest calls cancel by default', () { + AndroidNavigationDelegate(_buildCreationParams()); + + final MockHttpAuthHandler mockAuthHandler = MockHttpAuthHandler(); + + CapturingWebViewClient.lastCreatedDelegate.onReceivedHttpAuthRequest!( + CapturingWebViewClient(), + TestWebView(), + mockAuthHandler, + 'host', + 'realm', + ); + + verify(mockAuthHandler.cancel()); + }); + + test('setOnSSlAuthError', () async { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + final Completer errorCompleter = + Completer(); + await androidNavigationDelegate.setOnSSlAuthError(( + PlatformSslAuthError error, + ) { + errorCompleter.complete(error); + }); + + final Uint8List certificateData = Uint8List(0); + const String url = 'https://google.com'; + + final MockSslError mockSslError = MockSslError(); + when(mockSslError.url).thenReturn(url); + when( + mockSslError.getPrimaryError(), + ).thenAnswer((_) async => android_webview.SslErrorType.dateInvalid); + final MockSslCertificate mockSslCertificate = MockSslCertificate(); + final MockX509Certificate mockX509Certificate = MockX509Certificate(); + when( + mockX509Certificate.getEncoded(), + ).thenAnswer((_) async => certificateData); + when( + mockSslCertificate.getX509Certificate(), + ).thenAnswer((_) async => mockX509Certificate); + when(mockSslError.certificate).thenReturn(mockSslCertificate); + + final MockSslErrorHandler mockSslErrorHandler = MockSslErrorHandler(); + + CapturingWebViewClient.lastCreatedDelegate.onReceivedSslError!( + CapturingWebViewClient(), + TestWebView(), + mockSslErrorHandler, + mockSslError, + ); + + final AndroidSslAuthError error = + await errorCompleter.future as AndroidSslAuthError; + expect(error.certificate?.data, certificateData); + expect(error.description, 'The date of the certificate is invalid.'); + expect(error.url, url); + + await error.proceed(); + verify(mockSslErrorHandler.proceed()); + + clearInteractions(mockSslErrorHandler); + + await error.cancel(); + verify(mockSslErrorHandler.cancel()); + }); + + test('setOnSSlAuthError calls cancel by default', () async { + AndroidNavigationDelegate(_buildCreationParams()); + + final MockSslErrorHandler mockSslErrorHandler = MockSslErrorHandler(); + + CapturingWebViewClient.lastCreatedDelegate.onReceivedSslError!( + CapturingWebViewClient(), + TestWebView(), + mockSslErrorHandler, + MockSslError(), + ); + + verify(mockSslErrorHandler.cancel()); + }); + }); +} + +AndroidNavigationDelegateCreationParams _buildCreationParams() { + return AndroidNavigationDelegateCreationParams.fromPlatformNavigationDelegateCreationParams( + const PlatformNavigationDelegateCreationParams(), + androidWebViewProxy: const AndroidWebViewProxy( + newWebChromeClient: CapturingWebChromeClient.new, + newWebViewClient: CapturingWebViewClient.new, + newDownloadListener: CapturingDownloadListener.new, + ), + ); +} + +// Records the last created instance of itself. +// ignore: must_be_immutable +class CapturingWebViewClient extends android_webview.WebViewClient { + CapturingWebViewClient({ + super.onPageFinished, + super.onPageStarted, + super.onReceivedHttpError, + super.onReceivedHttpAuthRequest, + super.onReceivedRequestErrorCompat, + super.doUpdateVisitedHistory, + super.onReceivedRequestError, + super.requestLoading, + super.urlLoading, + super.onFormResubmission, + super.onLoadResource, + super.onPageCommitVisible, + super.onReceivedClientCertRequest, + super.onReceivedLoginRequest, + super.onReceivedSslError, + super.onScaleChanged, + }) : super.pigeon_detached( + pigeon_instanceManager: android_webview.PigeonInstanceManager( + onWeakReferenceRemoved: (_) {}, + ), + ) { + lastCreatedDelegate = this; + } + + static CapturingWebViewClient lastCreatedDelegate = CapturingWebViewClient(); + + bool synchronousReturnValueForShouldOverrideUrlLoading = false; + + @override + Future setSynchronousReturnValueForShouldOverrideUrlLoading( + bool value, + ) async { + synchronousReturnValueForShouldOverrideUrlLoading = value; + } +} + +// Records the last created instance of itself. +class CapturingWebChromeClient extends android_webview.WebChromeClient { + CapturingWebChromeClient({ + super.onProgressChanged, + required super.onShowFileChooser, + super.onGeolocationPermissionsShowPrompt, + super.onGeolocationPermissionsHidePrompt, + super.onShowCustomView, + super.onHideCustomView, + super.onPermissionRequest, + super.onConsoleMessage, + super.onJsAlert, + required super.onJsConfirm, + super.onJsPrompt, + }) : super.pigeon_detached( + pigeon_instanceManager: android_webview.PigeonInstanceManager( + onWeakReferenceRemoved: (_) {}, + ), + ) { + lastCreatedDelegate = this; + } + + static CapturingWebChromeClient lastCreatedDelegate = + CapturingWebChromeClient( + onJsConfirm: (_, __, ___, ____) async => false, + onShowFileChooser: (_, __, ___) async => [], + ); +} + +// Records the last created instance of itself. +class CapturingDownloadListener extends android_webview.DownloadListener { + CapturingDownloadListener({required super.onDownloadStart}) + : super.pigeon_detached( + pigeon_instanceManager: android_webview.PigeonInstanceManager( + onWeakReferenceRemoved: (_) {}, + ), + ) { + lastCreatedListener = this; + } + + static CapturingDownloadListener lastCreatedListener = + CapturingDownloadListener( + onDownloadStart: (_, __, ___, ____, _____, ______) {}, + ); +} + +class TestWebView extends android_webview.WebView { + TestWebView() + : super.pigeon_detached( + pigeon_instanceManager: android_webview.PigeonInstanceManager( + onWeakReferenceRemoved: (_) {}, + ), + ); +} + +class TestInstanceManager extends android_webview.PigeonInstanceManager { + TestInstanceManager() : super(onWeakReferenceRemoved: (_) {}); +} diff --git a/local_packages/webview_flutter_android-4.10.5/test/android_navigation_delegate_test.mocks.dart b/local_packages/webview_flutter_android-4.10.5/test/android_navigation_delegate_test.mocks.dart new file mode 100644 index 0000000..ee1f625 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/test/android_navigation_delegate_test.mocks.dart @@ -0,0 +1,405 @@ +// Mocks generated by Mockito 5.4.6 from annotations +// in webview_flutter_android/test/android_navigation_delegate_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; +import 'dart:typed_data' as _i5; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i4; +import 'package:webview_flutter_android/src/android_webkit.g.dart' as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakePigeonInstanceManager_0 extends _i1.SmartFake + implements _i2.PigeonInstanceManager { + _FakePigeonInstanceManager_0(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeHttpAuthHandler_1 extends _i1.SmartFake + implements _i2.HttpAuthHandler { + _FakeHttpAuthHandler_1(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeDownloadListener_2 extends _i1.SmartFake + implements _i2.DownloadListener { + _FakeDownloadListener_2(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeSslCertificate_3 extends _i1.SmartFake + implements _i2.SslCertificate { + _FakeSslCertificate_3(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeSslError_4 extends _i1.SmartFake implements _i2.SslError { + _FakeSslError_4(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeSslErrorHandler_5 extends _i1.SmartFake + implements _i2.SslErrorHandler { + _FakeSslErrorHandler_5(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeX509Certificate_6 extends _i1.SmartFake + implements _i2.X509Certificate { + _FakeX509Certificate_6(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +/// A class which mocks [HttpAuthHandler]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockHttpAuthHandler extends _i1.Mock implements _i2.HttpAuthHandler { + MockHttpAuthHandler() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i3.Future useHttpAuthUsernamePassword() => + (super.noSuchMethod( + Invocation.method(#useHttpAuthUsernamePassword, []), + returnValue: _i3.Future.value(false), + ) + as _i3.Future); + + @override + _i3.Future cancel() => + (super.noSuchMethod( + Invocation.method(#cancel, []), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i3.Future proceed(String? username, String? password) => + (super.noSuchMethod( + Invocation.method(#proceed, [username, password]), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i2.HttpAuthHandler pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeHttpAuthHandler_1( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.HttpAuthHandler); +} + +/// A class which mocks [DownloadListener]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockDownloadListener extends _i1.Mock implements _i2.DownloadListener { + MockDownloadListener() { + _i1.throwOnMissingStub(this); + } + + @override + void Function(_i2.DownloadListener, String, String, String, String, int) + get onDownloadStart => + (super.noSuchMethod( + Invocation.getter(#onDownloadStart), + returnValue: + ( + _i2.DownloadListener pigeon_instance, + String url, + String userAgent, + String contentDisposition, + String mimetype, + int contentLength, + ) {}, + ) + as void Function( + _i2.DownloadListener, + String, + String, + String, + String, + int, + )); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i2.DownloadListener pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeDownloadListener_2( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.DownloadListener); +} + +/// A class which mocks [SslCertificate]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockSslCertificate extends _i1.Mock implements _i2.SslCertificate { + MockSslCertificate() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i3.Future<_i2.SslCertificateDName?> getIssuedBy() => + (super.noSuchMethod( + Invocation.method(#getIssuedBy, []), + returnValue: _i3.Future<_i2.SslCertificateDName?>.value(), + ) + as _i3.Future<_i2.SslCertificateDName?>); + + @override + _i3.Future<_i2.SslCertificateDName?> getIssuedTo() => + (super.noSuchMethod( + Invocation.method(#getIssuedTo, []), + returnValue: _i3.Future<_i2.SslCertificateDName?>.value(), + ) + as _i3.Future<_i2.SslCertificateDName?>); + + @override + _i3.Future getValidNotAfterMsSinceEpoch() => + (super.noSuchMethod( + Invocation.method(#getValidNotAfterMsSinceEpoch, []), + returnValue: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i3.Future getValidNotBeforeMsSinceEpoch() => + (super.noSuchMethod( + Invocation.method(#getValidNotBeforeMsSinceEpoch, []), + returnValue: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i3.Future<_i2.X509Certificate?> getX509Certificate() => + (super.noSuchMethod( + Invocation.method(#getX509Certificate, []), + returnValue: _i3.Future<_i2.X509Certificate?>.value(), + ) + as _i3.Future<_i2.X509Certificate?>); + + @override + _i2.SslCertificate pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeSslCertificate_3( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.SslCertificate); +} + +/// A class which mocks [SslError]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockSslError extends _i1.Mock implements _i2.SslError { + MockSslError() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.SslCertificate get certificate => + (super.noSuchMethod( + Invocation.getter(#certificate), + returnValue: _FakeSslCertificate_3( + this, + Invocation.getter(#certificate), + ), + ) + as _i2.SslCertificate); + + @override + String get url => + (super.noSuchMethod( + Invocation.getter(#url), + returnValue: _i4.dummyValue(this, Invocation.getter(#url)), + ) + as String); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i3.Future<_i2.SslErrorType> getPrimaryError() => + (super.noSuchMethod( + Invocation.method(#getPrimaryError, []), + returnValue: _i3.Future<_i2.SslErrorType>.value( + _i2.SslErrorType.dateInvalid, + ), + ) + as _i3.Future<_i2.SslErrorType>); + + @override + _i3.Future hasError(_i2.SslErrorType? error) => + (super.noSuchMethod( + Invocation.method(#hasError, [error]), + returnValue: _i3.Future.value(false), + ) + as _i3.Future); + + @override + _i2.SslError pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeSslError_4( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.SslError); +} + +/// A class which mocks [SslErrorHandler]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockSslErrorHandler extends _i1.Mock implements _i2.SslErrorHandler { + MockSslErrorHandler() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i3.Future cancel() => + (super.noSuchMethod( + Invocation.method(#cancel, []), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i3.Future proceed() => + (super.noSuchMethod( + Invocation.method(#proceed, []), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i2.SslErrorHandler pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeSslErrorHandler_5( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.SslErrorHandler); +} + +/// A class which mocks [X509Certificate]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockX509Certificate extends _i1.Mock implements _i2.X509Certificate { + MockX509Certificate() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i2.X509Certificate pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeX509Certificate_6( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.X509Certificate); + + @override + _i3.Future<_i5.Uint8List> getEncoded() => + (super.noSuchMethod( + Invocation.method(#getEncoded, []), + returnValue: _i3.Future<_i5.Uint8List>.value(_i5.Uint8List(0)), + ) + as _i3.Future<_i5.Uint8List>); +} diff --git a/local_packages/webview_flutter_android-4.10.5/test/android_webview_controller_test.dart b/local_packages/webview_flutter_android-4.10.5/test/android_webview_controller_test.dart new file mode 100644 index 0000000..837a184 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/test/android_webview_controller_test.dart @@ -0,0 +1,2635 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:webview_flutter_android/src/android_proxy.dart'; +import 'package:webview_flutter_android/src/android_webkit.g.dart' + as android_webview; +import 'package:webview_flutter_android/src/android_webkit_constants.dart'; +import 'package:webview_flutter_android/src/platform_views_service_proxy.dart'; +import 'package:webview_flutter_android/webview_flutter_android.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'android_navigation_delegate_test.dart'; +import 'android_webview_controller_test.mocks.dart'; + +@GenerateNiceMocks(>[ + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), +]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + AndroidWebViewController createControllerWithMocks({ + android_webview.FlutterAssetManager? mockFlutterAssetManager, + android_webview.JavaScriptChannel? mockJavaScriptChannel, + android_webview.WebChromeClient Function({ + void Function( + android_webview.WebChromeClient, + android_webview.WebView, + int, + )? + onProgressChanged, + required Future> Function( + android_webview.WebChromeClient, + android_webview.WebView, + android_webview.FileChooserParams, + ) + onShowFileChooser, + void Function( + android_webview.WebChromeClient, + android_webview.PermissionRequest, + )? + onPermissionRequest, + void Function( + android_webview.WebChromeClient, + android_webview.View, + android_webview.CustomViewCallback, + )? + onShowCustomView, + void Function(android_webview.WebChromeClient)? onHideCustomView, + void Function( + android_webview.WebChromeClient, + String, + android_webview.GeolocationPermissionsCallback, + )? + onGeolocationPermissionsShowPrompt, + void Function(android_webview.WebChromeClient)? + onGeolocationPermissionsHidePrompt, + void Function( + android_webview.WebChromeClient, + android_webview.ConsoleMessage, + )? + onConsoleMessage, + Future Function( + android_webview.WebChromeClient, + android_webview.WebView, + String, + String, + )? + onJsAlert, + required Future Function( + android_webview.WebChromeClient, + android_webview.WebView, + String, + String, + ) + onJsConfirm, + Future Function( + android_webview.WebChromeClient, + android_webview.WebView, + String, + String, + String, + )? + onJsPrompt, + })? + createWebChromeClient, + android_webview.WebView? mockWebView, + android_webview.WebViewClient? mockWebViewClient, + android_webview.WebStorage? mockWebStorage, + android_webview.WebSettings? mockSettings, + Future Function(String)? isWebViewFeatureSupported, + Future Function(android_webview.WebSettings, bool)? + setPaymentRequestEnabled, + }) { + final android_webview.WebView nonNullMockWebView = + mockWebView ?? MockWebView(); + + final AndroidWebViewControllerCreationParams + creationParams = AndroidWebViewControllerCreationParams( + androidWebStorage: mockWebStorage ?? MockWebStorage(), + androidWebViewProxy: AndroidWebViewProxy( + newWebChromeClient: + createWebChromeClient ?? + ({ + void Function( + android_webview.WebChromeClient, + android_webview.WebView, + int, + )? + onProgressChanged, + Future> Function( + android_webview.WebChromeClient, + android_webview.WebView, + android_webview.FileChooserParams, + )? + onShowFileChooser, + void Function( + android_webview.WebChromeClient, + android_webview.PermissionRequest, + )? + onPermissionRequest, + void Function( + android_webview.WebChromeClient, + android_webview.View, + android_webview.CustomViewCallback, + )? + onShowCustomView, + void Function(android_webview.WebChromeClient)? onHideCustomView, + void Function( + android_webview.WebChromeClient, + String, + android_webview.GeolocationPermissionsCallback, + )? + onGeolocationPermissionsShowPrompt, + void Function(android_webview.WebChromeClient)? + onGeolocationPermissionsHidePrompt, + void Function( + android_webview.WebChromeClient, + android_webview.ConsoleMessage, + )? + onConsoleMessage, + Future Function( + android_webview.WebChromeClient, + android_webview.WebView, + String, + String, + )? + onJsAlert, + Future Function( + android_webview.WebChromeClient, + android_webview.WebView, + String, + String, + )? + onJsConfirm, + Future Function( + android_webview.WebChromeClient, + android_webview.WebView, + String, + String, + String, + )? + onJsPrompt, + }) => MockWebChromeClient(), + newWebView: + ({ + dynamic Function( + android_webview.WebView, + int left, + int top, + int oldLeft, + int oldTop, + )? + onScrollChanged, + }) => nonNullMockWebView, + newWebViewClient: + ({ + void Function( + android_webview.WebViewClient, + android_webview.WebView, + String, + )? + onPageStarted, + void Function( + android_webview.WebViewClient, + android_webview.WebView, + String, + )? + onPageFinished, + void Function( + android_webview.WebViewClient, + android_webview.WebView, + android_webview.WebResourceRequest, + android_webview.WebResourceResponse, + )? + onReceivedHttpError, + void Function( + android_webview.WebViewClient, + android_webview.WebView, + android_webview.WebResourceRequest, + android_webview.WebResourceError, + )? + onReceivedRequestError, + void Function( + android_webview.WebViewClient, + android_webview.WebView, + android_webview.WebResourceRequest, + android_webview.WebResourceErrorCompat, + )? + onReceivedRequestErrorCompat, + void Function( + android_webview.WebViewClient, + android_webview.WebView, + int, + String, + String, + )? + onReceivedError, + void Function( + android_webview.WebViewClient, + android_webview.WebView, + android_webview.WebResourceRequest, + )? + requestLoading, + void Function( + android_webview.WebViewClient, + android_webview.WebView, + String, + )? + urlLoading, + void Function( + android_webview.WebViewClient, + android_webview.WebView, + String, + bool, + )? + doUpdateVisitedHistory, + void Function( + android_webview.WebViewClient, + android_webview.WebView, + android_webview.HttpAuthHandler, + String, + String, + )? + onReceivedHttpAuthRequest, + void Function( + android_webview.WebViewClient, + android_webview.WebView, + android_webview.AndroidMessage, + android_webview.AndroidMessage, + )? + onFormResubmission, + void Function( + android_webview.WebViewClient, + android_webview.WebView, + String, + )? + onLoadResource, + void Function( + android_webview.WebViewClient, + android_webview.WebView, + String, + )? + onPageCommitVisible, + void Function( + android_webview.WebViewClient, + android_webview.WebView, + android_webview.ClientCertRequest, + )? + onReceivedClientCertRequest, + void Function( + android_webview.WebViewClient, + android_webview.WebView, + String, + String, + String, + )? + onReceivedLoginRequest, + void Function( + android_webview.WebViewClient, + android_webview.WebView, + android_webview.SslErrorHandler, + android_webview.SslError, + )? + onReceivedSslError, + void Function( + android_webview.WebViewClient, + android_webview.WebView, + double, + double, + )? + onScaleChanged, + }) => mockWebViewClient ?? MockWebViewClient(), + instanceFlutterAssetManager: () => + mockFlutterAssetManager ?? MockFlutterAssetManager(), + newJavaScriptChannel: + ({ + required String channelName, + required void Function(android_webview.JavaScriptChannel, String) + postMessage, + }) => mockJavaScriptChannel ?? MockJavaScriptChannel(), + isWebViewFeatureSupported: + isWebViewFeatureSupported ?? (_) async => false, + setPaymentRequestEnabled: setPaymentRequestEnabled ?? (_, __) async {}, + ), + ); + + when( + nonNullMockWebView.settings, + ).thenReturn(mockSettings ?? MockWebSettings()); + + return AndroidWebViewController(creationParams); + } + + group('AndroidWebViewController', () { + AndroidJavaScriptChannelParams + createAndroidJavaScriptChannelParamsWithMocks({ + String? name, + MockJavaScriptChannel? mockJavaScriptChannel, + }) { + return AndroidJavaScriptChannelParams( + name: name ?? 'test', + onMessageReceived: (JavaScriptMessage message) {}, + webViewProxy: AndroidWebViewProxy( + newJavaScriptChannel: + ({ + required String channelName, + required void Function( + android_webview.JavaScriptChannel, + String, + ) + postMessage, + }) => mockJavaScriptChannel ?? MockJavaScriptChannel(), + ), + ); + } + + test('Initializing WebView settings on controller creation', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockWebSettings = MockWebSettings(); + createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockWebSettings, + ); + + verify(mockWebSettings.setBuiltInZoomControls(true)).called(1); + verify(mockWebSettings.setDisplayZoomControls(false)).called(1); + verify(mockWebSettings.setDomStorageEnabled(true)).called(1); + verify( + mockWebSettings.setJavaScriptCanOpenWindowsAutomatically(true), + ).called(1); + verify(mockWebSettings.setLoadWithOverviewMode(true)).called(1); + verify(mockWebSettings.setSupportMultipleWindows(true)).called(1); + verify(mockWebSettings.setUseWideViewPort(false)).called(1); + }); + + group('loadFile', () { + test('Without file prefix', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockWebSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockWebSettings, + ); + + await controller.loadFile('/path/to/file.html'); + + verify(mockWebSettings.setAllowFileAccess(true)).called(1); + verify( + mockWebView.loadUrl('file:///path/to/file.html', {}), + ).called(1); + }); + + test('Without file prefix and characters to be escaped', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockWebSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockWebSettings, + ); + + await controller.loadFile('/path/to/?_<_>_.html'); + + verify(mockWebSettings.setAllowFileAccess(true)).called(1); + verify( + mockWebView.loadUrl( + 'file:///path/to/%3F_%3C_%3E_.html', + {}, + ), + ).called(1); + }); + + test('With file prefix', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockWebSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when(mockWebView.settings).thenReturn(mockWebSettings); + + await controller.loadFile('file:///path/to/file.html'); + + verify(mockWebSettings.setAllowFileAccess(true)).called(1); + verify( + mockWebView.loadUrl('file:///path/to/file.html', {}), + ).called(1); + }); + }); + + group('loadFileWithParams', () { + group('Using LoadFileParams model', () { + test('Without file prefix', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockWebSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockWebSettings, + ); + + await controller.loadFileWithParams( + const LoadFileParams(absoluteFilePath: '/path/to/file.html'), + ); + + verify(mockWebSettings.setAllowFileAccess(true)).called(1); + verify( + mockWebView.loadUrl( + 'file:///path/to/file.html', + {}, + ), + ).called(1); + }); + + test('Without file prefix and characters to be escaped', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockWebSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockWebSettings, + ); + + await controller.loadFileWithParams( + const LoadFileParams(absoluteFilePath: '/path/to/?_<_>_.html'), + ); + + verify(mockWebSettings.setAllowFileAccess(true)).called(1); + verify( + mockWebView.loadUrl( + 'file:///path/to/%3F_%3C_%3E_.html', + {}, + ), + ).called(1); + }); + + test('With file prefix', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockWebSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockWebSettings, + ); + + await controller.loadFileWithParams( + const LoadFileParams(absoluteFilePath: 'file:///path/to/file.html'), + ); + + verify(mockWebSettings.setAllowFileAccess(true)).called(1); + verify( + mockWebView.loadUrl( + 'file:///path/to/file.html', + {}, + ), + ).called(1); + }); + }); + + group('Using WebKitLoadFileParams model', () { + test('Without file prefix', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockWebSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockWebSettings, + ); + + await controller.loadFileWithParams( + AndroidLoadFileParams(absoluteFilePath: '/path/to/file.html'), + ); + + verify(mockWebSettings.setAllowFileAccess(true)).called(1); + verify( + mockWebView.loadUrl( + 'file:///path/to/file.html', + {}, + ), + ).called(1); + }); + + test('Without file prefix and characters to be escaped', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockWebSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockWebSettings, + ); + + await controller.loadFileWithParams( + AndroidLoadFileParams(absoluteFilePath: '/path/to/?_<_>_.html'), + ); + + verify(mockWebSettings.setAllowFileAccess(true)).called(1); + verify( + mockWebView.loadUrl( + 'file:///path/to/%3F_%3C_%3E_.html', + {}, + ), + ).called(1); + }); + + test('With file prefix', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockWebSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockWebSettings, + ); + + await controller.loadFileWithParams( + AndroidLoadFileParams( + absoluteFilePath: 'file:///path/to/file.html', + ), + ); + + verify(mockWebSettings.setAllowFileAccess(true)).called(1); + verify( + mockWebView.loadUrl( + 'file:///path/to/file.html', + {}, + ), + ).called(1); + }); + + test('With additional headers', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockWebSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockWebSettings, + ); + + await controller.loadFileWithParams( + AndroidLoadFileParams( + absoluteFilePath: 'file:///path/to/file.html', + headers: const { + 'Authorization': 'Bearer test_token', + 'Cache-Control': 'no-cache', + 'X-Custom-Header': 'test-value', + }, + ), + ); + + verify(mockWebSettings.setAllowFileAccess(true)).called(1); + verify( + mockWebView + .loadUrl('file:///path/to/file.html', const { + 'Authorization': 'Bearer test_token', + 'Cache-Control': 'no-cache', + 'X-Custom-Header': 'test-value', + }), + ).called(1); + }); + }); + }); + + test('loadFlutterAsset when asset does not exist', () async { + final MockWebView mockWebView = MockWebView(); + final MockFlutterAssetManager mockAssetManager = + MockFlutterAssetManager(); + final AndroidWebViewController controller = createControllerWithMocks( + mockFlutterAssetManager: mockAssetManager, + mockWebView: mockWebView, + ); + + when( + mockAssetManager.getAssetFilePathByName('mock_key'), + ).thenAnswer((_) => Future.value('')); + when( + mockAssetManager.list(''), + ).thenAnswer((_) => Future>.value([])); + + try { + await controller.loadFlutterAsset('mock_key'); + fail('Expected an `ArgumentError`.'); + } on ArgumentError catch (e) { + expect(e.message, 'Asset for key "mock_key" not found.'); + expect(e.name, 'key'); + } on Error { + fail('Expect an `ArgumentError`.'); + } + + verify(mockAssetManager.getAssetFilePathByName('mock_key')).called(1); + verify(mockAssetManager.list('')).called(1); + verifyNever(mockWebView.loadUrl(any, any)); + }); + + test('loadFlutterAsset when asset does exists', () async { + final MockWebView mockWebView = MockWebView(); + final MockFlutterAssetManager mockAssetManager = + MockFlutterAssetManager(); + final AndroidWebViewController controller = createControllerWithMocks( + mockFlutterAssetManager: mockAssetManager, + mockWebView: mockWebView, + ); + + when( + mockAssetManager.getAssetFilePathByName('mock_key'), + ).thenAnswer((_) => Future.value('www/mock_file.html')); + when(mockAssetManager.list('www')).thenAnswer( + (_) => Future>.value(['mock_file.html']), + ); + + await controller.loadFlutterAsset('mock_key'); + + verify(mockAssetManager.getAssetFilePathByName('mock_key')).called(1); + verify(mockAssetManager.list('www')).called(1); + verify( + mockWebView.loadUrl( + 'file:///android_asset/www/mock_file.html', + {}, + ), + ); + }); + + test( + 'loadFlutterAsset when asset name contains characters that should be escaped', + () async { + final MockWebView mockWebView = MockWebView(); + final MockFlutterAssetManager mockAssetManager = + MockFlutterAssetManager(); + final AndroidWebViewController controller = createControllerWithMocks( + mockFlutterAssetManager: mockAssetManager, + mockWebView: mockWebView, + ); + + when( + mockAssetManager.getAssetFilePathByName('mock_key'), + ).thenAnswer((_) => Future.value('www/?_<_>_.html')); + when(mockAssetManager.list('www')).thenAnswer( + (_) => Future>.value(['?_<_>_.html']), + ); + + await controller.loadFlutterAsset('mock_key'); + + verify(mockAssetManager.getAssetFilePathByName('mock_key')).called(1); + verify(mockAssetManager.list('www')).called(1); + verify( + mockWebView.loadUrl( + 'file:///android_asset/www/%3F_%3C_%3E_.html', + {}, + ), + ); + }, + ); + + test('loadHtmlString without baseUrl', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.loadHtmlString('

Hello Test!

'); + + verify( + mockWebView.loadDataWithBaseUrl( + null, + '

Hello Test!

', + 'text/html', + null, + null, + ), + ).called(1); + }); + + test('loadHtmlString with baseUrl', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.loadHtmlString( + '

Hello Test!

', + baseUrl: 'https://flutter.dev', + ); + + verify( + mockWebView.loadDataWithBaseUrl( + 'https://flutter.dev', + '

Hello Test!

', + 'text/html', + null, + null, + ), + ).called(1); + }); + + test('loadRequest without URI scheme', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final LoadRequestParams requestParams = LoadRequestParams( + uri: Uri.parse('flutter.dev'), + ); + + try { + await controller.loadRequest(requestParams); + fail('Expect an `ArgumentError`.'); + } on ArgumentError catch (e) { + expect(e.message, 'WebViewRequest#uri is required to have a scheme.'); + } on Error { + fail('Expect a `ArgumentError`.'); + } + + verifyNever(mockWebView.loadUrl(any, any)); + verifyNever(mockWebView.postUrl(any, any)); + }); + + test('loadRequest using the GET method', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final LoadRequestParams requestParams = LoadRequestParams( + uri: Uri.parse('https://flutter.dev'), + headers: const {'X-Test': 'Testing'}, + ); + + await controller.loadRequest(requestParams); + + verify( + mockWebView.loadUrl('https://flutter.dev', { + 'X-Test': 'Testing', + }), + ); + verifyNever(mockWebView.postUrl(any, any)); + }); + + test('loadRequest using the POST method without body', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final LoadRequestParams requestParams = LoadRequestParams( + uri: Uri.parse('https://flutter.dev'), + method: LoadRequestMethod.post, + headers: const {'X-Test': 'Testing'}, + ); + + await controller.loadRequest(requestParams); + + verify(mockWebView.postUrl('https://flutter.dev', Uint8List(0))); + verifyNever(mockWebView.loadUrl(any, any)); + }); + + test('loadRequest using the POST method with body', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final LoadRequestParams requestParams = LoadRequestParams( + uri: Uri.parse('https://flutter.dev'), + method: LoadRequestMethod.post, + headers: const {'X-Test': 'Testing'}, + body: Uint8List.fromList('{"message": "Hello World!"}'.codeUnits), + ); + + await controller.loadRequest(requestParams); + + verify( + mockWebView.postUrl( + 'https://flutter.dev', + Uint8List.fromList('{"message": "Hello World!"}'.codeUnits), + ), + ); + verifyNever(mockWebView.loadUrl(any, any)); + }); + + test('currentUrl', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.currentUrl(); + + verify(mockWebView.getUrl()).called(1); + }); + + test('canGoBack', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.canGoBack(); + + verify(mockWebView.canGoBack()).called(1); + }); + + test('canGoForward', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.canGoForward(); + + verify(mockWebView.canGoForward()).called(1); + }); + + test('goBack', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.goBack(); + + verify(mockWebView.goBack()).called(1); + }); + + test('goForward', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.goForward(); + + verify(mockWebView.goForward()).called(1); + }); + + test('reload', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.reload(); + + verify(mockWebView.reload()).called(1); + }); + + test('clearCache', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.clearCache(); + + verify(mockWebView.clearCache(true)).called(1); + }); + + test('clearLocalStorage', () async { + final MockWebStorage mockWebStorage = MockWebStorage(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebStorage: mockWebStorage, + ); + + await controller.clearLocalStorage(); + + verify(mockWebStorage.deleteAllData()).called(1); + }); + + test('setPlatformNavigationDelegate', () async { + final MockAndroidNavigationDelegate mockNavigationDelegate = + MockAndroidNavigationDelegate(); + final MockWebView mockWebView = MockWebView(); + final MockWebChromeClient mockWebChromeClient = MockWebChromeClient(); + final MockWebViewClient mockWebViewClient = MockWebViewClient(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when( + mockNavigationDelegate.androidWebChromeClient, + ).thenReturn(mockWebChromeClient); + when( + mockNavigationDelegate.androidWebViewClient, + ).thenReturn(mockWebViewClient); + + await controller.setPlatformNavigationDelegate(mockNavigationDelegate); + + verify(mockWebView.setWebViewClient(mockWebViewClient)); + verifyNever(mockWebView.setWebChromeClient(mockWebChromeClient)); + }); + + test('onProgress', () { + final AndroidNavigationDelegate + androidNavigationDelegate = AndroidNavigationDelegate( + AndroidNavigationDelegateCreationParams.fromPlatformNavigationDelegateCreationParams( + const PlatformNavigationDelegateCreationParams(), + androidWebViewProxy: const AndroidWebViewProxy( + newWebViewClient: TestWebViewClient.new, + newWebChromeClient: TestWebChromeClient.new, + newDownloadListener: TestDownloadListener.new, + ), + ), + ); + + late final int callbackProgress; + androidNavigationDelegate.setOnProgress( + (int progress) => callbackProgress = progress, + ); + + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: CapturingWebChromeClient.new, + ); + controller.setPlatformNavigationDelegate(androidNavigationDelegate); + + CapturingWebChromeClient.lastCreatedDelegate.onProgressChanged!( + TestWebChromeClient( + onJsConfirm: (_, __, ___, ____) async => false, + onShowFileChooser: (_, __, ___) async => [], + ), + MockWebView(), + 42, + ); + + expect(callbackProgress, 42); + }); + + test('onProgress does not cause LateInitializationError', () { + // ignore: unused_local_variable + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: CapturingWebChromeClient.new, + ); + + // Should not cause LateInitializationError + CapturingWebChromeClient.lastCreatedDelegate.onProgressChanged!( + TestWebChromeClient( + onJsConfirm: (_, __, ___, ____) async => false, + onShowFileChooser: (_, __, ___) async => [], + ), + MockWebView(), + 42, + ); + }); + + test('setOnShowFileSelector', () async { + late final Future> Function( + android_webview.WebChromeClient, + android_webview.WebView webView, + android_webview.FileChooserParams params, + ) + onShowFileChooserCallback; + final MockWebChromeClient mockWebChromeClient = MockWebChromeClient(); + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: + ({ + dynamic onProgressChanged, + Future> Function( + android_webview.WebChromeClient, + android_webview.WebView webView, + android_webview.FileChooserParams params, + )? + onShowFileChooser, + dynamic onGeolocationPermissionsShowPrompt, + dynamic onGeolocationPermissionsHidePrompt, + dynamic onPermissionRequest, + dynamic onShowCustomView, + dynamic onHideCustomView, + dynamic onConsoleMessage, + dynamic onJsAlert, + dynamic onJsConfirm, + dynamic onJsPrompt, + }) { + onShowFileChooserCallback = onShowFileChooser!; + return mockWebChromeClient; + }, + ); + + late final FileSelectorParams fileSelectorParams; + await controller.setOnShowFileSelector((FileSelectorParams params) async { + fileSelectorParams = params; + return []; + }); + + verify( + mockWebChromeClient.setSynchronousReturnValueForOnShowFileChooser(true), + ); + + await onShowFileChooserCallback( + MockWebChromeClient(), + MockWebView(), + android_webview.FileChooserParams.pigeon_detached( + isCaptureEnabled: false, + acceptTypes: const ['png'], + filenameHint: 'filenameHint', + mode: android_webview.FileChooserMode.open, + pigeon_instanceManager: testInstanceManager, + ), + ); + + expect(fileSelectorParams.isCaptureEnabled, isFalse); + expect(fileSelectorParams.acceptTypes, ['png']); + expect(fileSelectorParams.filenameHint, 'filenameHint'); + expect(fileSelectorParams.mode, FileSelectorMode.open); + }); + + test('setGeolocationPermissionsPromptCallbacks', () async { + late final Future Function( + android_webview.WebChromeClient, + String origin, + android_webview.GeolocationPermissionsCallback callback, + ) + onGeoPermissionHandle; + late final void Function(android_webview.WebChromeClient instance) + onGeoPermissionHidePromptHandle; + + final MockWebChromeClient mockWebChromeClient = MockWebChromeClient(); + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: + ({ + dynamic onProgressChanged, + dynamic onShowFileChooser, + void Function( + android_webview.WebChromeClient, + String origin, + android_webview.GeolocationPermissionsCallback callback, + )? + onGeolocationPermissionsShowPrompt, + void Function(android_webview.WebChromeClient instance)? + onGeolocationPermissionsHidePrompt, + dynamic onPermissionRequest, + dynamic onShowCustomView, + dynamic onHideCustomView, + dynamic onConsoleMessage, + dynamic onJsAlert, + dynamic onJsConfirm, + dynamic onJsPrompt, + }) { + onGeoPermissionHandle = + onGeolocationPermissionsShowPrompt! + as Future Function( + android_webview.WebChromeClient, + String origin, + android_webview.GeolocationPermissionsCallback callback, + ); + onGeoPermissionHidePromptHandle = + onGeolocationPermissionsHidePrompt!; + return mockWebChromeClient; + }, + ); + + String testValue = 'origin'; + const String allowOrigin = 'https://www.allow.com'; + bool isAllow = false; + + late final GeolocationPermissionsResponse response; + await controller.setGeolocationPermissionsPromptCallbacks( + onShowPrompt: (GeolocationPermissionsRequestParams request) async { + isAllow = request.origin == allowOrigin; + response = GeolocationPermissionsResponse( + allow: isAllow, + retain: isAllow, + ); + return response; + }, + onHidePrompt: () { + testValue = 'changed'; + }, + ); + + final android_webview.GeolocationPermissionsCallback mockCallback = + MockGeolocationPermissionsCallback(); + await onGeoPermissionHandle( + MockWebChromeClient(), + allowOrigin, + mockCallback, + ); + + expect(isAllow, true); + verify(mockCallback.invoke(allowOrigin, isAllow, isAllow)); + + onGeoPermissionHidePromptHandle(mockWebChromeClient); + expect(testValue, 'changed'); + }); + + test('setCustomViewCallbacks', () async { + late final void Function( + android_webview.WebChromeClient instance, + android_webview.View view, + android_webview.CustomViewCallback callback, + ) + onShowCustomViewHandle; + late final void Function(android_webview.WebChromeClient instance) + onHideCustomViewHandle; + + final MockWebChromeClient mockWebChromeClient = MockWebChromeClient(); + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: + ({ + dynamic onProgressChanged, + dynamic onShowFileChooser, + dynamic onGeolocationPermissionsShowPrompt, + dynamic onGeolocationPermissionsHidePrompt, + dynamic onPermissionRequest, + dynamic onJsAlert, + dynamic onJsConfirm, + dynamic onJsPrompt, + void Function( + android_webview.WebChromeClient instance, + android_webview.View view, + android_webview.CustomViewCallback callback, + )? + onShowCustomView, + void Function(android_webview.WebChromeClient instance)? + onHideCustomView, + dynamic onConsoleMessage, + }) { + onShowCustomViewHandle = onShowCustomView!; + onHideCustomViewHandle = onHideCustomView!; + return mockWebChromeClient; + }, + ); + + final android_webview.View testView = android_webview + .View.pigeon_detached(pigeon_instanceManager: testInstanceManager); + bool showCustomViewCalled = false; + bool hideCustomViewCalled = false; + + await controller.setCustomWidgetCallbacks( + onShowCustomWidget: + (Widget widget, OnHideCustomWidgetCallback callback) async { + showCustomViewCalled = true; + }, + onHideCustomWidget: () { + hideCustomViewCalled = true; + }, + ); + + onShowCustomViewHandle( + mockWebChromeClient, + testView, + android_webview.CustomViewCallback.pigeon_detached( + pigeon_instanceManager: testInstanceManager, + ), + ); + + expect(showCustomViewCalled, true); + + onHideCustomViewHandle(mockWebChromeClient); + expect(hideCustomViewCalled, true); + }); + + test('setOnPlatformPermissionRequest', () async { + late final void Function( + android_webview.WebChromeClient instance, + android_webview.PermissionRequest request, + ) + onPermissionRequestCallback; + + final MockWebChromeClient mockWebChromeClient = MockWebChromeClient(); + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: + ({ + dynamic onProgressChanged, + dynamic onShowFileChooser, + dynamic onGeolocationPermissionsShowPrompt, + dynamic onGeolocationPermissionsHidePrompt, + void Function( + android_webview.WebChromeClient instance, + android_webview.PermissionRequest request, + )? + onPermissionRequest, + dynamic onShowCustomView, + dynamic onHideCustomView, + dynamic onConsoleMessage, + dynamic onJsAlert, + dynamic onJsConfirm, + dynamic onJsPrompt, + }) { + onPermissionRequestCallback = onPermissionRequest!; + return mockWebChromeClient; + }, + ); + + late final PlatformWebViewPermissionRequest permissionRequest; + await controller.setOnPlatformPermissionRequest(( + PlatformWebViewPermissionRequest request, + ) async { + permissionRequest = request; + await request.grant(); + }); + + final List permissionTypes = [ + PermissionRequestConstants.audioCapture, + ]; + + final MockPermissionRequest mockPermissionRequest = + MockPermissionRequest(); + when(mockPermissionRequest.resources).thenReturn(permissionTypes); + + onPermissionRequestCallback( + android_webview.WebChromeClient.pigeon_detached( + onJsConfirm: (_, __, ___, ____) async => false, + onShowFileChooser: (_, __, ___) async => [], + pigeon_instanceManager: testInstanceManager, + ), + mockPermissionRequest, + ); + + expect(permissionRequest.types, [ + WebViewPermissionResourceType.microphone, + ]); + verify(mockPermissionRequest.grant(permissionTypes)); + }); + + test( + 'setOnPlatformPermissionRequest callback not invoked when type is not recognized', + () async { + late final void Function( + android_webview.WebChromeClient instance, + android_webview.PermissionRequest request, + ) + onPermissionRequestCallback; + + final MockWebChromeClient mockWebChromeClient = MockWebChromeClient(); + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: + ({ + dynamic onProgressChanged, + dynamic onShowFileChooser, + dynamic onGeolocationPermissionsShowPrompt, + dynamic onGeolocationPermissionsHidePrompt, + void Function( + android_webview.WebChromeClient instance, + android_webview.PermissionRequest request, + )? + onPermissionRequest, + dynamic onShowCustomView, + dynamic onHideCustomView, + dynamic onConsoleMessage, + dynamic onJsAlert, + dynamic onJsConfirm, + dynamic onJsPrompt, + }) { + onPermissionRequestCallback = onPermissionRequest!; + return mockWebChromeClient; + }, + ); + + bool callbackCalled = false; + await controller.setOnPlatformPermissionRequest(( + PlatformWebViewPermissionRequest request, + ) async { + callbackCalled = true; + }); + + final MockPermissionRequest mockPermissionRequest = + MockPermissionRequest(); + when( + mockPermissionRequest.resources, + ).thenReturn(['unknownType']); + + onPermissionRequestCallback( + android_webview.WebChromeClient.pigeon_detached( + onJsConfirm: (_, __, ___, ____) async => false, + onShowFileChooser: (_, __, ___) async => [], + pigeon_instanceManager: testInstanceManager, + ), + mockPermissionRequest, + ); + + expect(callbackCalled, isFalse); + }, + ); + + group('JavaScript Dialog', () { + test('setOnJavaScriptAlertDialog', () async { + late final Future Function( + android_webview.WebChromeClient, + android_webview.WebView, + String url, + String message, + ) + onJsAlertCallback; + + final MockWebChromeClient mockWebChromeClient = MockWebChromeClient(); + + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: + ({ + dynamic onProgressChanged, + dynamic onShowFileChooser, + dynamic onGeolocationPermissionsShowPrompt, + dynamic onGeolocationPermissionsHidePrompt, + dynamic onPermissionRequest, + dynamic onShowCustomView, + dynamic onHideCustomView, + Future Function( + android_webview.WebChromeClient, + android_webview.WebView, + String url, + String message, + )? + onJsAlert, + dynamic onJsConfirm, + dynamic onJsPrompt, + dynamic onConsoleMessage, + }) { + onJsAlertCallback = onJsAlert!; + return mockWebChromeClient; + }, + ); + + late final String message; + await controller.setOnJavaScriptAlertDialog(( + JavaScriptAlertDialogRequest request, + ) async { + message = request.message; + return; + }); + + const String callbackMessage = 'Message'; + await onJsAlertCallback( + MockWebChromeClient(), + MockWebView(), + '', + callbackMessage, + ); + expect(message, callbackMessage); + }); + + test('setOnJavaScriptConfirmDialog', () async { + late final Future Function( + android_webview.WebChromeClient, + android_webview.WebView, + String url, + String message, + ) + onJsConfirmCallback; + + final MockWebChromeClient mockWebChromeClient = MockWebChromeClient(); + + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: + ({ + dynamic onProgressChanged, + dynamic onShowFileChooser, + dynamic onGeolocationPermissionsShowPrompt, + dynamic onGeolocationPermissionsHidePrompt, + dynamic onPermissionRequest, + dynamic onShowCustomView, + dynamic onHideCustomView, + dynamic onJsAlert, + Future Function( + android_webview.WebChromeClient, + android_webview.WebView, + String url, + String message, + )? + onJsConfirm, + dynamic onJsPrompt, + dynamic onConsoleMessage, + }) { + onJsConfirmCallback = onJsConfirm!; + return mockWebChromeClient; + }, + ); + + late final String message; + const bool callbackReturnValue = true; + await controller.setOnJavaScriptConfirmDialog(( + JavaScriptConfirmDialogRequest request, + ) async { + message = request.message; + return callbackReturnValue; + }); + + const String callbackMessage = 'Message'; + final bool returnValue = await onJsConfirmCallback( + MockWebChromeClient(), + MockWebView(), + '', + callbackMessage, + ); + + expect(message, callbackMessage); + expect(returnValue, callbackReturnValue); + }); + + test('setOnJavaScriptTextInputDialog', () async { + late final Future Function( + android_webview.WebChromeClient, + android_webview.WebView, + String url, + String message, + String defaultValue, + ) + onJsPromptCallback; + final MockWebChromeClient mockWebChromeClient = MockWebChromeClient(); + + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: + ({ + dynamic onProgressChanged, + dynamic onShowFileChooser, + dynamic onGeolocationPermissionsShowPrompt, + dynamic onGeolocationPermissionsHidePrompt, + dynamic onPermissionRequest, + dynamic onShowCustomView, + dynamic onHideCustomView, + dynamic onJsAlert, + dynamic onJsConfirm, + Future Function( + android_webview.WebChromeClient, + android_webview.WebView, + String url, + String message, + String defaultText, + )? + onJsPrompt, + dynamic onConsoleMessage, + }) { + onJsPromptCallback = onJsPrompt!; + return mockWebChromeClient; + }, + ); + + late final String message; + late final String? defaultText; + const String callbackReturnValue = 'Return Value'; + await controller.setOnJavaScriptTextInputDialog(( + JavaScriptTextInputDialogRequest request, + ) async { + message = request.message; + defaultText = request.defaultText; + return callbackReturnValue; + }); + + const String callbackMessage = 'Message'; + const String callbackDefaultText = 'Default Text'; + + final String? returnValue = await onJsPromptCallback( + MockWebChromeClient(), + MockWebView(), + '', + callbackMessage, + callbackDefaultText, + ); + + expect(message, callbackMessage); + expect(defaultText, callbackDefaultText); + expect(returnValue, callbackReturnValue); + }); + }); + + test('setOnConsoleLogCallback', () async { + late final void Function( + android_webview.WebChromeClient instance, + android_webview.ConsoleMessage message, + ) + onConsoleMessageCallback; + + final MockWebChromeClient mockWebChromeClient = MockWebChromeClient(); + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: + ({ + dynamic onProgressChanged, + dynamic onShowFileChooser, + dynamic onGeolocationPermissionsShowPrompt, + dynamic onGeolocationPermissionsHidePrompt, + dynamic onPermissionRequest, + dynamic onShowCustomView, + dynamic onHideCustomView, + dynamic onJsAlert, + dynamic onJsConfirm, + dynamic onJsPrompt, + void Function( + android_webview.WebChromeClient, + android_webview.ConsoleMessage, + )? + onConsoleMessage, + }) { + onConsoleMessageCallback = onConsoleMessage!; + return mockWebChromeClient; + }, + ); + + final Map logs = + {}; + await controller.setOnConsoleMessage(( + JavaScriptConsoleMessage message, + ) async { + logs[message.message] = message.level; + }); + + onConsoleMessageCallback( + mockWebChromeClient, + android_webview.ConsoleMessage.pigeon_detached( + lineNumber: 42, + message: 'Debug message', + level: android_webview.ConsoleMessageLevel.debug, + sourceId: 'source', + pigeon_instanceManager: testInstanceManager, + ), + ); + onConsoleMessageCallback( + mockWebChromeClient, + android_webview.ConsoleMessage.pigeon_detached( + lineNumber: 42, + message: 'Error message', + level: android_webview.ConsoleMessageLevel.error, + sourceId: 'source', + pigeon_instanceManager: testInstanceManager, + ), + ); + onConsoleMessageCallback( + mockWebChromeClient, + android_webview.ConsoleMessage.pigeon_detached( + lineNumber: 42, + message: 'Log message', + level: android_webview.ConsoleMessageLevel.log, + sourceId: 'source', + pigeon_instanceManager: testInstanceManager, + ), + ); + onConsoleMessageCallback( + mockWebChromeClient, + android_webview.ConsoleMessage.pigeon_detached( + lineNumber: 42, + message: 'Tip message', + level: android_webview.ConsoleMessageLevel.tip, + sourceId: 'source', + pigeon_instanceManager: testInstanceManager, + ), + ); + onConsoleMessageCallback( + mockWebChromeClient, + android_webview.ConsoleMessage.pigeon_detached( + lineNumber: 42, + message: 'Warning message', + level: android_webview.ConsoleMessageLevel.warning, + sourceId: 'source', + pigeon_instanceManager: testInstanceManager, + ), + ); + onConsoleMessageCallback( + mockWebChromeClient, + android_webview.ConsoleMessage.pigeon_detached( + lineNumber: 42, + message: 'Unknown message', + level: android_webview.ConsoleMessageLevel.unknown, + sourceId: 'source', + pigeon_instanceManager: testInstanceManager, + ), + ); + + expect(logs.length, 6); + expect(logs['Debug message'], JavaScriptLogLevel.debug); + expect(logs['Error message'], JavaScriptLogLevel.error); + expect(logs['Log message'], JavaScriptLogLevel.log); + expect(logs['Tip message'], JavaScriptLogLevel.debug); + expect(logs['Warning message'], JavaScriptLogLevel.warning); + expect(logs['Unknown message'], JavaScriptLogLevel.log); + }); + + test('runJavaScript', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.runJavaScript('alert("This is a test.");'); + + verify( + mockWebView.evaluateJavascript('alert("This is a test.");'), + ).called(1); + }); + + test('runJavaScriptReturningResult with return value', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when( + mockWebView.evaluateJavascript('return "Hello" + " World!";'), + ).thenAnswer((_) => Future.value('Hello World!')); + + final String message = + await controller.runJavaScriptReturningResult( + 'return "Hello" + " World!";', + ) + as String; + + expect(message, 'Hello World!'); + }); + + test('runJavaScriptReturningResult returning null', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when( + mockWebView.evaluateJavascript('alert("This is a test.");'), + ).thenAnswer((_) => Future.value()); + + final String message = + await controller.runJavaScriptReturningResult( + 'alert("This is a test.");', + ) + as String; + + expect(message, ''); + }); + + test('runJavaScriptReturningResult parses num', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when( + mockWebView.evaluateJavascript('alert("This is a test.");'), + ).thenAnswer((_) => Future.value('3.14')); + + final num message = + await controller.runJavaScriptReturningResult( + 'alert("This is a test.");', + ) + as num; + + expect(message, 3.14); + }); + + test('runJavaScriptReturningResult parses true', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when( + mockWebView.evaluateJavascript('alert("This is a test.");'), + ).thenAnswer((_) => Future.value('true')); + + final bool message = + await controller.runJavaScriptReturningResult( + 'alert("This is a test.");', + ) + as bool; + + expect(message, true); + }); + + test('runJavaScriptReturningResult parses false', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when( + mockWebView.evaluateJavascript('alert("This is a test.");'), + ).thenAnswer((_) => Future.value('false')); + + final bool message = + await controller.runJavaScriptReturningResult( + 'alert("This is a test.");', + ) + as bool; + + expect(message, false); + }); + + test('addJavaScriptChannel', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final AndroidJavaScriptChannelParams paramsWithMock = + createAndroidJavaScriptChannelParamsWithMocks(name: 'test'); + await controller.addJavaScriptChannel(paramsWithMock); + verify( + mockWebView.addJavaScriptChannel( + argThat(isA()), + ), + ).called(1); + }); + + test( + 'addJavaScriptChannel add channel with same name should remove existing channel', + () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final AndroidJavaScriptChannelParams paramsWithMock = + createAndroidJavaScriptChannelParamsWithMocks(name: 'test'); + await controller.addJavaScriptChannel(paramsWithMock); + verify( + mockWebView.addJavaScriptChannel( + argThat(isA()), + ), + ).called(1); + + await controller.addJavaScriptChannel(paramsWithMock); + verifyInOrder([ + mockWebView.removeJavaScriptChannel('test'), + mockWebView.addJavaScriptChannel( + argThat(isA()), + ), + ]); + }, + ); + + test('removeJavaScriptChannel when channel is not registered', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.removeJavaScriptChannel('test'); + verifyNever(mockWebView.removeJavaScriptChannel(any)); + }); + + test('removeJavaScriptChannel when channel exists', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + final AndroidJavaScriptChannelParams paramsWithMock = + createAndroidJavaScriptChannelParamsWithMocks(name: 'test'); + + // Make sure channel exists before removing it. + await controller.addJavaScriptChannel(paramsWithMock); + verify( + mockWebView.addJavaScriptChannel( + argThat(isA()), + ), + ).called(1); + + await controller.removeJavaScriptChannel('test'); + verify(mockWebView.removeJavaScriptChannel('test')).called(1); + }); + + test('getTitle', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.getTitle(); + + verify(mockWebView.getTitle()).called(1); + }); + + test('scrollTo', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.scrollTo(4, 2); + + verify(mockWebView.scrollTo(4, 2)).called(1); + }); + + test('scrollBy', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.scrollBy(4, 2); + + verify(mockWebView.scrollBy(4, 2)).called(1); + }); + + test('verticalScrollBarEnabled', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.setVerticalScrollBarEnabled(false); + + verify(mockWebView.setVerticalScrollBarEnabled(false)).called(1); + }); + + test('horizontalScrollBarEnabled', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.setHorizontalScrollBarEnabled(false); + + verify(mockWebView.setHorizontalScrollBarEnabled(false)).called(1); + }); + + test('getScrollPosition', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + when(mockWebView.getScrollPosition()).thenAnswer( + (_) => Future.value( + android_webview.WebViewPoint.pigeon_detached( + x: 4, + y: 2, + pigeon_instanceManager: testInstanceManager, + ), + ), + ); + + final Offset position = await controller.getScrollPosition(); + + verify(mockWebView.getScrollPosition()).called(1); + expect(position.dx, 4); + expect(position.dy, 2); + }); + + test('enableZoom', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockSettings, + ); + + clearInteractions(mockWebView); + + await controller.enableZoom(true); + + verify(mockWebView.settings).called(1); + verify(mockSettings.setSupportZoom(true)).called(1); + }); + + test('setBackgroundColor', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.setBackgroundColor(Colors.blue); + + verify(mockWebView.setBackgroundColor(Colors.blue.value)).called(1); + }); + + test('setJavaScriptMode', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockSettings, + ); + + clearInteractions(mockWebView); + + await controller.setJavaScriptMode(JavaScriptMode.disabled); + + verify(mockWebView.settings).called(1); + verify(mockSettings.setJavaScriptEnabled(false)).called(1); + }); + + test('setUserAgent', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockSettings, + ); + + clearInteractions(mockWebView); + + await controller.setUserAgent('Test Framework'); + + verify(mockWebView.settings).called(1); + verify(mockSettings.setUserAgentString('Test Framework')).called(1); + }); + + test('getUserAgent', () async { + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockSettings: mockSettings, + ); + + const String userAgent = 'str'; + + when( + mockSettings.getUserAgentString(), + ).thenAnswer((_) => Future.value(userAgent)); + + expect(await controller.getUserAgent(), userAgent); + }); + + test('setAllowFileAccess', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockSettings, + ); + + clearInteractions(mockWebView); + + await controller.setAllowFileAccess(true); + + verify(mockWebView.settings).called(1); + verify(mockSettings.setAllowFileAccess(true)).called(1); + }); + }); + + test('setMediaPlaybackRequiresUserGesture', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockSettings, + ); + + await controller.setMediaPlaybackRequiresUserGesture(true); + + verify(mockSettings.setMediaPlaybackRequiresUserGesture(true)).called(1); + }); + + test('setUseWideViewPort', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockSettings, + ); + + clearInteractions(mockWebView); + + await controller.setUseWideViewPort(true); + + verify(mockWebView.settings).called(1); + verify(mockSettings.setUseWideViewPort(true)).called(1); + }); + + test('setAllowContentAccess', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockSettings, + ); + + clearInteractions(mockWebView); + + await controller.setAllowContentAccess(false); + + verify(mockWebView.settings).called(1); + verify(mockSettings.setAllowContentAccess(false)).called(1); + }); + + test('setGeolocationEnabled', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockSettings, + ); + + clearInteractions(mockWebView); + + await controller.setGeolocationEnabled(false); + + verify(mockWebView.settings).called(1); + verify(mockSettings.setGeolocationEnabled(false)).called(1); + }); + + test('setTextZoom', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockSettings, + ); + + clearInteractions(mockWebView); + + await controller.setTextZoom(100); + + verify(mockWebView.settings).called(1); + verify(mockSettings.setTextZoom(100)).called(1); + }); + + test('setMixedContentMode', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockSettings, + ); + + await controller.setMixedContentMode(MixedContentMode.compatibilityMode); + + verify( + mockSettings.setMixedContentMode( + android_webview.MixedContentMode.compatibilityMode, + ), + ).called(1); + }); + + test('setOverScrollMode', () async { + final MockWebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + await controller.setOverScrollMode(WebViewOverScrollMode.always); + + verify( + mockWebView.setOverScrollMode(android_webview.OverScrollMode.always), + ).called(1); + }); + + test('webViewIdentifier', () { + final MockWebView mockWebView = MockWebView(); + + final android_webview.PigeonInstanceManager instanceManager = + android_webview.PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + instanceManager.addHostCreatedInstance(mockWebView, 0); + + when(mockWebView.pigeon_instanceManager).thenReturn(instanceManager); + + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + expect(controller.webViewIdentifier, 0); + }); + + test('isWebViewFeatureSupported', () async { + String? captured; + const bool expectedIsWebViewFeatureEnabled = true; + + final AndroidWebViewController controller = createControllerWithMocks( + isWebViewFeatureSupported: (String feature) async { + captured = feature; + return expectedIsWebViewFeatureEnabled; + }, + ); + + final bool result = await controller.isWebViewFeatureSupported( + WebViewFeatureType.paymentRequest, + ); + + expect(WebViewFeatureConstants.paymentRequest, captured); + expect(expectedIsWebViewFeatureEnabled, result); + }); + + test('setPaymentRequestEnabled', () async { + android_webview.WebSettings? capturedSettings; + bool? capturedEnabled; + const bool expectedEnabled = true; + + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockSettings, + setPaymentRequestEnabled: + (android_webview.WebSettings settings, bool enabled) async { + capturedSettings = settings; + capturedEnabled = enabled; + }, + ); + + await controller.setPaymentRequestEnabled(expectedEnabled); + + expect(mockSettings, capturedSettings); + expect(expectedEnabled, capturedEnabled); + }); + + group('AndroidWebViewWidget', () { + testWidgets('Builds Android view using supplied parameters', ( + WidgetTester tester, + ) async { + final android_webview.WebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + final android_webview.PigeonInstanceManager instanceManager = + android_webview.PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + instanceManager.addDartCreatedInstance(mockWebView); + + final AndroidWebViewWidget webViewWidget = AndroidWebViewWidget( + AndroidWebViewWidgetCreationParams( + key: const Key('test_web_view'), + controller: controller, + instanceManager: instanceManager, + ), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) => webViewWidget.build(context), + ), + ); + + expect(find.byType(PlatformViewLink), findsOneWidget); + expect(find.byKey(const Key('test_web_view')), findsOneWidget); + }); + + testWidgets('displayWithHybridComposition is false', ( + WidgetTester tester, + ) async { + final android_webview.WebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + final android_webview.PigeonInstanceManager instanceManager = + android_webview.PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + instanceManager.addDartCreatedInstance(mockWebView); + + final MockPlatformViewsServiceProxy mockPlatformViewsService = + MockPlatformViewsServiceProxy(); + + when( + mockPlatformViewsService.initSurfaceAndroidView( + id: anyNamed('id'), + viewType: anyNamed('viewType'), + layoutDirection: anyNamed('layoutDirection'), + creationParams: anyNamed('creationParams'), + creationParamsCodec: anyNamed('creationParamsCodec'), + onFocus: anyNamed('onFocus'), + ), + ).thenReturn(MockSurfaceAndroidViewController()); + + final AndroidWebViewWidget webViewWidget = AndroidWebViewWidget( + AndroidWebViewWidgetCreationParams( + key: const Key('test_web_view'), + controller: controller, + platformViewsServiceProxy: mockPlatformViewsService, + instanceManager: instanceManager, + ), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) => webViewWidget.build(context), + ), + ); + await tester.pumpAndSettle(); + + verify( + mockPlatformViewsService.initSurfaceAndroidView( + id: anyNamed('id'), + viewType: anyNamed('viewType'), + layoutDirection: anyNamed('layoutDirection'), + creationParams: anyNamed('creationParams'), + creationParamsCodec: anyNamed('creationParamsCodec'), + onFocus: anyNamed('onFocus'), + ), + ); + }); + + testWidgets('displayWithHybridComposition is true', ( + WidgetTester tester, + ) async { + final android_webview.WebView mockWebView = MockWebView(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + final android_webview.PigeonInstanceManager instanceManager = + android_webview.PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + instanceManager.addDartCreatedInstance(mockWebView); + + final MockPlatformViewsServiceProxy mockPlatformViewsService = + MockPlatformViewsServiceProxy(); + + when( + mockPlatformViewsService.initExpensiveAndroidView( + id: anyNamed('id'), + viewType: anyNamed('viewType'), + layoutDirection: anyNamed('layoutDirection'), + creationParams: anyNamed('creationParams'), + creationParamsCodec: anyNamed('creationParamsCodec'), + onFocus: anyNamed('onFocus'), + ), + ).thenReturn(MockExpensiveAndroidViewController()); + + final AndroidWebViewWidget webViewWidget = AndroidWebViewWidget( + AndroidWebViewWidgetCreationParams( + key: const Key('test_web_view'), + controller: controller, + platformViewsServiceProxy: mockPlatformViewsService, + displayWithHybridComposition: true, + instanceManager: instanceManager, + ), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) => webViewWidget.build(context), + ), + ); + await tester.pumpAndSettle(); + + verify( + mockPlatformViewsService.initExpensiveAndroidView( + id: anyNamed('id'), + viewType: anyNamed('viewType'), + layoutDirection: anyNamed('layoutDirection'), + creationParams: anyNamed('creationParams'), + creationParamsCodec: anyNamed('creationParamsCodec'), + onFocus: anyNamed('onFocus'), + ), + ); + }); + + testWidgets('default handling of custom views', ( + WidgetTester tester, + ) async { + final MockWebChromeClient mockWebChromeClient = MockWebChromeClient(); + + void Function( + android_webview.WebChromeClient instance, + android_webview.View view, + android_webview.CustomViewCallback callback, + )? + onShowCustomViewCallback; + + final android_webview.WebView mockWebView = MockWebView(); + final android_webview.PigeonInstanceManager instanceManager = + android_webview.PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + instanceManager.addDartCreatedInstance(mockWebView); + + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: + ({ + dynamic onProgressChanged, + dynamic onShowFileChooser, + dynamic onGeolocationPermissionsShowPrompt, + dynamic onGeolocationPermissionsHidePrompt, + dynamic onPermissionRequest, + void Function( + android_webview.WebChromeClient instance, + android_webview.View view, + android_webview.CustomViewCallback callback, + )? + onShowCustomView, + dynamic onHideCustomView, + dynamic onConsoleMessage, + dynamic onJsAlert, + dynamic onJsConfirm, + dynamic onJsPrompt, + }) { + onShowCustomViewCallback = onShowCustomView; + return mockWebChromeClient; + }, + mockWebView: mockWebView, + ); + + final MockPlatformViewsServiceProxy mockPlatformViewsService = + MockPlatformViewsServiceProxy(); + + when( + mockPlatformViewsService.initSurfaceAndroidView( + id: anyNamed('id'), + viewType: anyNamed('viewType'), + layoutDirection: anyNamed('layoutDirection'), + creationParams: anyNamed('creationParams'), + creationParamsCodec: anyNamed('creationParamsCodec'), + onFocus: anyNamed('onFocus'), + ), + ).thenReturn(MockSurfaceAndroidViewController()); + + final AndroidWebViewWidget webViewWidget = AndroidWebViewWidget( + AndroidWebViewWidgetCreationParams( + key: const Key('test_web_view'), + controller: controller, + platformViewsServiceProxy: mockPlatformViewsService, + instanceManager: instanceManager, + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (BuildContext context) => webViewWidget.build(context), + ), + ), + ); + await tester.pumpAndSettle(); + + // ignore: invalid_use_of_protected_member + when(mockWebView.pigeon_instanceManager).thenReturn(instanceManager); + + onShowCustomViewCallback!( + MockWebChromeClient(), + mockWebView, + android_webview.CustomViewCallback.pigeon_detached( + pigeon_instanceManager: instanceManager, + ), + ); + await tester.pumpAndSettle(); + + expect(find.byType(AndroidCustomViewWidget), findsOneWidget); + }); + + testWidgets('PlatformView is recreated when the controller changes', ( + WidgetTester tester, + ) async { + final android_webview.WebView mockWebView = MockWebView(); + final android_webview.PigeonInstanceManager instanceManager = + android_webview.PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + instanceManager.addDartCreatedInstance(mockWebView); + + final MockPlatformViewsServiceProxy mockPlatformViewsService = + MockPlatformViewsServiceProxy(); + + when( + mockPlatformViewsService.initSurfaceAndroidView( + id: anyNamed('id'), + viewType: anyNamed('viewType'), + layoutDirection: anyNamed('layoutDirection'), + creationParams: anyNamed('creationParams'), + creationParamsCodec: anyNamed('creationParamsCodec'), + onFocus: anyNamed('onFocus'), + ), + ).thenReturn(MockSurfaceAndroidViewController()); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return AndroidWebViewWidget( + AndroidWebViewWidgetCreationParams( + controller: createControllerWithMocks(mockWebView: mockWebView), + platformViewsServiceProxy: mockPlatformViewsService, + instanceManager: instanceManager, + ), + ).build(context); + }, + ), + ); + await tester.pumpAndSettle(); + + verify( + mockPlatformViewsService.initSurfaceAndroidView( + id: anyNamed('id'), + viewType: anyNamed('viewType'), + layoutDirection: anyNamed('layoutDirection'), + creationParams: anyNamed('creationParams'), + creationParamsCodec: anyNamed('creationParamsCodec'), + onFocus: anyNamed('onFocus'), + ), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return AndroidWebViewWidget( + AndroidWebViewWidgetCreationParams( + controller: createControllerWithMocks(mockWebView: mockWebView), + platformViewsServiceProxy: mockPlatformViewsService, + instanceManager: instanceManager, + ), + ).build(context); + }, + ), + ); + await tester.pumpAndSettle(); + + verify( + mockPlatformViewsService.initSurfaceAndroidView( + id: anyNamed('id'), + viewType: anyNamed('viewType'), + layoutDirection: anyNamed('layoutDirection'), + creationParams: anyNamed('creationParams'), + creationParamsCodec: anyNamed('creationParamsCodec'), + onFocus: anyNamed('onFocus'), + ), + ); + }); + + testWidgets( + 'PlatformView does not rebuild when creation params stay the same', + (WidgetTester tester) async { + final android_webview.WebView mockWebView = MockWebView(); + final android_webview.PigeonInstanceManager instanceManager = + android_webview.PigeonInstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + instanceManager.addDartCreatedInstance(mockWebView); + + final MockPlatformViewsServiceProxy mockPlatformViewsService = + MockPlatformViewsServiceProxy(); + + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + when( + mockPlatformViewsService.initSurfaceAndroidView( + id: anyNamed('id'), + viewType: anyNamed('viewType'), + layoutDirection: anyNamed('layoutDirection'), + creationParams: anyNamed('creationParams'), + creationParamsCodec: anyNamed('creationParamsCodec'), + onFocus: anyNamed('onFocus'), + ), + ).thenReturn(MockSurfaceAndroidViewController()); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return AndroidWebViewWidget( + AndroidWebViewWidgetCreationParams( + controller: controller, + platformViewsServiceProxy: mockPlatformViewsService, + instanceManager: instanceManager, + ), + ).build(context); + }, + ), + ); + await tester.pumpAndSettle(); + + verify( + mockPlatformViewsService.initSurfaceAndroidView( + id: anyNamed('id'), + viewType: anyNamed('viewType'), + layoutDirection: anyNamed('layoutDirection'), + creationParams: anyNamed('creationParams'), + creationParamsCodec: anyNamed('creationParamsCodec'), + onFocus: anyNamed('onFocus'), + ), + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return AndroidWebViewWidget( + AndroidWebViewWidgetCreationParams( + controller: controller, + platformViewsServiceProxy: mockPlatformViewsService, + instanceManager: instanceManager, + ), + ).build(context); + }, + ), + ); + await tester.pumpAndSettle(); + + verifyNever( + mockPlatformViewsService.initSurfaceAndroidView( + id: anyNamed('id'), + viewType: anyNamed('viewType'), + layoutDirection: anyNamed('layoutDirection'), + creationParams: anyNamed('creationParams'), + creationParamsCodec: anyNamed('creationParamsCodec'), + onFocus: anyNamed('onFocus'), + ), + ); + }, + ); + }); + + group('AndroidCustomViewWidget', () { + testWidgets('Builds Android custom view using supplied parameters', ( + WidgetTester tester, + ) async { + final android_webview.WebView mockWebView = MockWebView(); + final android_webview.PigeonInstanceManager instanceManager = + android_webview.PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + instanceManager.addDartCreatedInstance(mockWebView); + + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + final AndroidCustomViewWidget customViewWidget = + AndroidCustomViewWidget.private( + key: const Key('test_custom_view'), + customView: mockWebView, + controller: controller, + instanceManager: instanceManager, + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) => customViewWidget.build(context), + ), + ); + + expect(find.byType(PlatformViewLink), findsOneWidget); + expect(find.byKey(const Key('test_custom_view')), findsOneWidget); + }); + + testWidgets('displayWithHybridComposition should be false', ( + WidgetTester tester, + ) async { + final android_webview.WebView mockWebView = MockWebView(); + final android_webview.PigeonInstanceManager instanceManager = + android_webview.PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + instanceManager.addDartCreatedInstance(mockWebView); + + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + final MockPlatformViewsServiceProxy mockPlatformViewsService = + MockPlatformViewsServiceProxy(); + + when( + mockPlatformViewsService.initSurfaceAndroidView( + id: anyNamed('id'), + viewType: anyNamed('viewType'), + layoutDirection: anyNamed('layoutDirection'), + creationParams: anyNamed('creationParams'), + creationParamsCodec: anyNamed('creationParamsCodec'), + onFocus: anyNamed('onFocus'), + ), + ).thenReturn(MockSurfaceAndroidViewController()); + + final AndroidCustomViewWidget customViewWidget = + AndroidCustomViewWidget.private( + controller: controller, + customView: mockWebView, + platformViewsServiceProxy: mockPlatformViewsService, + instanceManager: instanceManager, + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) => customViewWidget.build(context), + ), + ); + await tester.pumpAndSettle(); + + verify( + mockPlatformViewsService.initSurfaceAndroidView( + id: anyNamed('id'), + viewType: anyNamed('viewType'), + layoutDirection: anyNamed('layoutDirection'), + creationParams: anyNamed('creationParams'), + creationParamsCodec: anyNamed('creationParamsCodec'), + onFocus: anyNamed('onFocus'), + ), + ); + }); + }); +} + +/// Creates a PigeonInstanceManager that doesn't make a call to Java when an +/// object is garbage collected. Also, `PigeonInstanceManager.instance` makes +/// a call to Java, so this InstanceManager is used to prevent that. +final android_webview.PigeonInstanceManager testInstanceManager = + android_webview.PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + +class TestWebViewClient extends android_webview.WebViewClient { + TestWebViewClient({ + super.onPageStarted, + super.onPageFinished, + super.onReceivedHttpError, + super.onReceivedRequestError, + super.onReceivedRequestErrorCompat, + super.requestLoading, + super.urlLoading, + super.doUpdateVisitedHistory, + super.onReceivedHttpAuthRequest, + super.onFormResubmission, + super.onLoadResource, + super.onPageCommitVisible, + super.onReceivedClientCertRequest, + super.onReceivedLoginRequest, + super.onReceivedSslError, + super.onScaleChanged, + }) : super.pigeon_detached( + pigeon_instanceManager: android_webview.PigeonInstanceManager( + onWeakReferenceRemoved: (_) {}, + ), + ); +} + +class TestWebChromeClient extends android_webview.WebChromeClient { + TestWebChromeClient({ + super.onProgressChanged, + required super.onShowFileChooser, + super.onPermissionRequest, + super.onShowCustomView, + super.onHideCustomView, + super.onGeolocationPermissionsShowPrompt, + super.onGeolocationPermissionsHidePrompt, + super.onConsoleMessage, + super.onJsAlert, + required super.onJsConfirm, + super.onJsPrompt, + }) : super.pigeon_detached( + pigeon_instanceManager: android_webview.PigeonInstanceManager( + onWeakReferenceRemoved: (_) {}, + ), + ); +} + +class TestDownloadListener extends android_webview.DownloadListener { + TestDownloadListener({required super.onDownloadStart}) + : super.pigeon_detached( + pigeon_instanceManager: android_webview.PigeonInstanceManager( + onWeakReferenceRemoved: (_) {}, + ), + ); +} diff --git a/local_packages/webview_flutter_android-4.10.5/test/android_webview_controller_test.mocks.dart b/local_packages/webview_flutter_android-4.10.5/test/android_webview_controller_test.mocks.dart new file mode 100644 index 0000000..cde0f4c --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/test/android_webview_controller_test.mocks.dart @@ -0,0 +1,3060 @@ +// Mocks generated by Mockito 5.4.6 from annotations +// in webview_flutter_android/test/android_webview_controller_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i8; +import 'dart:typed_data' as _i13; +import 'dart:ui' as _i4; + +import 'package:flutter/foundation.dart' as _i10; +import 'package:flutter/gestures.dart' as _i11; +import 'package:flutter/services.dart' as _i6; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i12; +import 'package:webview_flutter_android/src/android_proxy.dart' as _i9; +import 'package:webview_flutter_android/src/android_webkit.g.dart' as _i2; +import 'package:webview_flutter_android/src/android_webview_controller.dart' + as _i7; +import 'package:webview_flutter_android/src/platform_views_service_proxy.dart' + as _i5; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart' + as _i3; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeWebChromeClient_0 extends _i1.SmartFake + implements _i2.WebChromeClient { + _FakeWebChromeClient_0(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeWebViewClient_1 extends _i1.SmartFake implements _i2.WebViewClient { + _FakeWebViewClient_1(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeDownloadListener_2 extends _i1.SmartFake + implements _i2.DownloadListener { + _FakeDownloadListener_2(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakePlatformNavigationDelegateCreationParams_3 extends _i1.SmartFake + implements _i3.PlatformNavigationDelegateCreationParams { + _FakePlatformNavigationDelegateCreationParams_3( + Object parent, + Invocation parentInvocation, + ) : super(parent, parentInvocation); +} + +class _FakePlatformWebViewControllerCreationParams_4 extends _i1.SmartFake + implements _i3.PlatformWebViewControllerCreationParams { + _FakePlatformWebViewControllerCreationParams_4( + Object parent, + Invocation parentInvocation, + ) : super(parent, parentInvocation); +} + +class _FakeObject_5 extends _i1.SmartFake implements Object { + _FakeObject_5(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeOffset_6 extends _i1.SmartFake implements _i4.Offset { + _FakeOffset_6(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeWebView_7 extends _i1.SmartFake implements _i2.WebView { + _FakeWebView_7(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeJavaScriptChannel_8 extends _i1.SmartFake + implements _i2.JavaScriptChannel { + _FakeJavaScriptChannel_8(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeCookieManager_9 extends _i1.SmartFake implements _i2.CookieManager { + _FakeCookieManager_9(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeFlutterAssetManager_10 extends _i1.SmartFake + implements _i2.FlutterAssetManager { + _FakeFlutterAssetManager_10(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeWebStorage_11 extends _i1.SmartFake implements _i2.WebStorage { + _FakeWebStorage_11(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakePigeonInstanceManager_12 extends _i1.SmartFake + implements _i2.PigeonInstanceManager { + _FakePigeonInstanceManager_12(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakePlatformViewsServiceProxy_13 extends _i1.SmartFake + implements _i5.PlatformViewsServiceProxy { + _FakePlatformViewsServiceProxy_13(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakePlatformWebViewController_14 extends _i1.SmartFake + implements _i3.PlatformWebViewController { + _FakePlatformWebViewController_14(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeSize_15 extends _i1.SmartFake implements _i4.Size { + _FakeSize_15(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeGeolocationPermissionsCallback_16 extends _i1.SmartFake + implements _i2.GeolocationPermissionsCallback { + _FakeGeolocationPermissionsCallback_16( + Object parent, + Invocation parentInvocation, + ) : super(parent, parentInvocation); +} + +class _FakePermissionRequest_17 extends _i1.SmartFake + implements _i2.PermissionRequest { + _FakePermissionRequest_17(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeExpensiveAndroidViewController_18 extends _i1.SmartFake + implements _i6.ExpensiveAndroidViewController { + _FakeExpensiveAndroidViewController_18( + Object parent, + Invocation parentInvocation, + ) : super(parent, parentInvocation); +} + +class _FakeSurfaceAndroidViewController_19 extends _i1.SmartFake + implements _i6.SurfaceAndroidViewController { + _FakeSurfaceAndroidViewController_19( + Object parent, + Invocation parentInvocation, + ) : super(parent, parentInvocation); +} + +class _FakeWebSettings_20 extends _i1.SmartFake implements _i2.WebSettings { + _FakeWebSettings_20(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeWebViewPoint_21 extends _i1.SmartFake implements _i2.WebViewPoint { + _FakeWebViewPoint_21(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +/// A class which mocks [AndroidNavigationDelegate]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAndroidNavigationDelegate extends _i1.Mock + implements _i7.AndroidNavigationDelegate { + @override + _i2.WebChromeClient get androidWebChromeClient => + (super.noSuchMethod( + Invocation.getter(#androidWebChromeClient), + returnValue: _FakeWebChromeClient_0( + this, + Invocation.getter(#androidWebChromeClient), + ), + returnValueForMissingStub: _FakeWebChromeClient_0( + this, + Invocation.getter(#androidWebChromeClient), + ), + ) + as _i2.WebChromeClient); + + @override + _i2.WebViewClient get androidWebViewClient => + (super.noSuchMethod( + Invocation.getter(#androidWebViewClient), + returnValue: _FakeWebViewClient_1( + this, + Invocation.getter(#androidWebViewClient), + ), + returnValueForMissingStub: _FakeWebViewClient_1( + this, + Invocation.getter(#androidWebViewClient), + ), + ) + as _i2.WebViewClient); + + @override + _i2.DownloadListener get androidDownloadListener => + (super.noSuchMethod( + Invocation.getter(#androidDownloadListener), + returnValue: _FakeDownloadListener_2( + this, + Invocation.getter(#androidDownloadListener), + ), + returnValueForMissingStub: _FakeDownloadListener_2( + this, + Invocation.getter(#androidDownloadListener), + ), + ) + as _i2.DownloadListener); + + @override + _i3.PlatformNavigationDelegateCreationParams get params => + (super.noSuchMethod( + Invocation.getter(#params), + returnValue: _FakePlatformNavigationDelegateCreationParams_3( + this, + Invocation.getter(#params), + ), + returnValueForMissingStub: + _FakePlatformNavigationDelegateCreationParams_3( + this, + Invocation.getter(#params), + ), + ) + as _i3.PlatformNavigationDelegateCreationParams); + + @override + _i8.Future setOnLoadRequest(_i7.LoadRequestCallback? onLoadRequest) => + (super.noSuchMethod( + Invocation.method(#setOnLoadRequest, [onLoadRequest]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setOnNavigationRequest( + _i3.NavigationRequestCallback? onNavigationRequest, + ) => + (super.noSuchMethod( + Invocation.method(#setOnNavigationRequest, [onNavigationRequest]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setOnPageStarted(_i3.PageEventCallback? onPageStarted) => + (super.noSuchMethod( + Invocation.method(#setOnPageStarted, [onPageStarted]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setOnPageFinished(_i3.PageEventCallback? onPageFinished) => + (super.noSuchMethod( + Invocation.method(#setOnPageFinished, [onPageFinished]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setOnHttpError(_i3.HttpResponseErrorCallback? onHttpError) => + (super.noSuchMethod( + Invocation.method(#setOnHttpError, [onHttpError]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setOnProgress(_i3.ProgressCallback? onProgress) => + (super.noSuchMethod( + Invocation.method(#setOnProgress, [onProgress]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setOnWebResourceError( + _i3.WebResourceErrorCallback? onWebResourceError, + ) => + (super.noSuchMethod( + Invocation.method(#setOnWebResourceError, [onWebResourceError]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setOnUrlChange(_i3.UrlChangeCallback? onUrlChange) => + (super.noSuchMethod( + Invocation.method(#setOnUrlChange, [onUrlChange]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setOnHttpAuthRequest( + _i3.HttpAuthRequestCallback? onHttpAuthRequest, + ) => + (super.noSuchMethod( + Invocation.method(#setOnHttpAuthRequest, [onHttpAuthRequest]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setOnSSlAuthError( + _i3.SslAuthErrorCallback? onSslAuthError, + ) => + (super.noSuchMethod( + Invocation.method(#setOnSSlAuthError, [onSslAuthError]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); +} + +/// A class which mocks [AndroidWebViewController]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAndroidWebViewController extends _i1.Mock + implements _i7.AndroidWebViewController { + @override + int get webViewIdentifier => + (super.noSuchMethod( + Invocation.getter(#webViewIdentifier), + returnValue: 0, + returnValueForMissingStub: 0, + ) + as int); + + @override + _i3.PlatformWebViewControllerCreationParams get params => + (super.noSuchMethod( + Invocation.getter(#params), + returnValue: _FakePlatformWebViewControllerCreationParams_4( + this, + Invocation.getter(#params), + ), + returnValueForMissingStub: + _FakePlatformWebViewControllerCreationParams_4( + this, + Invocation.getter(#params), + ), + ) + as _i3.PlatformWebViewControllerCreationParams); + + @override + _i8.Future setAllowFileAccess(bool? allow) => + (super.noSuchMethod( + Invocation.method(#setAllowFileAccess, [allow]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future loadFile(String? absoluteFilePath) => + (super.noSuchMethod( + Invocation.method(#loadFile, [absoluteFilePath]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future loadFileWithParams(_i3.LoadFileParams? params) => + (super.noSuchMethod( + Invocation.method(#loadFileWithParams, [params]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future loadFlutterAsset(String? key) => + (super.noSuchMethod( + Invocation.method(#loadFlutterAsset, [key]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future loadHtmlString(String? html, {String? baseUrl}) => + (super.noSuchMethod( + Invocation.method(#loadHtmlString, [html], {#baseUrl: baseUrl}), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future loadRequest(_i3.LoadRequestParams? params) => + (super.noSuchMethod( + Invocation.method(#loadRequest, [params]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future currentUrl() => + (super.noSuchMethod( + Invocation.method(#currentUrl, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future canGoBack() => + (super.noSuchMethod( + Invocation.method(#canGoBack, []), + returnValue: _i8.Future.value(false), + returnValueForMissingStub: _i8.Future.value(false), + ) + as _i8.Future); + + @override + _i8.Future canGoForward() => + (super.noSuchMethod( + Invocation.method(#canGoForward, []), + returnValue: _i8.Future.value(false), + returnValueForMissingStub: _i8.Future.value(false), + ) + as _i8.Future); + + @override + _i8.Future goBack() => + (super.noSuchMethod( + Invocation.method(#goBack, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future goForward() => + (super.noSuchMethod( + Invocation.method(#goForward, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future reload() => + (super.noSuchMethod( + Invocation.method(#reload, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future clearCache() => + (super.noSuchMethod( + Invocation.method(#clearCache, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future clearLocalStorage() => + (super.noSuchMethod( + Invocation.method(#clearLocalStorage, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setPlatformNavigationDelegate( + _i3.PlatformNavigationDelegate? handler, + ) => + (super.noSuchMethod( + Invocation.method(#setPlatformNavigationDelegate, [handler]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future runJavaScript(String? javaScript) => + (super.noSuchMethod( + Invocation.method(#runJavaScript, [javaScript]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future runJavaScriptReturningResult(String? javaScript) => + (super.noSuchMethod( + Invocation.method(#runJavaScriptReturningResult, [javaScript]), + returnValue: _i8.Future.value( + _FakeObject_5( + this, + Invocation.method(#runJavaScriptReturningResult, [javaScript]), + ), + ), + returnValueForMissingStub: _i8.Future.value( + _FakeObject_5( + this, + Invocation.method(#runJavaScriptReturningResult, [javaScript]), + ), + ), + ) + as _i8.Future); + + @override + _i8.Future addJavaScriptChannel( + _i3.JavaScriptChannelParams? javaScriptChannelParams, + ) => + (super.noSuchMethod( + Invocation.method(#addJavaScriptChannel, [javaScriptChannelParams]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future removeJavaScriptChannel(String? javaScriptChannelName) => + (super.noSuchMethod( + Invocation.method(#removeJavaScriptChannel, [ + javaScriptChannelName, + ]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future getTitle() => + (super.noSuchMethod( + Invocation.method(#getTitle, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future scrollTo(int? x, int? y) => + (super.noSuchMethod( + Invocation.method(#scrollTo, [x, y]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future scrollBy(int? x, int? y) => + (super.noSuchMethod( + Invocation.method(#scrollBy, [x, y]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future<_i4.Offset> getScrollPosition() => + (super.noSuchMethod( + Invocation.method(#getScrollPosition, []), + returnValue: _i8.Future<_i4.Offset>.value( + _FakeOffset_6(this, Invocation.method(#getScrollPosition, [])), + ), + returnValueForMissingStub: _i8.Future<_i4.Offset>.value( + _FakeOffset_6(this, Invocation.method(#getScrollPosition, [])), + ), + ) + as _i8.Future<_i4.Offset>); + + @override + _i8.Future enableZoom(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#enableZoom, [enabled]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setBackgroundColor(_i4.Color? color) => + (super.noSuchMethod( + Invocation.method(#setBackgroundColor, [color]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setJavaScriptMode(_i3.JavaScriptMode? javaScriptMode) => + (super.noSuchMethod( + Invocation.method(#setJavaScriptMode, [javaScriptMode]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setUserAgent(String? userAgent) => + (super.noSuchMethod( + Invocation.method(#setUserAgent, [userAgent]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setOnScrollPositionChange( + void Function(_i3.ScrollPositionChange)? onScrollPositionChange, + ) => + (super.noSuchMethod( + Invocation.method(#setOnScrollPositionChange, [ + onScrollPositionChange, + ]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setMediaPlaybackRequiresUserGesture(bool? require) => + (super.noSuchMethod( + Invocation.method(#setMediaPlaybackRequiresUserGesture, [require]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setTextZoom(int? textZoom) => + (super.noSuchMethod( + Invocation.method(#setTextZoom, [textZoom]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setUseWideViewPort(bool? use) => + (super.noSuchMethod( + Invocation.method(#setUseWideViewPort, [use]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setAllowContentAccess(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setAllowContentAccess, [enabled]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setGeolocationEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setGeolocationEnabled, [enabled]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setOnShowFileSelector( + _i8.Future> Function(_i7.FileSelectorParams)? + onShowFileSelector, + ) => + (super.noSuchMethod( + Invocation.method(#setOnShowFileSelector, [onShowFileSelector]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setOnPlatformPermissionRequest( + void Function(_i3.PlatformWebViewPermissionRequest)? onPermissionRequest, + ) => + (super.noSuchMethod( + Invocation.method(#setOnPlatformPermissionRequest, [ + onPermissionRequest, + ]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setGeolocationPermissionsPromptCallbacks({ + _i7.OnGeolocationPermissionsShowPrompt? onShowPrompt, + _i7.OnGeolocationPermissionsHidePrompt? onHidePrompt, + }) => + (super.noSuchMethod( + Invocation.method(#setGeolocationPermissionsPromptCallbacks, [], { + #onShowPrompt: onShowPrompt, + #onHidePrompt: onHidePrompt, + }), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setCustomWidgetCallbacks({ + required _i7.OnShowCustomWidgetCallback? onShowCustomWidget, + required _i7.OnHideCustomWidgetCallback? onHideCustomWidget, + }) => + (super.noSuchMethod( + Invocation.method(#setCustomWidgetCallbacks, [], { + #onShowCustomWidget: onShowCustomWidget, + #onHideCustomWidget: onHideCustomWidget, + }), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setOnConsoleMessage( + void Function(_i3.JavaScriptConsoleMessage)? onConsoleMessage, + ) => + (super.noSuchMethod( + Invocation.method(#setOnConsoleMessage, [onConsoleMessage]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future getUserAgent() => + (super.noSuchMethod( + Invocation.method(#getUserAgent, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setOnJavaScriptAlertDialog( + _i8.Future Function(_i3.JavaScriptAlertDialogRequest)? + onJavaScriptAlertDialog, + ) => + (super.noSuchMethod( + Invocation.method(#setOnJavaScriptAlertDialog, [ + onJavaScriptAlertDialog, + ]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setOnJavaScriptConfirmDialog( + _i8.Future Function(_i3.JavaScriptConfirmDialogRequest)? + onJavaScriptConfirmDialog, + ) => + (super.noSuchMethod( + Invocation.method(#setOnJavaScriptConfirmDialog, [ + onJavaScriptConfirmDialog, + ]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setOnJavaScriptTextInputDialog( + _i8.Future Function(_i3.JavaScriptTextInputDialogRequest)? + onJavaScriptTextInputDialog, + ) => + (super.noSuchMethod( + Invocation.method(#setOnJavaScriptTextInputDialog, [ + onJavaScriptTextInputDialog, + ]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setVerticalScrollBarEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setVerticalScrollBarEnabled, [enabled]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setHorizontalScrollBarEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setHorizontalScrollBarEnabled, [enabled]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + bool supportsSetScrollBarsEnabled() => + (super.noSuchMethod( + Invocation.method(#supportsSetScrollBarsEnabled, []), + returnValue: false, + returnValueForMissingStub: false, + ) + as bool); + + @override + _i8.Future setOverScrollMode(_i3.WebViewOverScrollMode? mode) => + (super.noSuchMethod( + Invocation.method(#setOverScrollMode, [mode]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setMixedContentMode(_i7.MixedContentMode? mode) => + (super.noSuchMethod( + Invocation.method(#setMixedContentMode, [mode]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future isWebViewFeatureSupported( + _i7.WebViewFeatureType? featureType, + ) => + (super.noSuchMethod( + Invocation.method(#isWebViewFeatureSupported, [featureType]), + returnValue: _i8.Future.value(false), + returnValueForMissingStub: _i8.Future.value(false), + ) + as _i8.Future); + + @override + _i8.Future setPaymentRequestEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setPaymentRequestEnabled, [enabled]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); +} + +/// A class which mocks [AndroidWebViewProxy]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAndroidWebViewProxy extends _i1.Mock + implements _i9.AndroidWebViewProxy { + @override + _i2.WebView Function({ + void Function(_i2.WebView, int, int, int, int)? onScrollChanged, + }) + get newWebView => + (super.noSuchMethod( + Invocation.getter(#newWebView), + returnValue: + ({ + void Function(_i2.WebView, int, int, int, int)? + onScrollChanged, + }) => _FakeWebView_7(this, Invocation.getter(#newWebView)), + returnValueForMissingStub: + ({ + void Function(_i2.WebView, int, int, int, int)? + onScrollChanged, + }) => _FakeWebView_7(this, Invocation.getter(#newWebView)), + ) + as _i2.WebView Function({ + void Function(_i2.WebView, int, int, int, int)? onScrollChanged, + })); + + @override + _i2.JavaScriptChannel Function({ + required String channelName, + required void Function(_i2.JavaScriptChannel, String) postMessage, + }) + get newJavaScriptChannel => + (super.noSuchMethod( + Invocation.getter(#newJavaScriptChannel), + returnValue: + ({ + required String channelName, + required void Function(_i2.JavaScriptChannel, String) + postMessage, + }) => _FakeJavaScriptChannel_8( + this, + Invocation.getter(#newJavaScriptChannel), + ), + returnValueForMissingStub: + ({ + required String channelName, + required void Function(_i2.JavaScriptChannel, String) + postMessage, + }) => _FakeJavaScriptChannel_8( + this, + Invocation.getter(#newJavaScriptChannel), + ), + ) + as _i2.JavaScriptChannel Function({ + required String channelName, + required void Function(_i2.JavaScriptChannel, String) postMessage, + })); + + @override + _i2.WebViewClient Function({ + void Function(_i2.WebViewClient, _i2.WebView, String, bool)? + doUpdateVisitedHistory, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.AndroidMessage, + _i2.AndroidMessage, + )? + onFormResubmission, + void Function(_i2.WebViewClient, _i2.WebView, String)? onLoadResource, + void Function(_i2.WebViewClient, _i2.WebView, String)? onPageCommitVisible, + void Function(_i2.WebViewClient, _i2.WebView, String)? onPageFinished, + void Function(_i2.WebViewClient, _i2.WebView, String)? onPageStarted, + void Function(_i2.WebViewClient, _i2.WebView, _i2.ClientCertRequest)? + onReceivedClientCertRequest, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.HttpAuthHandler, + String, + String, + )? + onReceivedHttpAuthRequest, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceResponse, + )? + onReceivedHttpError, + void Function(_i2.WebViewClient, _i2.WebView, String, String?, String)? + onReceivedLoginRequest, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceError, + )? + onReceivedRequestError, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceErrorCompat, + )? + onReceivedRequestErrorCompat, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.SslErrorHandler, + _i2.SslError, + )? + onReceivedSslError, + void Function(_i2.WebViewClient, _i2.WebView, double, double)? + onScaleChanged, + void Function(_i2.WebViewClient, _i2.WebView, _i2.WebResourceRequest)? + requestLoading, + void Function(_i2.WebViewClient, _i2.WebView, String)? urlLoading, + }) + get newWebViewClient => + (super.noSuchMethod( + Invocation.getter(#newWebViewClient), + returnValue: + ({ + void Function(_i2.WebViewClient, _i2.WebView, String, bool)? + doUpdateVisitedHistory, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.AndroidMessage, + _i2.AndroidMessage, + )? + onFormResubmission, + void Function(_i2.WebViewClient, _i2.WebView, String)? + onLoadResource, + void Function(_i2.WebViewClient, _i2.WebView, String)? + onPageCommitVisible, + void Function(_i2.WebViewClient, _i2.WebView, String)? + onPageFinished, + void Function(_i2.WebViewClient, _i2.WebView, String)? + onPageStarted, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.ClientCertRequest, + )? + onReceivedClientCertRequest, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.HttpAuthHandler, + String, + String, + )? + onReceivedHttpAuthRequest, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceResponse, + )? + onReceivedHttpError, + void Function( + _i2.WebViewClient, + _i2.WebView, + String, + String?, + String, + )? + onReceivedLoginRequest, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceError, + )? + onReceivedRequestError, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceErrorCompat, + )? + onReceivedRequestErrorCompat, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.SslErrorHandler, + _i2.SslError, + )? + onReceivedSslError, + void Function(_i2.WebViewClient, _i2.WebView, double, double)? + onScaleChanged, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.WebResourceRequest, + )? + requestLoading, + void Function(_i2.WebViewClient, _i2.WebView, String)? + urlLoading, + }) => _FakeWebViewClient_1( + this, + Invocation.getter(#newWebViewClient), + ), + returnValueForMissingStub: + ({ + void Function(_i2.WebViewClient, _i2.WebView, String, bool)? + doUpdateVisitedHistory, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.AndroidMessage, + _i2.AndroidMessage, + )? + onFormResubmission, + void Function(_i2.WebViewClient, _i2.WebView, String)? + onLoadResource, + void Function(_i2.WebViewClient, _i2.WebView, String)? + onPageCommitVisible, + void Function(_i2.WebViewClient, _i2.WebView, String)? + onPageFinished, + void Function(_i2.WebViewClient, _i2.WebView, String)? + onPageStarted, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.ClientCertRequest, + )? + onReceivedClientCertRequest, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.HttpAuthHandler, + String, + String, + )? + onReceivedHttpAuthRequest, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceResponse, + )? + onReceivedHttpError, + void Function( + _i2.WebViewClient, + _i2.WebView, + String, + String?, + String, + )? + onReceivedLoginRequest, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceError, + )? + onReceivedRequestError, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceErrorCompat, + )? + onReceivedRequestErrorCompat, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.SslErrorHandler, + _i2.SslError, + )? + onReceivedSslError, + void Function(_i2.WebViewClient, _i2.WebView, double, double)? + onScaleChanged, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.WebResourceRequest, + )? + requestLoading, + void Function(_i2.WebViewClient, _i2.WebView, String)? + urlLoading, + }) => _FakeWebViewClient_1( + this, + Invocation.getter(#newWebViewClient), + ), + ) + as _i2.WebViewClient Function({ + void Function(_i2.WebViewClient, _i2.WebView, String, bool)? + doUpdateVisitedHistory, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.AndroidMessage, + _i2.AndroidMessage, + )? + onFormResubmission, + void Function(_i2.WebViewClient, _i2.WebView, String)? + onLoadResource, + void Function(_i2.WebViewClient, _i2.WebView, String)? + onPageCommitVisible, + void Function(_i2.WebViewClient, _i2.WebView, String)? + onPageFinished, + void Function(_i2.WebViewClient, _i2.WebView, String)? + onPageStarted, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.ClientCertRequest, + )? + onReceivedClientCertRequest, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.HttpAuthHandler, + String, + String, + )? + onReceivedHttpAuthRequest, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceResponse, + )? + onReceivedHttpError, + void Function( + _i2.WebViewClient, + _i2.WebView, + String, + String?, + String, + )? + onReceivedLoginRequest, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceError, + )? + onReceivedRequestError, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceErrorCompat, + )? + onReceivedRequestErrorCompat, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.SslErrorHandler, + _i2.SslError, + )? + onReceivedSslError, + void Function(_i2.WebViewClient, _i2.WebView, double, double)? + onScaleChanged, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.WebResourceRequest, + )? + requestLoading, + void Function(_i2.WebViewClient, _i2.WebView, String)? urlLoading, + })); + + @override + _i2.DownloadListener Function({ + required void Function( + _i2.DownloadListener, + String, + String, + String, + String, + int, + ) + onDownloadStart, + }) + get newDownloadListener => + (super.noSuchMethod( + Invocation.getter(#newDownloadListener), + returnValue: + ({ + required void Function( + _i2.DownloadListener, + String, + String, + String, + String, + int, + ) + onDownloadStart, + }) => _FakeDownloadListener_2( + this, + Invocation.getter(#newDownloadListener), + ), + returnValueForMissingStub: + ({ + required void Function( + _i2.DownloadListener, + String, + String, + String, + String, + int, + ) + onDownloadStart, + }) => _FakeDownloadListener_2( + this, + Invocation.getter(#newDownloadListener), + ), + ) + as _i2.DownloadListener Function({ + required void Function( + _i2.DownloadListener, + String, + String, + String, + String, + int, + ) + onDownloadStart, + })); + + @override + _i2.WebChromeClient Function({ + required _i8.Future Function( + _i2.WebChromeClient, + _i2.WebView, + String, + String, + ) + onJsConfirm, + required _i8.Future> Function( + _i2.WebChromeClient, + _i2.WebView, + _i2.FileChooserParams, + ) + onShowFileChooser, + void Function(_i2.WebChromeClient, _i2.ConsoleMessage)? onConsoleMessage, + void Function(_i2.WebChromeClient)? onGeolocationPermissionsHidePrompt, + void Function( + _i2.WebChromeClient, + String, + _i2.GeolocationPermissionsCallback, + )? + onGeolocationPermissionsShowPrompt, + void Function(_i2.WebChromeClient)? onHideCustomView, + _i8.Future Function(_i2.WebChromeClient, _i2.WebView, String, String)? + onJsAlert, + _i8.Future Function( + _i2.WebChromeClient, + _i2.WebView, + String, + String, + String, + )? + onJsPrompt, + void Function(_i2.WebChromeClient, _i2.PermissionRequest)? + onPermissionRequest, + void Function(_i2.WebChromeClient, _i2.WebView, int)? onProgressChanged, + void Function(_i2.WebChromeClient, _i2.View, _i2.CustomViewCallback)? + onShowCustomView, + }) + get newWebChromeClient => + (super.noSuchMethod( + Invocation.getter(#newWebChromeClient), + returnValue: + ({ + void Function(_i2.WebChromeClient, _i2.ConsoleMessage)? + onConsoleMessage, + void Function(_i2.WebChromeClient)? + onGeolocationPermissionsHidePrompt, + void Function( + _i2.WebChromeClient, + String, + _i2.GeolocationPermissionsCallback, + )? + onGeolocationPermissionsShowPrompt, + void Function(_i2.WebChromeClient)? onHideCustomView, + _i8.Future Function( + _i2.WebChromeClient, + _i2.WebView, + String, + String, + )? + onJsAlert, + required _i8.Future Function( + _i2.WebChromeClient, + _i2.WebView, + String, + String, + ) + onJsConfirm, + _i8.Future Function( + _i2.WebChromeClient, + _i2.WebView, + String, + String, + String, + )? + onJsPrompt, + void Function(_i2.WebChromeClient, _i2.PermissionRequest)? + onPermissionRequest, + void Function(_i2.WebChromeClient, _i2.WebView, int)? + onProgressChanged, + void Function( + _i2.WebChromeClient, + _i2.View, + _i2.CustomViewCallback, + )? + onShowCustomView, + required _i8.Future> Function( + _i2.WebChromeClient, + _i2.WebView, + _i2.FileChooserParams, + ) + onShowFileChooser, + }) => _FakeWebChromeClient_0( + this, + Invocation.getter(#newWebChromeClient), + ), + returnValueForMissingStub: + ({ + void Function(_i2.WebChromeClient, _i2.ConsoleMessage)? + onConsoleMessage, + void Function(_i2.WebChromeClient)? + onGeolocationPermissionsHidePrompt, + void Function( + _i2.WebChromeClient, + String, + _i2.GeolocationPermissionsCallback, + )? + onGeolocationPermissionsShowPrompt, + void Function(_i2.WebChromeClient)? onHideCustomView, + _i8.Future Function( + _i2.WebChromeClient, + _i2.WebView, + String, + String, + )? + onJsAlert, + required _i8.Future Function( + _i2.WebChromeClient, + _i2.WebView, + String, + String, + ) + onJsConfirm, + _i8.Future Function( + _i2.WebChromeClient, + _i2.WebView, + String, + String, + String, + )? + onJsPrompt, + void Function(_i2.WebChromeClient, _i2.PermissionRequest)? + onPermissionRequest, + void Function(_i2.WebChromeClient, _i2.WebView, int)? + onProgressChanged, + void Function( + _i2.WebChromeClient, + _i2.View, + _i2.CustomViewCallback, + )? + onShowCustomView, + required _i8.Future> Function( + _i2.WebChromeClient, + _i2.WebView, + _i2.FileChooserParams, + ) + onShowFileChooser, + }) => _FakeWebChromeClient_0( + this, + Invocation.getter(#newWebChromeClient), + ), + ) + as _i2.WebChromeClient Function({ + required _i8.Future Function( + _i2.WebChromeClient, + _i2.WebView, + String, + String, + ) + onJsConfirm, + required _i8.Future> Function( + _i2.WebChromeClient, + _i2.WebView, + _i2.FileChooserParams, + ) + onShowFileChooser, + void Function(_i2.WebChromeClient, _i2.ConsoleMessage)? + onConsoleMessage, + void Function(_i2.WebChromeClient)? + onGeolocationPermissionsHidePrompt, + void Function( + _i2.WebChromeClient, + String, + _i2.GeolocationPermissionsCallback, + )? + onGeolocationPermissionsShowPrompt, + void Function(_i2.WebChromeClient)? onHideCustomView, + _i8.Future Function( + _i2.WebChromeClient, + _i2.WebView, + String, + String, + )? + onJsAlert, + _i8.Future Function( + _i2.WebChromeClient, + _i2.WebView, + String, + String, + String, + )? + onJsPrompt, + void Function(_i2.WebChromeClient, _i2.PermissionRequest)? + onPermissionRequest, + void Function(_i2.WebChromeClient, _i2.WebView, int)? + onProgressChanged, + void Function( + _i2.WebChromeClient, + _i2.View, + _i2.CustomViewCallback, + )? + onShowCustomView, + })); + + @override + _i8.Future Function(bool) get setWebContentsDebuggingEnabledWebView => + (super.noSuchMethod( + Invocation.getter(#setWebContentsDebuggingEnabledWebView), + returnValue: (bool __p0) => _i8.Future.value(), + returnValueForMissingStub: (bool __p0) => _i8.Future.value(), + ) + as _i8.Future Function(bool)); + + @override + _i2.CookieManager Function() get instanceCookieManager => + (super.noSuchMethod( + Invocation.getter(#instanceCookieManager), + returnValue: () => _FakeCookieManager_9( + this, + Invocation.getter(#instanceCookieManager), + ), + returnValueForMissingStub: () => _FakeCookieManager_9( + this, + Invocation.getter(#instanceCookieManager), + ), + ) + as _i2.CookieManager Function()); + + @override + _i2.FlutterAssetManager Function() get instanceFlutterAssetManager => + (super.noSuchMethod( + Invocation.getter(#instanceFlutterAssetManager), + returnValue: () => _FakeFlutterAssetManager_10( + this, + Invocation.getter(#instanceFlutterAssetManager), + ), + returnValueForMissingStub: () => _FakeFlutterAssetManager_10( + this, + Invocation.getter(#instanceFlutterAssetManager), + ), + ) + as _i2.FlutterAssetManager Function()); + + @override + _i2.WebStorage Function() get instanceWebStorage => + (super.noSuchMethod( + Invocation.getter(#instanceWebStorage), + returnValue: () => _FakeWebStorage_11( + this, + Invocation.getter(#instanceWebStorage), + ), + returnValueForMissingStub: () => _FakeWebStorage_11( + this, + Invocation.getter(#instanceWebStorage), + ), + ) + as _i2.WebStorage Function()); + + @override + _i8.Future Function(String) get isWebViewFeatureSupported => + (super.noSuchMethod( + Invocation.getter(#isWebViewFeatureSupported), + returnValue: (String __p0) => _i8.Future.value(false), + returnValueForMissingStub: (String __p0) => + _i8.Future.value(false), + ) + as _i8.Future Function(String)); + + @override + _i8.Future Function(_i2.WebSettings, bool) + get setPaymentRequestEnabled => + (super.noSuchMethod( + Invocation.getter(#setPaymentRequestEnabled), + returnValue: (_i2.WebSettings __p0, bool __p1) => + _i8.Future.value(), + returnValueForMissingStub: (_i2.WebSettings __p0, bool __p1) => + _i8.Future.value(), + ) + as _i8.Future Function(_i2.WebSettings, bool)); +} + +/// A class which mocks [AndroidWebViewWidgetCreationParams]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockAndroidWebViewWidgetCreationParams extends _i1.Mock + implements _i7.AndroidWebViewWidgetCreationParams { + @override + _i2.PigeonInstanceManager get instanceManager => + (super.noSuchMethod( + Invocation.getter(#instanceManager), + returnValue: _FakePigeonInstanceManager_12( + this, + Invocation.getter(#instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_12( + this, + Invocation.getter(#instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i5.PlatformViewsServiceProxy get platformViewsServiceProxy => + (super.noSuchMethod( + Invocation.getter(#platformViewsServiceProxy), + returnValue: _FakePlatformViewsServiceProxy_13( + this, + Invocation.getter(#platformViewsServiceProxy), + ), + returnValueForMissingStub: _FakePlatformViewsServiceProxy_13( + this, + Invocation.getter(#platformViewsServiceProxy), + ), + ) + as _i5.PlatformViewsServiceProxy); + + @override + bool get displayWithHybridComposition => + (super.noSuchMethod( + Invocation.getter(#displayWithHybridComposition), + returnValue: false, + returnValueForMissingStub: false, + ) + as bool); + + @override + _i3.PlatformWebViewController get controller => + (super.noSuchMethod( + Invocation.getter(#controller), + returnValue: _FakePlatformWebViewController_14( + this, + Invocation.getter(#controller), + ), + returnValueForMissingStub: _FakePlatformWebViewController_14( + this, + Invocation.getter(#controller), + ), + ) + as _i3.PlatformWebViewController); + + @override + _i4.TextDirection get layoutDirection => + (super.noSuchMethod( + Invocation.getter(#layoutDirection), + returnValue: _i4.TextDirection.rtl, + returnValueForMissingStub: _i4.TextDirection.rtl, + ) + as _i4.TextDirection); + + @override + Set<_i10.Factory<_i11.OneSequenceGestureRecognizer>> get gestureRecognizers => + (super.noSuchMethod( + Invocation.getter(#gestureRecognizers), + returnValue: <_i10.Factory<_i11.OneSequenceGestureRecognizer>>{}, + returnValueForMissingStub: + <_i10.Factory<_i11.OneSequenceGestureRecognizer>>{}, + ) + as Set<_i10.Factory<_i11.OneSequenceGestureRecognizer>>); +} + +/// A class which mocks [ExpensiveAndroidViewController]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockExpensiveAndroidViewController extends _i1.Mock + implements _i6.ExpensiveAndroidViewController { + @override + bool get requiresViewComposition => + (super.noSuchMethod( + Invocation.getter(#requiresViewComposition), + returnValue: false, + returnValueForMissingStub: false, + ) + as bool); + + @override + int get viewId => + (super.noSuchMethod( + Invocation.getter(#viewId), + returnValue: 0, + returnValueForMissingStub: 0, + ) + as int); + + @override + bool get awaitingCreation => + (super.noSuchMethod( + Invocation.getter(#awaitingCreation), + returnValue: false, + returnValueForMissingStub: false, + ) + as bool); + + @override + _i6.PointTransformer get pointTransformer => + (super.noSuchMethod( + Invocation.getter(#pointTransformer), + returnValue: (_i4.Offset position) => + _FakeOffset_6(this, Invocation.getter(#pointTransformer)), + returnValueForMissingStub: (_i4.Offset position) => + _FakeOffset_6(this, Invocation.getter(#pointTransformer)), + ) + as _i6.PointTransformer); + + @override + bool get isCreated => + (super.noSuchMethod( + Invocation.getter(#isCreated), + returnValue: false, + returnValueForMissingStub: false, + ) + as bool); + + @override + List<_i6.PlatformViewCreatedCallback> get createdCallbacks => + (super.noSuchMethod( + Invocation.getter(#createdCallbacks), + returnValue: <_i6.PlatformViewCreatedCallback>[], + returnValueForMissingStub: <_i6.PlatformViewCreatedCallback>[], + ) + as List<_i6.PlatformViewCreatedCallback>); + + @override + set pointTransformer(_i6.PointTransformer? transformer) => super.noSuchMethod( + Invocation.setter(#pointTransformer, transformer), + returnValueForMissingStub: null, + ); + + @override + _i8.Future setOffset(_i4.Offset? off) => + (super.noSuchMethod( + Invocation.method(#setOffset, [off]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future create({_i4.Size? size, _i4.Offset? position}) => + (super.noSuchMethod( + Invocation.method(#create, [], {#size: size, #position: position}), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future<_i4.Size> setSize(_i4.Size? size) => + (super.noSuchMethod( + Invocation.method(#setSize, [size]), + returnValue: _i8.Future<_i4.Size>.value( + _FakeSize_15(this, Invocation.method(#setSize, [size])), + ), + returnValueForMissingStub: _i8.Future<_i4.Size>.value( + _FakeSize_15(this, Invocation.method(#setSize, [size])), + ), + ) + as _i8.Future<_i4.Size>); + + @override + _i8.Future sendMotionEvent(_i6.AndroidMotionEvent? event) => + (super.noSuchMethod( + Invocation.method(#sendMotionEvent, [event]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + void addOnPlatformViewCreatedListener( + _i6.PlatformViewCreatedCallback? listener, + ) => super.noSuchMethod( + Invocation.method(#addOnPlatformViewCreatedListener, [listener]), + returnValueForMissingStub: null, + ); + + @override + void removeOnPlatformViewCreatedListener( + _i6.PlatformViewCreatedCallback? listener, + ) => super.noSuchMethod( + Invocation.method(#removeOnPlatformViewCreatedListener, [listener]), + returnValueForMissingStub: null, + ); + + @override + _i8.Future setLayoutDirection(_i4.TextDirection? layoutDirection) => + (super.noSuchMethod( + Invocation.method(#setLayoutDirection, [layoutDirection]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future dispatchPointerEvent(_i11.PointerEvent? event) => + (super.noSuchMethod( + Invocation.method(#dispatchPointerEvent, [event]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future clearFocus() => + (super.noSuchMethod( + Invocation.method(#clearFocus, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future dispose() => + (super.noSuchMethod( + Invocation.method(#dispose, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); +} + +/// A class which mocks [FlutterAssetManager]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockFlutterAssetManager extends _i1.Mock + implements _i2.FlutterAssetManager { + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_12( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_12( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i8.Future> list(String? path) => + (super.noSuchMethod( + Invocation.method(#list, [path]), + returnValue: _i8.Future>.value([]), + returnValueForMissingStub: _i8.Future>.value( + [], + ), + ) + as _i8.Future>); + + @override + _i8.Future getAssetFilePathByName(String? name) => + (super.noSuchMethod( + Invocation.method(#getAssetFilePathByName, [name]), + returnValue: _i8.Future.value( + _i12.dummyValue( + this, + Invocation.method(#getAssetFilePathByName, [name]), + ), + ), + returnValueForMissingStub: _i8.Future.value( + _i12.dummyValue( + this, + Invocation.method(#getAssetFilePathByName, [name]), + ), + ), + ) + as _i8.Future); + + @override + _i2.FlutterAssetManager pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeFlutterAssetManager_10( + this, + Invocation.method(#pigeon_copy, []), + ), + returnValueForMissingStub: _FakeFlutterAssetManager_10( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.FlutterAssetManager); +} + +/// A class which mocks [GeolocationPermissionsCallback]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockGeolocationPermissionsCallback extends _i1.Mock + implements _i2.GeolocationPermissionsCallback { + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_12( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_12( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i8.Future invoke(String? origin, bool? allow, bool? retain) => + (super.noSuchMethod( + Invocation.method(#invoke, [origin, allow, retain]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i2.GeolocationPermissionsCallback pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeGeolocationPermissionsCallback_16( + this, + Invocation.method(#pigeon_copy, []), + ), + returnValueForMissingStub: _FakeGeolocationPermissionsCallback_16( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.GeolocationPermissionsCallback); +} + +/// A class which mocks [JavaScriptChannel]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockJavaScriptChannel extends _i1.Mock implements _i2.JavaScriptChannel { + @override + String get channelName => + (super.noSuchMethod( + Invocation.getter(#channelName), + returnValue: _i12.dummyValue( + this, + Invocation.getter(#channelName), + ), + returnValueForMissingStub: _i12.dummyValue( + this, + Invocation.getter(#channelName), + ), + ) + as String); + + @override + void Function(_i2.JavaScriptChannel, String) get postMessage => + (super.noSuchMethod( + Invocation.getter(#postMessage), + returnValue: + (_i2.JavaScriptChannel pigeon_instance, String message) {}, + returnValueForMissingStub: + (_i2.JavaScriptChannel pigeon_instance, String message) {}, + ) + as void Function(_i2.JavaScriptChannel, String)); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_12( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_12( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i2.JavaScriptChannel pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeJavaScriptChannel_8( + this, + Invocation.method(#pigeon_copy, []), + ), + returnValueForMissingStub: _FakeJavaScriptChannel_8( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.JavaScriptChannel); +} + +/// A class which mocks [PermissionRequest]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPermissionRequest extends _i1.Mock implements _i2.PermissionRequest { + @override + List get resources => + (super.noSuchMethod( + Invocation.getter(#resources), + returnValue: [], + returnValueForMissingStub: [], + ) + as List); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_12( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_12( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i8.Future grant(List? resources) => + (super.noSuchMethod( + Invocation.method(#grant, [resources]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future deny() => + (super.noSuchMethod( + Invocation.method(#deny, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i2.PermissionRequest pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakePermissionRequest_17( + this, + Invocation.method(#pigeon_copy, []), + ), + returnValueForMissingStub: _FakePermissionRequest_17( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.PermissionRequest); +} + +/// A class which mocks [PlatformViewsServiceProxy]. +/// +/// See the documentation for Mockito's code generation for more information. +// ignore: must_be_immutable +class MockPlatformViewsServiceProxy extends _i1.Mock + implements _i5.PlatformViewsServiceProxy { + @override + _i6.ExpensiveAndroidViewController initExpensiveAndroidView({ + required int? id, + required String? viewType, + required _i4.TextDirection? layoutDirection, + dynamic creationParams, + _i6.MessageCodec? creationParamsCodec, + _i4.VoidCallback? onFocus, + }) => + (super.noSuchMethod( + Invocation.method(#initExpensiveAndroidView, [], { + #id: id, + #viewType: viewType, + #layoutDirection: layoutDirection, + #creationParams: creationParams, + #creationParamsCodec: creationParamsCodec, + #onFocus: onFocus, + }), + returnValue: _FakeExpensiveAndroidViewController_18( + this, + Invocation.method(#initExpensiveAndroidView, [], { + #id: id, + #viewType: viewType, + #layoutDirection: layoutDirection, + #creationParams: creationParams, + #creationParamsCodec: creationParamsCodec, + #onFocus: onFocus, + }), + ), + returnValueForMissingStub: _FakeExpensiveAndroidViewController_18( + this, + Invocation.method(#initExpensiveAndroidView, [], { + #id: id, + #viewType: viewType, + #layoutDirection: layoutDirection, + #creationParams: creationParams, + #creationParamsCodec: creationParamsCodec, + #onFocus: onFocus, + }), + ), + ) + as _i6.ExpensiveAndroidViewController); + + @override + _i6.SurfaceAndroidViewController initSurfaceAndroidView({ + required int? id, + required String? viewType, + required _i4.TextDirection? layoutDirection, + dynamic creationParams, + _i6.MessageCodec? creationParamsCodec, + _i4.VoidCallback? onFocus, + }) => + (super.noSuchMethod( + Invocation.method(#initSurfaceAndroidView, [], { + #id: id, + #viewType: viewType, + #layoutDirection: layoutDirection, + #creationParams: creationParams, + #creationParamsCodec: creationParamsCodec, + #onFocus: onFocus, + }), + returnValue: _FakeSurfaceAndroidViewController_19( + this, + Invocation.method(#initSurfaceAndroidView, [], { + #id: id, + #viewType: viewType, + #layoutDirection: layoutDirection, + #creationParams: creationParams, + #creationParamsCodec: creationParamsCodec, + #onFocus: onFocus, + }), + ), + returnValueForMissingStub: _FakeSurfaceAndroidViewController_19( + this, + Invocation.method(#initSurfaceAndroidView, [], { + #id: id, + #viewType: viewType, + #layoutDirection: layoutDirection, + #creationParams: creationParams, + #creationParamsCodec: creationParamsCodec, + #onFocus: onFocus, + }), + ), + ) + as _i6.SurfaceAndroidViewController); +} + +/// A class which mocks [SurfaceAndroidViewController]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockSurfaceAndroidViewController extends _i1.Mock + implements _i6.SurfaceAndroidViewController { + @override + bool get requiresViewComposition => + (super.noSuchMethod( + Invocation.getter(#requiresViewComposition), + returnValue: false, + returnValueForMissingStub: false, + ) + as bool); + + @override + int get viewId => + (super.noSuchMethod( + Invocation.getter(#viewId), + returnValue: 0, + returnValueForMissingStub: 0, + ) + as int); + + @override + bool get awaitingCreation => + (super.noSuchMethod( + Invocation.getter(#awaitingCreation), + returnValue: false, + returnValueForMissingStub: false, + ) + as bool); + + @override + _i6.PointTransformer get pointTransformer => + (super.noSuchMethod( + Invocation.getter(#pointTransformer), + returnValue: (_i4.Offset position) => + _FakeOffset_6(this, Invocation.getter(#pointTransformer)), + returnValueForMissingStub: (_i4.Offset position) => + _FakeOffset_6(this, Invocation.getter(#pointTransformer)), + ) + as _i6.PointTransformer); + + @override + bool get isCreated => + (super.noSuchMethod( + Invocation.getter(#isCreated), + returnValue: false, + returnValueForMissingStub: false, + ) + as bool); + + @override + List<_i6.PlatformViewCreatedCallback> get createdCallbacks => + (super.noSuchMethod( + Invocation.getter(#createdCallbacks), + returnValue: <_i6.PlatformViewCreatedCallback>[], + returnValueForMissingStub: <_i6.PlatformViewCreatedCallback>[], + ) + as List<_i6.PlatformViewCreatedCallback>); + + @override + set pointTransformer(_i6.PointTransformer? transformer) => super.noSuchMethod( + Invocation.setter(#pointTransformer, transformer), + returnValueForMissingStub: null, + ); + + @override + _i8.Future setOffset(_i4.Offset? off) => + (super.noSuchMethod( + Invocation.method(#setOffset, [off]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future create({_i4.Size? size, _i4.Offset? position}) => + (super.noSuchMethod( + Invocation.method(#create, [], {#size: size, #position: position}), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future<_i4.Size> setSize(_i4.Size? size) => + (super.noSuchMethod( + Invocation.method(#setSize, [size]), + returnValue: _i8.Future<_i4.Size>.value( + _FakeSize_15(this, Invocation.method(#setSize, [size])), + ), + returnValueForMissingStub: _i8.Future<_i4.Size>.value( + _FakeSize_15(this, Invocation.method(#setSize, [size])), + ), + ) + as _i8.Future<_i4.Size>); + + @override + _i8.Future sendMotionEvent(_i6.AndroidMotionEvent? event) => + (super.noSuchMethod( + Invocation.method(#sendMotionEvent, [event]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + void addOnPlatformViewCreatedListener( + _i6.PlatformViewCreatedCallback? listener, + ) => super.noSuchMethod( + Invocation.method(#addOnPlatformViewCreatedListener, [listener]), + returnValueForMissingStub: null, + ); + + @override + void removeOnPlatformViewCreatedListener( + _i6.PlatformViewCreatedCallback? listener, + ) => super.noSuchMethod( + Invocation.method(#removeOnPlatformViewCreatedListener, [listener]), + returnValueForMissingStub: null, + ); + + @override + _i8.Future setLayoutDirection(_i4.TextDirection? layoutDirection) => + (super.noSuchMethod( + Invocation.method(#setLayoutDirection, [layoutDirection]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future dispatchPointerEvent(_i11.PointerEvent? event) => + (super.noSuchMethod( + Invocation.method(#dispatchPointerEvent, [event]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future clearFocus() => + (super.noSuchMethod( + Invocation.method(#clearFocus, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future dispose() => + (super.noSuchMethod( + Invocation.method(#dispose, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); +} + +/// A class which mocks [WebChromeClient]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { + @override + _i8.Future> Function( + _i2.WebChromeClient, + _i2.WebView, + _i2.FileChooserParams, + ) + get onShowFileChooser => + (super.noSuchMethod( + Invocation.getter(#onShowFileChooser), + returnValue: + ( + _i2.WebChromeClient pigeon_instance, + _i2.WebView webView, + _i2.FileChooserParams params, + ) => _i8.Future>.value([]), + returnValueForMissingStub: + ( + _i2.WebChromeClient pigeon_instance, + _i2.WebView webView, + _i2.FileChooserParams params, + ) => _i8.Future>.value([]), + ) + as _i8.Future> Function( + _i2.WebChromeClient, + _i2.WebView, + _i2.FileChooserParams, + )); + + @override + _i8.Future Function(_i2.WebChromeClient, _i2.WebView, String, String) + get onJsConfirm => + (super.noSuchMethod( + Invocation.getter(#onJsConfirm), + returnValue: + ( + _i2.WebChromeClient pigeon_instance, + _i2.WebView webView, + String url, + String message, + ) => _i8.Future.value(false), + returnValueForMissingStub: + ( + _i2.WebChromeClient pigeon_instance, + _i2.WebView webView, + String url, + String message, + ) => _i8.Future.value(false), + ) + as _i8.Future Function( + _i2.WebChromeClient, + _i2.WebView, + String, + String, + )); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_12( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_12( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i8.Future setSynchronousReturnValueForOnShowFileChooser(bool? value) => + (super.noSuchMethod( + Invocation.method(#setSynchronousReturnValueForOnShowFileChooser, [ + value, + ]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setSynchronousReturnValueForOnConsoleMessage(bool? value) => + (super.noSuchMethod( + Invocation.method(#setSynchronousReturnValueForOnConsoleMessage, [ + value, + ]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setSynchronousReturnValueForOnJsAlert(bool? value) => + (super.noSuchMethod( + Invocation.method(#setSynchronousReturnValueForOnJsAlert, [value]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setSynchronousReturnValueForOnJsConfirm(bool? value) => + (super.noSuchMethod( + Invocation.method(#setSynchronousReturnValueForOnJsConfirm, [ + value, + ]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setSynchronousReturnValueForOnJsPrompt(bool? value) => + (super.noSuchMethod( + Invocation.method(#setSynchronousReturnValueForOnJsPrompt, [value]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i2.WebChromeClient pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeWebChromeClient_0( + this, + Invocation.method(#pigeon_copy, []), + ), + returnValueForMissingStub: _FakeWebChromeClient_0( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.WebChromeClient); +} + +/// A class which mocks [WebSettings]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebSettings extends _i1.Mock implements _i2.WebSettings { + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_12( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_12( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i8.Future setDomStorageEnabled(bool? flag) => + (super.noSuchMethod( + Invocation.method(#setDomStorageEnabled, [flag]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setJavaScriptCanOpenWindowsAutomatically(bool? flag) => + (super.noSuchMethod( + Invocation.method(#setJavaScriptCanOpenWindowsAutomatically, [ + flag, + ]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setSupportMultipleWindows(bool? support) => + (super.noSuchMethod( + Invocation.method(#setSupportMultipleWindows, [support]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setJavaScriptEnabled(bool? flag) => + (super.noSuchMethod( + Invocation.method(#setJavaScriptEnabled, [flag]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setUserAgentString(String? userAgentString) => + (super.noSuchMethod( + Invocation.method(#setUserAgentString, [userAgentString]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setMediaPlaybackRequiresUserGesture(bool? require) => + (super.noSuchMethod( + Invocation.method(#setMediaPlaybackRequiresUserGesture, [require]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setSupportZoom(bool? support) => + (super.noSuchMethod( + Invocation.method(#setSupportZoom, [support]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setLoadWithOverviewMode(bool? overview) => + (super.noSuchMethod( + Invocation.method(#setLoadWithOverviewMode, [overview]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setUseWideViewPort(bool? use) => + (super.noSuchMethod( + Invocation.method(#setUseWideViewPort, [use]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setDisplayZoomControls(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setDisplayZoomControls, [enabled]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setBuiltInZoomControls(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setBuiltInZoomControls, [enabled]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setAllowFileAccess(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setAllowFileAccess, [enabled]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setAllowContentAccess(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setAllowContentAccess, [enabled]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setGeolocationEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setGeolocationEnabled, [enabled]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setTextZoom(int? textZoom) => + (super.noSuchMethod( + Invocation.method(#setTextZoom, [textZoom]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future getUserAgentString() => + (super.noSuchMethod( + Invocation.method(#getUserAgentString, []), + returnValue: _i8.Future.value( + _i12.dummyValue( + this, + Invocation.method(#getUserAgentString, []), + ), + ), + returnValueForMissingStub: _i8.Future.value( + _i12.dummyValue( + this, + Invocation.method(#getUserAgentString, []), + ), + ), + ) + as _i8.Future); + + @override + _i8.Future setMixedContentMode(_i2.MixedContentMode? mode) => + (super.noSuchMethod( + Invocation.method(#setMixedContentMode, [mode]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i2.WebSettings pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeWebSettings_20( + this, + Invocation.method(#pigeon_copy, []), + ), + returnValueForMissingStub: _FakeWebSettings_20( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.WebSettings); +} + +/// A class which mocks [WebView]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebView extends _i1.Mock implements _i2.WebView { + @override + _i2.WebSettings get settings => + (super.noSuchMethod( + Invocation.getter(#settings), + returnValue: _FakeWebSettings_20( + this, + Invocation.getter(#settings), + ), + returnValueForMissingStub: _FakeWebSettings_20( + this, + Invocation.getter(#settings), + ), + ) + as _i2.WebSettings); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_12( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_12( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i2.WebSettings pigeonVar_settings() => + (super.noSuchMethod( + Invocation.method(#pigeonVar_settings, []), + returnValue: _FakeWebSettings_20( + this, + Invocation.method(#pigeonVar_settings, []), + ), + returnValueForMissingStub: _FakeWebSettings_20( + this, + Invocation.method(#pigeonVar_settings, []), + ), + ) + as _i2.WebSettings); + + @override + _i8.Future loadData(String? data, String? mimeType, String? encoding) => + (super.noSuchMethod( + Invocation.method(#loadData, [data, mimeType, encoding]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future loadDataWithBaseUrl( + String? baseUrl, + String? data, + String? mimeType, + String? encoding, + String? historyUrl, + ) => + (super.noSuchMethod( + Invocation.method(#loadDataWithBaseUrl, [ + baseUrl, + data, + mimeType, + encoding, + historyUrl, + ]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future loadUrl(String? url, Map? headers) => + (super.noSuchMethod( + Invocation.method(#loadUrl, [url, headers]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future postUrl(String? url, _i13.Uint8List? data) => + (super.noSuchMethod( + Invocation.method(#postUrl, [url, data]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future getUrl() => + (super.noSuchMethod( + Invocation.method(#getUrl, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future canGoBack() => + (super.noSuchMethod( + Invocation.method(#canGoBack, []), + returnValue: _i8.Future.value(false), + returnValueForMissingStub: _i8.Future.value(false), + ) + as _i8.Future); + + @override + _i8.Future canGoForward() => + (super.noSuchMethod( + Invocation.method(#canGoForward, []), + returnValue: _i8.Future.value(false), + returnValueForMissingStub: _i8.Future.value(false), + ) + as _i8.Future); + + @override + _i8.Future goBack() => + (super.noSuchMethod( + Invocation.method(#goBack, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future goForward() => + (super.noSuchMethod( + Invocation.method(#goForward, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future reload() => + (super.noSuchMethod( + Invocation.method(#reload, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future clearCache(bool? includeDiskFiles) => + (super.noSuchMethod( + Invocation.method(#clearCache, [includeDiskFiles]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future evaluateJavascript(String? javascriptString) => + (super.noSuchMethod( + Invocation.method(#evaluateJavascript, [javascriptString]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future getTitle() => + (super.noSuchMethod( + Invocation.method(#getTitle, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setWebViewClient(_i2.WebViewClient? client) => + (super.noSuchMethod( + Invocation.method(#setWebViewClient, [client]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future addJavaScriptChannel(_i2.JavaScriptChannel? channel) => + (super.noSuchMethod( + Invocation.method(#addJavaScriptChannel, [channel]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future removeJavaScriptChannel(String? name) => + (super.noSuchMethod( + Invocation.method(#removeJavaScriptChannel, [name]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setDownloadListener(_i2.DownloadListener? listener) => + (super.noSuchMethod( + Invocation.method(#setDownloadListener, [listener]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setWebChromeClient(_i2.WebChromeClient? client) => + (super.noSuchMethod( + Invocation.method(#setWebChromeClient, [client]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setBackgroundColor(int? color) => + (super.noSuchMethod( + Invocation.method(#setBackgroundColor, [color]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future destroy() => + (super.noSuchMethod( + Invocation.method(#destroy, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i2.WebView pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeWebView_7( + this, + Invocation.method(#pigeon_copy, []), + ), + returnValueForMissingStub: _FakeWebView_7( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.WebView); + + @override + _i8.Future scrollTo(int? x, int? y) => + (super.noSuchMethod( + Invocation.method(#scrollTo, [x, y]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future scrollBy(int? x, int? y) => + (super.noSuchMethod( + Invocation.method(#scrollBy, [x, y]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future<_i2.WebViewPoint> getScrollPosition() => + (super.noSuchMethod( + Invocation.method(#getScrollPosition, []), + returnValue: _i8.Future<_i2.WebViewPoint>.value( + _FakeWebViewPoint_21( + this, + Invocation.method(#getScrollPosition, []), + ), + ), + returnValueForMissingStub: _i8.Future<_i2.WebViewPoint>.value( + _FakeWebViewPoint_21( + this, + Invocation.method(#getScrollPosition, []), + ), + ), + ) + as _i8.Future<_i2.WebViewPoint>); + + @override + _i8.Future setVerticalScrollBarEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setVerticalScrollBarEnabled, [enabled]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setHorizontalScrollBarEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setHorizontalScrollBarEnabled, [enabled]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setOverScrollMode(_i2.OverScrollMode? mode) => + (super.noSuchMethod( + Invocation.method(#setOverScrollMode, [mode]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); +} + +/// A class which mocks [WebViewClient]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewClient extends _i1.Mock implements _i2.WebViewClient { + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_12( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_12( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i8.Future setSynchronousReturnValueForShouldOverrideUrlLoading( + bool? value, + ) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForShouldOverrideUrlLoading, + [value], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i2.WebViewClient pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeWebViewClient_1( + this, + Invocation.method(#pigeon_copy, []), + ), + returnValueForMissingStub: _FakeWebViewClient_1( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.WebViewClient); +} + +/// A class which mocks [WebStorage]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebStorage extends _i1.Mock implements _i2.WebStorage { + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_12( + this, + Invocation.getter(#pigeon_instanceManager), + ), + returnValueForMissingStub: _FakePigeonInstanceManager_12( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i8.Future deleteAllData() => + (super.noSuchMethod( + Invocation.method(#deleteAllData, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i2.WebStorage pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeWebStorage_11( + this, + Invocation.method(#pigeon_copy, []), + ), + returnValueForMissingStub: _FakeWebStorage_11( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.WebStorage); +} diff --git a/local_packages/webview_flutter_android-4.10.5/test/android_webview_cookie_manager_test.dart b/local_packages/webview_flutter_android-4.10.5/test/android_webview_cookie_manager_test.dart new file mode 100644 index 0000000..2dfcbf9 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/test/android_webview_cookie_manager_test.dart @@ -0,0 +1,116 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:webview_flutter_android/src/android_webkit.g.dart' + as android_webview; +import 'package:webview_flutter_android/webview_flutter_android.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; + +import 'android_webview_cookie_manager_test.mocks.dart'; + +@GenerateMocks([android_webview.CookieManager, AndroidWebViewController]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + test('clearCookies should call android_webview.clearCookies', () async { + final android_webview.CookieManager mockCookieManager = MockCookieManager(); + + when( + mockCookieManager.removeAllCookies(), + ).thenAnswer((_) => Future.value(true)); + + final AndroidWebViewCookieManagerCreationParams params = + AndroidWebViewCookieManagerCreationParams.fromPlatformWebViewCookieManagerCreationParams( + const PlatformWebViewCookieManagerCreationParams(), + ); + + final bool hasClearedCookies = await AndroidWebViewCookieManager( + params, + cookieManager: mockCookieManager, + ).clearCookies(); + + expect(hasClearedCookies, true); + verify(mockCookieManager.removeAllCookies()); + }); + + test('setCookie should throw ArgumentError for cookie with invalid path', () { + final AndroidWebViewCookieManagerCreationParams params = + AndroidWebViewCookieManagerCreationParams.fromPlatformWebViewCookieManagerCreationParams( + const PlatformWebViewCookieManagerCreationParams(), + ); + + final AndroidWebViewCookieManager androidCookieManager = + AndroidWebViewCookieManager(params, cookieManager: MockCookieManager()); + + expect( + () => androidCookieManager.setCookie( + const WebViewCookie( + name: 'foo', + value: 'bar', + domain: 'flutter.dev', + path: 'invalid;path', + ), + ), + throwsA(const TypeMatcher()), + ); + }); + + test( + 'setCookie should call android_webview.setCookie with properly formatted cookie value', + () { + final android_webview.CookieManager mockCookieManager = + MockCookieManager(); + final AndroidWebViewCookieManagerCreationParams params = + AndroidWebViewCookieManagerCreationParams.fromPlatformWebViewCookieManagerCreationParams( + const PlatformWebViewCookieManagerCreationParams(), + ); + + AndroidWebViewCookieManager( + params, + cookieManager: mockCookieManager, + ).setCookie( + const WebViewCookie(name: 'foo&', value: 'bar@', domain: 'flutter.dev'), + ); + + verify( + mockCookieManager.setCookie('flutter.dev', 'foo%26=bar%40; path=/'), + ); + }, + ); + + test('setAcceptThirdPartyCookies', () async { + final MockAndroidWebViewController mockController = + MockAndroidWebViewController(); + + final android_webview.PigeonInstanceManager instanceManager = + android_webview.PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + final android_webview.WebView webView = android_webview + .WebView.pigeon_detached(pigeon_instanceManager: instanceManager); + + final int webViewIdentifier = instanceManager.addDartCreatedInstance( + webView, + ); + + when(mockController.webViewIdentifier).thenReturn(webViewIdentifier); + + final AndroidWebViewCookieManagerCreationParams params = + AndroidWebViewCookieManagerCreationParams.fromPlatformWebViewCookieManagerCreationParams( + const PlatformWebViewCookieManagerCreationParams(), + ); + + final MockCookieManager mockCookieManager = MockCookieManager(); + // ignore: invalid_use_of_protected_member + when(mockCookieManager.pigeon_instanceManager).thenReturn(instanceManager); + + await AndroidWebViewCookieManager( + params, + cookieManager: mockCookieManager, + ).setAcceptThirdPartyCookies(mockController, false); + + verify(mockCookieManager.setAcceptThirdPartyCookies(webView, false)); + }); +} diff --git a/local_packages/webview_flutter_android-4.10.5/test/android_webview_cookie_manager_test.mocks.dart b/local_packages/webview_flutter_android-4.10.5/test/android_webview_cookie_manager_test.mocks.dart new file mode 100644 index 0000000..da53a4d --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/test/android_webview_cookie_manager_test.mocks.dart @@ -0,0 +1,630 @@ +// Mocks generated by Mockito 5.4.6 from annotations +// in webview_flutter_android/test/android_webview_cookie_manager_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i5; +import 'dart:ui' as _i4; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:webview_flutter_android/src/android_webkit.g.dart' as _i2; +import 'package:webview_flutter_android/src/android_webview_controller.dart' + as _i6; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart' + as _i3; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakePigeonInstanceManager_0 extends _i1.SmartFake + implements _i2.PigeonInstanceManager { + _FakePigeonInstanceManager_0(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeCookieManager_1 extends _i1.SmartFake implements _i2.CookieManager { + _FakeCookieManager_1(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakePlatformWebViewControllerCreationParams_2 extends _i1.SmartFake + implements _i3.PlatformWebViewControllerCreationParams { + _FakePlatformWebViewControllerCreationParams_2( + Object parent, + Invocation parentInvocation, + ) : super(parent, parentInvocation); +} + +class _FakeObject_3 extends _i1.SmartFake implements Object { + _FakeObject_3(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeOffset_4 extends _i1.SmartFake implements _i4.Offset { + _FakeOffset_4(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +/// A class which mocks [CookieManager]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCookieManager extends _i1.Mock implements _i2.CookieManager { + MockCookieManager() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i5.Future setCookie(String? url, String? value) => + (super.noSuchMethod( + Invocation.method(#setCookie, [url, value]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future removeAllCookies() => + (super.noSuchMethod( + Invocation.method(#removeAllCookies, []), + returnValue: _i5.Future.value(false), + ) + as _i5.Future); + + @override + _i5.Future setAcceptThirdPartyCookies( + _i2.WebView? webView, + bool? accept, + ) => + (super.noSuchMethod( + Invocation.method(#setAcceptThirdPartyCookies, [webView, accept]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i2.CookieManager pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeCookieManager_1( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.CookieManager); +} + +/// A class which mocks [AndroidWebViewController]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAndroidWebViewController extends _i1.Mock + implements _i6.AndroidWebViewController { + MockAndroidWebViewController() { + _i1.throwOnMissingStub(this); + } + + @override + int get webViewIdentifier => + (super.noSuchMethod(Invocation.getter(#webViewIdentifier), returnValue: 0) + as int); + + @override + _i3.PlatformWebViewControllerCreationParams get params => + (super.noSuchMethod( + Invocation.getter(#params), + returnValue: _FakePlatformWebViewControllerCreationParams_2( + this, + Invocation.getter(#params), + ), + ) + as _i3.PlatformWebViewControllerCreationParams); + + @override + _i5.Future setAllowFileAccess(bool? allow) => + (super.noSuchMethod( + Invocation.method(#setAllowFileAccess, [allow]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future loadFile(String? absoluteFilePath) => + (super.noSuchMethod( + Invocation.method(#loadFile, [absoluteFilePath]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future loadFileWithParams(_i3.LoadFileParams? params) => + (super.noSuchMethod( + Invocation.method(#loadFileWithParams, [params]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future loadFlutterAsset(String? key) => + (super.noSuchMethod( + Invocation.method(#loadFlutterAsset, [key]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future loadHtmlString(String? html, {String? baseUrl}) => + (super.noSuchMethod( + Invocation.method(#loadHtmlString, [html], {#baseUrl: baseUrl}), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future loadRequest(_i3.LoadRequestParams? params) => + (super.noSuchMethod( + Invocation.method(#loadRequest, [params]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future currentUrl() => + (super.noSuchMethod( + Invocation.method(#currentUrl, []), + returnValue: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future canGoBack() => + (super.noSuchMethod( + Invocation.method(#canGoBack, []), + returnValue: _i5.Future.value(false), + ) + as _i5.Future); + + @override + _i5.Future canGoForward() => + (super.noSuchMethod( + Invocation.method(#canGoForward, []), + returnValue: _i5.Future.value(false), + ) + as _i5.Future); + + @override + _i5.Future goBack() => + (super.noSuchMethod( + Invocation.method(#goBack, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future goForward() => + (super.noSuchMethod( + Invocation.method(#goForward, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future reload() => + (super.noSuchMethod( + Invocation.method(#reload, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future clearCache() => + (super.noSuchMethod( + Invocation.method(#clearCache, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future clearLocalStorage() => + (super.noSuchMethod( + Invocation.method(#clearLocalStorage, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setPlatformNavigationDelegate( + _i3.PlatformNavigationDelegate? handler, + ) => + (super.noSuchMethod( + Invocation.method(#setPlatformNavigationDelegate, [handler]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future runJavaScript(String? javaScript) => + (super.noSuchMethod( + Invocation.method(#runJavaScript, [javaScript]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future runJavaScriptReturningResult(String? javaScript) => + (super.noSuchMethod( + Invocation.method(#runJavaScriptReturningResult, [javaScript]), + returnValue: _i5.Future.value( + _FakeObject_3( + this, + Invocation.method(#runJavaScriptReturningResult, [javaScript]), + ), + ), + ) + as _i5.Future); + + @override + _i5.Future addJavaScriptChannel( + _i3.JavaScriptChannelParams? javaScriptChannelParams, + ) => + (super.noSuchMethod( + Invocation.method(#addJavaScriptChannel, [javaScriptChannelParams]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future removeJavaScriptChannel(String? javaScriptChannelName) => + (super.noSuchMethod( + Invocation.method(#removeJavaScriptChannel, [ + javaScriptChannelName, + ]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future getTitle() => + (super.noSuchMethod( + Invocation.method(#getTitle, []), + returnValue: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future scrollTo(int? x, int? y) => + (super.noSuchMethod( + Invocation.method(#scrollTo, [x, y]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future scrollBy(int? x, int? y) => + (super.noSuchMethod( + Invocation.method(#scrollBy, [x, y]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future<_i4.Offset> getScrollPosition() => + (super.noSuchMethod( + Invocation.method(#getScrollPosition, []), + returnValue: _i5.Future<_i4.Offset>.value( + _FakeOffset_4(this, Invocation.method(#getScrollPosition, [])), + ), + ) + as _i5.Future<_i4.Offset>); + + @override + _i5.Future enableZoom(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#enableZoom, [enabled]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setBackgroundColor(_i4.Color? color) => + (super.noSuchMethod( + Invocation.method(#setBackgroundColor, [color]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setJavaScriptMode(_i3.JavaScriptMode? javaScriptMode) => + (super.noSuchMethod( + Invocation.method(#setJavaScriptMode, [javaScriptMode]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setUserAgent(String? userAgent) => + (super.noSuchMethod( + Invocation.method(#setUserAgent, [userAgent]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setOnScrollPositionChange( + void Function(_i3.ScrollPositionChange)? onScrollPositionChange, + ) => + (super.noSuchMethod( + Invocation.method(#setOnScrollPositionChange, [ + onScrollPositionChange, + ]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setMediaPlaybackRequiresUserGesture(bool? require) => + (super.noSuchMethod( + Invocation.method(#setMediaPlaybackRequiresUserGesture, [require]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setTextZoom(int? textZoom) => + (super.noSuchMethod( + Invocation.method(#setTextZoom, [textZoom]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setUseWideViewPort(bool? use) => + (super.noSuchMethod( + Invocation.method(#setUseWideViewPort, [use]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setAllowContentAccess(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setAllowContentAccess, [enabled]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setGeolocationEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setGeolocationEnabled, [enabled]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setOnShowFileSelector( + _i5.Future> Function(_i6.FileSelectorParams)? + onShowFileSelector, + ) => + (super.noSuchMethod( + Invocation.method(#setOnShowFileSelector, [onShowFileSelector]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setOnPlatformPermissionRequest( + void Function(_i3.PlatformWebViewPermissionRequest)? onPermissionRequest, + ) => + (super.noSuchMethod( + Invocation.method(#setOnPlatformPermissionRequest, [ + onPermissionRequest, + ]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setGeolocationPermissionsPromptCallbacks({ + _i6.OnGeolocationPermissionsShowPrompt? onShowPrompt, + _i6.OnGeolocationPermissionsHidePrompt? onHidePrompt, + }) => + (super.noSuchMethod( + Invocation.method(#setGeolocationPermissionsPromptCallbacks, [], { + #onShowPrompt: onShowPrompt, + #onHidePrompt: onHidePrompt, + }), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setCustomWidgetCallbacks({ + required _i6.OnShowCustomWidgetCallback? onShowCustomWidget, + required _i6.OnHideCustomWidgetCallback? onHideCustomWidget, + }) => + (super.noSuchMethod( + Invocation.method(#setCustomWidgetCallbacks, [], { + #onShowCustomWidget: onShowCustomWidget, + #onHideCustomWidget: onHideCustomWidget, + }), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setOnConsoleMessage( + void Function(_i3.JavaScriptConsoleMessage)? onConsoleMessage, + ) => + (super.noSuchMethod( + Invocation.method(#setOnConsoleMessage, [onConsoleMessage]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future getUserAgent() => + (super.noSuchMethod( + Invocation.method(#getUserAgent, []), + returnValue: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setOnJavaScriptAlertDialog( + _i5.Future Function(_i3.JavaScriptAlertDialogRequest)? + onJavaScriptAlertDialog, + ) => + (super.noSuchMethod( + Invocation.method(#setOnJavaScriptAlertDialog, [ + onJavaScriptAlertDialog, + ]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setOnJavaScriptConfirmDialog( + _i5.Future Function(_i3.JavaScriptConfirmDialogRequest)? + onJavaScriptConfirmDialog, + ) => + (super.noSuchMethod( + Invocation.method(#setOnJavaScriptConfirmDialog, [ + onJavaScriptConfirmDialog, + ]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setOnJavaScriptTextInputDialog( + _i5.Future Function(_i3.JavaScriptTextInputDialogRequest)? + onJavaScriptTextInputDialog, + ) => + (super.noSuchMethod( + Invocation.method(#setOnJavaScriptTextInputDialog, [ + onJavaScriptTextInputDialog, + ]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setVerticalScrollBarEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setVerticalScrollBarEnabled, [enabled]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setHorizontalScrollBarEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setHorizontalScrollBarEnabled, [enabled]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + bool supportsSetScrollBarsEnabled() => + (super.noSuchMethod( + Invocation.method(#supportsSetScrollBarsEnabled, []), + returnValue: false, + ) + as bool); + + @override + _i5.Future setOverScrollMode(_i3.WebViewOverScrollMode? mode) => + (super.noSuchMethod( + Invocation.method(#setOverScrollMode, [mode]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setMixedContentMode(_i6.MixedContentMode? mode) => + (super.noSuchMethod( + Invocation.method(#setMixedContentMode, [mode]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future isWebViewFeatureSupported( + _i6.WebViewFeatureType? featureType, + ) => + (super.noSuchMethod( + Invocation.method(#isWebViewFeatureSupported, [featureType]), + returnValue: _i5.Future.value(false), + ) + as _i5.Future); + + @override + _i5.Future setPaymentRequestEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setPaymentRequestEnabled, [enabled]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); +} diff --git a/local_packages/webview_flutter_android-4.10.5/test/legacy/surface_android_test.dart b/local_packages/webview_flutter_android-4.10.5/test/legacy/surface_android_test.dart new file mode 100644 index 0000000..14570fb --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/test/legacy/surface_android_test.dart @@ -0,0 +1,132 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:webview_flutter_android/src/legacy/webview_surface_android.dart'; +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('SurfaceAndroidWebView', () { + late List log; + + setUpAll(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(SystemChannels.platform_views, ( + MethodCall call, + ) async { + log.add(call); + if (call.method == 'resize') { + final Map arguments = + (call.arguments as Map) + .cast(); + return { + 'width': arguments['width'], + 'height': arguments['height'], + }; + } + return null; + }); + }); + + tearDownAll(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(SystemChannels.platform_views, null); + }); + + setUp(() { + log = []; + }); + + testWidgets( + 'uses hybrid composition when background color is not 100% opaque', + (WidgetTester tester) async { + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return SurfaceAndroidWebView().build( + context: context, + creationParams: CreationParams( + backgroundColor: Colors.transparent, + webSettings: WebSettings( + userAgent: const WebSetting.absent(), + hasNavigationDelegate: false, + ), + ), + javascriptChannelRegistry: JavascriptChannelRegistry(null), + webViewPlatformCallbacksHandler: + TestWebViewPlatformCallbacksHandler(), + ); + }, + ), + ); + await tester.pumpAndSettle(); + + final MethodCall createMethodCall = log[0]; + expect(createMethodCall.method, 'create'); + expect(createMethodCall.arguments, containsPair('hybrid', true)); + }, + ); + + testWidgets('default text direction is ltr', (WidgetTester tester) async { + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return SurfaceAndroidWebView().build( + context: context, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: const WebSetting.absent(), + hasNavigationDelegate: false, + ), + ), + javascriptChannelRegistry: JavascriptChannelRegistry(null), + webViewPlatformCallbacksHandler: + TestWebViewPlatformCallbacksHandler(), + ); + }, + ), + ); + await tester.pumpAndSettle(); + + final MethodCall createMethodCall = log[0]; + expect(createMethodCall.method, 'create'); + expect( + createMethodCall.arguments, + containsPair( + 'direction', + AndroidViewController.kAndroidLayoutDirectionLtr, + ), + ); + }); + }); +} + +class TestWebViewPlatformCallbacksHandler + implements WebViewPlatformCallbacksHandler { + @override + FutureOr onNavigationRequest({ + required String url, + required bool isForMainFrame, + }) { + throw UnimplementedError(); + } + + @override + void onPageFinished(String url) {} + + @override + void onPageStarted(String url) {} + + @override + void onProgress(int progress) {} + + @override + void onWebResourceError(WebResourceError error) {} +} diff --git a/local_packages/webview_flutter_android-4.10.5/test/legacy/webview_android_cookie_manager_test.dart b/local_packages/webview_flutter_android-4.10.5/test/legacy/webview_android_cookie_manager_test.dart new file mode 100644 index 0000000..9b1881d --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/test/legacy/webview_android_cookie_manager_test.dart @@ -0,0 +1,57 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:webview_flutter_android/src/android_webkit.g.dart' + as android_webview; +import 'package:webview_flutter_android/src/legacy/webview_android_cookie_manager.dart'; +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; + +import 'webview_android_cookie_manager_test.mocks.dart'; + +@GenerateMocks([android_webview.CookieManager]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + test('clearCookies should call android_webview.clearCookies', () { + final MockCookieManager mockCookieManager = MockCookieManager(); + when( + mockCookieManager.removeAllCookies(), + ).thenAnswer((_) => Future.value(true)); + WebViewAndroidCookieManager( + cookieManager: mockCookieManager, + ).clearCookies(); + verify(mockCookieManager.removeAllCookies()); + }); + + test('setCookie should throw ArgumentError for cookie with invalid path', () { + expect( + () => WebViewAndroidCookieManager(cookieManager: MockCookieManager()) + .setCookie( + const WebViewCookie( + name: 'foo', + value: 'bar', + domain: 'flutter.dev', + path: 'invalid;path', + ), + ), + throwsA(const TypeMatcher()), + ); + }); + + test( + 'setCookie should call android_webview.csetCookie with properly formatted cookie value', + () { + final MockCookieManager mockCookieManager = MockCookieManager(); + WebViewAndroidCookieManager(cookieManager: mockCookieManager).setCookie( + const WebViewCookie(name: 'foo&', value: 'bar@', domain: 'flutter.dev'), + ); + verify( + mockCookieManager.setCookie('flutter.dev', 'foo%26=bar%40; path=/'), + ); + }, + ); +} diff --git a/local_packages/webview_flutter_android-4.10.5/test/legacy/webview_android_cookie_manager_test.mocks.dart b/local_packages/webview_flutter_android-4.10.5/test/legacy/webview_android_cookie_manager_test.mocks.dart new file mode 100644 index 0000000..7b26a90 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/test/legacy/webview_android_cookie_manager_test.mocks.dart @@ -0,0 +1,94 @@ +// Mocks generated by Mockito 5.4.6 from annotations +// in webview_flutter_android/test/legacy/webview_android_cookie_manager_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:webview_flutter_android/src/android_webkit.g.dart' as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakePigeonInstanceManager_0 extends _i1.SmartFake + implements _i2.PigeonInstanceManager { + _FakePigeonInstanceManager_0(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeCookieManager_1 extends _i1.SmartFake implements _i2.CookieManager { + _FakeCookieManager_1(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +/// A class which mocks [CookieManager]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCookieManager extends _i1.Mock implements _i2.CookieManager { + MockCookieManager() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i3.Future setCookie(String? url, String? value) => + (super.noSuchMethod( + Invocation.method(#setCookie, [url, value]), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i3.Future removeAllCookies() => + (super.noSuchMethod( + Invocation.method(#removeAllCookies, []), + returnValue: _i3.Future.value(false), + ) + as _i3.Future); + + @override + _i3.Future setAcceptThirdPartyCookies( + _i2.WebView? webView, + bool? accept, + ) => + (super.noSuchMethod( + Invocation.method(#setAcceptThirdPartyCookies, [webView, accept]), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) + as _i3.Future); + + @override + _i2.CookieManager pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeCookieManager_1( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.CookieManager); +} diff --git a/local_packages/webview_flutter_android-4.10.5/test/legacy/webview_android_widget_test.dart b/local_packages/webview_flutter_android-4.10.5/test/legacy/webview_android_widget_test.dart new file mode 100644 index 0000000..10fa642 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/test/legacy/webview_android_widget_test.dart @@ -0,0 +1,1016 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:typed_data'; + +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:webview_flutter_android/src/android_webkit.g.dart' + as android_webview; +import 'package:webview_flutter_android/src/android_webkit_constants.dart'; +import 'package:webview_flutter_android/src/legacy/webview_android_widget.dart'; +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; + +import 'webview_android_widget_test.mocks.dart'; + +@GenerateMocks([ + android_webview.FlutterAssetManager, + android_webview.WebSettings, + android_webview.WebStorage, + android_webview.WebView, + android_webview.WebResourceRequest, + android_webview.DownloadListener, + WebViewAndroidJavaScriptChannel, + android_webview.WebChromeClient, + android_webview.WebViewClient, + JavascriptChannelRegistry, + WebViewPlatformCallbacksHandler, + WebViewProxy, +]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('WebViewAndroidWidget', () { + late MockFlutterAssetManager mockFlutterAssetManager; + late MockWebView mockWebView; + late MockWebSettings mockWebSettings; + late MockWebStorage mockWebStorage; + late MockWebViewProxy mockWebViewProxy; + + late MockWebViewPlatformCallbacksHandler mockCallbacksHandler; + late MockWebViewClient mockWebViewClient; + late android_webview.DownloadListener downloadListener; + late android_webview.WebChromeClient webChromeClient; + + late MockJavascriptChannelRegistry mockJavascriptChannelRegistry; + + late WebViewAndroidPlatformController testController; + + setUp(() { + mockFlutterAssetManager = MockFlutterAssetManager(); + mockWebView = MockWebView(); + mockWebSettings = MockWebSettings(); + mockWebStorage = MockWebStorage(); + mockWebViewClient = MockWebViewClient(); + when(mockWebView.settings).thenReturn(mockWebSettings); + + mockWebViewProxy = MockWebViewProxy(); + when(mockWebViewProxy.createWebView()).thenReturn(mockWebView); + when( + mockWebViewProxy.createWebViewClient( + onPageStarted: anyNamed('onPageStarted'), + onPageFinished: anyNamed('onPageFinished'), + onReceivedRequestError: anyNamed('onReceivedRequestError'), + requestLoading: anyNamed('requestLoading'), + urlLoading: anyNamed('urlLoading'), + onReceivedSslError: anyNamed('onReceivedSslError'), + onFormResubmission: anyNamed('onFormResubmission'), + onReceivedClientCertRequest: anyNamed('onReceivedClientCertRequest'), + ), + ).thenReturn(mockWebViewClient); + + mockCallbacksHandler = MockWebViewPlatformCallbacksHandler(); + mockJavascriptChannelRegistry = MockJavascriptChannelRegistry(); + }); + + // Builds a AndroidWebViewWidget with default parameters. + Future buildWidget( + WidgetTester tester, { + CreationParams? creationParams, + bool hasNavigationDelegate = false, + bool hasProgressTracking = false, + bool useHybridComposition = false, + }) async { + await tester.pumpWidget( + WebViewAndroidWidget( + creationParams: + creationParams ?? + CreationParams( + webSettings: WebSettings( + userAgent: const WebSetting.absent(), + hasNavigationDelegate: hasNavigationDelegate, + hasProgressTracking: hasProgressTracking, + ), + ), + callbacksHandler: mockCallbacksHandler, + javascriptChannelRegistry: mockJavascriptChannelRegistry, + webViewProxy: mockWebViewProxy, + flutterAssetManager: mockFlutterAssetManager, + webStorage: mockWebStorage, + onBuildWidget: (WebViewAndroidPlatformController controller) { + testController = controller; + return Container(); + }, + ), + ); + + mockWebViewClient = testController.webViewClient as MockWebViewClient; + downloadListener = testController.downloadListener; + webChromeClient = testController.webChromeClient; + } + + testWidgets('WebViewAndroidWidget', (WidgetTester tester) async { + await buildWidget(tester); + + verify(mockWebSettings.setDomStorageEnabled(true)); + verify(mockWebSettings.setJavaScriptCanOpenWindowsAutomatically(true)); + verify(mockWebSettings.setSupportMultipleWindows(true)); + verify(mockWebSettings.setLoadWithOverviewMode(true)); + verify(mockWebSettings.setUseWideViewPort(true)); + verify(mockWebSettings.setDisplayZoomControls(false)); + verify(mockWebSettings.setBuiltInZoomControls(true)); + + verifyInOrder(>[ + mockWebView.setDownloadListener(downloadListener), + mockWebView.setWebChromeClient(webChromeClient), + mockWebView.setWebViewClient(mockWebViewClient), + ]); + }); + + testWidgets('Create Widget with Hybrid Composition', ( + WidgetTester tester, + ) async { + await buildWidget(tester, useHybridComposition: true); + verify(mockWebViewProxy.createWebView()); + }); + + group('CreationParams', () { + testWidgets('initialUrl', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + initialUrl: 'https://www.google.com', + webSettings: WebSettings( + userAgent: const WebSetting.absent(), + hasNavigationDelegate: false, + ), + ), + ); + verify( + mockWebView.loadUrl('https://www.google.com', {}), + ); + }); + + testWidgets('userAgent', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + userAgent: 'MyUserAgent', + webSettings: WebSettings( + userAgent: const WebSetting.absent(), + hasNavigationDelegate: false, + ), + ), + ); + + verify(mockWebSettings.setUserAgentString('MyUserAgent')); + }); + + testWidgets('autoMediaPlaybackPolicy true', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: const WebSetting.absent(), + hasNavigationDelegate: false, + ), + ), + ); + + verify(mockWebSettings.setMediaPlaybackRequiresUserGesture(any)); + }); + + testWidgets('autoMediaPlaybackPolicy false', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + autoMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, + webSettings: WebSettings( + userAgent: const WebSetting.absent(), + hasNavigationDelegate: false, + ), + ), + ); + + verify(mockWebSettings.setMediaPlaybackRequiresUserGesture(false)); + }); + + testWidgets('javascriptChannelNames', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + javascriptChannelNames: {'a', 'b'}, + webSettings: WebSettings( + userAgent: const WebSetting.absent(), + hasNavigationDelegate: false, + ), + ), + ); + + final List javaScriptChannels = + verify( + mockWebView.addJavaScriptChannel(captureAny), + ).captured.cast(); + expect(javaScriptChannels[0].channelName, 'a'); + expect(javaScriptChannels[1].channelName, 'b'); + }); + + group('WebSettings', () { + testWidgets('javascriptMode', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: const WebSetting.absent(), + javascriptMode: JavascriptMode.unrestricted, + hasNavigationDelegate: false, + ), + ), + ); + + verify(mockWebSettings.setJavaScriptEnabled(true)); + }); + + testWidgets('hasNavigationDelegate', (WidgetTester tester) async { + final MockWebViewClient mockWebViewClient = MockWebViewClient(); + when( + mockWebViewProxy.createWebViewClient( + onPageStarted: anyNamed('onPageStarted'), + onPageFinished: anyNamed('onPageFinished'), + onReceivedRequestError: anyNamed('onReceivedRequestError'), + requestLoading: anyNamed('requestLoading'), + urlLoading: anyNamed('urlLoading'), + onReceivedSslError: anyNamed('onReceivedSslError'), + onFormResubmission: anyNamed('onFormResubmission'), + onReceivedClientCertRequest: anyNamed( + 'onReceivedClientCertRequest', + ), + ), + ).thenReturn(mockWebViewClient); + + await buildWidget( + tester, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: const WebSetting.absent(), + hasNavigationDelegate: true, + ), + ), + ); + + verify( + mockWebViewClient + .setSynchronousReturnValueForShouldOverrideUrlLoading(true), + ); + }); + + testWidgets('debuggingEnabled true', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: const WebSetting.absent(), + debuggingEnabled: true, + hasNavigationDelegate: false, + ), + ), + ); + + verify(mockWebViewProxy.setWebContentsDebuggingEnabled(true)); + }); + + testWidgets('debuggingEnabled false', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: const WebSetting.absent(), + debuggingEnabled: false, + hasNavigationDelegate: false, + ), + ), + ); + + verify(mockWebViewProxy.setWebContentsDebuggingEnabled(false)); + }); + + testWidgets('userAgent', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: const WebSetting.of('myUserAgent'), + hasNavigationDelegate: false, + ), + ), + ); + + verify(mockWebSettings.setUserAgentString('myUserAgent')); + }); + + testWidgets('zoomEnabled', (WidgetTester tester) async { + await buildWidget( + tester, + creationParams: CreationParams( + webSettings: WebSettings( + userAgent: const WebSetting.absent(), + zoomEnabled: false, + hasNavigationDelegate: false, + ), + ), + ); + + verify(mockWebSettings.setSupportZoom(false)); + }); + }); + }); + + group('WebViewPlatformController', () { + testWidgets('loadFile without "file://" prefix', ( + WidgetTester tester, + ) async { + await buildWidget(tester); + + const String filePath = '/path/to/file.html'; + await testController.loadFile(filePath); + + verify(mockWebView.loadUrl('file://$filePath', {})); + }); + + testWidgets('loadFile with "file://" prefix', ( + WidgetTester tester, + ) async { + await buildWidget(tester); + + await testController.loadFile('file:///path/to/file.html'); + + verify( + mockWebView.loadUrl('file:///path/to/file.html', {}), + ); + }); + + testWidgets('loadFile should setAllowFileAccess to true', ( + WidgetTester tester, + ) async { + await buildWidget(tester); + + await testController.loadFile('file:///path/to/file.html'); + + verify(mockWebSettings.setAllowFileAccess(true)); + }); + + testWidgets('loadFlutterAsset', (WidgetTester tester) async { + await buildWidget(tester); + const String assetKey = 'test_assets/index.html'; + + when( + mockFlutterAssetManager.getAssetFilePathByName(assetKey), + ).thenAnswer((_) => Future.value('flutter_assets/$assetKey')); + when( + mockFlutterAssetManager.list('flutter_assets/test_assets'), + ).thenAnswer((_) => Future>.value(['index.html'])); + + await testController.loadFlutterAsset(assetKey); + + verify( + mockWebView.loadUrl( + 'file:///android_asset/flutter_assets/$assetKey', + {}, + ), + ); + }); + + testWidgets('loadFlutterAsset with file in root', ( + WidgetTester tester, + ) async { + await buildWidget(tester); + const String assetKey = 'index.html'; + + when( + mockFlutterAssetManager.getAssetFilePathByName(assetKey), + ).thenAnswer((_) => Future.value('flutter_assets/$assetKey')); + when( + mockFlutterAssetManager.list('flutter_assets'), + ).thenAnswer((_) => Future>.value(['index.html'])); + + await testController.loadFlutterAsset(assetKey); + + verify( + mockWebView.loadUrl( + 'file:///android_asset/flutter_assets/$assetKey', + {}, + ), + ); + }); + + testWidgets( + 'loadFlutterAsset throws ArgumentError when asset does not exist', + (WidgetTester tester) async { + await buildWidget(tester); + const String assetKey = 'test_assets/index.html'; + + when( + mockFlutterAssetManager.getAssetFilePathByName(assetKey), + ).thenAnswer((_) => Future.value('flutter_assets/$assetKey')); + when( + mockFlutterAssetManager.list('flutter_assets/test_assets'), + ).thenAnswer((_) => Future>.value([''])); + + expect( + () => testController.loadFlutterAsset(assetKey), + throwsA( + isA() + .having((ArgumentError error) => error.name, 'name', 'key') + .having( + (ArgumentError error) => error.message, + 'message', + 'Asset for key "$assetKey" not found.', + ), + ), + ); + }, + ); + + testWidgets('loadHtmlString without base URL', ( + WidgetTester tester, + ) async { + await buildWidget(tester); + + const String htmlString = + 'Test data.'; + await testController.loadHtmlString(htmlString); + + verify( + mockWebView.loadDataWithBaseUrl( + null, + htmlString, + 'text/html', + null, + null, + ), + ); + }); + + testWidgets('loadHtmlString with base URL', (WidgetTester tester) async { + await buildWidget(tester); + + const String htmlString = + 'Test data.'; + await testController.loadHtmlString( + htmlString, + baseUrl: 'https://flutter.dev', + ); + + verify( + mockWebView.loadDataWithBaseUrl( + 'https://flutter.dev', + htmlString, + 'text/html', + null, + null, + ), + ); + }); + + testWidgets('loadUrl', (WidgetTester tester) async { + await buildWidget(tester); + + await testController.loadUrl('https://www.google.com', { + 'a': 'header', + }); + + verify( + mockWebView.loadUrl('https://www.google.com', { + 'a': 'header', + }), + ); + }); + + group('loadRequest', () { + testWidgets('Throws ArgumentError for empty scheme', ( + WidgetTester tester, + ) async { + await buildWidget(tester); + + expect( + () async => testController.loadRequest( + WebViewRequest( + uri: Uri.parse('www.google.com'), + method: WebViewRequestMethod.get, + ), + ), + throwsA(const TypeMatcher()), + ); + }); + + testWidgets('GET without headers', (WidgetTester tester) async { + await buildWidget(tester); + + await testController.loadRequest( + WebViewRequest( + uri: Uri.parse('https://www.google.com'), + method: WebViewRequestMethod.get, + ), + ); + + verify( + mockWebView.loadUrl('https://www.google.com', {}), + ); + }); + + testWidgets('GET with headers', (WidgetTester tester) async { + await buildWidget(tester); + + await testController.loadRequest( + WebViewRequest( + uri: Uri.parse('https://www.google.com'), + method: WebViewRequestMethod.get, + headers: {'a': 'header'}, + ), + ); + + verify( + mockWebView.loadUrl('https://www.google.com', { + 'a': 'header', + }), + ); + }); + + testWidgets('POST without body', (WidgetTester tester) async { + await buildWidget(tester); + + await testController.loadRequest( + WebViewRequest( + uri: Uri.parse('https://www.google.com'), + method: WebViewRequestMethod.post, + ), + ); + + verify(mockWebView.postUrl('https://www.google.com', Uint8List(0))); + }); + + testWidgets('POST with body', (WidgetTester tester) async { + await buildWidget(tester); + + final Uint8List body = Uint8List.fromList('Test Body'.codeUnits); + + await testController.loadRequest( + WebViewRequest( + uri: Uri.parse('https://www.google.com'), + method: WebViewRequestMethod.post, + body: body, + ), + ); + + verify(mockWebView.postUrl('https://www.google.com', body)); + }); + }); + + testWidgets('no update to userAgentString when there is no change', ( + WidgetTester tester, + ) async { + await buildWidget(tester); + + reset(mockWebSettings); + + await testController.updateSettings( + WebSettings(userAgent: const WebSetting.absent()), + ); + + verifyNever(mockWebSettings.setUserAgentString(any)); + }); + + testWidgets('update null userAgentString with empty string', ( + WidgetTester tester, + ) async { + await buildWidget(tester); + + reset(mockWebSettings); + + await testController.updateSettings( + WebSettings(userAgent: const WebSetting.of(null)), + ); + + verify(mockWebSettings.setUserAgentString('')); + }); + + testWidgets('currentUrl', (WidgetTester tester) async { + await buildWidget(tester); + + when( + mockWebView.getUrl(), + ).thenAnswer((_) => Future.value('https://www.google.com')); + expect( + testController.currentUrl(), + completion('https://www.google.com'), + ); + }); + + testWidgets('canGoBack', (WidgetTester tester) async { + await buildWidget(tester); + + when( + mockWebView.canGoBack(), + ).thenAnswer((_) => Future.value(false)); + expect(testController.canGoBack(), completion(false)); + }); + + testWidgets('canGoForward', (WidgetTester tester) async { + await buildWidget(tester); + + when( + mockWebView.canGoForward(), + ).thenAnswer((_) => Future.value(true)); + expect(testController.canGoForward(), completion(true)); + }); + + testWidgets('goBack', (WidgetTester tester) async { + await buildWidget(tester); + + await testController.goBack(); + verify(mockWebView.goBack()); + }); + + testWidgets('goForward', (WidgetTester tester) async { + await buildWidget(tester); + + await testController.goForward(); + verify(mockWebView.goForward()); + }); + + testWidgets('reload', (WidgetTester tester) async { + await buildWidget(tester); + + await testController.reload(); + verify(mockWebView.reload()); + }); + + testWidgets('clearCache', (WidgetTester tester) async { + await buildWidget(tester); + + await testController.clearCache(); + verify(mockWebView.clearCache(true)); + verify(mockWebStorage.deleteAllData()); + }); + + testWidgets('evaluateJavascript', (WidgetTester tester) async { + await buildWidget(tester); + + when( + mockWebView.evaluateJavascript('runJavaScript'), + ).thenAnswer((_) => Future.value('returnString')); + expect( + testController.evaluateJavascript('runJavaScript'), + completion('returnString'), + ); + }); + + testWidgets('runJavascriptReturningResult', (WidgetTester tester) async { + await buildWidget(tester); + + when( + mockWebView.evaluateJavascript('runJavaScript'), + ).thenAnswer((_) => Future.value('returnString')); + expect( + testController.runJavascriptReturningResult('runJavaScript'), + completion('returnString'), + ); + }); + + testWidgets('runJavascript', (WidgetTester tester) async { + await buildWidget(tester); + + when( + mockWebView.evaluateJavascript('runJavaScript'), + ).thenAnswer((_) => Future.value('returnString')); + expect(testController.runJavascript('runJavaScript'), completes); + }); + + testWidgets('addJavascriptChannels', (WidgetTester tester) async { + await buildWidget(tester); + + await testController.addJavascriptChannels({'c', 'd'}); + final List javaScriptChannels = + verify( + mockWebView.addJavaScriptChannel(captureAny), + ).captured.cast(); + expect(javaScriptChannels[0].channelName, 'c'); + expect(javaScriptChannels[1].channelName, 'd'); + }); + + testWidgets('removeJavascriptChannels', (WidgetTester tester) async { + await buildWidget(tester); + + await testController.addJavascriptChannels({'c', 'd'}); + await testController.removeJavascriptChannels({'c', 'd'}); + verify(mockWebView.removeJavaScriptChannel('c')); + verify(mockWebView.removeJavaScriptChannel('d')); + }); + + testWidgets('getTitle', (WidgetTester tester) async { + await buildWidget(tester); + + when( + mockWebView.getTitle(), + ).thenAnswer((_) => Future.value('Web Title')); + expect(testController.getTitle(), completion('Web Title')); + }); + + testWidgets('scrollTo', (WidgetTester tester) async { + await buildWidget(tester); + + await testController.scrollTo(1, 2); + verify(mockWebView.scrollTo(1, 2)); + }); + + testWidgets('scrollBy', (WidgetTester tester) async { + await buildWidget(tester); + + await testController.scrollBy(3, 4); + verify(mockWebView.scrollBy(3, 4)); + }); + + testWidgets('getScrollX', (WidgetTester tester) async { + await buildWidget(tester); + + when(mockWebView.getScrollPosition()).thenAnswer( + (_) => Future.value( + android_webview.WebViewPoint.pigeon_detached(x: 23, y: 24), + ), + ); + expect(testController.getScrollX(), completion(23)); + }); + + testWidgets('getScrollY', (WidgetTester tester) async { + await buildWidget(tester); + + when(mockWebView.getScrollPosition()).thenAnswer( + (_) => Future.value( + android_webview.WebViewPoint.pigeon_detached(x: 23, y: 25), + ), + ); + expect(testController.getScrollY(), completion(25)); + }); + }); + + group('WebViewPlatformCallbacksHandler', () { + testWidgets('onPageStarted', (WidgetTester tester) async { + await buildWidget(tester); + final void Function( + android_webview.WebViewClient, + android_webview.WebView, + String, + ) + onPageStarted = + verify( + mockWebViewProxy.createWebViewClient( + onPageStarted: captureAnyNamed('onPageStarted'), + onPageFinished: anyNamed('onPageFinished'), + onReceivedRequestError: anyNamed('onReceivedRequestError'), + requestLoading: anyNamed('requestLoading'), + urlLoading: anyNamed('urlLoading'), + onReceivedSslError: anyNamed('onReceivedSslError'), + onFormResubmission: anyNamed('onFormResubmission'), + onReceivedClientCertRequest: anyNamed( + 'onReceivedClientCertRequest', + ), + ), + ).captured.single + as void Function( + android_webview.WebViewClient, + android_webview.WebView, + String, + ); + + onPageStarted(MockWebViewClient(), mockWebView, 'https://google.com'); + verify(mockCallbacksHandler.onPageStarted('https://google.com')); + }); + + testWidgets('onPageFinished', (WidgetTester tester) async { + await buildWidget(tester); + + final void Function( + android_webview.WebViewClient, + android_webview.WebView, + String, + ) + onPageFinished = + verify( + mockWebViewProxy.createWebViewClient( + onPageStarted: anyNamed('onPageStarted'), + onPageFinished: captureAnyNamed('onPageFinished'), + onReceivedRequestError: anyNamed('onReceivedRequestError'), + requestLoading: anyNamed('requestLoading'), + urlLoading: anyNamed('urlLoading'), + onReceivedSslError: anyNamed('onReceivedSslError'), + onFormResubmission: anyNamed('onFormResubmission'), + onReceivedClientCertRequest: anyNamed( + 'onReceivedClientCertRequest', + ), + ), + ).captured.single + as void Function( + android_webview.WebViewClient, + android_webview.WebView, + String, + ); + + onPageFinished(MockWebViewClient(), mockWebView, 'https://google.com'); + verify(mockCallbacksHandler.onPageFinished('https://google.com')); + }); + + testWidgets('onWebResourceError from onReceivedRequestError', ( + WidgetTester tester, + ) async { + await buildWidget(tester); + + final void Function( + android_webview.WebViewClient, + android_webview.WebView, + android_webview.WebResourceRequest, + android_webview.WebResourceError, + ) + onReceivedRequestError = + verify( + mockWebViewProxy.createWebViewClient( + onPageStarted: anyNamed('onPageStarted'), + onPageFinished: anyNamed('onPageFinished'), + onReceivedRequestError: captureAnyNamed( + 'onReceivedRequestError', + ), + requestLoading: anyNamed('requestLoading'), + urlLoading: anyNamed('urlLoading'), + onReceivedSslError: anyNamed('onReceivedSslError'), + onFormResubmission: anyNamed('onFormResubmission'), + onReceivedClientCertRequest: anyNamed( + 'onReceivedClientCertRequest', + ), + ), + ).captured.single + as void Function( + android_webview.WebViewClient, + android_webview.WebView, + android_webview.WebResourceRequest, + android_webview.WebResourceError, + ); + + onReceivedRequestError( + MockWebViewClient(), + mockWebView, + android_webview.WebResourceRequest.pigeon_detached( + url: 'https://google.com', + isForMainFrame: true, + isRedirect: false, + hasGesture: false, + method: 'POST', + requestHeaders: const {}, + ), + android_webview.WebResourceError.pigeon_detached( + errorCode: WebViewClientConstants.errorUnsafeResource, + description: 'description', + ), + ); + + final WebResourceError error = + verify( + mockCallbacksHandler.onWebResourceError(captureAny), + ).captured.single + as WebResourceError; + expect(error.description, 'description'); + expect(error.errorCode, -16); + expect(error.failingUrl, 'https://google.com'); + expect(error.domain, isNull); + expect(error.errorType, WebResourceErrorType.unsafeResource); + }); + + testWidgets('onNavigationRequest from urlLoading', ( + WidgetTester tester, + ) async { + await buildWidget(tester, hasNavigationDelegate: true); + when( + mockCallbacksHandler.onNavigationRequest( + isForMainFrame: argThat(isTrue, named: 'isForMainFrame'), + url: 'https://google.com', + ), + ).thenReturn(true); + + final void Function( + android_webview.WebViewClient, + android_webview.WebView, + String, + ) + urlLoading = + verify( + mockWebViewProxy.createWebViewClient( + onPageStarted: anyNamed('onPageStarted'), + onPageFinished: anyNamed('onPageFinished'), + onReceivedRequestError: anyNamed('onReceivedRequestError'), + requestLoading: anyNamed('requestLoading'), + urlLoading: captureAnyNamed('urlLoading'), + onReceivedSslError: anyNamed('onReceivedSslError'), + onFormResubmission: anyNamed('onFormResubmission'), + onReceivedClientCertRequest: anyNamed( + 'onReceivedClientCertRequest', + ), + ), + ).captured.single + as void Function( + android_webview.WebViewClient, + android_webview.WebView, + String, + ); + + urlLoading(MockWebViewClient(), mockWebView, 'https://google.com'); + verify( + mockCallbacksHandler.onNavigationRequest( + url: 'https://google.com', + isForMainFrame: true, + ), + ); + verify(mockWebView.loadUrl('https://google.com', {})); + }); + + testWidgets('onNavigationRequest from requestLoading', ( + WidgetTester tester, + ) async { + await buildWidget(tester, hasNavigationDelegate: true); + when( + mockCallbacksHandler.onNavigationRequest( + isForMainFrame: argThat(isTrue, named: 'isForMainFrame'), + url: 'https://google.com', + ), + ).thenReturn(true); + + final void Function( + android_webview.WebViewClient, + android_webview.WebView, + android_webview.WebResourceRequest, + ) + requestLoading = + verify( + mockWebViewProxy.createWebViewClient( + onPageStarted: anyNamed('onPageStarted'), + onPageFinished: anyNamed('onPageFinished'), + onReceivedRequestError: anyNamed('onReceivedRequestError'), + requestLoading: captureAnyNamed('requestLoading'), + urlLoading: anyNamed('urlLoading'), + onReceivedSslError: anyNamed('onReceivedSslError'), + onFormResubmission: anyNamed('onFormResubmission'), + onReceivedClientCertRequest: anyNamed( + 'onReceivedClientCertRequest', + ), + ), + ).captured.single + as void Function( + android_webview.WebViewClient, + android_webview.WebView, + android_webview.WebResourceRequest, + ); + + requestLoading( + MockWebViewClient(), + mockWebView, + android_webview.WebResourceRequest.pigeon_detached( + url: 'https://google.com', + isForMainFrame: true, + isRedirect: false, + hasGesture: false, + method: 'POST', + requestHeaders: const {}, + ), + ); + verify( + mockCallbacksHandler.onNavigationRequest( + url: 'https://google.com', + isForMainFrame: true, + ), + ); + verify(mockWebView.loadUrl('https://google.com', {})); + }); + + group('JavascriptChannelRegistry', () { + testWidgets('onJavascriptChannelMessage', (WidgetTester tester) async { + await buildWidget(tester); + + await testController.addJavascriptChannels({'hello'}); + + final WebViewAndroidJavaScriptChannel javaScriptChannel = + verify( + mockWebView.addJavaScriptChannel(captureAny), + ).captured.single + as WebViewAndroidJavaScriptChannel; + javaScriptChannel.postMessage(javaScriptChannel, 'goodbye'); + verify( + mockJavascriptChannelRegistry.onJavascriptChannelMessage( + 'hello', + 'goodbye', + ), + ); + }); + }); + }); + }); +} diff --git a/local_packages/webview_flutter_android-4.10.5/test/legacy/webview_android_widget_test.mocks.dart b/local_packages/webview_flutter_android-4.10.5/test/legacy/webview_android_widget_test.mocks.dart new file mode 100644 index 0000000..9c81ee5 --- /dev/null +++ b/local_packages/webview_flutter_android-4.10.5/test/legacy/webview_android_widget_test.mocks.dart @@ -0,0 +1,1192 @@ +// Mocks generated by Mockito 5.4.6 from annotations +// in webview_flutter_android/test/legacy/webview_android_widget_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i4; +import 'dart:typed_data' as _i6; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i5; +import 'package:webview_flutter_android/src/android_webkit.g.dart' as _i2; +import 'package:webview_flutter_android/src/legacy/webview_android_widget.dart' + as _i7; +import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart' + as _i3; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakePigeonInstanceManager_0 extends _i1.SmartFake + implements _i2.PigeonInstanceManager { + _FakePigeonInstanceManager_0(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeFlutterAssetManager_1 extends _i1.SmartFake + implements _i2.FlutterAssetManager { + _FakeFlutterAssetManager_1(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeWebSettings_2 extends _i1.SmartFake implements _i2.WebSettings { + _FakeWebSettings_2(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeWebStorage_3 extends _i1.SmartFake implements _i2.WebStorage { + _FakeWebStorage_3(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeWebView_4 extends _i1.SmartFake implements _i2.WebView { + _FakeWebView_4(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeWebViewPoint_5 extends _i1.SmartFake implements _i2.WebViewPoint { + _FakeWebViewPoint_5(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeWebResourceRequest_6 extends _i1.SmartFake + implements _i2.WebResourceRequest { + _FakeWebResourceRequest_6(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeDownloadListener_7 extends _i1.SmartFake + implements _i2.DownloadListener { + _FakeDownloadListener_7(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeJavascriptChannelRegistry_8 extends _i1.SmartFake + implements _i3.JavascriptChannelRegistry { + _FakeJavascriptChannelRegistry_8(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeJavaScriptChannel_9 extends _i1.SmartFake + implements _i2.JavaScriptChannel { + _FakeJavaScriptChannel_9(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeWebChromeClient_10 extends _i1.SmartFake + implements _i2.WebChromeClient { + _FakeWebChromeClient_10(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeWebViewClient_11 extends _i1.SmartFake implements _i2.WebViewClient { + _FakeWebViewClient_11(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +/// A class which mocks [FlutterAssetManager]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockFlutterAssetManager extends _i1.Mock + implements _i2.FlutterAssetManager { + MockFlutterAssetManager() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i4.Future> list(String? path) => + (super.noSuchMethod( + Invocation.method(#list, [path]), + returnValue: _i4.Future>.value([]), + ) + as _i4.Future>); + + @override + _i4.Future getAssetFilePathByName(String? name) => + (super.noSuchMethod( + Invocation.method(#getAssetFilePathByName, [name]), + returnValue: _i4.Future.value( + _i5.dummyValue( + this, + Invocation.method(#getAssetFilePathByName, [name]), + ), + ), + ) + as _i4.Future); + + @override + _i2.FlutterAssetManager pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeFlutterAssetManager_1( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.FlutterAssetManager); +} + +/// A class which mocks [WebSettings]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebSettings extends _i1.Mock implements _i2.WebSettings { + MockWebSettings() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i4.Future setDomStorageEnabled(bool? flag) => + (super.noSuchMethod( + Invocation.method(#setDomStorageEnabled, [flag]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setJavaScriptCanOpenWindowsAutomatically(bool? flag) => + (super.noSuchMethod( + Invocation.method(#setJavaScriptCanOpenWindowsAutomatically, [ + flag, + ]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setSupportMultipleWindows(bool? support) => + (super.noSuchMethod( + Invocation.method(#setSupportMultipleWindows, [support]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setJavaScriptEnabled(bool? flag) => + (super.noSuchMethod( + Invocation.method(#setJavaScriptEnabled, [flag]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setUserAgentString(String? userAgentString) => + (super.noSuchMethod( + Invocation.method(#setUserAgentString, [userAgentString]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setMediaPlaybackRequiresUserGesture(bool? require) => + (super.noSuchMethod( + Invocation.method(#setMediaPlaybackRequiresUserGesture, [require]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setSupportZoom(bool? support) => + (super.noSuchMethod( + Invocation.method(#setSupportZoom, [support]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setLoadWithOverviewMode(bool? overview) => + (super.noSuchMethod( + Invocation.method(#setLoadWithOverviewMode, [overview]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setUseWideViewPort(bool? use) => + (super.noSuchMethod( + Invocation.method(#setUseWideViewPort, [use]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setDisplayZoomControls(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setDisplayZoomControls, [enabled]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setBuiltInZoomControls(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setBuiltInZoomControls, [enabled]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setAllowFileAccess(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setAllowFileAccess, [enabled]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setAllowContentAccess(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setAllowContentAccess, [enabled]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setGeolocationEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setGeolocationEnabled, [enabled]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setTextZoom(int? textZoom) => + (super.noSuchMethod( + Invocation.method(#setTextZoom, [textZoom]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future getUserAgentString() => + (super.noSuchMethod( + Invocation.method(#getUserAgentString, []), + returnValue: _i4.Future.value( + _i5.dummyValue( + this, + Invocation.method(#getUserAgentString, []), + ), + ), + ) + as _i4.Future); + + @override + _i4.Future setMixedContentMode(_i2.MixedContentMode? mode) => + (super.noSuchMethod( + Invocation.method(#setMixedContentMode, [mode]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i2.WebSettings pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeWebSettings_2( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.WebSettings); +} + +/// A class which mocks [WebStorage]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebStorage extends _i1.Mock implements _i2.WebStorage { + MockWebStorage() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i4.Future deleteAllData() => + (super.noSuchMethod( + Invocation.method(#deleteAllData, []), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i2.WebStorage pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeWebStorage_3( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.WebStorage); +} + +/// A class which mocks [WebView]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebView extends _i1.Mock implements _i2.WebView { + MockWebView() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.WebSettings get settings => + (super.noSuchMethod( + Invocation.getter(#settings), + returnValue: _FakeWebSettings_2(this, Invocation.getter(#settings)), + ) + as _i2.WebSettings); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i2.WebSettings pigeonVar_settings() => + (super.noSuchMethod( + Invocation.method(#pigeonVar_settings, []), + returnValue: _FakeWebSettings_2( + this, + Invocation.method(#pigeonVar_settings, []), + ), + ) + as _i2.WebSettings); + + @override + _i4.Future loadData(String? data, String? mimeType, String? encoding) => + (super.noSuchMethod( + Invocation.method(#loadData, [data, mimeType, encoding]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future loadDataWithBaseUrl( + String? baseUrl, + String? data, + String? mimeType, + String? encoding, + String? historyUrl, + ) => + (super.noSuchMethod( + Invocation.method(#loadDataWithBaseUrl, [ + baseUrl, + data, + mimeType, + encoding, + historyUrl, + ]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future loadUrl(String? url, Map? headers) => + (super.noSuchMethod( + Invocation.method(#loadUrl, [url, headers]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future postUrl(String? url, _i6.Uint8List? data) => + (super.noSuchMethod( + Invocation.method(#postUrl, [url, data]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future getUrl() => + (super.noSuchMethod( + Invocation.method(#getUrl, []), + returnValue: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future canGoBack() => + (super.noSuchMethod( + Invocation.method(#canGoBack, []), + returnValue: _i4.Future.value(false), + ) + as _i4.Future); + + @override + _i4.Future canGoForward() => + (super.noSuchMethod( + Invocation.method(#canGoForward, []), + returnValue: _i4.Future.value(false), + ) + as _i4.Future); + + @override + _i4.Future goBack() => + (super.noSuchMethod( + Invocation.method(#goBack, []), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future goForward() => + (super.noSuchMethod( + Invocation.method(#goForward, []), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future reload() => + (super.noSuchMethod( + Invocation.method(#reload, []), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future clearCache(bool? includeDiskFiles) => + (super.noSuchMethod( + Invocation.method(#clearCache, [includeDiskFiles]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future evaluateJavascript(String? javascriptString) => + (super.noSuchMethod( + Invocation.method(#evaluateJavascript, [javascriptString]), + returnValue: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future getTitle() => + (super.noSuchMethod( + Invocation.method(#getTitle, []), + returnValue: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setWebViewClient(_i2.WebViewClient? client) => + (super.noSuchMethod( + Invocation.method(#setWebViewClient, [client]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future addJavaScriptChannel(_i2.JavaScriptChannel? channel) => + (super.noSuchMethod( + Invocation.method(#addJavaScriptChannel, [channel]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future removeJavaScriptChannel(String? name) => + (super.noSuchMethod( + Invocation.method(#removeJavaScriptChannel, [name]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setDownloadListener(_i2.DownloadListener? listener) => + (super.noSuchMethod( + Invocation.method(#setDownloadListener, [listener]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setWebChromeClient(_i2.WebChromeClient? client) => + (super.noSuchMethod( + Invocation.method(#setWebChromeClient, [client]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setBackgroundColor(int? color) => + (super.noSuchMethod( + Invocation.method(#setBackgroundColor, [color]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future destroy() => + (super.noSuchMethod( + Invocation.method(#destroy, []), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i2.WebView pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeWebView_4( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.WebView); + + @override + _i4.Future scrollTo(int? x, int? y) => + (super.noSuchMethod( + Invocation.method(#scrollTo, [x, y]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future scrollBy(int? x, int? y) => + (super.noSuchMethod( + Invocation.method(#scrollBy, [x, y]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future<_i2.WebViewPoint> getScrollPosition() => + (super.noSuchMethod( + Invocation.method(#getScrollPosition, []), + returnValue: _i4.Future<_i2.WebViewPoint>.value( + _FakeWebViewPoint_5( + this, + Invocation.method(#getScrollPosition, []), + ), + ), + ) + as _i4.Future<_i2.WebViewPoint>); + + @override + _i4.Future setVerticalScrollBarEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setVerticalScrollBarEnabled, [enabled]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setHorizontalScrollBarEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setHorizontalScrollBarEnabled, [enabled]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setOverScrollMode(_i2.OverScrollMode? mode) => + (super.noSuchMethod( + Invocation.method(#setOverScrollMode, [mode]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); +} + +/// A class which mocks [WebResourceRequest]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebResourceRequest extends _i1.Mock + implements _i2.WebResourceRequest { + MockWebResourceRequest() { + _i1.throwOnMissingStub(this); + } + + @override + String get url => + (super.noSuchMethod( + Invocation.getter(#url), + returnValue: _i5.dummyValue(this, Invocation.getter(#url)), + ) + as String); + + @override + bool get isForMainFrame => + (super.noSuchMethod( + Invocation.getter(#isForMainFrame), + returnValue: false, + ) + as bool); + + @override + bool get hasGesture => + (super.noSuchMethod(Invocation.getter(#hasGesture), returnValue: false) + as bool); + + @override + String get method => + (super.noSuchMethod( + Invocation.getter(#method), + returnValue: _i5.dummyValue( + this, + Invocation.getter(#method), + ), + ) + as String); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i2.WebResourceRequest pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeWebResourceRequest_6( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.WebResourceRequest); +} + +/// A class which mocks [DownloadListener]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockDownloadListener extends _i1.Mock implements _i2.DownloadListener { + MockDownloadListener() { + _i1.throwOnMissingStub(this); + } + + @override + void Function(_i2.DownloadListener, String, String, String, String, int) + get onDownloadStart => + (super.noSuchMethod( + Invocation.getter(#onDownloadStart), + returnValue: + ( + _i2.DownloadListener pigeon_instance, + String url, + String userAgent, + String contentDisposition, + String mimetype, + int contentLength, + ) {}, + ) + as void Function( + _i2.DownloadListener, + String, + String, + String, + String, + int, + )); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i2.DownloadListener pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeDownloadListener_7( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.DownloadListener); +} + +/// A class which mocks [WebViewAndroidJavaScriptChannel]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewAndroidJavaScriptChannel extends _i1.Mock + implements _i7.WebViewAndroidJavaScriptChannel { + MockWebViewAndroidJavaScriptChannel() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.JavascriptChannelRegistry get javascriptChannelRegistry => + (super.noSuchMethod( + Invocation.getter(#javascriptChannelRegistry), + returnValue: _FakeJavascriptChannelRegistry_8( + this, + Invocation.getter(#javascriptChannelRegistry), + ), + ) + as _i3.JavascriptChannelRegistry); + + @override + String get channelName => + (super.noSuchMethod( + Invocation.getter(#channelName), + returnValue: _i5.dummyValue( + this, + Invocation.getter(#channelName), + ), + ) + as String); + + @override + void Function(_i2.JavaScriptChannel, String) get postMessage => + (super.noSuchMethod( + Invocation.getter(#postMessage), + returnValue: + (_i2.JavaScriptChannel pigeon_instance, String message) {}, + ) + as void Function(_i2.JavaScriptChannel, String)); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i2.JavaScriptChannel pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeJavaScriptChannel_9( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.JavaScriptChannel); +} + +/// A class which mocks [WebChromeClient]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { + MockWebChromeClient() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.Future> Function( + _i2.WebChromeClient, + _i2.WebView, + _i2.FileChooserParams, + ) + get onShowFileChooser => + (super.noSuchMethod( + Invocation.getter(#onShowFileChooser), + returnValue: + ( + _i2.WebChromeClient pigeon_instance, + _i2.WebView webView, + _i2.FileChooserParams params, + ) => _i4.Future>.value([]), + ) + as _i4.Future> Function( + _i2.WebChromeClient, + _i2.WebView, + _i2.FileChooserParams, + )); + + @override + _i4.Future Function(_i2.WebChromeClient, _i2.WebView, String, String) + get onJsConfirm => + (super.noSuchMethod( + Invocation.getter(#onJsConfirm), + returnValue: + ( + _i2.WebChromeClient pigeon_instance, + _i2.WebView webView, + String url, + String message, + ) => _i4.Future.value(false), + ) + as _i4.Future Function( + _i2.WebChromeClient, + _i2.WebView, + String, + String, + )); + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i4.Future setSynchronousReturnValueForOnShowFileChooser(bool? value) => + (super.noSuchMethod( + Invocation.method(#setSynchronousReturnValueForOnShowFileChooser, [ + value, + ]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setSynchronousReturnValueForOnConsoleMessage(bool? value) => + (super.noSuchMethod( + Invocation.method(#setSynchronousReturnValueForOnConsoleMessage, [ + value, + ]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setSynchronousReturnValueForOnJsAlert(bool? value) => + (super.noSuchMethod( + Invocation.method(#setSynchronousReturnValueForOnJsAlert, [value]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setSynchronousReturnValueForOnJsConfirm(bool? value) => + (super.noSuchMethod( + Invocation.method(#setSynchronousReturnValueForOnJsConfirm, [ + value, + ]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setSynchronousReturnValueForOnJsPrompt(bool? value) => + (super.noSuchMethod( + Invocation.method(#setSynchronousReturnValueForOnJsPrompt, [value]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i2.WebChromeClient pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeWebChromeClient_10( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.WebChromeClient); +} + +/// A class which mocks [WebViewClient]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewClient extends _i1.Mock implements _i2.WebViewClient { + MockWebViewClient() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.PigeonInstanceManager get pigeon_instanceManager => + (super.noSuchMethod( + Invocation.getter(#pigeon_instanceManager), + returnValue: _FakePigeonInstanceManager_0( + this, + Invocation.getter(#pigeon_instanceManager), + ), + ) + as _i2.PigeonInstanceManager); + + @override + _i4.Future setSynchronousReturnValueForShouldOverrideUrlLoading( + bool? value, + ) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForShouldOverrideUrlLoading, + [value], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i2.WebViewClient pigeon_copy() => + (super.noSuchMethod( + Invocation.method(#pigeon_copy, []), + returnValue: _FakeWebViewClient_11( + this, + Invocation.method(#pigeon_copy, []), + ), + ) + as _i2.WebViewClient); +} + +/// A class which mocks [JavascriptChannelRegistry]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockJavascriptChannelRegistry extends _i1.Mock + implements _i3.JavascriptChannelRegistry { + MockJavascriptChannelRegistry() { + _i1.throwOnMissingStub(this); + } + + @override + Map get channels => + (super.noSuchMethod( + Invocation.getter(#channels), + returnValue: {}, + ) + as Map); + + @override + void onJavascriptChannelMessage(String? channel, String? message) => + super.noSuchMethod( + Invocation.method(#onJavascriptChannelMessage, [channel, message]), + returnValueForMissingStub: null, + ); + + @override + void updateJavascriptChannelsFromSet(Set<_i3.JavascriptChannel>? channels) => + super.noSuchMethod( + Invocation.method(#updateJavascriptChannelsFromSet, [channels]), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [WebViewPlatformCallbacksHandler]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewPlatformCallbacksHandler extends _i1.Mock + implements _i3.WebViewPlatformCallbacksHandler { + MockWebViewPlatformCallbacksHandler() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.FutureOr onNavigationRequest({ + required String? url, + required bool? isForMainFrame, + }) => + (super.noSuchMethod( + Invocation.method(#onNavigationRequest, [], { + #url: url, + #isForMainFrame: isForMainFrame, + }), + returnValue: _i4.Future.value(false), + ) + as _i4.FutureOr); + + @override + void onPageStarted(String? url) => super.noSuchMethod( + Invocation.method(#onPageStarted, [url]), + returnValueForMissingStub: null, + ); + + @override + void onPageFinished(String? url) => super.noSuchMethod( + Invocation.method(#onPageFinished, [url]), + returnValueForMissingStub: null, + ); + + @override + void onProgress(int? progress) => super.noSuchMethod( + Invocation.method(#onProgress, [progress]), + returnValueForMissingStub: null, + ); + + @override + void onWebResourceError(_i3.WebResourceError? error) => super.noSuchMethod( + Invocation.method(#onWebResourceError, [error]), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [WebViewProxy]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWebViewProxy extends _i1.Mock implements _i7.WebViewProxy { + MockWebViewProxy() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.WebView createWebView() => + (super.noSuchMethod( + Invocation.method(#createWebView, []), + returnValue: _FakeWebView_4( + this, + Invocation.method(#createWebView, []), + ), + ) + as _i2.WebView); + + @override + _i2.WebViewClient createWebViewClient({ + void Function(_i2.WebViewClient, _i2.WebView, String)? onPageStarted, + void Function(_i2.WebViewClient, _i2.WebView, String)? onPageFinished, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceError, + )? + onReceivedRequestError, + void Function(_i2.WebViewClient, _i2.WebView, _i2.WebResourceRequest)? + requestLoading, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.AndroidMessage, + _i2.AndroidMessage, + )? + onFormResubmission, + void Function(_i2.WebViewClient, _i2.WebView, _i2.ClientCertRequest)? + onReceivedClientCertRequest, + void Function( + _i2.WebViewClient, + _i2.WebView, + _i2.SslErrorHandler, + _i2.SslError, + )? + onReceivedSslError, + void Function(_i2.WebViewClient, _i2.WebView, String)? urlLoading, + }) => + (super.noSuchMethod( + Invocation.method(#createWebViewClient, [], { + #onPageStarted: onPageStarted, + #onPageFinished: onPageFinished, + #onReceivedRequestError: onReceivedRequestError, + #requestLoading: requestLoading, + #onFormResubmission: onFormResubmission, + #onReceivedClientCertRequest: onReceivedClientCertRequest, + #onReceivedSslError: onReceivedSslError, + #urlLoading: urlLoading, + }), + returnValue: _FakeWebViewClient_11( + this, + Invocation.method(#createWebViewClient, [], { + #onPageStarted: onPageStarted, + #onPageFinished: onPageFinished, + #onReceivedRequestError: onReceivedRequestError, + #requestLoading: requestLoading, + #onFormResubmission: onFormResubmission, + #onReceivedClientCertRequest: onReceivedClientCertRequest, + #onReceivedSslError: onReceivedSslError, + #urlLoading: urlLoading, + }), + ), + ) + as _i2.WebViewClient); + + @override + _i4.Future setWebContentsDebuggingEnabled(bool? enabled) => + (super.noSuchMethod( + Invocation.method(#setWebContentsDebuggingEnabled, [enabled]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..1061339 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,1776 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.59" + agora_rtc_engine: + dependency: "direct main" + description: + name: agora_rtc_engine + sha256: "6559294d18ce4445420e19dbdba10fb58cac955cd8f22dbceae26716e194d70e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.5.3" + app_links: + dependency: "direct main" + description: + name: app_links + sha256: "5f88447519add627fe1cbcab4fd1da3d4fed15b9baf29f28b22535c95ecee3e8" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.4.1" + app_links_linux: + dependency: transitive + description: + name: app_links_linux + sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.3" + app_links_platform_interface: + dependency: transitive + description: + name: app_links_platform_interface + sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.2" + app_links_web: + dependency: transitive + description: + name: app_links_web + sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.4" + archive: + dependency: transitive + description: + name: archive + sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.9" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.12.0" + audioplayers: + dependency: transitive + description: + name: audioplayers + sha256: a72dd459d1a48f61a6fb9c0134dba26597c9236af40639ff0eb70eb4e0baab70 + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.6.0" + audioplayers_android: + dependency: transitive + description: + name: audioplayers_android + sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.2.1" + audioplayers_darwin: + dependency: transitive + description: + name: audioplayers_darwin + sha256: c994b3bb3a921e4904ac40e013fbc94488e824fd7c1de6326f549943b0b44a91 + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.4.0" + audioplayers_linux: + dependency: transitive + description: + name: audioplayers_linux + sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.2.1" + audioplayers_platform_interface: + dependency: transitive + description: + name: audioplayers_platform_interface + sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656" + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.1.1" + audioplayers_web: + dependency: transitive + description: + name: audioplayers_web + sha256: faa8fa6587f996a6f604433b53af44c57a1407d4fe8dff5766cf63d6875e8de9 + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.2.0" + audioplayers_windows: + dependency: transitive + description: + name: audioplayers_windows + sha256: bafff2b38b6f6d331887558ba6e0a01c9c208d9dbb3ad0005234db065122a734 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.3.0" + back_button_interceptor: + dependency: "direct main" + description: + name: back_button_interceptor + sha256: b85977faabf4aeb95164b3b8bf81784bed4c54ea1aef90a036ab6927ecf80c5a + url: "https://pub.flutter-io.cn" + source: hosted + version: "8.0.4" + badges: + dependency: "direct main" + description: + name: badges + sha256: a7b6bbd60dce418df0db3058b53f9d083c22cdb5132a052145dc267494df0b84 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.2" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.4.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.1" + carousel_slider: + dependency: "direct main" + description: + name: carousel_slider + sha256: febf4b0163e0242adc13d7a863b04965351f59e7dfea56675c7c2caa7bcd7476 + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.3" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.4.2" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.19.1" + cookie_jar: + dependency: "direct main" + description: + name: cookie_jar + sha256: "963da02c1ef64cb5ac20de948c9e5940aa351f1e34a12b1d327c83d85b7e8fff" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.9" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "942a4791cd385a68ccb3b32c71c427aba508a1bb949b86dff2adbe4049f16239" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.5" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.7" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.2" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.8" + dbus: + dependency: transitive + description: + name: dbus + sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.12" + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074 + url: "https://pub.flutter-io.cn" + source: hosted + version: "10.1.2" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.0.3" + dio: + dependency: "direct main" + description: + name: dio + sha256: b9d46faecab38fc8cc286f80bc4d61a3bb5d4ac49e51ed877b4d6706efe57b25 + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.9.1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.1" + event_bus: + dependency: "direct main" + description: + name: event_bus + sha256: "1a55e97923769c286d295240048fc180e7b0768902c3c2e869fe059aafa15304" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.1" + extended_image: + dependency: "direct main" + description: + name: extended_image + sha256: f6cbb1d798f51262ed1a3d93b4f1f2aa0d76128df39af18ecb77fa740f88b2e0 + url: "https://pub.flutter-io.cn" + source: hosted + version: "10.0.1" + extended_image_library: + dependency: transitive + description: + name: extended_image_library + sha256: "1f9a24d3a00c2633891c6a7b5cab2807999eb2d5b597e5133b63f49d113811fe" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.1" + extended_nested_scroll_view: + dependency: "direct main" + description: + name: extended_nested_scroll_view + sha256: "835580d40c2c62b448bd14adecd316acba469ba61f1510ef559d17668a85e777" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.2.1" + extended_text: + dependency: "direct main" + description: + name: extended_text + sha256: d8f4a6e2676505b54dc0d5f5e8de9020667b402e9c1b3a8b030a83e568c99654 + url: "https://pub.flutter-io.cn" + source: hosted + version: "15.0.2" + extended_text_field: + dependency: "direct main" + description: + name: extended_text_field + sha256: "3996195c117c6beb734026a7bc0ba80d7e4e84e4edd4728caa544d8209ab4d7d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "16.0.2" + extended_text_library: + dependency: transitive + description: + name: extended_text_library + sha256: "13d99f8a10ead472d5e2cf4770d3d047203fe5054b152e9eb5dc692a71befbba" + url: "https://pub.flutter-io.cn" + source: hosted + version: "12.0.1" + fading_edge_scrollview: + dependency: transitive + description: + name: fading_edge_scrollview + sha256: "1f84fe3ea8e251d00d5735e27502a6a250e4aa3d3b330d3fdcb475af741464ef" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.0.1" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.3+2" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.4+4" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.7.0" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.3+4" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + sha256: "0fed2133bee1369ee1118c1fef27b2ce0d84c54b7819a2b17dada5cfec3b03ff" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.7.0" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: "871c9df4ec9a754d1a793f7eb42fa3b94249d464cfb19152ba93e14a5966b386" + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.7.3" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: d9ada769c43261fd1b18decf113186e915c921a811bd5014f5ea08f4cf4bc57e + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.15.3" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.15.2" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64 + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.0.2" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.24.1" + firebase_crashlytics: + dependency: "direct main" + description: + name: firebase_crashlytics + sha256: "662ae6443da91bca1fb0be8aeeac026fa2975e8b7ddfca36e4d90ebafa35dde1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.3.10" + firebase_crashlytics_platform_interface: + dependency: transitive + description: + name: firebase_crashlytics_platform_interface + sha256: "7222a8a40077c79f6b8b3f3439241c9f2b34e9ddfde8381ffc512f7b2e61f7eb" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.8.10" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.1" + fluro: + dependency: "direct main" + description: + name: fluro + sha256: "24d07d0b285b213ec2045b83e85d076185fa5c23651e44dae0ac6755784b97d0" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.5" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_cache_manager: + dependency: "direct main" + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.4.1" + flutter_debouncer: + dependency: "direct main" + description: + name: flutter_debouncer + sha256: "89f98f874e6abbb212f3027a7a27d5ce42c5b6544c8f5967d91140c0ae06ae22" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.1" + flutter_foreground_task: + dependency: "direct main" + description: + path: "local_packages/flutter_foreground_task-9.1.0" + relative: true + source: path + version: "9.1.0" + flutter_image_compress: + dependency: "direct main" + description: + name: flutter_image_compress + sha256: "51d23be39efc2185e72e290042a0da41aed70b14ef97db362a6b5368d0523b27" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.0" + flutter_image_compress_common: + dependency: transitive + description: + name: flutter_image_compress_common + sha256: c5c5d50c15e97dd7dc72ff96bd7077b9f791932f2076c5c5b6c43f2c88607bfb + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.6" + flutter_image_compress_macos: + dependency: transitive + description: + name: flutter_image_compress_macos + sha256: "20019719b71b743aba0ef874ed29c50747461e5e8438980dfa5c2031898f7337" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.3" + flutter_image_compress_ohos: + dependency: transitive + description: + name: flutter_image_compress_ohos + sha256: e76b92bbc830ee08f5b05962fc78a532011fcd2041f620b5400a593e96da3f51 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.3" + flutter_image_compress_platform_interface: + dependency: transitive + description: + name: flutter_image_compress_platform_interface + sha256: "579cb3947fd4309103afe6442a01ca01e1e6f93dc53bb4cbd090e8ce34a41889" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.5" + flutter_image_compress_web: + dependency: transitive + description: + name: flutter_image_compress_web + sha256: b9b141ac7c686a2ce7bb9a98176321e1182c9074650e47bb140741a44b6f5a96 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.5" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.14.4" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_ninepatch_image: + dependency: "direct main" + description: + path: "local_packages/flutter_ninepatch_image-1.0.2" + relative: true + source: path + version: "1.0.2" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: c2fe1001710127dfa7da89977a08d591398370d099aacdaa6d44da7eb14b8476 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.31" + flutter_screenutil: + dependency: "direct main" + description: + name: flutter_screenutil + sha256: "8239210dd68bee6b0577aa4a090890342d04a136ce1c81f98ee513fc0ce891de" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.9.3" + flutter_smart_dialog: + dependency: "direct main" + description: + name: flutter_smart_dialog + sha256: "0852df132cb03fd8fc5144eb404c31eb7eb50c22aecb1cc2504f2f598090d756" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.9.8+9" + flutter_staggered_grid_view: + dependency: "direct main" + description: + name: flutter_staggered_grid_view + sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.0" + flutter_svga: + dependency: "direct main" + description: + name: flutter_svga + sha256: a7de69f2b51aea08b2f7e1dfc6c078beb3b677a43f966bfc703ed9199e70ffd0 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.5" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + fluttertoast: + dependency: "direct main" + description: + name: fluttertoast + sha256: "90778fe0497fe3a09166e8cf2e0867310ff434b794526589e77ec03cf08ba8e8" + url: "https://pub.flutter-io.cn" + source: hosted + version: "8.2.14" + google_identity_services_web: + dependency: transitive + description: + name: google_identity_services_web + sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.3+1" + google_sign_in: + dependency: "direct main" + description: + name: google_sign_in + sha256: d0a2c3bcb06e607bb11e4daca48bd4b6120f0bbc4015ccebbe757d24ea60ed2a + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.3.0" + google_sign_in_android: + dependency: transitive + description: + name: google_sign_in_android + sha256: d5e23c56a4b84b6427552f1cf3f98f716db3b1d1a647f16b96dbb5b93afa2805 + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.2.1" + google_sign_in_ios: + dependency: transitive + description: + name: google_sign_in_ios + sha256: "102005f498ce18442e7158f6791033bbc15ad2dcc0afa4cf4752e2722a516c96" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.9.0" + google_sign_in_platform_interface: + dependency: transitive + description: + name: google_sign_in_platform_interface + sha256: "5f6f79cf139c197261adb6ac024577518ae48fdff8e53205c5373b5f6430a8aa" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.0" + google_sign_in_web: + dependency: transitive + description: + name: google_sign_in_web + sha256: "460547beb4962b7623ac0fb8122d6b8268c951cf0b646dd150d60498430e4ded" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.12.4+4" + gtk: + dependency: transitive + description: + name: gtk + sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.15.6" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.6.0" + http_client_helper: + dependency: transitive + description: + name: http_client_helper + sha256: "8a9127650734da86b5c73760de2b404494c968a3fd55602045ffec789dac3cb1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.2" + id3: + dependency: transitive + description: + name: id3 + sha256: "24176a6e08db6297c8450079e94569cd8387f913c817e5e3d862be7dc191e0b8" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.2" + image: + dependency: transitive + description: + name: image + sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.8.0" + image_cropper: + dependency: "direct main" + description: + name: image_cropper + sha256: f4bad5ed2dfff5a7ce0dfbad545b46a945c702bb6182a921488ef01ba7693111 + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.1" + image_cropper_for_web: + dependency: transitive + description: + name: image_cropper_for_web + sha256: "865d798b5c9d826f1185b32e5d0018c4183ddb77b7b82a931e1a06aa3b74974e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.0" + image_cropper_platform_interface: + dependency: transitive + description: + name: image_cropper_platform_interface + sha256: ee160d686422272aa306125f3b6fb1c1894d9b87a5e20ed33fa008e7285da11e + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.0" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.1" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.8.13+1" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.0" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.8.13" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.2" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.11.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.2" + image_pixels: + dependency: transitive + description: + name: image_pixels + sha256: c88bb386a9067f489c9e9a9701ddb48e456e3f92c8c4ce575fac062809a1daae + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.2" + in_app_purchase: + dependency: "direct main" + description: + name: in_app_purchase + sha256: "5cddd7f463f3bddb1d37a72b95066e840d5822d66291331d7f8f05ce32c24b6c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.3" + in_app_purchase_android: + dependency: transitive + description: + name: in_app_purchase_android + sha256: "67f135ca215b8cd84eca3ea46b934607709ab6826a8473129f01b12fd235c8c6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.4.0+5" + in_app_purchase_platform_interface: + dependency: transitive + description: + name: in_app_purchase_platform_interface + sha256: "1d353d38251da5b9fea6635c0ebfc6bb17a2d28d0e86ea5e083bf64244f1fb4c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.0" + in_app_purchase_storekit: + dependency: transitive + description: + name: in_app_purchase_storekit + sha256: aedbeea5beae10af3e5c380b65049842715b2bb014983e2a48b9006473f33cd9 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.4.4" + intl: + dependency: transitive + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.20.2" + iris_method_channel: + dependency: transitive + description: + name: iris_method_channel + sha256: bfb5cfc6c6eae42da8cd1b35977a72d8b8881848a5dfc3d672e4760a907d11a0 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.4" + isolate_easy_pool: + dependency: "direct main" + description: + name: isolate_easy_pool + sha256: f0204cfdecbb84d61c46240a603bb21c3b2ac925475faf3f4afe18526fcb8f64 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.8" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.9.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.flutter-io.cn" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.1.1" + loading_indicator_view_plus: + dependency: "direct main" + description: + name: loading_indicator_view_plus + sha256: "23ad380dd677e8e0182f679f29e1221269ad66b9a1a2d70b19e716ee4723c99e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + marquee: + dependency: "direct main" + description: + name: marquee + sha256: a87e7e80c5d21434f90ad92add9f820cf68be374b226404fe881d2bba7be0862 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + mime_type: + dependency: transitive + description: + name: mime_type + sha256: d652b613e84dac1af28030a9fba82c0999be05b98163f9e18a0849c6e63838bb + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0" + on_audio_query: + dependency: "direct main" + description: + path: "local_packages/on_audio_query-2.9.0" + relative: true + source: path + version: "2.9.0" + on_audio_query_android: + dependency: transitive + description: + path: "local_packages/on_audio_query_android-1.1.0" + relative: true + source: path + version: "1.1.0" + on_audio_query_ios: + dependency: transitive + description: + name: on_audio_query_ios + sha256: "9b3efa39a656fa3720980e3c6a1f55b7257d0032a45ffeb3f70eaa2c7f10f929" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" + on_audio_query_platform_interface: + dependency: transitive + description: + name: on_audio_query_platform_interface + sha256: c23e019a31bd0774828476e428fd33b0dd1d82c9d4791dba80429358fc65dcd3 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.7.0" + on_audio_query_web: + dependency: transitive + description: + name: on_audio_query_web + sha256: "990efa52d879e6caffa97f24b34acd9caa1ce2c4c4cb873fe5a899a9b1af02c7" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.6.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" + url: "https://pub.flutter-io.cn" + source: hosted + version: "8.3.1" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.1" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.9.1" + path_drawing: + dependency: transitive + description: + name: path_drawing + sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.19" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.0" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1 + url: "https://pub.flutter-io.cn" + source: hosted + version: "12.0.1" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "13.0.1" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + url: "https://pub.flutter-io.cn" + source: hosted + version: "9.4.7" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.3+5" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.3.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.1.0" + photo_view: + dependency: "direct main" + description: + name: photo_view + sha256: "1fc3d970a91295fbd1364296575f854c9863f225505c28c46e0a03e48960c75e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.15.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.8" + posix: + dependency: transitive + description: + name: posix + sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.5.0" + pretty_dio_logger: + dependency: "direct main" + description: + name: pretty_dio_logger + sha256: "36f2101299786d567869493e2f5731de61ce130faa14679473b26905a92b6407" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.0" + protobuf: + dependency: transitive + description: + name: protobuf + sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.0" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.1.5+1" + pull_to_refresh: + dependency: "direct main" + description: + name: pull_to_refresh + sha256: bbadd5a931837b57739cf08736bea63167e284e71fb23b218c8c9a6e042aad12 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + readmore: + dependency: "direct main" + description: + name: readmore + sha256: e8fca2bd397b86342483b409e2ec26f06560a5963aceaa39b27f30722b506187 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.0" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.28.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.13" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.1" + sign_in_with_apple: + dependency: "direct main" + description: + name: sign_in_with_apple + sha256: "8bd875c8e8748272749eb6d25b896f768e7e9d60988446d543fe85a37a2392b8" + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.0.1" + sign_in_with_apple_platform_interface: + dependency: transitive + description: + name: sign_in_with_apple_platform_interface + sha256: "981bca52cf3bb9c3ad7ef44aace2d543e5c468bb713fd8dda4275ff76dfa6659" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + sign_in_with_apple_web: + dependency: transitive + description: + name: sign_in_with_apple_web + sha256: f316400827f52cafcf50d00e1a2e8a0abc534ca1264e856a81c5f06bd5b10fed + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + social_sharing_plus: + dependency: "direct main" + description: + name: social_sharing_plus + sha256: d0bd9dc3358cf66a3aac7cce549902360f30bccf44cf6090a708d97cc54ca854 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.3" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.10.1" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.5" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.12.1" + store_redirect: + dependency: "direct main" + description: + name: store_redirect + sha256: ec635bc2f89c1f19b2f384784ddbfc8eb46b27d28224db32cadfe3820eeea6ca + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.4" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.3.1" + tancent_vap: + dependency: "direct main" + description: + path: "local_packages/tancent_vap-1.0.0+1" + relative: true + source: path + version: "1.0.0+1" + tencent_cloud_chat_sdk: + dependency: "direct main" + description: + name: tencent_cloud_chat_sdk + sha256: fb930c86017fa4a546f3d0c15bc5a54197f07f003934737e80cf2f9a70cab1ba + url: "https://pub.flutter-io.cn" + source: hosted + version: "8.3.6498+3" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.6" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.0" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.1" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.3.20" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.3.4" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.3" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.4" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.5.3" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" + video_player: + dependency: "direct main" + description: + name: video_player + sha256: "096bc28ce10d131be80dfb00c223024eb0fba301315a406728ab43dd99c45bdf" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.10.1" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: a8dc4324f67705de057678372bedb66cd08572fe7c495605ac68c5f503324a39 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.8.15" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: f9a780aac57802b2892f93787e5ea53b5f43cc57dc107bee9436458365be71cd + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.8.4" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "57c5d73173f76d801129d0531c2774052c5a7c11ccb962f1830630decd9f24ec" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.6.0" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.0" + video_thumbnail: + dependency: "direct main" + description: + name: video_thumbnail + sha256: "181a0c205b353918954a881f53a3441476b9e301641688a581e0c13f00dc588b" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.5.6" + visibility_detector: + dependency: transitive + description: + name: visibility_detector + sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.4.0+2" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + url: "https://pub.flutter-io.cn" + source: hosted + version: "14.3.1" + wakelock_plus: + dependency: "direct main" + description: + name: wakelock_plus + sha256: "61713aa82b7f85c21c9f4cd0a148abd75f38a74ec645fcb1e446f882c82fd09b" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.3" + wakelock_plus_platform_interface: + dependency: transitive + description: + name: wakelock_plus_platform_interface + sha256: "036deb14cd62f558ca3b73006d52ce049fabcdcb2eddfe0bf0fe4e8a943b5cf2" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.1" + webview_flutter: + dependency: "direct main" + description: + path: "local_packages/webview_flutter-4.13.0" + relative: true + source: path + version: "4.13.0" + webview_flutter_android: + dependency: transitive + description: + path: "local_packages/webview_flutter_android-4.10.5" + relative: true + source: path + version: "4.10.5" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: "63d26ee3aca7256a83ccb576a50272edd7cfc80573a4305caa98985feb493ee0" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.14.0" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: fb46db8216131a3e55bcf44040ca808423539bc6732e7ed34fb6d8044e3d512f + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.23.0" + win32: + dependency: transitive + description: + name: win32 + sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.13.0" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.5" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.5.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.8.0-0 <4.0.0" + flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..0138415 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,191 @@ +name: aslan +description: "Aslan" +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.6+7 + + +environment: + sdk: ^3.7.2 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + + # The following adds the Cupertino Icons fonts to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + #状态管理 + provider: ^6.1.5 + #屏幕适配 + flutter_screenutil: 5.9.3 + #网络请求工具 + dio: ^5.8.0+1 + #网络请求日志打印 + pretty_dio_logger: ^1.1.1 + #下拉刷新 + pull_to_refresh: 2.0.0 + #声网 + agora_rtc_engine: ^6.5.2 + # agora_rtm: 1.6.0 + #腾讯IM + tencent_cloud_chat_sdk: 8.3.6498+3 + #缓存 + flutter_cache_manager: ^3.0.0 + #路由 + fluro: 2.0.5 + #权限请求 + permission_handler: ^12.0.0+1 + #webview: + webview_flutter: + path: ./local_packages/webview_flutter-4.13.0 + # webview_flutter: 4.4.2 + #跳转外链 + url_launcher: ^6.3.1 + #键值对存储 + # mmkv: ^2.2.2 + shared_preferences: ^2.5.3 + #cookie + cookie_jar: ^4.0.8 + #toast + fluttertoast: ^8.2.12 + #isolate池 + isolate_easy_pool: ^0.0.8 + #图片加载 + cached_network_image: ^3.4.1 + extended_image: ^10.0.1 + #视频播放 + video_player: ^2.10.0 + #谷歌登录 + google_sign_in: ^6.3.0 + firebase_auth: ^5.7.0 + firebase_core: ^3.15.2 + firebase_crashlytics: ^4.3.9 + #苹果登录 + sign_in_with_apple: ^7.0.1 + #相册 + # wechat_assets_picker: ^9.5.0 + # wechat_camera_picker: ^4.2.0 + image_picker: ^1.2.0 + #图片裁剪 + image_cropper: ^5.0.0 + loading_indicator_view_plus: ^2.0.0 + back_button_interceptor: ^8.0.4 + #vap特效 + # flutter_vap_plus: ^1.2.10 + # tancent_vap: ^1.0.0+1 + tancent_vap: + path: ./local_packages/tancent_vap-1.0.0+1 + #svga特效 + flutter_svga: ^0.0.5 + #banner + carousel_slider: ^5.1.1 + #谷歌支付 + in_app_purchase: ^3.2.3 + + device_info_plus: ^10.0.0 + package_info_plus: ^8.0.0 + uuid: ^4.5.1 + wakelock_plus: ^1.1.0 + #防抖 + flutter_debouncer: ^2.0.0 + #弹框 + flutter_smart_dialog: ^4.9.8+9 + extended_text_field: 16.0.2 + # flutter_sound: 9.28.0 + #图片压缩 + flutter_image_compress: ^2.4.0 + extended_text: ^15.0.2 + #缩略图 + video_thumbnail: ^0.5.6 + badges: ^3.1.2 + #本地路径 + path_provider: ^2.1.4 + photo_view: ^0.15.0 + flutter_staggered_grid_view: ^0.7.0 + marquee: ^2.3.0 + #九宫格 + flutter_ninepatch_image: + path: ./local_packages/flutter_ninepatch_image-1.0.2 + extended_nested_scroll_view: ^6.2.1 + #跳转到应用商店 + store_redirect: ^2.0.4 + event_bus: ^2.0.1 + readmore: ^3.0.0 + #分享 + social_sharing_plus: ^1.2.3 + #外链跳转 + app_links: ^6.4.1 + flutter_foreground_task: + path: ./local_packages/flutter_foreground_task-9.1.0 + #本地音乐检索 + on_audio_query: + path: ./local_packages/on_audio_query-2.9.0 + +dev_dependencies: + flutter_launcher_icons: ^0.14.4 + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter_launcher_icons: + android: false + ios: true + image_path: "atu_images/at_icon_app_lunch.png" # 你的图标路径 flutter pub run flutter_launcher_icons:main + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # The following line ensures that the Material Icons fonts is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true +# fonts: +# - family: MyCustomFont +# fonts: +# - asset: fonts/AAA-bold.ttf + assets: + # 共享资源 + # 应用资源路径 + - assets/ + - assets/l10n/ + - atu_images/splash/ + - atu_images/login/ + - atu_images/general/ + - atu_images/index/ + - atu_images/room/ + - atu_images/room/entrance/ + - atu_images/room/anim/ + - atu_images/person/ + - atu_images/store/ + - atu_images/msg/ + - atu_images/vip/ + - atu_images/level/ + - atu_images/coupon/ + - atu_images/family/ + - atu_images/dynamic/ + - fonts/ \ No newline at end of file diff --git a/test/business_logic_strategy_test.dart b/test/business_logic_strategy_test.dart new file mode 100644 index 0000000..8b5579f --- /dev/null +++ b/test/business_logic_strategy_test.dart @@ -0,0 +1,1724 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:aslan/chatvibe_core/config/app_config.dart'; +import 'package:aslan/chatvibe_core/config/business_logic_strategy.dart'; +import 'package:aslan/chatvibe_core/config/configs/variant1_config.dart'; +import 'package:aslan/chatvibe_core/config/strategies/base_business_logic_strategy.dart'; +import 'package:aslan/chatvibe_core/config/strategies/variant1_business_logic_strategy.dart'; +import 'package:aslan/chatvibe_ui/theme/chatvibe_theme.dart'; + +void main() { + group('BusinessLogicStrategy Tests', () { + test('BaseBusinessLogicStrategy shouldShowFamilyTab returns correct value', + () { + final strategy = BaseBusinessLogicStrategy(); + // 由于没有真实用户数据,我们无法测试具体逻辑 + // 但可以测试方法存在且不抛出异常 + expect(strategy.shouldShowFamilyTab(), isA()); + }); + + test('BaseBusinessLogicStrategy getHomeInitialTabIndex returns int', () { + final strategy = BaseBusinessLogicStrategy(); + expect(strategy.getHomeInitialTabIndex(), isA()); + }); + + test( + 'BaseBusinessLogicStrategy getFirstRechargePosition returns Map with all keys', + () { + final strategy = BaseBusinessLogicStrategy(); + final position = strategy.getFirstRechargePosition(); + expect(position, isA>()); + expect(position.containsKey('top'), true); + expect(position.containsKey('bottom'), true); + expect(position.containsKey('start'), true); + expect(position.containsKey('end'), true); + }); + + test('Variant1BusinessLogicStrategy shouldShowFamilyTab always returns true', + () { + final strategy = Variant1BusinessLogicStrategy(); + expect(strategy.shouldShowFamilyTab(), true); + }); + + test('Variant1BusinessLogicStrategy getHomeInitialTabIndex returns 0', () { + final strategy = Variant1BusinessLogicStrategy(); + expect(strategy.getHomeInitialTabIndex(), 0); + }); + + test( + 'Variant1BusinessLogicStrategy getFirstRechargePosition returns correct values', + () { + final strategy = Variant1BusinessLogicStrategy(); + final position = strategy.getFirstRechargePosition(); + expect(position['top'], 100.0); + expect(position['start'], 15.0); + expect(position['bottom'], null); + expect(position['end'], null); + }); + + test('Variant1Config uses Variant1BusinessLogicStrategy', () { + final config = Variant1Config(); + expect(config.businessLogicStrategy, isA()); + }); + + test('AppConfig initialization works with Variant1Config', () { + // 直接测试初始化方法 + AppConfig.initialize(); + expect(AppConfig.current, isA()); + expect(AppConfig.current.businessLogicStrategy, + isA()); + }); + }); + + group('Admin Page Strategy Tests', () { + test('BaseBusinessLogicStrategy admin editing methods return correct values', + () { + final strategy = BaseBusinessLogicStrategy(); + + // 测试编辑页面背景图片 + expect(strategy.getAdminEditingBackgroundImage(), + "atu_images/room/at_icon_room_settig_bg.png"); + + // 测试编辑页面按钮渐变颜色 + final warningGradient = strategy.getAdminEditingButtonGradient('warning'); + expect(warningGradient, isA>()); + expect(warningGradient.length, 2); + expect(warningGradient[0], const Color(0xffC670FF)); + expect(warningGradient[1], const Color(0xff7726FF)); + + // 测试编辑页面图标路径 + expect(strategy.getAdminEditingIcon('specialIdBg'), + "atu_images/general/at_icon_special_id_bg.png"); + expect(strategy.getAdminEditingIcon('normalIdBg'), + "atu_images/general/at_icon_id_bg.png"); + expect(strategy.getAdminEditingIcon('copyId'), + "atu_images/room/at_icon_user_card_copy_id.png"); + expect(strategy.getAdminEditingIcon('addPic'), + "atu_images/general/at_icon_add_pic.png"); + expect(strategy.getAdminEditingIcon('closePic'), + "atu_images/general/at_icon_pic_close.png"); + expect(strategy.getAdminEditingIcon('checked'), + "atu_images/general/at_icon_checked.png"); + expect(strategy.getAdminEditingIcon('unknown'), ""); + + // 测试编辑页面输入框装饰 + final inputDecoration = strategy.getAdminEditingInputDecoration(); + expect(inputDecoration, isA()); + expect(inputDecoration.color, Colors.white); + expect(inputDecoration.borderRadius, + BorderRadius.circular(8)); // 注意:BorderRadius.circular(8) 与 BorderRadius.circular(8) 比较 + + // 测试最大图片上传数量 + expect(strategy.getAdminEditingMaxImageUploadCount(), 3); + + // 测试描述最大长度 + expect(strategy.getAdminEditingDescriptionMaxLength(), 200); + + // 测试违规类型映射 + final userViolationMapping = + strategy.getAdminEditingViolationTypeMapping('User'); + expect(userViolationMapping, isA>()); + expect(userViolationMapping['Pornography'], 1); + expect(userViolationMapping['Illegal information'], 2); + + final roomViolationMapping = + strategy.getAdminEditingViolationTypeMapping('Room'); + expect(roomViolationMapping, isA>()); + expect(roomViolationMapping['Pornography'], 3); + expect(roomViolationMapping['Illegal information'], 4); + expect(roomViolationMapping['Fraud'], 5); + expect(roomViolationMapping['Other'], 6); + }); + + test('BaseBusinessLogicStrategy admin search methods return correct values', + () { + final strategy = BaseBusinessLogicStrategy(); + + // 测试搜索页面背景图片 + expect(strategy.getAdminSearchBackgroundImage('userSearch'), + "atu_images/index/at_icon_coupon_head_bg.png"); + expect(strategy.getAdminSearchBackgroundImage('roomSearch'), + "atu_images/index/at_icon_coupon_head_bg.png"); + + // 测试搜索按钮渐变颜色 + final buttonGradient = strategy.getAdminSearchButtonGradient('userSearch'); + expect(buttonGradient, isA>()); + expect(buttonGradient.length, 2); + expect(buttonGradient[0], Colors.transparent); + expect(buttonGradient[1], Colors.transparent); + + // 测试搜索按钮文本颜色 + expect(strategy.getAdminSearchButtonTextColor('userSearch'), Colors.black); + + // 测试编辑图标路径 + expect(strategy.getAdminSearchEditIcon('userSearch'), + "atu_images/room/at_icon_room_edit.png"); + + // 测试搜索框边框颜色 + expect(strategy.getAdminSearchInputBorderColor('userSearch'), + Colors.black12); + + // 测试搜索框文本颜色 + expect(strategy.getAdminSearchInputTextColor('userSearch'), Colors.grey); + + // 测试用户信息图标路径 + expect(strategy.getAdminSearchUserInfoIcon('specialIdBg'), + "atu_images/general/at_icon_special_id_bg.png"); + expect(strategy.getAdminSearchUserInfoIcon('normalIdBg'), + "atu_images/general/at_icon_id_bg.png"); + expect(strategy.getAdminSearchUserInfoIcon('copyId'), + "atu_images/room/at_icon_user_card_copy_id.png"); + expect(strategy.getAdminSearchUserInfoIcon('unknown'), ""); + }); + + test('Variant1BusinessLogicStrategy admin editing methods return variant values', + () { + final strategy = Variant1BusinessLogicStrategy(); + + // 测试编辑页面背景图片(马甲包目前使用相同图片) + expect(strategy.getAdminEditingBackgroundImage(), + "atu_images/room/at_icon_room_settig_bg.png"); + + // 测试编辑页面按钮渐变颜色(马甲包使用不同渐变) + final buttonGradient = strategy.getAdminEditingButtonGradient('warning'); + expect(buttonGradient, isA>()); + expect(buttonGradient.length, 2); + expect(buttonGradient[0], const Color(0xffFF9326)); + expect(buttonGradient[1], const Color(0xffFEB219)); + + // 测试编辑页面图标路径(马甲包目前使用相同图标) + expect(strategy.getAdminEditingIcon('specialIdBg'), + "atu_images/general/at_icon_special_id_bg.png"); + + // 测试编辑页面输入框装饰(马甲包使用不同装饰) + final inputDecoration = strategy.getAdminEditingInputDecoration(); + expect(inputDecoration, isA()); + expect(inputDecoration.color, const Color(0xfff2f2f2)); + expect(inputDecoration.borderRadius, BorderRadius.circular(56)); + + // 测试最大图片上传数量(马甲包使用不同值) + expect(strategy.getAdminEditingMaxImageUploadCount(), 5); + + // 测试描述最大长度(马甲包使用不同值) + expect(strategy.getAdminEditingDescriptionMaxLength(), 300); + + // 测试违规类型映射(马甲包使用相同映射) + final roomViolationMapping = + strategy.getAdminEditingViolationTypeMapping('Room'); + expect(roomViolationMapping['Pornography'], 3); + expect(roomViolationMapping['Other'], 6); + }); + + test('Variant1BusinessLogicStrategy admin search methods return variant values', + () { + final strategy = Variant1BusinessLogicStrategy(); + + // 测试搜索页面背景图片(马甲包使用相同图片) + expect(strategy.getAdminSearchBackgroundImage('userSearch'), + "atu_images/index/at_icon_coupon_head_bg.png"); + + // 测试搜索按钮渐变颜色(马甲包使用不同渐变) + final buttonGradient = strategy.getAdminSearchButtonGradient('userSearch'); + expect(buttonGradient, isA>()); + expect(buttonGradient.length, 2); + expect(buttonGradient[0], const Color(0xffFF9326)); + expect(buttonGradient[1], const Color(0xffFEB219)); + + // 测试搜索按钮文本颜色(马甲包使用不同颜色) + expect(strategy.getAdminSearchButtonTextColor('userSearch'), Colors.white); + + // 测试编辑图标路径(马甲包使用相同图标) + expect(strategy.getAdminSearchEditIcon('userSearch'), + "atu_images/room/at_icon_room_edit.png"); + + // 测试搜索框边框颜色(马甲包使用不同颜色) + expect(strategy.getAdminSearchInputBorderColor('userSearch'), + const Color(0xffFF5722)); + + // 测试搜索框文本颜色(马甲包使用不同颜色) + expect(strategy.getAdminSearchInputTextColor('userSearch'), Colors.black87); + + // 测试用户信息图标路径(马甲包使用相同图标) + expect(strategy.getAdminSearchUserInfoIcon('specialIdBg'), + "atu_images/general/at_icon_special_id_bg.png"); + }); + }); + + group('Media Page Strategy Tests', () { + test('BaseBusinessLogicStrategy image preview methods return correct values', + () { + final strategy = BaseBusinessLogicStrategy(); + + // 测试图片预览页面背景颜色 + expect(strategy.getImagePreviewBackgroundColor(), Colors.black); + + // 测试图片预览画廊背景颜色 + expect(strategy.getImagePreviewGalleryBackgroundColor(), Colors.black); + + // 测试图片预览AppBar背景颜色 + expect(strategy.getImagePreviewAppBarBackgroundColor(), Colors.black54); + + // 测试图片预览返回图标颜色 + expect(strategy.getImagePreviewBackIconColor(), Colors.white); + + // 测试图片预览文本颜色 + expect(strategy.getImagePreviewTextColor(), Colors.white); + + // 测试图片预览加载指示器颜色 + expect(strategy.getImagePreviewLoadingIndicatorColor(), + const Color(0xFFFF9500)); + + // 测试图片预览AppBar透明度 + expect(strategy.getImagePreviewAppBarOpacity(), 0.54); + }); + + test('BaseBusinessLogicStrategy video player methods return correct values', + () { + final strategy = BaseBusinessLogicStrategy(); + + // 测试视频播放页面背景颜色 + expect(strategy.getVideoPlayerBackgroundColor(), Colors.black); + + // 测试视频播放控制界面背景颜色 + expect(strategy.getVideoPlayerControlsBackgroundColor(), Colors.black38); + + // 测试视频播放图标颜色 + expect(strategy.getVideoPlayerIconColor(), Colors.white); + + // 测试视频播放底部控制栏背景颜色 + expect(strategy.getVideoPlayerBottomControlsBackgroundColor(), + Colors.black54); + + // 测试视频播放进度条活动颜色 + expect(strategy.getVideoPlayerProgressBarActiveColor(), Colors.red); + + // 测试视频播放进度条非活动颜色 + expect(strategy.getVideoPlayerProgressBarInactiveColor(), Colors.grey); + + // 测试视频播放文本颜色 + expect(strategy.getVideoPlayerTextColor(), Colors.white); + + // 测试视频播放加载指示器颜色 + expect(strategy.getVideoPlayerLoadingIndicatorColor(), Colors.red); + + // 测试视频播放控制界面透明度 + expect(strategy.getVideoPlayerControlsOpacity(), 0.38); + + // 测试视频播放底部控制栏透明度 + expect(strategy.getVideoPlayerBottomControlsOpacity(), 0.54); + + // 测试视频播放控制界面显示持续时间 + expect(strategy.getVideoPlayerControlsDisplayDuration(), 3); + }); + + test('Variant1BusinessLogicStrategy image preview methods return variant values', + () { + final strategy = Variant1BusinessLogicStrategy(); + + // 测试马甲包图片预览页面背景颜色(深灰色) + expect(strategy.getImagePreviewBackgroundColor(), + const Color(0xff1a1a1a)); + + // 测试马甲包图片预览画廊背景颜色(深灰色) + expect(strategy.getImagePreviewGalleryBackgroundColor(), + const Color(0xff1a1a1a)); + + // 测试马甲包图片预览AppBar背景颜色(深灰色70%透明度) + expect(strategy.getImagePreviewAppBarBackgroundColor(), + const Color(0xff1a1a1a).withOpacity(0.7)); + + // 测试马甲包图片预览返回图标颜色(浅灰色) + expect(strategy.getImagePreviewBackIconColor(), + const Color(0xffe6e6e6)); + + // 测试马甲包图片预览文本颜色(浅灰色) + expect(strategy.getImagePreviewTextColor(), + const Color(0xffe6e6e6)); + + // 测试马甲包图片预览加载指示器颜色(橙色主题色) + expect(strategy.getImagePreviewLoadingIndicatorColor(), + const Color(0xffFF5722)); + + // 测试马甲包图片预览AppBar透明度 + expect(strategy.getImagePreviewAppBarOpacity(), 0.7); + }); + + test('Variant1BusinessLogicStrategy video player methods return variant values', + () { + final strategy = Variant1BusinessLogicStrategy(); + + // 测试马甲包视频播放页面背景颜色(深灰色) + expect(strategy.getVideoPlayerBackgroundColor(), + const Color(0xff1a1a1a)); + + // 测试马甲包视频播放控制界面背景颜色(深灰色50%透明度) + expect(strategy.getVideoPlayerControlsBackgroundColor(), + const Color(0xff1a1a1a).withOpacity(0.5)); + + // 测试马甲包视频播放图标颜色(浅灰色) + expect(strategy.getVideoPlayerIconColor(), + const Color(0xffe6e6e6)); + + // 测试马甲包视频播放底部控制栏背景颜色(深灰色60%透明度) + expect(strategy.getVideoPlayerBottomControlsBackgroundColor(), + const Color(0xff1a1a1a).withOpacity(0.6)); + + // 测试马甲包视频播放进度条活动颜色(橙色主题色) + expect(strategy.getVideoPlayerProgressBarActiveColor(), + const Color(0xffFF5722)); + + // 测试马甲包视频播放进度条非活动颜色(深灰色) + expect(strategy.getVideoPlayerProgressBarInactiveColor(), + const Color(0xff666666)); + + // 测试马甲包视频播放文本颜色(浅灰色) + expect(strategy.getVideoPlayerTextColor(), + const Color(0xffe6e6e6)); + + // 测试马甲包视频播放加载指示器颜色(橙色主题色) + expect(strategy.getVideoPlayerLoadingIndicatorColor(), + const Color(0xffFF5722)); + + // 测试马甲包视频播放控制界面透明度 + expect(strategy.getVideoPlayerControlsOpacity(), 0.5); + + // 测试马甲包视频播放底部控制栏透明度 + expect(strategy.getVideoPlayerBottomControlsOpacity(), 0.6); + + // 测试马甲包视频播放控制界面显示持续时间(5秒) + expect(strategy.getVideoPlayerControlsDisplayDuration(), 5); + }); + }); + + group('Report Page Strategy Tests', () { + test('BaseBusinessLogicStrategy report page methods return correct values', () { + final strategy = BaseBusinessLogicStrategy(); + + // 测试报告页面背景图片 + expect(strategy.getReportPageBackgroundImage(), + "atu_images/room/at_icon_room_settig_bg.png"); + + // 测试报告页面提示文本颜色 + expect(strategy.getReportPageHintTextColor(), Colors.black38); + + // 测试报告页面容器背景颜色 + expect(strategy.getReportPageContainerBackgroundColor(), Colors.white); + + // 测试报告页面边框颜色 + expect(strategy.getReportPageBorderColor(), const Color(0xffE6E6E6)); + + // 测试报告页面主要文本颜色 + expect(strategy.getReportPagePrimaryTextColor(), Colors.black); + + // 测试报告页面次要提示文本颜色 + expect(strategy.getReportPageSecondaryHintTextColor(), Colors.black45); + + // 测试报告页面未选中边框颜色 + expect(strategy.getReportPageUnselectedBorderColor(), Colors.grey); + + // 测试报告页面提交按钮背景颜色(原始应用使用主题色,但Base策略返回蓝色占位符) + expect(strategy.getReportPageSubmitButtonBackgroundColor(), Colors.blue); + + // 测试报告页面提交按钮文本颜色 + expect(strategy.getReportPageSubmitButtonTextColor(), Colors.white); + + // 测试报告页面图标 + expect(strategy.getReportPageIcon('addPic'), + "atu_images/general/at_icon_add_pic.png"); + expect(strategy.getReportPageIcon('closePic'), + "atu_images/general/at_icon_pic_close.png"); + expect(strategy.getReportPageIcon('checked'), + "atu_images/general/at_icon_checked.png"); + expect(strategy.getReportPageIcon('unknown'), ""); + }); + + test('Variant1BusinessLogicStrategy report page methods return variant values', () { + final strategy = Variant1BusinessLogicStrategy(); + + // 测试报告页面背景图片(马甲包使用相同图片) + expect(strategy.getReportPageBackgroundImage(), + "atu_images/room/at_icon_room_settig_bg.png"); + + // 测试报告页面提示文本颜色(马甲包使用深灰色) + expect(strategy.getReportPageHintTextColor(), + const Color(0xff666666)); + + // 测试报告页面容器背景颜色(马甲包使用白色) + expect(strategy.getReportPageContainerBackgroundColor(), Colors.white); + + // 测试报告页面边框颜色(马甲包使用浅灰色) + expect(strategy.getReportPageBorderColor(), + const Color(0xffCCCCCC)); + + // 测试报告页面主要文本颜色(马甲包使用黑色) + expect(strategy.getReportPagePrimaryTextColor(), Colors.black); + + // 测试报告页面次要提示文本颜色(马甲包使用深灰色) + expect(strategy.getReportPageSecondaryHintTextColor(), + const Color(0xff666666)); + + // 测试报告页面未选中边框颜色(马甲包使用灰色) + expect(strategy.getReportPageUnselectedBorderColor(), Colors.grey); + + // 测试报告页面提交按钮背景颜色(马甲包使用橙色主题色) + expect(strategy.getReportPageSubmitButtonBackgroundColor(), + const Color(0xffFF5722)); + + // 测试报告页面提交按钮文本颜色(马甲包使用白色) + expect(strategy.getReportPageSubmitButtonTextColor(), Colors.white); + + // 测试报告页面图标(马甲包使用相同图标) + expect(strategy.getReportPageIcon('addPic'), + "atu_images/general/at_icon_add_pic.png"); + expect(strategy.getReportPageIcon('closePic'), + "atu_images/general/at_icon_pic_close.png"); + expect(strategy.getReportPageIcon('checked'), + "atu_images/general/at_icon_checked.png"); + expect(strategy.getReportPageIcon('unknown'), ""); + }); + }); + + group('Store Page Strategy Tests', () { + test('BaseBusinessLogicStrategy store page methods return correct values', () { + final strategy = BaseBusinessLogicStrategy(); + + // 测试Store页面背景图片 + expect(strategy.getStorePageBackgroundImage(), + "atu_images/room/at_icon_room_settig_bg.png"); + + // 测试Store页面购物袋图标 + expect(strategy.getStorePageShoppingBagIcon(), + "atu_images/store/at_icon_shop_bag.png"); + + // 测试Store页面购物袋图标外边距 + final shoppingBagMargin = strategy.getStorePageShoppingBagMargin(); + expect(shoppingBagMargin, isA()); + expect(shoppingBagMargin.end, 10.0); + + // 测试Store页面Tab标签内边距 + final tabLabelPadding = strategy.getStorePageTabLabelPadding(); + expect(tabLabelPadding, isA()); + expect(tabLabelPadding.left, 12.0); + expect(tabLabelPadding.right, 12.0); + + // 测试Store页面Tab标签选中文本样式 + final selectedTabStyle = strategy.getStorePageTabLabelStyle(); + expect(selectedTabStyle, isA()); + expect(selectedTabStyle.fontSize, 15.0); + expect(selectedTabStyle.color, Colors.black87); + expect(selectedTabStyle.fontWeight, FontWeight.w600); + + // 测试Store页面Tab标签未选中文本样式 + final unselectedTabStyle = strategy.getStorePageTabUnselectedLabelStyle(); + expect(unselectedTabStyle, isA()); + expect(unselectedTabStyle.fontSize, 13.0); + expect(unselectedTabStyle.color, Colors.black38); + expect(unselectedTabStyle.fontWeight, FontWeight.w500); + + // 测试Store页面Tab指示器颜色 + expect(strategy.getStorePageTabIndicatorColor(), + const Color(0xffFF5722)); // ChatVibeTheme.primaryColor + + // 测试Store页面Tab分割线颜色 + expect(strategy.getStorePageTabDividerColor(), Colors.transparent); + + // 测试Store页面底部Divider颜色 + expect(strategy.getStorePageBottomDividerColor(), + const Color(0x1F000000)); // Colors.black12 + + // 测试Store页面底部Divider厚度 + expect(strategy.getStorePageBottomDividerThickness(), 0.5); + + // 测试Store页面底部Divider高度 + expect(strategy.getStorePageBottomDividerHeight(), 24.0); + + // 测试Store页面金币图标 + expect(strategy.getStorePageGoldIcon(), + "atu_images/general/at_icon_jb.png"); + + // 测试Store页面金币文本颜色 + expect(strategy.getStorePageGoldTextColor(), + const Color(0xFFFFE134)); + + // 测试Store页面金币图标颜色 + expect(strategy.getStorePageGoldIconColor(), + const Color(0xFFFFE134)); + }); + + test('BaseBusinessLogicStrategy store item methods return correct values', () { + final strategy = BaseBusinessLogicStrategy(); + + // 测试Store商品项背景颜色 + expect(strategy.getStoreItemBackgroundColor(), Colors.white); + + // 测试Store商品项未选中边框颜色 + expect(strategy.getStoreItemUnselectedBorderColor(), + const Color(0x1F000000)); // Colors.black12 + + // 测试Store商品项选中边框颜色 + expect(strategy.getStoreItemSelectedBorderColor(), + const Color(0xffFF9500)); // ChatVibeTheme.primaryLight + + // 测试Store商品项金币图标 + expect(strategy.getStoreItemGoldIcon(), + "atu_images/general/at_icon_jb.png"); + + // 测试Store商品项价格文本颜色 + expect(strategy.getStoreItemPriceTextColor(), Colors.black); + + // 测试Store商品项购买按钮背景颜色 + expect(strategy.getStoreItemBuyButtonColor(), + const Color(0xffFF9500)); // ChatVibeTheme.primaryLight + + // 测试Store商品项购买按钮文本颜色 + expect(strategy.getStoreItemBuyButtonTextColor(), Colors.white); + + // 测试Store商品项操作图标 + expect(strategy.getStoreItemActionIcon('headdress'), + "atu_images/store/at_icon_shop_item_search.png"); + expect(strategy.getStoreItemActionIcon('mountains'), + "atu_images/store/at_icon_shop_item_play.png"); + expect(strategy.getStoreItemActionIcon('chatbox'), + "atu_images/store/at_icon_shop_item_search.png"); + expect(strategy.getStoreItemActionIcon('theme'), + "atu_images/store/at_icon_store_theme_rev.png"); + expect(strategy.getStoreItemActionIcon('unknown'), "atu_images/store/at_icon_store_theme_rev.png"); + }); + + test('Variant1BusinessLogicStrategy store page methods return variant values', () { + final strategy = Variant1BusinessLogicStrategy(); + + // 测试Store页面背景图片(马甲包使用相同图片) + expect(strategy.getStorePageBackgroundImage(), + "atu_images/room/at_icon_room_settig_bg.png"); + + // 测试Store页面购物袋图标(马甲包使用相同图标) + expect(strategy.getStorePageShoppingBagIcon(), + "atu_images/store/at_icon_shop_bag.png"); + + // 测试Store页面购物袋图标外边距(马甲包保持相同外边距) + final shoppingBagMargin = strategy.getStorePageShoppingBagMargin(); + expect(shoppingBagMargin, isA()); + expect(shoppingBagMargin.end, 10.0); + + // 测试Store页面Tab标签内边距(马甲包保持相同内边距) + final tabLabelPadding = strategy.getStorePageTabLabelPadding(); + expect(tabLabelPadding, isA()); + expect(tabLabelPadding.left, 12.0); + expect(tabLabelPadding.right, 12.0); + + // 测试Store页面Tab标签选中文本样式(马甲包使用主题色) + final selectedTabStyle = strategy.getStorePageTabLabelStyle(); + expect(selectedTabStyle, isA()); + expect(selectedTabStyle.fontSize, 15.0); + expect(selectedTabStyle.color, const Color(0xffFF5722)); // 马甲包主题橙色 + expect(selectedTabStyle.fontWeight, FontWeight.w600); + + // 测试Store页面Tab标签未选中文本样式(马甲包使用灰色) + final unselectedTabStyle = strategy.getStorePageTabUnselectedLabelStyle(); + expect(unselectedTabStyle, isA()); + expect(unselectedTabStyle.fontSize, 13.0); + expect(unselectedTabStyle.color, const Color(0xff757575)); // 马甲包灰色 + expect(unselectedTabStyle.fontWeight, FontWeight.w500); + + // 测试Store页面Tab指示器颜色(马甲包使用主题色) + expect(strategy.getStorePageTabIndicatorColor(), + const Color(0xffFF5722)); // 马甲包主题橙色 + + // 测试Store页面Tab分割线颜色(马甲包保持透明) + expect(strategy.getStorePageTabDividerColor(), Colors.transparent); + + // 测试Store页面底部Divider颜色(马甲包使用浅灰色) + expect(strategy.getStorePageBottomDividerColor(), + const Color(0xffEEEEEE)); // 浅灰色,替代Colors.black12 + + // 测试Store页面底部Divider厚度(马甲包保持相同) + expect(strategy.getStorePageBottomDividerThickness(), 0.5); + + // 测试Store页面底部Divider高度(马甲包保持相同) + expect(strategy.getStorePageBottomDividerHeight(), 24.0); + + // 测试Store页面金币图标(马甲包使用相同图标) + expect(strategy.getStorePageGoldIcon(), + "atu_images/general/at_icon_jb.png"); + + // 测试Store页面金币文本颜色(马甲包使用主题色) + expect(strategy.getStorePageGoldTextColor(), + const Color(0xffFF5722)); // 马甲包主题橙色 + + // 测试Store页面金币图标颜色(马甲包使用主题色) + expect(strategy.getStorePageGoldIconColor(), + const Color(0xffFF5722)); // 马甲包主题橙色 + }); + + test('Variant1BusinessLogicStrategy store item methods return variant values', () { + final strategy = Variant1BusinessLogicStrategy(); + + // 测试Store商品项背景颜色(马甲包使用表面色) + expect(strategy.getStoreItemBackgroundColor(), + const Color(0xffffffff)); // ChatVibeTheme.surfaceColor - 白色 + + // 测试Store商品项未选中边框颜色(马甲包使用浅灰色) + expect(strategy.getStoreItemUnselectedBorderColor(), + const Color(0xffE0E0E0)); // 浅灰色,替代Colors.black12 + + // 测试Store商品项选中边框颜色(马甲包使用主题色) + expect(strategy.getStoreItemSelectedBorderColor(), + const Color(0xffFF5722)); // 马甲包主题橙色 + + // 测试Store商品项金币图标(马甲包使用相同图标) + expect(strategy.getStoreItemGoldIcon(), + "atu_images/general/at_icon_jb.png"); + + // 测试Store商品项价格文本颜色(马甲包使用主要文本颜色) + expect(strategy.getStoreItemPriceTextColor(), + const Color(0xff212121)); // ChatVibeTheme.textPrimary - 深灰色 + + // 测试Store商品项购买按钮背景颜色(马甲包使用主题色) + expect(strategy.getStoreItemBuyButtonColor(), + const Color(0xffFF5722)); // 马甲包主题橙色 + + // 测试Store商品项购买按钮文本颜色(马甲包使用主色上的文本颜色) + expect(strategy.getStoreItemBuyButtonTextColor(), + const Color(0xffffffff)); // ChatVibeTheme.textOnPrimary - 白色 + + // 测试Store商品项操作图标(马甲包使用相同图标) + expect(strategy.getStoreItemActionIcon('headdress'), + "atu_images/store/at_icon_shop_item_search.png"); + expect(strategy.getStoreItemActionIcon('mountains'), + "atu_images/store/at_icon_shop_item_play.png"); + expect(strategy.getStoreItemActionIcon('chatbox'), + "atu_images/store/at_icon_shop_item_search.png"); + expect(strategy.getStoreItemActionIcon('theme'), + "atu_images/store/at_icon_store_theme_rev.png"); + expect(strategy.getStoreItemActionIcon('unknown'), "atu_images/store/at_icon_store_theme_rev.png"); + }); + }); + + group('Voice Room Page Strategy Tests', () { + test('BaseBusinessLogicStrategy voice room methods return correct values', () { + final strategy = BaseBusinessLogicStrategy(); + + // 测试语音房间页面背景颜色 + expect(strategy.getVoiceRoomBackgroundColor(), Colors.transparent); + + // 测试语音房间页面Tab标签选中颜色 + expect(strategy.getVoiceRoomTabLabelColor(), Colors.white); + + // 测试语音房间页面Tab标签未选中颜色 + expect(strategy.getVoiceRoomTabUnselectedLabelColor(), Colors.white54); + + // 测试语音房间页面Tab指示器颜色 + expect(strategy.getVoiceRoomTabIndicatorColor(), Colors.transparent); + + // 测试语音房间页面Tab分割线颜色 + expect(strategy.getVoiceRoomTabDividerColor(), Colors.transparent); + + // 测试语音房间页面Tab标签内边距 + final tabLabelPadding = strategy.getVoiceRoomTabLabelPadding(); + expect(tabLabelPadding, isA()); + expect(tabLabelPadding.left, 12.0); + expect(tabLabelPadding.right, 12.0); + + // 测试语音房间页面聊天容器外边距 + final chatContainerMargin = strategy.getVoiceRoomChatContainerMargin(); + expect(chatContainerMargin, isA()); + expect(chatContainerMargin.end, 52.0); + + // 测试语音房间页面Tab标签选中文本样式 + final selectedTabStyle = strategy.getVoiceRoomTabLabelStyle(); + expect(selectedTabStyle, isA()); + expect(selectedTabStyle.fontSize, 15.0); + expect(selectedTabStyle.fontFamily, "MyCustomFont"); + + // 测试语音房间页面Tab标签未选中文本样式 + final unselectedTabStyle = strategy.getVoiceRoomTabUnselectedLabelStyle(); + expect(unselectedTabStyle, isA()); + expect(unselectedTabStyle.fontSize, 13.0); + expect(unselectedTabStyle.fontFamily, "MyCustomFont"); + + // 测试语音房间默认背景图像路径 + expect(strategy.getVoiceRoomDefaultBackgroundImage(), + "atu_images/room/at_icon_room_defaut_bg.png"); + }); + + test('Variant1BusinessLogicStrategy voice room methods return variant values', () { + final strategy = Variant1BusinessLogicStrategy(); + + // 测试马甲包语音房间页面背景颜色(深灰色) + expect(strategy.getVoiceRoomBackgroundColor(), + const Color(0xff1a1a1a)); + + // 测试马甲包语音房间页面Tab标签选中颜色(浅灰色) + expect(strategy.getVoiceRoomTabLabelColor(), + const Color(0xffe6e6e6)); + + // 测试马甲包语音房间页面Tab标签未选中颜色(灰色) + expect(strategy.getVoiceRoomTabUnselectedLabelColor(), + const Color(0xff999999)); + + // 测试马甲包语音房间页面Tab指示器颜色(马甲包主题色) + expect(strategy.getVoiceRoomTabIndicatorColor(), + const Color(0xffFF5722)); + + // 测试马甲包语音房间页面Tab分割线颜色(透明) + expect(strategy.getVoiceRoomTabDividerColor(), Colors.transparent); + + // 测试马甲包语音房间页面Tab标签内边距(与原始应用相同) + final tabLabelPadding = strategy.getVoiceRoomTabLabelPadding(); + expect(tabLabelPadding, isA()); + expect(tabLabelPadding.left, 12.0); + expect(tabLabelPadding.right, 12.0); + + // 测试马甲包语音房间页面聊天容器外边距(与原始应用相同) + final chatContainerMargin = strategy.getVoiceRoomChatContainerMargin(); + expect(chatContainerMargin, isA()); + expect(chatContainerMargin.end, 52.0); + + // 测试马甲包语音房间页面Tab标签选中文本样式(与原始应用相同) + final selectedTabStyle = strategy.getVoiceRoomTabLabelStyle(); + expect(selectedTabStyle, isA()); + expect(selectedTabStyle.fontSize, 15.0); + expect(selectedTabStyle.fontFamily, "MyCustomFont"); + + // 测试马甲包语音房间页面Tab标签未选中文本样式(与原始应用相同) + final unselectedTabStyle = strategy.getVoiceRoomTabUnselectedLabelStyle(); + expect(unselectedTabStyle, isA()); + expect(unselectedTabStyle.fontSize, 13.0); + expect(unselectedTabStyle.fontFamily, "MyCustomFont"); + + // 测试马甲包语音房间默认背景图像路径(与原始应用相同) + expect(strategy.getVoiceRoomDefaultBackgroundImage(), + "atu_images/room/at_icon_room_defaut_bg.png"); + }); + }); + + group('Recharge Page Strategy Tests', () { + test('BaseBusinessLogicStrategy recharge page methods return correct values', () { + final strategy = BaseBusinessLogicStrategy(); + + // 测试充值页面背景图片 + expect(strategy.getRechargePageBackgroundImage(), + "atu_images/room/at_icon_room_settig_bg.png"); + + // 测试充值页面Scaffold背景颜色 + expect(strategy.getRechargePageScaffoldBackgroundColor(), Colors.transparent); + + // 测试充值页面对话框屏障颜色 + expect(strategy.getRechargePageDialogBarrierColor(), Colors.transparent); + + // 测试充值页面对话框容器背景颜色 + expect(strategy.getRechargePageDialogContainerBackgroundColor(), Colors.white30); + + // 测试充值页面对话框文本颜色 + expect(strategy.getRechargePageDialogTextColor(), Colors.black38); + + // 测试充值页面主容器背景颜色 + expect(strategy.getRechargePageMainContainerBackgroundColor(), Colors.white); + + // 测试充值页面按钮背景颜色 + expect(strategy.getRechargePageButtonBackgroundColor(), + const Color(0xFFE6F7FF)); // 近似主题浅色 + + // 测试充值页面按钮文本颜色 + expect(strategy.getRechargePageButtonTextColor(), Colors.white); + + // 测试充值页面钱包背景图像路径 + expect(strategy.getRechargePageWalletBackgroundImage(), + "atu_images/index/at_icon_wallet_bg.png"); + + // 测试充值页面钱包图标路径 + expect(strategy.getRechargePageWalletIcon(), + "atu_images/index/at_icon_wallet_icon.png"); + + // 测试充值页面钱包文本颜色 + expect(strategy.getRechargePageWalletTextColor(), Colors.white); + + // 测试充值页面金币图标路径(默认图标) + expect(strategy.getRechargePageGoldIcon(), + "atu_images/general/at_icon_jb.png"); + + // 测试充值页面选中项背景颜色 + expect(strategy.getRechargePageSelectedItemBackgroundColor(), + const Color(0xFFE6F7FF)); // 近似主题浅色 + + // 测试充值页面选中项边框颜色 + expect(strategy.getRechargePageSelectedItemBorderColor(), + const Color(0xFF1890FF)); // 近似主题主色 + + // 测试充值页面未选中项边框颜色 + expect(strategy.getRechargePageUnselectedItemBorderColor(), + const Color(0xffE6E6E6)); + + // 测试充值页面未选中项背景颜色 + expect(strategy.getRechargePageUnselectedItemBackgroundColor(), Colors.white); + + // 测试充值页面项目文本颜色 + expect(strategy.getRechargePageItemTextColor(), Colors.black54); + + // 测试充值页面价格文本颜色 + expect(strategy.getRechargePageItemPriceTextColor(), Colors.black); + + // 测试根据索引获取充值页面金币图标路径 + expect(strategy.getRechargePageGoldIconByIndex(0), + "atu_images/general/at_icon_jb.png"); + expect(strategy.getRechargePageGoldIconByIndex(1), + "atu_images/general/at_icon_jb2.png"); + expect(strategy.getRechargePageGoldIconByIndex(2), + "atu_images/general/at_icon_jb3.png"); + expect(strategy.getRechargePageGoldIconByIndex(99), + "atu_images/general/at_icon_jb.png"); // 默认图标 + + // 测试充值页面记录图标路径 + expect(strategy.getRechargePageRecordIcon(), + "atu_images/index/at_icon_recharge_recod.png"); + + // 测试充值页面Apple产品项背景颜色 + expect(strategy.getRechargePageAppleItemBackgroundColor(), Colors.white); + }); + + test('Variant1BusinessLogicStrategy recharge page methods return variant values', () { + final strategy = Variant1BusinessLogicStrategy(); + + // 测试充值页面背景图片(马甲包使用相同图片) + expect(strategy.getRechargePageBackgroundImage(), + "atu_images/room/at_icon_room_settig_bg.png"); + + // 测试充值页面Scaffold背景颜色(马甲包使用深灰色) + expect(strategy.getRechargePageScaffoldBackgroundColor(), + const Color(0xff1a1a1a)); // #1a1a1a + + // 测试充值页面对话框屏障颜色(马甲包使用深灰色半透明) + expect(strategy.getRechargePageDialogBarrierColor(), + const Color(0xCC1a1a1a)); // withOpacity(0.8) + + // 测试充值页面对话框容器背景颜色(马甲包使用深灰色) + expect(strategy.getRechargePageDialogContainerBackgroundColor(), + const Color(0xff333333)); // 深灰色 + + // 测试充值页面对话框文本颜色(马甲包使用浅灰色) + expect(strategy.getRechargePageDialogTextColor(), + const Color(0xffe6e6e6)); // #e6e6e6 + + // 测试充值页面主容器背景颜色(马甲包使用深灰色) + expect(strategy.getRechargePageMainContainerBackgroundColor(), + const Color(0xff1a1a1a)); // #1a1a1a + + // 测试充值页面按钮背景颜色(马甲包使用主题色) + expect(strategy.getRechargePageButtonBackgroundColor(), + const Color(0xffFF5722)); // 马甲包主题色 + + // 测试充值页面按钮文本颜色(马甲包使用白色,与原始应用相同) + expect(strategy.getRechargePageButtonTextColor(), Colors.white); + + // 测试充值页面钱包背景图像路径(马甲包使用相同图片) + expect(strategy.getRechargePageWalletBackgroundImage(), + "atu_images/index/at_icon_wallet_bg.png"); + + // 测试充值页面钱包图标路径(马甲包使用相同图标) + expect(strategy.getRechargePageWalletIcon(), + "atu_images/index/at_icon_wallet_icon.png"); + + // 测试充值页面钱包文本颜色(马甲包使用浅灰色) + expect(strategy.getRechargePageWalletTextColor(), + const Color(0xffe6e6e6)); // #e6e6e6 + + // 测试充值页面金币图标路径(马甲包使用相同图标) + expect(strategy.getRechargePageGoldIcon(), + "atu_images/general/at_icon_jb.png"); + + // 测试充值页面选中项背景颜色(马甲包使用主题色半透明) + expect(strategy.getRechargePageSelectedItemBackgroundColor(), + const Color(0x33FF5722)); // withOpacity(0.2) + + // 测试充值页面选中项边框颜色(马甲包使用主题色) + expect(strategy.getRechargePageSelectedItemBorderColor(), + const Color(0xffFF5722)); // 马甲包主题色 + + // 测试充值页面未选中项边框颜色(马甲包使用深灰色) + expect(strategy.getRechargePageUnselectedItemBorderColor(), + const Color(0xff666666)); // 深灰色 + + // 测试充值页面未选中项背景颜色(马甲包使用深灰色) + expect(strategy.getRechargePageUnselectedItemBackgroundColor(), + const Color(0xff2a2a2a)); // 深灰色背景 + + // 测试充值页面项目文本颜色(马甲包使用浅灰色) + expect(strategy.getRechargePageItemTextColor(), + const Color(0xffe6e6e6)); // #e6e6e6 + + // 测试充值页面价格文本颜色(马甲包使用白色) + expect(strategy.getRechargePageItemPriceTextColor(), Colors.white); + + // 测试根据索引获取充值页面金币图标路径(马甲包使用相同图标) + expect(strategy.getRechargePageGoldIconByIndex(0), + "atu_images/general/at_icon_jb.png"); + expect(strategy.getRechargePageGoldIconByIndex(1), + "atu_images/general/at_icon_jb2.png"); + expect(strategy.getRechargePageGoldIconByIndex(2), + "atu_images/general/at_icon_jb3.png"); + expect(strategy.getRechargePageGoldIconByIndex(99), + "atu_images/general/at_icon_jb.png"); // 默认图标 + + // 测试充值页面记录图标路径(马甲包使用相同图标) + expect(strategy.getRechargePageRecordIcon(), + "atu_images/index/at_icon_recharge_recod.png"); + + // 测试充值页面Apple产品项背景颜色(马甲包使用深灰色) + expect(strategy.getRechargePageAppleItemBackgroundColor(), + const Color(0xff2a2a2a)); // 深灰色 + }); + }); + + group('Dynamic Page Strategy Tests', () { + test('BaseBusinessLogicStrategy dynamic page methods return correct values', () { + final strategy = BaseBusinessLogicStrategy(); + + // 测试动态页面背景图像路径 + expect(strategy.getDynamicPageBackgroundImage(), + "atu_images/index/at_icon_index_mask.png"); + + // 测试动态页面Scaffold背景颜色 + expect(strategy.getDynamicPageScaffoldBackgroundColor(), Colors.transparent); + + // 测试动态页面AppBar背景颜色 + expect(strategy.getDynamicPageAppBarBackgroundColor(), Colors.transparent); + + // 测试动态页面Tab标签选中颜色 + expect(strategy.getDynamicPageTabLabelColor(), Colors.black87); + + // 测试动态页面Tab标签未选中颜色 + expect(strategy.getDynamicPageTabUnselectedLabelColor(), Colors.black38); + + // 测试动态页面Tab指示器颜色 + expect(strategy.getDynamicPageTabIndicatorColor(), Colors.transparent); + + // 测试动态页面Tab分割线颜色 + expect(strategy.getDynamicPageTabDividerColor(), Colors.transparent); + + // 测试动态页面添加动态按钮图标路径 + expect(strategy.getDynamicPageAddButtonIcon(), + "atu_images/dynamic/at_icon_add_dynamic_btn.png"); + + // 测试动态页面Tab标签是否可滚动 + expect(strategy.shouldDynamicPageTabScrollable(), true); + + // 测试动态页面Tab标签选中文本样式 + expect(strategy.getDynamicPageTabLabelStyle(), + const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + )); + + // 测试动态页面Tab标签未选中文本样式 + expect(strategy.getDynamicPageTabUnselectedLabelStyle(), + const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 15, + )); + }); + + test('Variant1BusinessLogicStrategy dynamic page methods return variant values', () { + final strategy = Variant1BusinessLogicStrategy(); + + // 测试动态页面背景图像路径(马甲包使用相同图片) + expect(strategy.getDynamicPageBackgroundImage(), + "atu_images/index/at_icon_index_mask.png"); + + // 测试动态页面Scaffold背景颜色(马甲包保持透明) + expect(strategy.getDynamicPageScaffoldBackgroundColor(), Colors.transparent); + + // 测试动态页面AppBar背景颜色(马甲包保持透明) + expect(strategy.getDynamicPageAppBarBackgroundColor(), Colors.transparent); + + // 测试动态页面Tab标签选中颜色(马甲包使用主要文本颜色) + expect(strategy.getDynamicPageTabLabelColor(), + const Color(0xff212121)); // ChatVibeTheme.textPrimary + + // 测试动态页面Tab标签未选中颜色(马甲包使用次要文本颜色) + expect(strategy.getDynamicPageTabUnselectedLabelColor(), + const Color(0xff757575)); // ChatVibeTheme.textSecondary + + // 测试动态页面Tab指示器颜色(马甲包使用主题色) + expect(strategy.getDynamicPageTabIndicatorColor(), + const Color(0xffFF5722)); // 马甲包主题色 + + // 测试动态页面Tab分割线颜色(马甲包保持透明) + expect(strategy.getDynamicPageTabDividerColor(), Colors.transparent); + + // 测试动态页面添加动态按钮图标路径(马甲包使用相同图标) + expect(strategy.getDynamicPageAddButtonIcon(), + "atu_images/dynamic/at_icon_add_dynamic_btn.png"); + + // 测试动态页面Tab标签是否可滚动(马甲包保持相同行为) + expect(strategy.shouldDynamicPageTabScrollable(), true); + + // 测试动态页面Tab标签选中文本样式(马甲包应用主题颜色) + expect(strategy.getDynamicPageTabLabelStyle(), + const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: Color(0xff212121), // ChatVibeTheme.textPrimary + )); + + // 测试动态页面Tab标签未选中文本样式(马甲包应用主题颜色) + expect(strategy.getDynamicPageTabUnselectedLabelStyle(), + const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 15, + color: Color(0xff757575), // ChatVibeTheme.textSecondary + )); + }); + }); + + group('Splash Page Strategy Tests', () { + test('BaseBusinessLogicStrategy splash page methods return correct values', () { + final strategy = BaseBusinessLogicStrategy(); + + // 测试启动页面背景图像路径 + expect(strategy.getSplashPageBackgroundImage(), "atu_images/splash/splash.png"); + + // 测试启动页面图标路径 + expect(strategy.getSplashPageIcon(), "atu_images/splash/at_icon_splash_icon.png"); + + // 测试启动页面跳过按钮背景图像路径 + expect(strategy.getSplashPageSkipButtonBackground(), + "atu_images/splash/at_icon_splash_skip_bg.png"); + + // 测试启动页面跳过按钮文本颜色 + expect(strategy.getSplashPageSkipButtonTextColor(), Colors.white); + + // 测试启动页面游戏名称背景图像路径 + expect(strategy.getSplashPageKingGamesNameBackground(), + "atu_images/index/at_icon_splash_king_games_name_bg.png"); + + // 测试启动页面游戏名称文本颜色 + expect(strategy.getSplashPageKingGamesTextColor(), Colors.white); + + // 测试启动页面CP名称背景图像路径 + expect(strategy.getSplashPageCpNameBackground(), + "atu_images/index/at_icon_splash_cp_name_bg.png"); + + // 测试启动页面CP名称文本颜色 + expect(strategy.getSplashPageCpTextColor(), Colors.white); + }); + + test('Variant1BusinessLogicStrategy splash page methods return variant values', () { + final strategy = Variant1BusinessLogicStrategy(); + + // 测试启动页面背景图像路径(马甲包目前使用相同图像) + expect(strategy.getSplashPageBackgroundImage(), "atu_images/splash/splash.png"); + + // 测试启动页面图标路径(马甲包目前使用相同图标) + expect(strategy.getSplashPageIcon(), "atu_images/splash/at_icon_splash_icon.png"); + + // 测试启动页面跳过按钮背景图像路径(马甲包目前使用相同图像) + expect(strategy.getSplashPageSkipButtonBackground(), + "atu_images/splash/at_icon_splash_skip_bg.png"); + + // 测试启动页面跳过按钮文本颜色(马甲包使用主色上的文本颜色) + expect(strategy.getSplashPageSkipButtonTextColor(), isA()); + + // 测试启动页面游戏名称背景图像路径(马甲包目前使用相同图像) + expect(strategy.getSplashPageKingGamesNameBackground(), + "atu_images/index/at_icon_splash_king_games_name_bg.png"); + + // 测试启动页面游戏名称文本颜色(马甲包使用主色上的文本颜色) + expect(strategy.getSplashPageKingGamesTextColor(), isA()); + + // 测试启动页面CP名称背景图像路径(马甲包目前使用相同图像) + expect(strategy.getSplashPageCpNameBackground(), + "atu_images/index/at_icon_splash_cp_name_bg.png"); + + // 测试启动页面CP名称文本颜色(马甲包使用主色上的文本颜色) + expect(strategy.getSplashPageCpTextColor(), isA()); + }); + + }); + + group('WebView Page Strategy Tests', () { + test('BaseBusinessLogicStrategy webview page methods return correct values', () { + final strategy = BaseBusinessLogicStrategy(); + + // 测试WebView页面AppBar背景颜色 + expect(strategy.getWebViewPageAppBarBackgroundColor(), Colors.white); + + // 测试WebView页面标题文本颜色 + expect(strategy.getWebViewPageTitleTextColor(), const Color(0xff333333)); + + // 测试WebView页面返回箭头图标颜色 + expect(strategy.getWebViewPageBackArrowColor(), const Color(0xff333333)); + + // 测试WebView页面进度条背景颜色 + expect(strategy.getWebViewPageProgressBarBackgroundColor(), Colors.white70.withOpacity(0)); + + // 测试WebView页面进度条活动颜色 + expect(strategy.getWebViewPageProgressBarActiveColor(), const Color(0xffFF6000)); + + // 测试游戏WebView页面关闭图标颜色 + expect(strategy.getGameWebViewCloseIconColor(), Colors.white); + + // 测试游戏WebView页面Scaffold背景颜色 + expect(strategy.getGameWebViewScaffoldBackgroundColor(), Colors.transparent); + + // 测试游戏WebView页面加载指示器颜色 + expect(strategy.getGameWebViewLoadingIndicatorColor(), const Color(0xffFF6000)); + + // 测试游戏WebView页面进度条背景颜色 + expect(strategy.getGameWebViewProgressBarBackgroundColor(), Colors.white70.withOpacity(0)); + }); + + test('Variant1BusinessLogicStrategy webview page methods return variant values', () { + final strategy = Variant1BusinessLogicStrategy(); + + // 测试WebView页面AppBar背景颜色(马甲包使用背景颜色) + expect(strategy.getWebViewPageAppBarBackgroundColor(), isA()); + + // 测试WebView页面标题文本颜色(马甲包使用主要文本颜色) + expect(strategy.getWebViewPageTitleTextColor(), isA()); + + // 测试WebView页面返回箭头图标颜色(马甲包使用主要文本颜色) + expect(strategy.getWebViewPageBackArrowColor(), isA()); + + // 测试WebView页面进度条背景颜色(马甲包使用透明背景) + expect(strategy.getWebViewPageProgressBarBackgroundColor(), Colors.transparent); + + // 测试WebView页面进度条活动颜色(马甲包使用主题色) + expect(strategy.getWebViewPageProgressBarActiveColor(), isA()); + + // 测试游戏WebView页面关闭图标颜色(马甲包使用主要文本颜色) + expect(strategy.getGameWebViewCloseIconColor(), isA()); + + // 测试游戏WebView页面Scaffold背景颜色(马甲包使用背景颜色) + expect(strategy.getGameWebViewScaffoldBackgroundColor(), isA()); + + // 测试游戏WebView页面进度条背景颜色(马甲包使用背景颜色半透明) + expect(strategy.getGameWebViewProgressBarBackgroundColor(), isA()); + + // 测试游戏WebView页面加载指示器颜色(马甲包使用主题色) + expect(strategy.getGameWebViewLoadingIndicatorColor(), isA()); + }); + }); + + group('Search Page Strategy Tests', () { + test('BaseBusinessLogicStrategy search page methods return correct values', () { + final strategy = BaseBusinessLogicStrategy(); + + // 测试搜索页面Scaffold背景颜色 + expect(strategy.getSearchPageScaffoldBackgroundColor(), Colors.white); + + // 测试搜索页面返回图标颜色 + expect(strategy.getSearchPageBackIconColor(), const Color(0xffBBBBBB)); + + // 测试搜索页面输入框边框颜色 + expect(strategy.getSearchPageInputBorderColor(), Colors.transparent); + + // 测试搜索页面输入框文本颜色 + expect(strategy.getSearchPageInputTextColor(), Colors.grey); + + // 测试搜索页面按钮文本颜色 + expect(strategy.getSearchPageButtonTextColor(), Colors.black); + + // 测试搜索页面按钮渐变颜色 + final buttonGradient = strategy.getSearchPageButtonGradient(); + expect(buttonGradient, isA>()); + expect(buttonGradient.length, 2); + expect(buttonGradient[0], Colors.transparent); + expect(buttonGradient[1], Colors.transparent); + + // 测试搜索页面搜索结果Tab标签内边距 + final tabLabelPadding = strategy.getSearchPageResultTabLabelPadding(); + expect(tabLabelPadding, isA()); + expect(tabLabelPadding.left, 15.0); + expect(tabLabelPadding.right, 15.0); + + // 测试搜索页面搜索结果Tab标签选中颜色 + expect(strategy.getSearchPageResultTabLabelColor(), Colors.black); + + // 测试搜索页面搜索结果Tab标签未选中颜色 + expect(strategy.getSearchPageResultTabUnselectedLabelColor(), Colors.black26); + + // 测试搜索页面搜索结果Tab标签未选中文本样式 + final unselectedTabStyle = strategy.getSearchPageResultTabUnselectedLabelStyle(); + expect(unselectedTabStyle, isA()); + expect(unselectedTabStyle.fontSize, 17.0); + expect(unselectedTabStyle.fontWeight, FontWeight.w400); + + // 测试搜索页面搜索结果Tab标签选中文本样式 + final selectedTabStyle = strategy.getSearchPageResultTabLabelStyle(); + expect(selectedTabStyle, isA()); + expect(selectedTabStyle.fontSize, 18.0); + expect(selectedTabStyle.fontWeight, FontWeight.w600); + + // 测试搜索页面搜索结果Tab指示器颜色 + expect(strategy.getSearchPageResultTabIndicatorColor(), isA()); + + // 测试搜索页面搜索结果Tab指示器宽度 + expect(strategy.getSearchPageResultTabIndicatorWidth(), 15.0); + + // 测试搜索页面搜索结果背景颜色 + expect(strategy.getSearchPageResultBackgroundColor(), const Color(0xffFFF8DA)); + + // 测试搜索页面历史项背景颜色 + expect(strategy.getSearchPageHistoryItemBackgroundColor(), const Color(0xffFF9500)); + + // 测试搜索页面历史项文本颜色 + expect(strategy.getSearchPageHistoryItemTextColor(), Colors.white); + + // 测试搜索页面清空历史图标路径 + expect(strategy.getSearchPageClearHistoryIcon(), + "atu_images/general/at_icon_delete.png"); + + // 测试搜索页面空数据图标路径 + expect(strategy.getSearchPageEmptyDataIcon(), + "atu_images/room/at_icon_room_defaut_bg.png"); + }); + + test('Variant1BusinessLogicStrategy search page methods return variant values', () { + final strategy = Variant1BusinessLogicStrategy(); + + // 测试搜索页面Scaffold背景颜色(马甲包使用背景颜色) + expect(strategy.getSearchPageScaffoldBackgroundColor(), + const Color(0xfff5f5f5)); // 马甲包背景颜色 + + // 测试搜索页面返回图标颜色(马甲包使用主要文本颜色) + expect(strategy.getSearchPageBackIconColor(), + const Color(0xff666666)); // 深灰色 + + // 测试搜索页面输入框边框颜色(马甲包使用主题色) + expect(strategy.getSearchPageInputBorderColor(), + const Color(0xffFF5722)); // 马甲包主题色 + + // 测试搜索页面输入框文本颜色(马甲包使用主要文本颜色) + expect(strategy.getSearchPageInputTextColor(), + const Color(0xff212121)); // 主要文本颜色 + + // 测试搜索页面按钮文本颜色(马甲包使用主色上的文本颜色) + expect(strategy.getSearchPageButtonTextColor(), + Colors.white); // 白色 + + // 测试搜索页面按钮渐变颜色(马甲包使用主题渐变) + final buttonGradient = strategy.getSearchPageButtonGradient(); + expect(buttonGradient, isA>()); + expect(buttonGradient.length, 2); + expect(buttonGradient[0], const Color(0xffFF5722).withOpacity(0.8)); // 马甲包主题色,不透明度0.8 + expect(buttonGradient[1], const Color(0xffFF5722).withOpacity(0.6)); // 马甲包主题色,不透明度0.6 + + // 测试搜索页面搜索结果Tab标签内边距(与原始应用相同) + final tabLabelPadding = strategy.getSearchPageResultTabLabelPadding(); + expect(tabLabelPadding, isA()); + expect(tabLabelPadding.left, 15.0); + expect(tabLabelPadding.right, 15.0); + + // 测试搜索页面搜索结果Tab标签选中颜色(马甲包使用主要文本颜色) + expect(strategy.getSearchPageResultTabLabelColor(), + const Color(0xff212121)); // 主要文本颜色 + + // 测试搜索页面搜索结果Tab标签未选中颜色(马甲包使用次要文本颜色) + expect(strategy.getSearchPageResultTabUnselectedLabelColor(), + const Color(0xff757575)); // 次要文本颜色 + + // 测试搜索页面搜索结果Tab标签未选中文本样式(马甲包使用次要文本颜色) + final unselectedTabStyle = strategy.getSearchPageResultTabUnselectedLabelStyle(); + expect(unselectedTabStyle, isA()); + expect(unselectedTabStyle.fontSize, 17.0); + expect(unselectedTabStyle.fontWeight, FontWeight.w400); + expect(unselectedTabStyle.color, const Color(0xff757575)); // 次要文本颜色 + + // 测试搜索页面搜索结果Tab标签选中文本样式(马甲包使用主要文本颜色) + final selectedTabStyle = strategy.getSearchPageResultTabLabelStyle(); + expect(selectedTabStyle, isA()); + expect(selectedTabStyle.fontSize, 18.0); + expect(selectedTabStyle.fontWeight, FontWeight.w600); + expect(selectedTabStyle.color, const Color(0xff212121)); // 主要文本颜色 + + // 测试搜索页面搜索结果Tab指示器颜色(马甲包使用主题色) + final indicatorGradient = strategy.getSearchPageResultTabIndicatorColor(); + expect(indicatorGradient, isA()); + expect(indicatorGradient.colors.length, 2); + expect(indicatorGradient.colors[0], const Color(0xffFF5722)); // 马甲包主题色 + expect(indicatorGradient.colors[1], const Color(0xffFF9A00)); // 主题色渐变 + + // 测试搜索页面搜索结果Tab指示器宽度(与原始应用相同) + expect(strategy.getSearchPageResultTabIndicatorWidth(), 15.0); + + // 测试搜索页面搜索结果背景颜色(马甲包使用浅主题色) + expect(strategy.getSearchPageResultBackgroundColor(), + const Color(0xffFFECB3)); // 浅橙色背景 + + // 测试搜索页面历史项背景颜色(马甲包使用主题色) + expect(strategy.getSearchPageHistoryItemBackgroundColor(), + const Color(0xffFF5722)); // 马甲包主题色 + + // 测试搜索页面历史项文本颜色(马甲包使用主色上的文本颜色) + expect(strategy.getSearchPageHistoryItemTextColor(), + Colors.white); // 白色 + + // 测试搜索页面清空历史图标路径(与原始应用相同) + expect(strategy.getSearchPageClearHistoryIcon(), + "atu_images/general/at_icon_delete.png"); + + // 测试搜索页面空数据图标路径(与原始应用相同) + expect(strategy.getSearchPageEmptyDataIcon(), + "atu_images/room/at_icon_room_defaut_bg.png"); + }); + }); + + group('Task Page Strategy Tests', () { + test('BaseBusinessLogicStrategy task page methods return correct values', () { + final strategy = BaseBusinessLogicStrategy(); + + // 测试任务页面背景图片 + expect(strategy.getTaskPageBackgroundImage(), + "atu_images/room/at_icon_room_settig_bg.png"); + + // 测试任务页面返回按钮颜色 + expect(strategy.getTaskPageBackButtonColor(), Colors.black); + + // 测试任务页面Scaffold背景颜色 + expect(strategy.getTaskPageScaffoldBackgroundColor(), Colors.white); + + // 测试任务页面容器背景颜色 + expect(strategy.getTaskPageContainerBackgroundColor(), Colors.white); + + // 测试任务页面边框颜色 + expect(strategy.getTaskPageBorderColor(), const Color(0xffE6E6E6)); + + // 测试任务页面特殊边框颜色 + expect(strategy.getTaskPageSpecialBorderColor(), const Color(0xffBB92FF)); + + // 测试任务页面主要文本颜色 + expect(strategy.getTaskPagePrimaryTextColor(), Colors.white); + + // 测试任务页面金币文本颜色 + expect(strategy.getTaskPageGoldTextColor(), const Color(0xffFFB627)); + + // 测试任务页面经验文本颜色 + expect(strategy.getTaskPageExpTextColor(), const Color(0xff52FF90)); + + // 测试任务页面主题颜色 + expect(strategy.getTaskPageThemeColor(), ChatVibeTheme.primaryColor); + + // 测试任务页面主题浅色 + expect(strategy.getTaskPageThemeLightColor(), ChatVibeTheme.primaryLight); + + // 测试任务页面可领取按钮渐变颜色 + final receivableGradient = strategy.getTaskPageReceivableButtonGradient(); + expect(receivableGradient, isA>()); + expect(receivableGradient.length, 2); + expect(receivableGradient[0], const Color(0xffBB92FF)); + expect(receivableGradient[1], const Color(0xff8B45FF)); + + // 测试任务页面已完成按钮渐变颜色 + final completedGradient = strategy.getTaskPageCompletedButtonGradient(); + expect(completedGradient, isA>()); + expect(completedGradient.length, 2); + expect(completedGradient[0], const Color(0xffDADADA)); + expect(completedGradient[1], const Color(0xff585859)); + + // 测试任务页面按钮文本颜色 + expect(strategy.getTaskPageButtonTextColor(), Colors.white); + + // 测试任务页面蒙版颜色 + expect(strategy.getTaskPageMaskColor(), Colors.black); + + // 测试任务页面头部背景图片 + expect(strategy.getTaskPageHeadBackgroundImage(), + "atu_images/index/at_icon_task_head_bg.png"); + + // 测试任务页面金币图标 + expect(strategy.getTaskPageGoldIcon(), + "atu_images/general/at_icon_jb.png"); + + // 测试任务页面经验图标 + expect(strategy.getTaskPageExpIcon(), + "atu_images/index/at_icon_task_exp.png"); + + // 测试任务页面邀请奖励背景图片 + expect(strategy.getTaskPageInvitationRewardBackgroundImage(), + "atu_images/index/at_icon_invitation_bg.png"); + + // 测试任务页面礼物袋文本颜色 + expect(strategy.getTaskPageGiftBagTextColor(), Colors.white); + + // 测试任务页面AppBar背景颜色 + expect(strategy.getTaskPageAppBarBackgroundColor(), Colors.transparent); + + // 测试任务页面透明容器颜色 + expect(strategy.getTaskPageTransparentContainerColor(), Colors.transparent); + }); + + test('Variant1BusinessLogicStrategy task page methods return variant values', () { + final strategy = Variant1BusinessLogicStrategy(); + + // 测试任务页面背景图片(马甲包使用相同图片) + expect(strategy.getTaskPageBackgroundImage(), + "atu_images/room/at_icon_room_settig_bg.png"); + + // 测试任务页面返回按钮颜色(马甲包使用主要文本颜色) + expect(strategy.getTaskPageBackButtonColor(), + const Color(0xff212121)); // ChatVibeTheme.textPrimary + + // 测试任务页面Scaffold背景颜色(马甲包使用背景颜色) + expect(strategy.getTaskPageScaffoldBackgroundColor(), + const Color(0xfff5f5f5)); // 马甲包背景颜色 + + // 测试任务页面容器背景颜色(马甲包使用表面颜色) + expect(strategy.getTaskPageContainerBackgroundColor(), + const Color(0xffffffff)); // ChatVibeTheme.surfaceColor + + // 测试任务页面边框颜色(马甲包使用边框颜色) + expect(strategy.getTaskPageBorderColor(), + const Color(0xffE0E0E0)); // ChatVibeTheme.borderColor + + // 测试任务页面特殊边框颜色(马甲包使用主题色) + expect(strategy.getTaskPageSpecialBorderColor(), + const Color(0xffFF5722)); // 马甲包主题色 + + // 测试任务页面主要文本颜色(马甲包使用主要文本颜色) + expect(strategy.getTaskPagePrimaryTextColor(), + const Color(0xff212121)); // ChatVibeTheme.textPrimary + + // 测试任务页面金币文本颜色(马甲包使用主题色) + expect(strategy.getTaskPageGoldTextColor(), + const Color(0xffFF5722)); // 马甲包主题色 + + // 测试任务页面经验文本颜色(马甲包使用成功颜色) + expect(strategy.getTaskPageExpTextColor(), + const Color(0xff4CAF50)); // ChatVibeTheme.successColor + + // 测试任务页面主题颜色(马甲包使用主题色) + expect(strategy.getTaskPageThemeColor(), + const Color(0xffFF5722)); // 马甲包主题色 + + // 测试任务页面主题浅色(马甲包使用主题浅色) + expect(strategy.getTaskPageThemeLightColor(), + const Color(0xffFF9500)); // 马甲包主题浅色 + + // 测试任务页面可领取按钮渐变颜色(马甲包使用主题渐变) + final receivableGradient = strategy.getTaskPageReceivableButtonGradient(); + expect(receivableGradient, isA>()); + expect(receivableGradient.length, 2); + expect(receivableGradient[0], const Color(0xffFF5722)); // 马甲包主题色 + expect(receivableGradient[1], const Color(0xffFF9A00)); // 主题色渐变 + + // 测试任务页面已完成按钮渐变颜色(马甲包使用灰色渐变) + final completedGradient = strategy.getTaskPageCompletedButtonGradient(); + expect(completedGradient, isA>()); + expect(completedGradient.length, 2); + expect(completedGradient[0], const Color(0xffCCCCCC)); // 浅灰色 + expect(completedGradient[1], const Color(0xff999999)); // 深灰色 + + // 测试任务页面按钮文本颜色(马甲包使用主色上的文本颜色) + expect(strategy.getTaskPageButtonTextColor(), + const Color(0xffffffff)); // ChatVibeTheme.textOnPrimary + + // 测试任务页面蒙版颜色(马甲包使用透明黑色) + expect(strategy.getTaskPageMaskColor(), + Colors.black.withOpacity(0.5)); // 半透明黑色 + + // 测试任务页面头部背景图片(马甲包使用相同图片) + expect(strategy.getTaskPageHeadBackgroundImage(), + "atu_images/index/at_icon_task_head_bg.png"); + + // 测试任务页面金币图标(马甲包使用相同图标) + expect(strategy.getTaskPageGoldIcon(), + "atu_images/general/at_icon_jb.png"); + + // 测试任务页面经验图标(马甲包使用相同图标) + expect(strategy.getTaskPageExpIcon(), + "atu_images/index/at_icon_task_exp.png"); + + // 测试任务页面邀请奖励背景图片(马甲包使用相同图片) + expect(strategy.getTaskPageInvitationRewardBackgroundImage(), + "atu_images/index/at_icon_invitation_bg.png"); + + // 测试任务页面礼物袋文本颜色(马甲包使用白色) + expect(strategy.getTaskPageGiftBagTextColor(), Colors.white); + + // 测试任务页面AppBar背景颜色(马甲包使用透明) + expect(strategy.getTaskPageAppBarBackgroundColor(), Colors.transparent); + + // 测试任务页面透明容器颜色(马甲包使用透明) + expect(strategy.getTaskPageTransparentContainerColor(), Colors.transparent); + }); + }); + + group('Settings Pages Strategy Tests', () { + test('BaseBusinessLogicStrategy settings page methods return correct values', () { + final strategy = BaseBusinessLogicStrategy(); + + // 测试设置页面背景图片 + expect(strategy.getSettingsPageBackgroundImage(), + "atu_images/person/at_icon_edit_userinfo_bg.png"); + + // 测试设置页面次要文本颜色 + expect(strategy.getSettingsPageSecondaryTextColor(), Colors.black38); + + // 测试设置页面主要容器背景颜色 + expect(strategy.getSettingsPageMainContainerBackgroundColor(), Colors.white); + + // 测试设置页面主要容器边框颜色 + expect(strategy.getSettingsPageMainContainerBorderColor(), + const Color(0xffE6E6E6)); + + // 测试设置页面主要文本颜色 + expect(strategy.getSettingsPagePrimaryTextColor(), Colors.black); + + // 测试设置页面图标颜色 + expect(strategy.getSettingsPageIconColor(), Colors.black26); + }); + + test('BaseBusinessLogicStrategy language page methods return correct values', () { + final strategy = BaseBusinessLogicStrategy(); + + // 测试语言页面背景图片 + expect(strategy.getLanguagePageBackgroundImage(), + "atu_images/person/at_icon_edit_userinfo_bg.png"); + + // 测试语言页面主要文本颜色 + expect(strategy.getLanguagePagePrimaryTextColor(), Colors.black); + + // 测试语言页面复选框活动颜色 + expect(strategy.getLanguagePageCheckboxActiveColor(), + ChatVibeTheme.primaryColor); + + // 测试语言页面复选框边框颜色 + expect(strategy.getLanguagePageCheckboxBorderColor(), + const Color(0xffD4D4D4)); + + // 测试语言页面复选框对勾颜色 + expect(strategy.getLanguagePageCheckColor(), Colors.transparent); + + // 测试语言页面复选框边框圆角 + expect(strategy.getLanguagePageCheckboxBorderRadius(), 15); + + // 测试语言页面复选框边框宽度 + expect(strategy.getLanguagePageCheckboxBorderWidth(), 2); + + // 测试语言页面Scaffold背景颜色 + expect(strategy.getLanguagePageScaffoldBackgroundColor(), Colors.transparent); + }); + + test('BaseBusinessLogicStrategy account page methods return correct values', () { + final strategy = BaseBusinessLogicStrategy(); + + // 测试账户页面背景图片 + expect(strategy.getAccountPageBackgroundImage(), + "atu_images/person/at_icon_edit_userinfo_bg.png"); + + // 测试账户页面次要文本颜色 + expect(strategy.getAccountPageSecondaryTextColor(), + Colors.black38); // 原始应用次要文本颜色 + + // 测试账户页面主容器背景颜色 + expect(strategy.getAccountPageMainContainerBackgroundColor(), Colors.white); + + // 测试账户页面主容器边框颜色 + expect(strategy.getAccountPageMainContainerBorderColor(), + const Color(0xffE6E6E6)); + + // 测试账户页面主要文本颜色 + expect(strategy.getAccountPagePrimaryTextColor(), Colors.black); + + // 测试账户页面图标颜色 + expect(strategy.getAccountPageIconColor(), Colors.black26); + + // 测试账户页面分隔线颜色 + expect(strategy.getAccountPageDividerColor(), Colors.black12); + }); + + test('Variant1BusinessLogicStrategy settings page methods return variant values', () { + final strategy = Variant1BusinessLogicStrategy(); + + // 测试马甲包设置页面背景图片(目前使用相同图片) + expect(strategy.getSettingsPageBackgroundImage(), + "atu_images/person/at_icon_edit_userinfo_bg.png"); + + // 测试马甲包设置页面次要文本颜色(使用次要文本颜色) + expect(strategy.getSettingsPageSecondaryTextColor(), + const Color(0xff757575)); // ChatVibeTheme.textSecondary + + // 测试马甲包设置页面主要容器背景颜色(使用表面颜色) + expect(strategy.getSettingsPageMainContainerBackgroundColor(), + const Color(0xffffffff)); // ChatVibeTheme.surfaceColor + + // 测试马甲包设置页面主要容器边框颜色(使用边框颜色) + expect(strategy.getSettingsPageMainContainerBorderColor(), + const Color(0xffE0E0E0)); // ChatVibeTheme.borderColor + + // 测试马甲包设置页面主要文本颜色(使用主要文本颜色) + expect(strategy.getSettingsPagePrimaryTextColor(), + const Color(0xff212121)); // ChatVibeTheme.textPrimary + + // 测试马甲包设置页面图标颜色(使用次要文本颜色) + expect(strategy.getSettingsPageIconColor(), + const Color(0xff757575)); // ChatVibeTheme.textSecondary + }); + + test('Variant1BusinessLogicStrategy language page methods return variant values', () { + final strategy = Variant1BusinessLogicStrategy(); + + // 测试马甲包语言页面背景图片(目前使用相同图片) + expect(strategy.getLanguagePageBackgroundImage(), + "atu_images/person/at_icon_edit_userinfo_bg.png"); + + // 测试马甲包语言页面主要文本颜色(使用主要文本颜色) + expect(strategy.getLanguagePagePrimaryTextColor(), + const Color(0xff212121)); // ChatVibeTheme.textPrimary + + // 测试马甲包语言页面复选框活动颜色(使用马甲包主题色) + expect(strategy.getLanguagePageCheckboxActiveColor(), + const Color(0xffFF5722)); // 马甲包主题色 + + // 测试马甲包语言页面复选框边框颜色(使用马甲包边框色) + expect(strategy.getLanguagePageCheckboxBorderColor(), + ChatVibeTheme.borderColor); // 马甲包边框色 + + // 测试马甲包语言页面复选框对勾颜色(使用透明色) + expect(strategy.getLanguagePageCheckColor(), Colors.transparent); + + // 测试马甲包语言页面复选框边框圆角(使用相同圆角值15) + expect(strategy.getLanguagePageCheckboxBorderRadius(), 15); + + // 测试马甲包语言页面复选框边框宽度(使用相同边框宽度2) + expect(strategy.getLanguagePageCheckboxBorderWidth(), 2); + + // 测试马甲包语言页面Scaffold背景颜色(使用透明色) + expect(strategy.getLanguagePageScaffoldBackgroundColor(), Colors.transparent); + }); + + test('Variant1BusinessLogicStrategy account page methods return variant values', () { + final strategy = Variant1BusinessLogicStrategy(); + + // 测试马甲包账户页面背景图片(目前使用相同图片) + expect(strategy.getAccountPageBackgroundImage(), + "atu_images/person/at_icon_edit_userinfo_bg.png"); + + // 测试马甲包账户页面次要文本颜色(使用次要文本颜色) + expect(strategy.getAccountPageSecondaryTextColor(), + ChatVibeTheme.textSecondary); // 马甲包次要文本颜色 + + // 测试马甲包账户页面主容器背景颜色(使用表面颜色) + expect(strategy.getAccountPageMainContainerBackgroundColor(), + const Color(0xffffffff)); // ChatVibeTheme.surfaceColor + + // 测试马甲包账户页面主容器边框颜色(使用边框颜色) + expect(strategy.getAccountPageMainContainerBorderColor(), + const Color(0xffE0E0E0)); // ChatVibeTheme.borderColor + + // 测试马甲包账户页面主要文本颜色(使用主要文本颜色) + expect(strategy.getAccountPagePrimaryTextColor(), + const Color(0xff212121)); // ChatVibeTheme.textPrimary + + // 测试马甲包账户页面图标颜色(使用次要文本颜色) + expect(strategy.getAccountPageIconColor(), + const Color(0xff757575)); // ChatVibeTheme.textSecondary + + // 测试马甲包账户页面分隔线颜色(使用分隔线颜色) + expect(strategy.getAccountPageDividerColor(), + const Color(0xffE0E0E0)); // ChatVibeTheme.dividerColor + }); + }); + +} \ No newline at end of file diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..0008c26 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can create tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:aslan/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +}