Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into new_dev

weicu
fdp 11 months ago
parent
commit
9d2a0a4fea
  1. 5
      ios/Runner/RunnerProfile.entitlements
  2. 2
      lib/data/models/appconfig_model.dart
  3. 2
      lib/data/models/appconfig_model.g.dart
  4. 18
      lib/data/services/ota_check_service.dart
  5. 2
      lib/modules/home/controllers/home_controller.dart
  6. 13
      lib/modules/ota_upgrade/controllers/ota_core_contropller.dart
  7. 115
      lib/modules/translation/controllers/translation_controller.dart
  8. 68
      lib/modules/translation/views/translation_view.dart
  9. 33
      local_plugins/native_plugin/.gitignore
  10. 33
      local_plugins/native_plugin/.metadata
  11. 3
      local_plugins/native_plugin/CHANGELOG.md
  12. 1
      local_plugins/native_plugin/LICENSE
  13. 15
      local_plugins/native_plugin/README.md
  14. 4
      local_plugins/native_plugin/analysis_options.yaml
  15. 9
      local_plugins/native_plugin/android/.gitignore
  16. 52
      local_plugins/native_plugin/android/build.gradle
  17. 1
      local_plugins/native_plugin/android/settings.gradle
  18. 3
      local_plugins/native_plugin/android/src/main/AndroidManifest.xml
  19. 38
      local_plugins/native_plugin/android/src/main/java/com/example/native_plugin/NativePlugin.java
  20. 29
      local_plugins/native_plugin/android/src/test/java/com/example/native_plugin/NativePluginTest.java
  21. 38
      local_plugins/native_plugin/ios/.gitignore
  22. 0
      local_plugins/native_plugin/ios/Assets/.gitkeep
  23. 15
      local_plugins/native_plugin/ios/Classes/FLNativeView.h
  24. 83
      local_plugins/native_plugin/ios/Classes/FLNativeView.m
  25. 4
      local_plugins/native_plugin/ios/Classes/NativePlugin.h
  26. 114
      local_plugins/native_plugin/ios/Classes/NativePlugin.m
  27. 15
      local_plugins/native_plugin/ios/Classes/PlayerView.h
  28. 100
      local_plugins/native_plugin/ios/Classes/PlayerView.m
  29. 14
      local_plugins/native_plugin/ios/Resources/PrivacyInfo.xcprivacy
  30. 29
      local_plugins/native_plugin/ios/native_plugin.podspec
  31. 19
      local_plugins/native_plugin/lib/native_plugin.dart
  32. 37
      local_plugins/native_plugin/lib/native_plugin_method_channel.dart
  33. 47
      local_plugins/native_plugin/lib/native_plugin_platform_interface.dart
  34. 72
      local_plugins/native_plugin/pubspec.yaml
  35. 5
      pubspec.yaml

5
ios/Runner/RunnerProfile.entitlements

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>

2
lib/data/models/appconfig_model.dart

@ -79,6 +79,7 @@ class DBProduct {
final String? productimage;
final String scanuuid;
final String? version;
bool? isforceupdate;
final bool? iscontinuouschat;
final String? updatedescription;
final String? updatepackageaddress;
@ -91,6 +92,7 @@ class DBProduct {
required this.productimage,
required this.scanuuid,
required this.version,
required this.isforceupdate, //该产品固件版本是否强制更新
required this.iscontinuouschat,
required this.updatedescription,
required this.updatepackageaddress,

2
lib/data/models/appconfig_model.g.dart

@ -79,6 +79,7 @@ DBProduct _$DBProductFromJson(Map<String, dynamic> json) => DBProduct(
productimage: json['productimage'] as String?,
scanuuid: json['scanuuid'] as String,
version: json['version'] as String?,
isforceupdate: json['isforceupdate'] as bool?,
iscontinuouschat: json['iscontinuouschat'] as bool?,
updatedescription: json['updatedescription'] as String?,
updatepackageaddress: json['updatepackageaddress'] as String?,
@ -91,6 +92,7 @@ Map<String, dynamic> _$DBProductToJson(DBProduct instance) => <String, dynamic>{
'productimage': instance.productimage,
'scanuuid': instance.scanuuid,
'version': instance.version,
'isforceupdate': instance.isforceupdate,
'iscontinuouschat': instance.iscontinuouschat,
'devicename': instance.devicename,
'updatedescription': instance.updatedescription,

18
lib/data/services/ota_check_service.dart

@ -57,6 +57,7 @@ class OtaCheckService extends GetxService {
/// [isFullyConnected] 设备是否完全连接
/// 返回是否成功执行了检查
Future<bool> performOtaCheck(bool isFullyConnected) async {
bool isForceUpdate = false;
try {
// 检查是否应该进行OTA检查
if (!shouldCheckOta()) {
@ -95,7 +96,12 @@ class OtaCheckService extends GetxService {
_otaCoreService.isNewVersionAvailable(currentVersion, latestVersion);
if (hasUpdate) {
Logger.d(_tag, '发现新版本,准备显示升级提醒');
isForceUpdate = latestInfo['isForceUpdate'] as bool? ?? false;
if (isForceUpdate) {
Logger.d(_tag, '发现强制更新版本,准备显示升级提醒');
} else {
Logger.d(_tag, '发现新版本,准备显示升级提醒');
}
// 再次确认当前在Home页面(防止异步执行期间页面切换)
if (_isCurrentlyOnHomePage()) {
@ -109,12 +115,18 @@ class OtaCheckService extends GetxService {
}
// 标记为已检查(无论是否有更新都标记为已检查)
_markAsChecked();
if (!isForceUpdate) {
_markAsChecked();
}
_isCheckingInProgress = false;
return true;
} catch (e) {
Logger.e(_tag, '检查OTA升级失败: $e');
// 即使检查失败,也标记为已检查,避免重复尝试
_markAsChecked();
if (!isForceUpdate) {
_markAsChecked();
}
_isCheckingInProgress = false;
return false;
}
}

2
lib/modules/home/controllers/home_controller.dart

@ -277,7 +277,7 @@ class HomeController extends GetxController {
// 连接成功后检查OTA升级
if (noviceGuideService.isHomeGuide) {
Future.delayed(const Duration(seconds: 2), () {
Future.delayed(const Duration(seconds: 1), () {
_checkOtaUpgrade();
});
}

13
lib/modules/ota_upgrade/controllers/ota_core_contropller.dart

@ -525,17 +525,20 @@ class OtaCoreService {
}
}
product.isforceupdate = product.version?.contains("+");
return {
'latestVersion': product.version ?? "",
'latestVersion': product.version?.split('+').first ?? "",
'updateDescription': product.updatedescription ?? "",
'updatePackageAddress': product.updatepackageaddress ?? "",
'isForceUpdate': product.isforceupdate ?? false,
};
}
/// 比较版本号,判断是否有新版本可用
/// [currentVersion] 当前版本号
/// [latestVersion] 最新版本号
/// 返回true表示有新版本可用(支持升级和降级)
/// 返回true表示有新版本可用(只支持升级)
bool isNewVersionAvailable(String currentVersion, String latestVersion) {
try {
List<int> currentParts =
@ -555,10 +558,12 @@ class OtaCoreService {
latestParts.add(0);
}
// 比较每一段,只要有不同就返回true(支持升级和降级)
// 从高位到低位比较版本号
for (int i = 0; i < maxLength; i++) {
if (latestParts[i] != currentParts[i]) {
if (latestParts[i] > currentParts[i]) {
return true;
} else if (latestParts[i] < currentParts[i]) {
return false;
}
}

115
lib/modules/translation/controllers/translation_controller.dart

@ -2,12 +2,15 @@ import 'dart:io';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:intl/intl.dart';
import 'package:native_plugin/native_plugin.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:pip/pip.dart';
import '../../../data/services/ast_service.dart';
import '../../../data/services/bluetooth_manager.dart';
import '../../../data/services/music_manager.dart';
@ -49,7 +52,12 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
final BleManager bleManager = Get.find<BleManager>();
final _musiceManager = Get.find<MusicManager>();
late final TranslationUsageService _usageService;
// 安卓悬浮窗管理器
late final FloatingWindowManager _floatingWindowManager;
// 苹果悬浮窗管理器
late final Pip _pip;
late final NativePlugin _nativePlugin;
int _pipContentView = 0;
// ==================== 悬浮窗相关 ====================
final isFloatingWindowEnabled = false.obs; // 悬浮窗状态
@ -135,7 +143,7 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
// 底部控制栏高度
double _bottomBarHeight = 0.0;
/// 初始化悬浮窗管理器
/// 初始化安卓悬浮窗管理器
void _initFloatingWindowManager() {
try {
if (!Get.isRegistered<FloatingWindowManager>()) {
@ -148,13 +156,68 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
}
}
/// 初始化苹果悬浮窗管理器
Future<void> initPlatformState() async {
_pip = Pip();
_nativePlugin = NativePlugin();
try {
isFloatingWindowEnabled.value = await _pip.isActived();
await _pip.registerStateChangedObserver(
PipStateChangedObserver(
onPipStateChanged: (state, error) {
isFloatingWindowEnabled.value = state == PipState.pipStateStarted;
if (state == PipState.pipStateFailed) {
_pip.dispose();
}
},
),
);
} on PlatformException {
isFloatingWindowEnabled.value = false;
}
}
/// 切换悬浮窗功能
Future<void> toggleFloatingWindow() async {
if (isFloatingWindowEnabled.value) {
await _disableFloatingWindow();
if (Platform.isAndroid) {
await _disableFloatingWindow();
} else if (Platform.isIOS) {
await _pip.dispose();
}
} else {
await _enableFloatingWindow();
if (Platform.isAndroid) {
await _enableFloatingWindow();
} else if (Platform.isIOS) {
await _startPip();
}
}
}
Future<void> _startPip() async {
if (_pipContentView == 0) {
_pipContentView = await _nativePlugin.createPipContentView();
}
final options = PipOptions(
autoEnterEnabled: true,
aspectRatioX: 375,
aspectRatioY: 400,
sourceRectHintLeft: 0,
sourceRectHintTop: 0,
sourceRectHintRight: 0,
sourceRectHintBottom: 0,
seamlessResizeEnabled: true,
useExternalStateMonitor: true,
externalStateMonitorInterval: 100,
contentView: _pipContentView,
preferredContentWidth: 375,
preferredContentHeight: 400,
controlStyle: 2,
);
await _pip.setup(options);
await _pip.start();
}
/// 启用悬浮窗功能
@ -217,16 +280,24 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
_languageManager.getChineseNameByAsrCode(item.targetLanguageCode) ??
'unknown';
final data = FloatingWindowData(
sourceText: item.sourceText,
translatedText: item.translatedText,
sourceLanguage: sourceLanguageName,
targetLanguage: targetLanguageName,
timestamp: item.timestamp,
isIntermediate: item.isIntermediate,
);
await _floatingWindowManager.updateFloatingWindowContent(data);
if (Platform.isAndroid) {
final data = FloatingWindowData(
sourceText: item.sourceText,
translatedText: item.translatedText,
sourceLanguage: sourceLanguageName,
targetLanguage: targetLanguageName,
timestamp: item.timestamp,
isIntermediate: item.isIntermediate,
);
await _floatingWindowManager.updateFloatingWindowContent(data);
} else if (Platform.isIOS) {
await _nativePlugin.updatePipContentView({
'viewId': _pipContentView,
'cn': item.sourceText,
'en': item.translatedText,
'lang': '$sourceLanguageName -> $targetLanguageName',
});
}
}
/// 更改翻译模式
@ -374,8 +445,13 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
_timerManager = RecordTimerController();
_historyManager = TranslationHistoryManager();
// 初始化悬浮窗管理器
_initFloatingWindowManager();
if (Platform.isAndroid) {
// 初始化安卓悬浮窗管理器
_initFloatingWindowManager();
} else if (Platform.isIOS) {
// 初始化苹果悬浮窗管理器
initPlatformState();
}
// 预先检查权限,避免在用户交互时阻塞
_preCheckPermissions();
@ -665,6 +741,15 @@ class TranslationController extends GetxController with WidgetsBindingObserver {
// _asrService.dispose();
// _astService.dispose();
if (Platform.isAndroid) {
_disableFloatingWindow();
} else if (Platform.isIOS) {
_pip.dispose();
if (_pipContentView != 0) {
_nativePlugin.disposePipContentView(_pipContentView);
}
}
// 释放管理器资源
_timerManager.dispose();
_historyManager.dispose();

68
lib/modules/translation/views/translation_view.dart

@ -80,6 +80,31 @@ class TranslationView extends GetView<TranslationController> {
controller: controller,
),
Positioned(
top: 2,
left: 0,
right: 0,
child: SafeArea(
top: true,
child: Container(
height: 56.h,
padding: EdgeInsets.symmetric(horizontal: 8.w),
color: Colors.transparent,
child: Align(
alignment: Alignment.centerLeft,
child: IconButton(
icon: Icon(
Icons.arrow_back_ios,
color: isDarkMode ? Colors.white : Colors.black,
size: 20.sp,
),
onPressed: () => Get.back(),
),
),
),
),
),
// 顶部按钮(倒过来)
Positioned(
top: 50.h,
@ -210,28 +235,27 @@ class TranslationView extends GetView<TranslationController> {
return Row(
children: [
// 悬浮窗切换按钮(仅在支持的模式下显示)
if (Platform.isAndroid)
Obx(() {
if (controller.currentMode.value == 'call' ||
controller.currentMode.value == 'audioVideo') {
return IconButton(
icon: Icon(
controller.isFloatingWindowEnabled.value
? Icons.picture_in_picture
: Icons.picture_in_picture_alt_outlined,
color: controller.isFloatingWindowEnabled.value
? Colors.blue
: (isDarkMode ? Colors.white : Colors.black),
size: 24.sp,
),
onPressed: controller.toggleFloatingWindow,
tooltip: controller.isFloatingWindowEnabled.value
? '禁用悬浮窗'
: '启用悬浮窗',
);
}
return const SizedBox.shrink();
}),
Obx(() {
if (controller.currentMode.value == 'call' ||
controller.currentMode.value == 'audioVideo') {
return IconButton(
icon: Icon(
controller.isFloatingWindowEnabled.value
? Icons.picture_in_picture
: Icons.picture_in_picture_alt_outlined,
color: controller.isFloatingWindowEnabled.value
? Colors.blue
: (isDarkMode ? Colors.white : Colors.black),
size: 24.sp,
),
onPressed: controller.toggleFloatingWindow,
tooltip: controller.isFloatingWindowEnabled.value
? '禁用悬浮窗'
: '启用悬浮窗',
);
}
return const SizedBox.shrink();
}),
IconButton(
icon: Icon(
Icons.radio_button_checked,

33
local_plugins/native_plugin/.gitignore

@ -0,0 +1,33 @@
# 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
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
build/

33
local_plugins/native_plugin/.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: "35c388afb57ef061d06a39b537336c87e0e3d1b1"
channel: "stable"
project_type: plugin
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
- platform: android
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
- platform: ios
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
# 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'

3
local_plugins/native_plugin/CHANGELOG.md

@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.

1
local_plugins/native_plugin/LICENSE

@ -0,0 +1 @@
TODO: Add your license here.

15
local_plugins/native_plugin/README.md

@ -0,0 +1,15 @@
# native_plugin
A new Flutter plugin project.
## Getting Started
This project is a starting point for a Flutter
[plug-in package](https://flutter.dev/to/develop-plugins),
a specialized package that includes platform-specific implementation code for
Android and/or iOS.
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.

4
local_plugins/native_plugin/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

9
local_plugins/native_plugin/android/.gitignore

@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx

52
local_plugins/native_plugin/android/build.gradle

@ -0,0 +1,52 @@
group = "com.example.native_plugin"
version = "1.0"
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.7.0")
}
}
rootProject.allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: "com.android.library"
android {
namespace = "com.example.native_plugin"
compileSdk = 35
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
defaultConfig {
minSdk = 21
}
dependencies {
testImplementation("junit:junit:4.13.2")
testImplementation("org.mockito:mockito-core:5.0.0")
}
testOptions {
unitTests.all {
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen {false}
showStandardStreams = true
}
}
}
}

1
local_plugins/native_plugin/android/settings.gradle

@ -0,0 +1 @@
rootProject.name = 'native_plugin'

3
local_plugins/native_plugin/android/src/main/AndroidManifest.xml

@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.native_plugin">
</manifest>

38
local_plugins/native_plugin/android/src/main/java/com/example/native_plugin/NativePlugin.java

@ -0,0 +1,38 @@
package com.example.native_plugin;
import androidx.annotation.NonNull;
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;
/** NativePlugin */
public class NativePlugin implements 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 MethodChannel channel;
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "native_plugin");
channel.setMethodCallHandler(this);
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
if (call.method.equals("getPlatformVersion")) {
result.success("Android " + android.os.Build.VERSION.RELEASE);
} else {
result.notImplemented();
}
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
}
}

29
local_plugins/native_plugin/android/src/test/java/com/example/native_plugin/NativePluginTest.java

@ -0,0 +1,29 @@
package com.example.native_plugin;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import org.junit.Test;
/**
* This demonstrates a simple unit test of the Java portion of this plugin's implementation.
*
* Once you have built the plugin's example app, you can run these tests from the command
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
* you can run them directly from IDEs that support JUnit such as Android Studio.
*/
public class NativePluginTest {
@Test
public void onMethodCall_getPlatformVersion_returnsExpectedValue() {
NativePlugin plugin = new NativePlugin();
final MethodCall call = new MethodCall("getPlatformVersion", null);
MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
plugin.onMethodCall(call, mockResult);
verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE);
}
}

38
local_plugins/native_plugin/ios/.gitignore

@ -0,0 +1,38 @@
.idea/
.vagrant/
.sconsign.dblite
.svn/
.DS_Store
*.swp
profile
DerivedData/
build/
GeneratedPluginRegistrant.h
GeneratedPluginRegistrant.m
.generated/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
/Flutter/Generated.xcconfig
/Flutter/ephemeral/
/Flutter/flutter_export_environment.sh

0
local_plugins/native_plugin/ios/Assets/.gitkeep

15
local_plugins/native_plugin/ios/Classes/FLNativeView.h

@ -0,0 +1,15 @@
#import <flutter/flutter.h>
@interface FLNativeViewFactory : NSObject <FlutterPlatformViewFactory>
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger> *)messenger;
@end
@interface FLNativeView : NSObject <FlutterBinaryMessenger>
- (instancetype)initWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args
binaryMessenger:(NSObject<FlutterBinaryMessenger> *)messenger;
- (UIView *)view;
@end

83
local_plugins/native_plugin/ios/Classes/FLNativeView.m

@ -0,0 +1,83 @@
#import "FLNativeView.h"
#import "PlayerView.h"
@implementation FLNativeViewFactory {
NSObject<FlutterBinaryMessenger> *_messenger;
}
- (instancetype)initWithMessenger:
(NSObject<FlutterBinaryMessenger> *)messenger {
self = [super init];
if (self) {
_messenger = messenger;
}
return self;
}
- (NSObject<FlutterPlatformView> *)createWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args {
return [[FLNativeView alloc] initWithFrame:frame
viewIdentifier:viewId
arguments:args
binaryMessenger:_messenger];
}
/// Implementing this method is only necessary when the `arguments` in
/// `createWithFrame` is not `nil`.
- (NSObject<FlutterMessageCodec> *)createArgsCodec {
return [FlutterStandardMessageCodec sharedInstance];
}
@end
@implementation FLNativeView {
PlayerView *_view;
FlutterMethodChannel *_methodChannel;
}
- (instancetype)initWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args
binaryMessenger:(NSObject<FlutterBinaryMessenger> *)messenger {
if (self = [super init]) {
_view = [[PlayerView alloc] init];
_methodChannel = [FlutterMethodChannel
methodChannelWithName:
[NSString
stringWithFormat:@"native_plugin/native_view_%lld", viewId]
binaryMessenger:messenger];
__weak typeof(self) weakSelf = self;
[_methodChannel setMethodCallHandler:^(FlutterMethodCall *_Nonnull call,
FlutterResult _Nonnull result) {
typeof(self) strongSelf = weakSelf;
if (strongSelf != nil) {
[strongSelf onMethodCall:call result:result];
}
}];
}
return self;
}
- (UIView *)view {
return _view;
}
- (void)onMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
if ([@"getInternalView" isEqualToString:call.method]) {
result(@((uint64_t)(_view)));
} else if ([@"updateContent" isEqualToString:call.method]) {
NSDictionary *data = call.arguments;
NSString *lang = data[@"lang"];
NSString *cn = data[@"cn"];
NSString *en = data[@"en"];
dispatch_async(dispatch_get_main_queue(), ^{
[_view updateWithLang:lang cn:cn en:en];
});
result(nil);
} else {
result(@(0));
}
}
@end

4
local_plugins/native_plugin/ios/Classes/NativePlugin.h

@ -0,0 +1,4 @@
#import <Flutter/Flutter.h>
@interface NativePlugin : NSObject<FlutterPlugin>
@end

114
local_plugins/native_plugin/ios/Classes/NativePlugin.m

@ -0,0 +1,114 @@
#import "NativePlugin.h"
#import "FLNativeView.h"
#import "PlayerView.h"
#import <AVKit/AVKit.h>
@interface NativePlugin ()
@property(nonatomic, strong) NSMutableArray *_Nonnull pipContentViewArray;
@end
@implementation NativePlugin
+ (void)updateAudioSession {
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
NSError *categoryError = nil;
if (@available(iOS 14.5, *)) {
[audioSession
setCategory:AVAudioSessionCategoryPlayback
mode:AVAudioSessionModeMoviePlayback
options:
AVAudioSessionCategoryOptionOverrideMutedMicrophoneInterruption
error:&categoryError];
} else {
// Fallback on earlier versions
}
if (categoryError) {
NSLog(@"Set audio session category error: %@",
categoryError.localizedDescription);
}
NSError *activeError = nil;
[audioSession setActive:YES error:&activeError];
if (activeError) {
NSLog(@"Set audio session active error: %@",
activeError.localizedDescription);
}
}
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
// must call this, otherwise pip will not possible
// https://github.com/jazzychad/PiPBugDemo
[NativePlugin updateAudioSession];
FlutterMethodChannel *channel =
[FlutterMethodChannel methodChannelWithName:@"native_plugin"
binaryMessenger:[registrar messenger]];
NativePlugin *instance = [[NativePlugin alloc] init];
FLNativeViewFactory *factory =
[[FLNativeViewFactory alloc] initWithMessenger:registrar.messenger];
[registrar registerViewFactory:factory withId:@"native_view"];
[registrar addMethodCallDelegate:instance channel:channel];
}
- (instancetype)init {
self = [super init];
if (self) {
_pipContentViewArray = [NSMutableArray array];
}
return self;
}
- (void)handleMethodCall:(FlutterMethodCall *)call
result:(FlutterResult)result {
if ([@"getPlatformVersion" isEqualToString:call.method]) {
result([@"iOS "
stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
} else if ([@"createPipContentView" isEqualToString:call.method]) {
PlayerView *playerView = [[PlayerView alloc] init];
// should control the play state by event of pip, optimize it later
[playerView play];
[self.pipContentViewArray addObject:playerView];
result(@((uint64_t)playerView));
} else if ([@"disposePipContentView" isEqualToString:call.method]) {
uint64_t viewId = [call.arguments unsignedLongLongValue];
for (PlayerView *playerView in self.pipContentViewArray) {
if ((uint64_t)playerView == viewId) {
[playerView pause];
[self.pipContentViewArray removeObject:playerView];
break;
}
}
} else if ([@"updatePipContentView" isEqualToString:call.method]) {
NSDictionary *data = call.arguments;
NSString *lang = data[@"lang"];
NSString *cn = data[@"cn"];
NSString *en = data[@"en"];
NSNumber *viewIdNum = data[@"viewId"];
dispatch_async(dispatch_get_main_queue(), ^{
if (viewIdNum != nil) {
uint64_t targetId = viewIdNum.unsignedLongLongValue;
for (PlayerView *playerView in self.pipContentViewArray) {
if ((uint64_t)playerView == targetId) {
[playerView updateWithLang:lang cn:cn en:en];
break;
}
}
} else {
PlayerView *playerView = [self.pipContentViewArray lastObject];
[playerView updateWithLang:lang cn:cn en:en];
}
});
result(nil);
} else {
result(FlutterMethodNotImplemented);
}
}
@end

15
local_plugins/native_plugin/ios/Classes/PlayerView.h

@ -0,0 +1,15 @@
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface PlayerView : UIView
- (void)play;
- (void)pause;
- (void)updateWithLang:(NSString *)lang cn:(NSString *)cn en:(NSString *)en;
@end
NS_ASSUME_NONNULL_END

100
local_plugins/native_plugin/ios/Classes/PlayerView.m

@ -0,0 +1,100 @@
#import "PlayerView.h"
@interface PlayerView ()
@property(nonatomic, strong) UILabel *titleLabel;
@property(nonatomic, strong) UILabel *langLabel;
@property(nonatomic, strong) UILabel *cnLabel;
@property(nonatomic, strong) UILabel *enLabel;
@property(nonatomic, strong) UIStackView *stackView;
@end
@implementation PlayerView
- (instancetype)init {
if (self = [super init]) {
self.backgroundColor = [UIColor whiteColor];
UILabel *title = [[UILabel alloc] init];
title.text = @"实时翻译";
title.font = [UIFont boldSystemFontOfSize:22];
title.textColor = [UIColor blackColor];
title.textAlignment = NSTextAlignmentCenter;
title.numberOfLines = 1;
self.titleLabel = title;
UILabel *lang = [[UILabel alloc] init];
lang.text = @"中文(简体) -> 英语";
lang.font = [UIFont systemFontOfSize:14];
lang.textColor = [UIColor darkGrayColor];
lang.textAlignment = NSTextAlignmentLeft;
lang.numberOfLines = 1;
self.langLabel = lang;
UILabel *cn = [[UILabel alloc] init];
cn.font = [UIFont systemFontOfSize:18];
cn.textColor = [UIColor blackColor];
cn.textAlignment = NSTextAlignmentLeft;
cn.numberOfLines = 0;
self.cnLabel = cn;
UILabel *en = [[UILabel alloc] init];
en.font = [UIFont systemFontOfSize:18];
en.textColor = [UIColor colorWithRed:0.24 green:0.54 blue:0.90 alpha:1.0];
en.textAlignment = NSTextAlignmentLeft;
en.numberOfLines = 0;
self.enLabel = en;
NSMutableParagraphStyle *para = [[NSMutableParagraphStyle alloc] init];
para.lineSpacing = 4;
self.cnLabel.attributedText = [[NSAttributedString alloc] initWithString:@"正在识别..." attributes:@{NSParagraphStyleAttributeName: para}];
self.enLabel.attributedText = [[NSAttributedString alloc] initWithString:@"翻译中..." attributes:@{NSParagraphStyleAttributeName: para}];
UIStackView *stack = [[UIStackView alloc] initWithArrangedSubviews:@[self.titleLabel, self.langLabel, self.cnLabel, self.enLabel]];
stack.axis = UILayoutConstraintAxisVertical;
stack.spacing = 12;
stack.alignment = UIStackViewAlignmentFill;
stack.distribution = UIStackViewDistributionFill;
stack.translatesAutoresizingMaskIntoConstraints = NO;
self.stackView = stack;
self.layoutMargins = UIEdgeInsetsMake(16, 16, 16, 16);
[self addSubview:self.stackView];
UILayoutGuide *guide = self.layoutMarginsGuide;
[NSLayoutConstraint activateConstraints:@[
[self.stackView.topAnchor constraintEqualToAnchor:guide.topAnchor],
[self.stackView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor],
[self.stackView.trailingAnchor constraintEqualToAnchor:guide.trailingAnchor],
[self.stackView.bottomAnchor constraintLessThanOrEqualToAnchor:guide.bottomAnchor]
]];
}
return self;
}
- (void)play {
}
- (void)pause {
}
- (void)updateWithLang:(NSString *)lang cn:(NSString *)cn en:(NSString *)en {
if (lang != nil) {
self.langLabel.text = lang;
}
NSMutableParagraphStyle *para = [[NSMutableParagraphStyle alloc] init];
para.lineSpacing = 4;
if (cn != nil) {
self.cnLabel.attributedText = [[NSAttributedString alloc] initWithString:cn attributes:@{NSParagraphStyleAttributeName: para}];
}
if (en != nil) {
self.enLabel.attributedText = [[NSAttributedString alloc] initWithString:en attributes:@{NSParagraphStyleAttributeName: para}];
}
}
@end

14
local_plugins/native_plugin/ios/Resources/PrivacyInfo.xcprivacy

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

29
local_plugins/native_plugin/ios/native_plugin.podspec

@ -0,0 +1,29 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint native_plugin.podspec` to validate before publishing.
#
Pod::Spec.new do |s|
s.name = 'native_plugin'
s.version = '0.0.1'
s.summary = 'A new Flutter plugin project.'
s.description = <<-DESC
A new Flutter plugin project.
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.public_header_files = 'Classes/**/*.h'
s.dependency 'Flutter'
s.platform = :ios, '12.0'
# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
# 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 = {'native_plugin_privacy' => ['Resources/PrivacyInfo.xcprivacy']}
end

19
local_plugins/native_plugin/lib/native_plugin.dart

@ -0,0 +1,19 @@
import 'native_plugin_platform_interface.dart';
class NativePlugin {
Future<String?> getPlatformVersion() {
return NativePluginPlatform.instance.getPlatformVersion();
}
Future<int> createPipContentView() {
return NativePluginPlatform.instance.createPipContentView();
}
Future<void> updatePipContentView(Map data) {
return NativePluginPlatform.instance.updatePipContentView(data);
}
Future<void> disposePipContentView(int viewId) {
return NativePluginPlatform.instance.disposePipContentView(viewId);
}
}

37
local_plugins/native_plugin/lib/native_plugin_method_channel.dart

@ -0,0 +1,37 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'native_plugin_platform_interface.dart';
/// An implementation of [NativePluginPlatform] that uses method channels.
class MethodChannelNativePlugin extends NativePluginPlatform {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('native_plugin');
@override
Future<String?> getPlatformVersion() async {
final version = await methodChannel.invokeMethod<String>(
'getPlatformVersion',
);
return version;
}
@override
Future<int> createPipContentView() async {
final viewId = await methodChannel.invokeMethod<int>(
'createPipContentView',
);
return viewId ?? 0;
}
@override
Future<void> updatePipContentView(Map data) async {
await methodChannel.invokeMethod<void>('updatePipContentView', data);
}
@override
Future<void> disposePipContentView(int viewId) async {
await methodChannel.invokeMethod<void>('disposePipContentView', viewId);
}
}

47
local_plugins/native_plugin/lib/native_plugin_platform_interface.dart

@ -0,0 +1,47 @@
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'native_plugin_method_channel.dart';
abstract class NativePluginPlatform extends PlatformInterface {
/// Constructs a NativePluginPlatform.
NativePluginPlatform() : super(token: _token);
static final Object _token = Object();
static NativePluginPlatform _instance = MethodChannelNativePlugin();
/// The default instance of [NativePluginPlatform] to use.
///
/// Defaults to [MethodChannelNativePlugin].
static NativePluginPlatform get instance => _instance;
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [NativePluginPlatform] when
/// they register themselves.
static set instance(NativePluginPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
Future<String?> getPlatformVersion() {
throw UnimplementedError('platformVersion() has not been implemented.');
}
Future<int> createPipContentView() {
throw UnimplementedError(
'createPipContentView() has not been implemented.',
);
}
Future<void> updatePipContentView(Map data) {
throw UnimplementedError(
'updatePipContentView() has not been implemented.',
);
}
Future<void> disposePipContentView(int viewId) {
throw UnimplementedError(
'disposePipContentView() has not been implemented.',
);
}
}

72
local_plugins/native_plugin/pubspec.yaml

@ -0,0 +1,72 @@
name: native_plugin
description: "A new Flutter plugin project."
version: 0.0.1
homepage:
environment:
sdk: ^3.7.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
# 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:
# This section identifies this Flutter project as a plugin project.
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
# which should be registered in the plugin registry. This is required for
# using method channels.
# The Android 'package' specifies package in which the registered class is.
# This is required for using method channels on Android.
# The 'ffiPlugin' specifies that native code should be built and bundled.
# This is required for using `dart:ffi`.
# All these are used by the tooling to maintain consistency when
# adding or updating assets for this project.
plugin:
platforms:
android:
package: com.example.native_plugin
pluginClass: NativePlugin
ios:
pluginClass: NativePlugin
# To add assets to your plugin 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 plugin 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

5
pubspec.yaml

@ -1,7 +1,7 @@
name: voitrans
description: "Voitrans - AI Voice Assistant."
publish_to: "none"
version: 1.0.18+54
version: 1.0.19+61
environment:
sdk: ">=3.3.0 <4.0.0"
@ -76,6 +76,7 @@ dependencies:
dio: ^5.8.0+1
sqflite: ^2.4.2
path: ^1.9.1
pip: ^0.0.3
connectivity_plus: ^6.1.5 # 网络连接检测依赖
speech:
path: local_plugins/speech
@ -111,6 +112,8 @@ dependencies:
path: local_plugins/user_portrait
qq_music:
path: local_plugins/qq_music
native_plugin:
path: local_plugins/native_plugin
test:
path: local_plugins/test
in_app_purchase: ^3.2.3 # 应用内购买插件

Loading…
Cancel
Save