Browse Source

Merge commit '0c7dd8d1b21cdc4a1c552df9add64f3eb8d63678' into lxm

weicu
lxm 1 year ago
parent
commit
1964538205
  1. 1
      android/app/build.gradle.kts
  2. 1
      lib/core/translations/language/zh_cn.dart
  3. 2
      lib/data/services/ble_manager.dart
  4. 5
      lib/data/services/db/sqflite_api.dart
  5. 38
      lib/data/services/deapsound_ai_service.dart
  6. 1
      lib/data/services/network/api.dart
  7. 2
      lib/data/services/network/dio_manager.dart
  8. 1
      lib/data/services/network/nw_api.dart
  9. 87
      lib/modules/agent/views/message_bubble.dart
  10. 4
      lib/modules/login/controllers/login_controller.dart
  11. 54
      lib/modules/meeting/controllers/meeting_details_controller.dart
  12. 497
      lib/modules/meeting/views/speaker_all_bottom_sheet.dart
  13. 267
      lib/modules/meeting/views/speaker_bottom_sheet.dart
  14. 6
      lib/modules/meeting/views/speech_tab.dart
  15. 6
      lib/modules/meeting/views/summary_tab.dart
  16. 124
      lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart
  17. 3
      lib/modules/translation/controllers/translation_controller.dart
  18. 6
      lib/modules/translation/views/translation_view.dart
  19. 1
      local_plugins/jieli/android/build.gradle.kts
  20. 33
      local_plugins/jl_ota/.gitignore
  21. 33
      local_plugins/jl_ota/.metadata
  22. 3
      local_plugins/jl_ota/CHANGELOG.md
  23. 1
      local_plugins/jl_ota/LICENSE
  24. 15
      local_plugins/jl_ota/README.md
  25. 4
      local_plugins/jl_ota/analysis_options.yaml
  26. 9
      local_plugins/jl_ota/android/.gitignore
  27. 71
      local_plugins/jl_ota/android/build.gradle
  28. BIN
      local_plugins/jl_ota/android/libs/jl_bt_ota_V1.9.0-release.aar
  29. BIN
      local_plugins/jl_ota/android/libs/jl_bt_ota_V1.9.2-release.aar
  30. 1
      local_plugins/jl_ota/android/settings.gradle
  31. 3
      local_plugins/jl_ota/android/src/main/AndroidManifest.xml
  32. 279
      local_plugins/jl_ota/android/src/main/kotlin/com/yunqiinnovation/jl_ota/JlOtaPlugin.kt
  33. 38
      local_plugins/jl_ota/ios/.gitignore
  34. 0
      local_plugins/jl_ota/ios/Assets/.gitkeep
  35. 19
      local_plugins/jl_ota/ios/Classes/JlOtaPlugin.swift
  36. 14
      local_plugins/jl_ota/ios/Resources/PrivacyInfo.xcprivacy
  37. 29
      local_plugins/jl_ota/ios/jl_ota.podspec
  38. 8
      local_plugins/jl_ota/lib/jl_ota.dart
  39. 17
      local_plugins/jl_ota/lib/jl_ota_method_channel.dart
  40. 29
      local_plugins/jl_ota/lib/jl_ota_platform_interface.dart
  41. 72
      local_plugins/jl_ota/pubspec.yaml
  42. 2
      pubspec.yaml

1
android/app/build.gradle.kts

@ -5,6 +5,7 @@ repositories {
mavenCentral()
flatDir {
dirs(project(":spotify_sdk").projectDir.resolve("libs"))
dirs(project(":jl_ota").projectDir.resolve("libs"))
}
}

1
lib/core/translations/language/zh_cn.dart

@ -123,6 +123,7 @@ const Map<String, String> zhCN = {
"allRoundAssistant": "全能助理",
"allRoundAssistantInfo": "帮你解决各类问题",
"simultaneousTranslation": "同声翻译",
"faceToFaceTranslation": "面对面翻译",
'audioVideoTranslation': '音视频翻译',
'callTranslation': '通话翻译',
"simultaneousTranslationInfo": "实时翻译80+种语言",

2
lib/data/services/ble_manager.dart

@ -54,7 +54,7 @@ class DeviceInfo {
this.rightEarCharging = false,
this.caseCharging = false,
this.firmwareVersion = '',
this.firmwareProductId = '',
this.firmwareProductId = 's3',
this.firmwareProductColor = '',
DateTime? lastUpdated,
}) : lastUpdated = lastUpdated ?? DateTime.now();

5
lib/data/services/db/sqflite_api.dart

@ -123,9 +123,12 @@ class SqfliteApi {
try {
final batch = db.batch();
for (var item in data) {
Map<String, Object?> values = item['content'] != null
? {'content': item['content']}
: {'speaker': item['speaker']};
batch.update(
'meetingspeaker',
{'content': item['content']},
values,
where: 'id = ?',
whereArgs: [item['id']],
);

38
lib/data/services/deapsound_ai_service.dart

@ -1,17 +1,20 @@
import 'dart:convert';
import 'package:get_storage/get_storage.dart';
import 'package:http/http.dart' as http;
import '../../core/utils/logger.dart';
import '../models/appconfig.dart';
import 'ai_service.dart';
class DeapsoundAIService implements AiService {
late final String baseUrl;
late final String apiKey;
late final String model;
DeapsoundAIService() {
final GetStorage storage = GetStorage();
apiKey = storage.read('logintoken');
apiKey = AppConfig.env('OPENAI_API_KEY') ?? '';
baseUrl = AppConfig.env('OPENAI_BASE_URL') ?? '';
model = AppConfig.env('OPENAI_MODEL') ?? '';
}
@override
@ -20,7 +23,34 @@ class DeapsoundAIService implements AiService {
String? systemPrompt,
String? userProperties,
}) async {
return '';
final url = Uri.parse(baseUrl);
final headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer $apiKey',
};
final requestBody = {
"model": model,
"messages": messages,
};
try {
final response = await http.post(
url,
headers: headers,
body: jsonEncode(requestBody),
);
if (response.statusCode != 200) {
throw Exception('Unknown error');
}
final result = jsonDecode(response.body);
return result['choices'].first['message']['content'];
} catch (e) {
throw Exception('Unknown error');
}
}
@override

1
lib/data/services/network/api.dart

@ -4,7 +4,6 @@ import 'nw_method.dart';
class Api {
// 登录
static login([params]) {
print("请求后台数据login params=${params}");
return DioManager().request(
NWMethod.post,
'/api/home/user_sgin',

2
lib/data/services/network/dio_manager.dart

@ -20,7 +20,7 @@ class DioManager {
);
dio = Dio(options);
//fdp
dio.interceptors.add(AuthInterceptor());
}

1
lib/data/services/network/nw_api.dart

@ -1,4 +1,5 @@
class NWApi {
// static const baseApi = "https://www.deapsound.com";
static const baseApi = "https://api.deapsound.com";
// static const baseApi = "https://api.ideapsound.com";
}

87
lib/modules/agent/views/message_bubble.dart

@ -275,6 +275,12 @@ class MessageBubble extends GetView<AgentController> {
now,
daylist,
);
} else if (mate!.keys.contains('card_travel')) {
final rawList = mate!['card_travel']['items'] as List;
final travellist =
rawList.map((e) => Map<String, dynamic>.from(e)).toList();
// 旅游
return _buildTravelContent(context, travellist);
}
// if (message.contains('播放列表') || message.contains('音乐')) {
@ -472,12 +478,13 @@ class MessageBubble extends GetView<AgentController> {
}
/// 构建旅游内容卡片
Widget _buildTravelContent(BuildContext context) {
Widget _buildTravelContent(
BuildContext context, List<Map<String, dynamic>> travellist) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'以下是一些推荐的景点:',
const Text(
'以下是一些推搜索结果:',
style: TextStyle(
color: Colors.black87,
fontSize: 14,
@ -503,32 +510,54 @@ class MessageBubble extends GetView<AgentController> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.place,
size: 16,
color: Color(0xFFE11D48),
),
const SizedBox(width: 6),
Text(
'热门景点 · 东京',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Colors.grey[700],
),
),
],
),
const SizedBox(height: 10),
// Row(
// children: [
// Icon(
// Icons.place,
// size: 16,
// color: Color(0xFFE11D48),
// ),
// const SizedBox(width: 6),
// Text(
// '热门景点 · 东京',
// style: TextStyle(
// fontSize: 13,
// fontWeight: FontWeight.w600,
// color: Colors.grey[700],
// ),
// ),
// ],
// ),
// const SizedBox(height: 10),
// 景点列表
_buildLocationItem('东京塔', '标志性建筑,可俯瞰东京全景', context),
Divider(height: 1, thickness: 1, color: Colors.grey[100]),
_buildLocationItem('涩谷十字路口', '世界上最繁忙的十字路口之一', context),
Divider(height: 1, thickness: 1, color: Colors.grey[100]),
_buildLocationItem('浅草寺', '东京最古老的寺庙', context),
// 动态生成音乐条目
...travellist.asMap().entries.map((entry) {
final index = entry.key;
final item = entry.value;
final name = item["name"] ?? "未知歌曲"; //名字
final addr = item["addr"] ?? ""; //地址
final tag = item["tag"] ?? "未知歌手"; //标签
final rating = item["rating"] ?? ""; //评分
final distance = item["distance"] ?? ""; //距离
final images = item["images"] ?? []; // 图片列表
return Column(
children: [
_buildLocationItem(
context,
name,
addr,
),
if (index != travellist.length - 1)
Divider(height: 1, thickness: 1, color: Colors.grey[100]),
],
);
}).toList(),
// // 景点列表
// _buildLocationItem('东京塔', '标志性建筑,可俯瞰东京全景', context),
// Divider(height: 1, thickness: 1, color: Colors.grey[100]),
// _buildLocationItem('涩谷十字路口', '世界上最繁忙的十字路口之一', context),
// Divider(height: 1, thickness: 1, color: Colors.grey[100]),
// _buildLocationItem('浅草寺', '东京最古老的寺庙', context),
const SizedBox(height: 8),
Center(
@ -712,7 +741,7 @@ class MessageBubble extends GetView<AgentController> {
/// 构建地点项目
Widget _buildLocationItem(
String name, String description, BuildContext context) {
BuildContext context, String name, String description) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Padding(

4
lib/modules/login/controllers/login_controller.dart

@ -695,8 +695,8 @@ class LoginController extends GetxController {
await _storage.write("logintoken", response['token'].toString());
// 存储到 SharedPreferences (仅存储用户数据)
//final prefs = await GetStorage();
await _storage.write(
'user', jsonEncode(user.toJson())); // 使用 user.toJson()
await _storage.write('user_info', user.toJson());
//获取app的配置信息
await getAppConfig();
Get.offAllNamed(Routes.pairing);

54
lib/modules/meeting/controllers/meeting_details_controller.dart

@ -7,14 +7,12 @@ import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:http/http.dart' as http;
import 'package:uuid/uuid.dart';
import '../../../core/utils/logger.dart';
import '../../../core/utils/upload_oss.dart';
import '../../../data/models/appconfig_model.dart';
import '../../../data/services/ai_service.dart';
import '../../../data/models/appconfig.dart';
import '../../../data/services/db/sqflite_api.dart';
import '../../../data/services/open_ai_service_adapter.dart';
import '../../../data/services/deapsound_ai_service.dart';
import '../model/meeting_details_model.dart';
import '../model/meeting_model.dart';
@ -40,11 +38,8 @@ class MeetingDetailsController extends GetxController
RxInt editSpeakerTextId = 0.obs;
String editSpeakerTextChange = '';
final AiService _aiService = Get.find<OpenAIServiceAdapter>();
// Agent 信息
late final DBAgent _agent;
String _systemPrompt = '';
String _userId = '';
StreamSubscription? _aiResponseSubscription;
bool _isDisposed = false;
@ -78,7 +73,6 @@ class MeetingDetailsController extends GetxController
scrollController.dispose();
tabController.dispose();
playerController.dispose();
_aiResponseSubscription?.cancel();
_isDisposed = true;
super.onClose();
}
@ -110,10 +104,9 @@ class MeetingDetailsController extends GetxController
void _getAgent() async {
final GetStorage storage = GetStorage();
// final List<DBAgent> agents = storage.read("AGENTS");
Map userInfo = storage.read("user_info");
_userId = userInfo['user']['uid'];
// _agent = agents.firstWhere((agent) => agent.id == 'meet_assistant');
_systemPrompt = AppConfig.agentsystemPrompt("personal_assistant") ?? '';
}
void _getDataDetails() async {
@ -173,8 +166,6 @@ class MeetingDetailsController extends GetxController
}
}
void editSpeakerName() {}
void speakerCreate() async {
EasyLoading.show();
await _audioSpeaker();
@ -196,28 +187,20 @@ class MeetingDetailsController extends GetxController
}
allText = text;
}
_aiServiceListen();
// const uuid = Uuid();
// _aiService.send(
// sid: uuid.v4(),
// messages: [
// {'role': 'user', 'content': allText},
// ],
// systemPrompt: _agent.systemPrompt,
// );
await _aiServiceSummary();
EasyLoading.dismiss();
}
void _aiServiceListen() {
String summaryText = '';
// _aiResponseSubscription = _aiService.start().listen((AIStreamEvent event) {
// summaryText += event.content ?? '';
// if (event.type == StreamEventType.complete) {
// SqfliteApi.editMeeting(id, {'summary': summaryText});
// meetingDetails.value.summary = summaryText;
// meetingDetails.refresh();
// EasyLoading.dismiss();
// }
// });
Future<void> _aiServiceSummary() async {
String summaryText = await DeapsoundAIService().sendMessage(
messages: [
{'role': 'system', 'content': _systemPrompt},
{'role': 'user', 'content': allText},
],
);
SqfliteApi.editMeeting(id, {'summary': summaryText});
meetingDetails.value.summary = summaryText;
meetingDetails.refresh();
}
Future<void> _audioSpeaker() async {
@ -345,12 +328,14 @@ class MeetingDetailsController extends GetxController
Future<void> _addTextList(List utterances) async {
String text = '';
String personnel = '';
List<Map<String, Object?>> list = [];
for (var element in utterances) {
String speaker = '';
if (element['additions'].isNotEmpty) {
speaker = 'Speaker ${element['additions']['speaker']}';
text += '$speaker:';
personnel += '[$speaker] ';
}
list.add({
'meetingid': id,
@ -363,10 +348,11 @@ class MeetingDetailsController extends GetxController
}
var result = await SqfliteApi.insertMeetingSpeaker(list);
if (result != null) {
await SqfliteApi.editMeeting(id, {'tasktype': 1});
await SqfliteApi.editMeeting(id, {'tasktype': 1, 'personnel': personnel});
List speakerList = await SqfliteApi.getMeetingSpeaker(id);
textList.value = List.from(speakerList);
meetingDetails.value.tasktype = 1;
meetingDetails.value.personnel = personnel;
meetingDetails.refresh();
}
allText = text;

497
lib/modules/meeting/views/speaker_all_bottom_sheet.dart

@ -0,0 +1,497 @@
import 'package:common_utils/common_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import '../../../data/services/db/sqflite_api.dart';
import '../controllers/meeting_details_controller.dart';
class SpeakerAllBottomSheet extends StatefulWidget {
const SpeakerAllBottomSheet({super.key});
@override
State<SpeakerAllBottomSheet> createState() => _SpeakerAllBottomSheetState();
}
class _SpeakerAllBottomSheetState extends State<SpeakerAllBottomSheet> {
final _controller = Get.find<MeetingDetailsController>();
final GetStorage _storage = GetStorage();
List _dataList = [];
List _historySpeakerList = [];
List _speakerList = [];
@override
void initState() {
super.initState();
_getSpeakerList();
}
void _getSpeakerList() {
_historySpeakerList = _storage.read("speaker_list") ?? [];
_speakerList = List.from(_historySpeakerList);
List textList = _controller.textList;
List dataList = [];
for (var item in textList) {
String speaker = item['speaker'];
Map? foundElement = dataList.firstWhere(
(element) => element['speaker'] == speaker,
orElse: () => null,
);
if (foundElement != null) {
int time = item['endtime'] - item['starttime'];
foundElement['totaltime'] = foundElement['totaltime'] + time;
if (!foundElement['isOmit']) {
if (foundElement['list'].length < 2) {
foundElement['list'].add(Map.from(item));
} else {
foundElement['isOmit'] = true;
}
}
} else {
dataList.add({
'speaker': speaker,
'speakerText': TextEditingController(),
'isShow': false,
'totaltime': item['endtime'] - item['starttime'],
'isOmit': false,
'list': [Map.from(item)],
});
}
if (speaker.isNotEmpty &&
!RegExp(r'^Speaker \d+$').hasMatch(speaker) &&
!_speakerList.contains(speaker)) {
_speakerList.add(speaker);
}
}
setState(() {
_dataList = dataList;
});
}
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(
left: 12.w,
right: 12.w,
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: EdgeInsets.symmetric(vertical: 10.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'命名发言者',
style: TextStyle(
fontSize: 16.sp,
color: Colors.black87,
fontWeight: FontWeight.bold,
),
),
GestureDetector(
onTap: () => Get.back(),
child: Container(
width: 26.w,
height: 26.w,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(26.r),
),
child: Icon(
Icons.close,
size: 18.w,
color: Colors.grey[400],
),
),
)
],
),
),
Divider(
height: 1,
color: Colors.grey[300],
),
Container(
constraints: BoxConstraints(
maxHeight:
1.sh - 240.w - MediaQuery.of(context).viewInsets.bottom,
),
child: ListView.builder(
itemCount: _dataList.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
Map item = _dataList[index];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.only(top: 15.w),
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: item['isShow'] && _speakerList.isNotEmpty
? BorderRadius.vertical(
top: Radius.circular(12.r),
)
: BorderRadius.circular(12.r),
),
child: Focus(
onFocusChange: (bool hasFocus) {
item['isShow'] = hasFocus;
},
child: TextField(
controller: item['speakerText'],
style: TextStyle(
fontSize: 16.sp,
color: Colors.black87,
letterSpacing: 0,
height: 1.4,
),
decoration: InputDecoration(
hintText: item['speaker'],
hintStyle: TextStyle(
fontSize: 16.sp,
color: Colors.grey[400],
letterSpacing: 0,
height: 1.4,
),
isDense: true,
fillColor: Colors.transparent,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 10.w,
vertical: 12.w,
),
),
),
),
),
if (item['isShow'] && _speakerList.isNotEmpty)
Container(
padding: EdgeInsets.only(left: 10.w),
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(12.r),
),
border: Border(
top: BorderSide(
width: 1,
color: Colors.grey[300]!,
),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(top: 10.w, bottom: 5.w),
child: Text(
'最近使用过的名称',
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey[600]!,
),
),
),
Container(
constraints: BoxConstraints(
maxHeight: 150.h,
),
child: ListView.builder(
itemCount: _speakerList.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int idx) {
return GestureDetector(
onTap: () {
item['speakerText'].text =
_speakerList[idx];
FocusScope.of(context).unfocus();
},
behavior: HitTestBehavior.translucent,
child: SizedBox(
height: 42.w,
child: Align(
alignment: Alignment.centerLeft,
child: Text(
_speakerList[idx],
style: TextStyle(
fontSize: 14.sp,
color: Colors.black87,
),
),
),
),
);
},
),
),
],
),
),
Padding(
padding: EdgeInsets.symmetric(
horizontal: 10.w,
vertical: 8.w,
),
child: Row(
children: [
Text(
'总计',
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey,
),
),
5.horizontalSpace,
Text(
'${item['totaltime'] / 1000}s',
style: TextStyle(
fontSize: 12.sp,
color: Colors.black87,
),
),
],
),
),
...List.generate(
item['list'].length,
(int idx) {
Map data = item['list'][idx];
return Padding(
padding: EdgeInsets.symmetric(horizontal: 10.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.play_circle,
color: Colors.red[400],
size: 15.w,
),
2.horizontalSpace,
Text(
DateUtil.formatDateMs(
data['starttime'],
format: 'HH:mm:ss',
isUtc: true,
),
style: TextStyle(
color: Colors.grey,
fontSize: 12.sp,
),
),
Padding(
padding:
EdgeInsets.symmetric(horizontal: 2.w),
child: Text(
'-',
style: TextStyle(
color: Colors.grey,
fontSize: 12.sp,
),
),
),
Text(
DateUtil.formatDateMs(
data['endtime'],
format: 'HH:mm:ss',
isUtc: true,
),
style: TextStyle(
color: Colors.grey,
fontSize: 12.sp,
),
),
5.horizontalSpace,
Text(
'${(data['endtime'] - data['starttime']) / 1000}s',
style: TextStyle(
color: Colors.black54,
fontSize: 12.sp,
),
),
],
),
Padding(
padding: EdgeInsets.symmetric(vertical: 8.w),
child: Text(
data['content'],
overflow: TextOverflow.ellipsis,
maxLines: 2,
style: TextStyle(
color: Colors.grey[700],
fontSize: 12.sp,
),
),
),
],
),
);
},
),
if (item['isOmit'])
Padding(
padding:
EdgeInsets.only(left: 10.w, top: 5.w, bottom: 8.w),
child: Row(
children: List.generate(
6,
(int idx) => Container(
width: 3.w,
height: 3.w,
margin: EdgeInsets.only(right: 3.w),
decoration: BoxDecoration(
color: Colors.grey[700],
borderRadius: BorderRadius.circular(3.r),
),
),
),
),
),
Padding(
padding: EdgeInsets.only(top: 7.w),
child: _separator(),
),
],
);
},
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5.w),
child: Text(
'总结的发言者将自动更新',
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey[600],
),
),
),
Padding(
padding: EdgeInsets.only(bottom: 20.w),
child: Row(
children: [
Expanded(
child: GestureDetector(
onTap: () => Get.back(),
child: Container(
height: 48.w,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(12.r),
),
child: Text(
'取消',
style: TextStyle(
fontSize: 16.sp,
color: Colors.black,
),
),
),
),
),
10.horizontalSpace,
Expanded(
child: GestureDetector(
onTap: _save,
child: Container(
height: 48.w,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(12.r),
),
child: Text(
'确认',
style: TextStyle(
fontSize: 16.sp,
color: Colors.white,
),
),
),
),
),
],
),
),
],
),
);
}
Widget _separator() {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final boxWidth = constraints.constrainWidth();
const dashWidth = 2.0;
const dashHeight = 1.0;
final dashCount = (boxWidth / (2 * dashWidth)).floor();
return Flex(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
direction: Axis.horizontal,
children: List.generate(dashCount, (_) {
return SizedBox(
width: dashWidth,
height: dashHeight,
child: DecoratedBox(
decoration: BoxDecoration(color: Colors.grey[300]),
),
);
}),
);
},
);
}
void _save() async {
EasyLoading.show();
List textList = _controller.textList;
List newTextList = [];
List editSpeakerList = [];
String personnel = '';
for (var element in textList) {
final dataMap = Map<String, dynamic>.from(element);
for (var data in _dataList) {
String speaker = data['speakerText'].text;
if (speaker.isNotEmpty && speaker != data['speaker']) {
if (dataMap['speaker'] == data['speaker']) {
dataMap['speaker'] = speaker;
editSpeakerList.add({
'id': dataMap['id'],
'speaker': dataMap['speaker'],
});
}
if (!RegExp(r'^Speaker \d+$').hasMatch(speaker) &&
!_historySpeakerList.contains(speaker)) {
_historySpeakerList.insert(0, speaker);
}
}
}
if (!personnel.contains(dataMap['speaker'])) {
personnel += '[${dataMap['speaker']}] ';
}
newTextList.add(dataMap);
}
var result = await SqfliteApi.editMeetingSpeaker(editSpeakerList);
if (result != null) {
_controller.textList.value = newTextList;
SqfliteApi.editMeeting(_controller.id, {'personnel': personnel});
_controller.meetingDetails.value.personnel = personnel;
_storage.write("speaker_list", _historySpeakerList);
Get.back();
}
EasyLoading.dismiss();
}
}

267
lib/modules/meeting/views/speaker_bottom_sheet.dart

@ -1,14 +1,68 @@
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
class SpeakerBottomSheet extends StatelessWidget {
const SpeakerBottomSheet({super.key});
import '../../../data/services/db/sqflite_api.dart';
import '../controllers/meeting_details_controller.dart';
class SpeakerBottomSheet extends StatefulWidget {
final int id;
final String speakerName;
const SpeakerBottomSheet(this.id, this.speakerName, {super.key});
@override
State<SpeakerBottomSheet> createState() => _SpeakerBottomSheetState();
}
class _SpeakerBottomSheetState extends State<SpeakerBottomSheet> {
final _controller = Get.find<MeetingDetailsController>();
final GetStorage _storage = GetStorage();
final TextEditingController _speakerController = TextEditingController();
int _type = 1;
List _historySpeakerList = [];
List _speakerList = [];
@override
void initState() {
super.initState();
_getSpeakerList();
}
void _getSpeakerList() {
_historySpeakerList = _storage.read("speaker_list") ?? [];
List speakerList = List.from(_historySpeakerList);
List textList = _controller.textList;
for (var element in textList) {
String speaker = element['speaker'];
if (speaker.isNotEmpty &&
!RegExp(r'^Speaker \d+$').hasMatch(speaker) &&
!speakerList.contains(speaker)) {
speakerList.add(speaker);
}
}
setState(() {
_speakerList = speakerList;
});
}
@override
void dispose() {
_speakerController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 12.w),
padding: EdgeInsets.only(
left: 12.w,
right: 12.w,
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
@ -49,17 +103,45 @@ class SpeakerBottomSheet extends StatelessWidget {
color: Colors.grey[300],
),
Container(
height: 46.w,
margin: EdgeInsets.symmetric(vertical: 15.w),
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
),
child: TextField(
controller: _speakerController,
style: TextStyle(
fontSize: 16.sp,
color: Colors.black87,
letterSpacing: 0,
height: 1.4,
),
decoration: InputDecoration(
hintText: widget.speakerName,
hintStyle: TextStyle(
fontSize: 16.sp,
color: Colors.grey[400],
letterSpacing: 0,
height: 1.4,
),
isDense: true,
fillColor: Colors.transparent,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 10.w,
vertical: 12.w,
),
),
),
),
Container(
constraints: BoxConstraints(maxHeight: 1.sh - 390.w),
constraints: BoxConstraints(
maxHeight:
1.sh - 390.w - MediaQuery.of(context).viewInsets.bottom,
),
child: ListView.builder(
itemCount: 30,
itemCount: _speakerList.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return Container(
@ -83,15 +165,21 @@ class SpeakerBottomSheet extends StatelessWidget {
color: Colors.grey[600]!,
),
),
SizedBox(
height: 42.w,
child: Align(
alignment: Alignment.centerLeft,
child: Text(
'龙哥',
style: TextStyle(
fontSize: 14.sp,
color: Colors.black87,
GestureDetector(
onTap: () {
_speakerController.text = _speakerList[index];
},
behavior: HitTestBehavior.translucent,
child: SizedBox(
height: 42.w,
child: Align(
alignment: Alignment.centerLeft,
child: Text(
_speakerList[index],
style: TextStyle(
fontSize: 14.sp,
color: Colors.black87,
),
),
),
),
@ -102,12 +190,25 @@ class SpeakerBottomSheet extends StatelessWidget {
},
),
),
_row('应用到当前段落', false),
_row('应用到这个发言者的所有段落', true, description: '总结将根据模版自动更新'),
_row('应用到当前段落', _type == 0, onTap: () {
setState(() {
_type = 0;
});
}),
_row(
'应用到这个发言者的所有段落',
_type == 1,
description: '总结将根据模版自动更新',
onTap: () {
setState(() {
_type = 1;
});
},
),
Padding(
padding: EdgeInsets.only(top: 10.w, bottom: 20.w),
child: GestureDetector(
onTap: () {},
onTap: _save,
child: Container(
height: 48.w,
alignment: Alignment.center,
@ -130,35 +231,39 @@ class SpeakerBottomSheet extends StatelessWidget {
);
}
Widget _row(String name, bool isActive, {String? description}) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 10.w),
child: Row(
children: [
isActive ? _round() : _circle(),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: TextStyle(
fontSize: 14.sp,
color: Colors.black87,
),
),
if (description != null)
Widget _row(String name, bool isActive,
{String? description, Function()? onTap}) {
return GestureDetector(
onTap: onTap,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 10.w),
child: Row(
children: [
isActive ? _round() : _circle(),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
description,
name,
style: TextStyle(
fontSize: 10.sp,
color: Colors.grey[500]!,
fontSize: 14.sp,
color: Colors.black87,
),
),
],
if (description != null)
Text(
description,
style: TextStyle(
fontSize: 10.sp,
color: Colors.grey[500]!,
),
),
],
),
),
),
],
],
),
),
);
}
@ -199,4 +304,82 @@ class SpeakerBottomSheet extends StatelessWidget {
),
);
}
void _save() {
if (_speakerController.text.isNotEmpty &&
_speakerController.text != widget.speakerName) {
if (_type == 0) {
_single();
} else {
_all();
}
} else {
Get.back();
}
}
void _single() async {
EasyLoading.show();
List list = [
{
'id': widget.id,
'speaker': _speakerController.text,
}
];
var result = await SqfliteApi.editMeetingSpeaker(list);
if (result != null) {
int index =
_controller.textList.indexWhere((item) => item['id'] == widget.id);
if (index != -1) {
final dataMap = Map<String, dynamic>.from(_controller.textList[index]);
dataMap['speaker'] = _speakerController.text;
_controller.textList[index] = dataMap;
}
_addHistorySpeaker();
Get.back();
}
EasyLoading.dismiss();
}
void _all() async {
EasyLoading.show();
List textList = _controller.textList;
List newTextList = [];
List editSpeakerList = [];
String personnel = '';
for (var element in textList) {
final dataMap = Map<String, dynamic>.from(element);
if (dataMap['speaker'] == widget.speakerName) {
dataMap['speaker'] = _speakerController.text;
editSpeakerList.add({
'id': dataMap['id'],
'speaker': dataMap['speaker'],
});
}
String speaker = dataMap['speaker'];
if (!personnel.contains(speaker)) {
personnel += '[$speaker] ';
}
newTextList.add(dataMap);
}
var result = await SqfliteApi.editMeetingSpeaker(editSpeakerList);
if (result != null) {
_controller.textList.value = newTextList;
SqfliteApi.editMeeting(_controller.id, {'personnel': personnel});
_controller.meetingDetails.value.personnel = personnel;
_addHistorySpeaker();
Get.back();
}
EasyLoading.dismiss();
}
void _addHistorySpeaker() {
String speaker = _speakerController.text;
if (speaker.isNotEmpty &&
!RegExp(r'^Speaker \d+$').hasMatch(speaker) &&
!_historySpeakerList.contains(speaker)) {
_historySpeakerList.insert(0, speaker);
_storage.write("speaker_list", _historySpeakerList);
}
}
}

6
lib/modules/meeting/views/speech_tab.dart

@ -4,6 +4,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../controllers/meeting_details_controller.dart';
import 'speaker_all_bottom_sheet.dart';
import 'speaker_bottom_sheet.dart';
class SpeechTab extends GetView<MeetingDetailsController> {
@ -59,7 +60,8 @@ class SpeechTab extends GetView<MeetingDetailsController> {
),
),
builder: (BuildContext context) {
return const SpeakerBottomSheet();
return SpeakerBottomSheet(
item['id'], item['speaker']);
},
);
},
@ -199,7 +201,7 @@ class SpeechTab extends GetView<MeetingDetailsController> {
),
),
builder: (BuildContext context) {
return const SpeakerBottomSheet();
return const SpeakerAllBottomSheet();
},
);
},

6
lib/modules/meeting/views/summary_tab.dart

@ -5,7 +5,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../controllers/meeting_details_controller.dart';
import 'speaker_bottom_sheet.dart';
import 'speaker_all_bottom_sheet.dart';
class SummaryTab extends GetView<MeetingDetailsController> {
const SummaryTab({super.key});
@ -126,7 +126,7 @@ class SummaryTab extends GetView<MeetingDetailsController> {
),
),
Text(
'与会人员:${controller.meetingDetails.value.address}',
'与会人员:${controller.meetingDetails.value.personnel.isNotEmpty ? controller.meetingDetails.value.personnel : '[输入与会人员]'}',
style: TextStyle(
color:
isDarkMode ? Colors.grey[400] : Colors.grey[700],
@ -169,7 +169,7 @@ class SummaryTab extends GetView<MeetingDetailsController> {
),
),
builder: (BuildContext context) {
return const SpeakerBottomSheet();
return const SpeakerAllBottomSheet();
},
);
},

124
lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart

@ -7,6 +7,7 @@ import '../model/ota_upgrade_model.dart';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';
import 'package:jl_ota/jl_ota.dart';
import '../../../data/services/ble_manager.dart';
class OtaUpgradeController extends GetxController {
@ -40,6 +41,8 @@ class OtaUpgradeController extends GetxController {
Future<void> startUpdate() async {
try {
isUpdating.value = true;
checkUpdate();
// 实现更新逻辑...
} catch (e) {
errorMessage.value = e.toString();
@ -50,64 +53,68 @@ class OtaUpgradeController extends GetxController {
}
Future<void> _getCurrentVersion() async {
try {
// 添加获取设备信息的重试机制
DeviceInfo? deviceInfo = _bleManager.deviceInfo.value;
// 第一次检查设备信息
if (deviceInfo == null) {
// 显示加载提示
isFetchingDeviceInfo.value = true;
// 尝试重新获取设备信息
await _bleManager.fetchDeviceInfo();
// 等待后再次获取设备信息
deviceInfo = _bleManager.deviceInfo.value;
isFetchingDeviceInfo.value = false;
// 仍然为空则抛出异常
if (deviceInfo == null) {
throw const FormatException('无法获取设备信息,请检查设备连接');
}
}
// 检查固件信息有效性
if (deviceInfo.firmwareVersion.isEmpty ||
deviceInfo.firmwareProductId.isEmpty) {
throw const FormatException('设备固件信息不完整');
}
// 正常流程
currentVersion.value = deviceInfo.firmwareVersion;
// API请求
final response = await Api.getequipmentinfo({
"id": deviceInfo.firmwareProductId,
});
// 处理API响应
if (response['info'] == null) {
throw const FormatException('API 响应格式错误');
}
final resp = OtaConfigResp.fromJson(response);
latestVersion.value = resp.systemversion!;
updatepackageaddress = resp.updatepackageaddress!;
// 版本检查
isUpdateAvailable.value = _isNewVersionAvailable(resp.systemversion!);
} catch (e) {
// 统一错误处理
errorMessage.value = e.toString();
Get.snackbar('错误', e.toString(),
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
colorText: Colors.white);
// 重要:重新抛出异常以便上层处理(可选)
} finally {
isFetchingDeviceInfo.value = false;
}
JlOta().getPlatformVersion();
//try {
// // 添加获取设备信息的重试机制
// DeviceInfo? deviceInfo = _bleManager.deviceInfo.value;
// // 第一次检查设备信息
// if (deviceInfo == null) {
// // 显示加载提示
// isFetchingDeviceInfo.value = true;
// // 尝试重新获取设备信息
// await _bleManager.fetchDeviceInfo();
// // 等待后再次获取设备信息
// deviceInfo = _bleManager.deviceInfo.value;
// isFetchingDeviceInfo.value = false;
// // 仍然为空则抛出异常
// if (deviceInfo == null) {
// throw const FormatException('无法获取设备信息,请检查设备连接');
// }
// }
// // 检查固件信息有效性
// if (deviceInfo.firmwareVersion.isEmpty
// // ||
// // deviceInfo.firmwareProductId.isEmpty
// ) {
// throw const FormatException('设备固件信息不完整');
// }
// // 正常流程
// currentVersion.value = "0"; //deviceInfo.firmwareVersion;
// // API请求
// final response = await Api.getequipmentinfo({
// "id": "s3",
// });
// // 处理API响应
// if (response['info'] == null) {
// throw const FormatException('API 响应格式错误');
// }
// final resp = OtaConfigResp.fromJson(response);
// latestVersion.value = resp.systemversion!;
// updatepackageaddress = resp.updatepackageaddress!;
// // 版本检查
// isUpdateAvailable.value = _isNewVersionAvailable(resp.systemversion!);
// } catch (e) {
// // 统一错误处理
// errorMessage.value = e.toString();
// Get.snackbar('错误', e.toString(),
// snackPosition: SnackPosition.BOTTOM,
// backgroundColor: Colors.red,
// colorText: Colors.white);
// // 重要:重新抛出异常以便上层处理(可选)
// } finally {
// isFetchingDeviceInfo.value = false;
// }
}
// 版本比较方法优化
@ -288,6 +295,7 @@ class OtaUpgradeController extends GetxController {
// 开始升级
void _installApp(String path) async {
// JlOtaPlugin()
// final File file = File(path);
// if (await file.exists()) {
// // OpenResult result = await OpenFile.open(path);

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

@ -59,6 +59,9 @@ class TranslationController extends GetxController {
return 'audioVideoTranslation';
case 'call':
return 'callTranslation';
case 'faceToFace':
return 'faceToFaceTranslation';
default:
return 'simultaneousTranslation';
}

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

@ -57,6 +57,7 @@ class TranslationView extends GetView<TranslationController> {
color: isDarkMode ? AppColors.cardBackground : Colors.white,
itemBuilder: (BuildContext context) => [
_buildPopupMenuItem('simultaneous', 'simultaneousTranslation'.tr),
_buildPopupMenuItem('faceToFace', 'faceToFaceTranslation'.tr),
_buildPopupMenuItem('audioVideo', 'audioVideoTranslation'.tr),
_buildPopupMenuItem('call', 'callTranslation'.tr),
],
@ -86,12 +87,11 @@ class TranslationView extends GetView<TranslationController> {
// TTS toggle button
Obx(() => IconButton(
icon: Icon(
controller.isTtsEnabled.value
? Icons.volume_up
: Icons.volume_off,
controller.isTtsEnabled.value ? Icons.mic : Icons.mic_off,
color: isDarkMode ? Colors.white : Colors.black,
size: 24.sp,
),
// onPressed: controller.toggleTts,默认播放
onPressed: controller.toggleTts,
tooltip: '自动播放翻译结果',
)),

1
local_plugins/jieli/android/build.gradle.kts

@ -54,5 +54,4 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2")
implementation(project(":azure_speech"))
implementation(project(":jl_opus"))
implementation(project(":jl_opus"))
}

33
local_plugins/jl_ota/.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/jl_ota/.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: plugin
# 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'

3
local_plugins/jl_ota/CHANGELOG.md

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

1
local_plugins/jl_ota/LICENSE

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

15
local_plugins/jl_ota/README.md

@ -0,0 +1,15 @@
# jl_ota
A new Flutter 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/jl_ota/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/jl_ota/android/.gitignore

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

71
local_plugins/jl_ota/android/build.gradle

@ -0,0 +1,71 @@
group = "com.yunqiinnovation.jl_ota"
version = "1.0-SNAPSHOT"
buildscript {
ext.kotlin_version = "1.8.22"
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.7.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
}
}
allprojects {
repositories {
google()
mavenCentral()
flatDir {
dirs 'libs'
}
}
}
apply plugin: "com.android.library"
apply plugin: "kotlin-android"
android {
namespace = "com.yunqiinnovation.jl_ota"
compileSdk = 35
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 {
implementation(name: 'jl_bt_ota_V1.9.0-release', ext: 'aar')
implementation(project(":ble_service"))
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
}
}
}
}

BIN
local_plugins/jl_ota/android/libs/jl_bt_ota_V1.9.0-release.aar

Binary file not shown.

BIN
local_plugins/jl_ota/android/libs/jl_bt_ota_V1.9.2-release.aar

Binary file not shown.

1
local_plugins/jl_ota/android/settings.gradle

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

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

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

279
local_plugins/jl_ota/android/src/main/kotlin/com/yunqiinnovation/jl_ota/JlOtaPlugin.kt

@ -0,0 +1,279 @@
package com.yunqiinnovation.jl_ota
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.EventChannel
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 android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothDevice
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.annotation.NonNull
import com.jieli.jl_bt_ota.constant.ErrorCode;
import com.jieli.jl_bt_ota.constant.StateCode;
import com.jieli.jl_bt_ota.impl.BluetoothOTAManager;
import com.jieli.jl_bt_ota.interfaces.BtEventCallback;
import com.jieli.jl_bt_ota.interfaces.IActionCallback;
import com.jieli.jl_bt_ota.interfaces.IUpgradeCallback;
import com.jieli.jl_bt_ota.model.BluetoothOTAConfigure;
import com.jieli.jl_bt_ota.model.base.BaseError;
import com.jieli.jl_bt_ota.model.response.TargetInfoResponse;
import com.yunqiinnovation.ble_service.BleService
/** JlOtaPlugin */
class JlOtaPlugin(): 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
private lateinit var context: Context
private lateinit var eventChannel: EventChannel
private var streamSink: EventChannel.EventSink? = null
/// BLE服务实例
private var otaManager: OTAManager? = null
companion object {
private const val TAG = "JlOtaPlugin"
private const val CHANNEL_NAME = "com.yunqiinnovation.jl_ota"
private const val EVENT_CHANNEL_NAME = "com.yunqiinnovation.jl_ota/events"
}
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "jl_ota")
channel.setMethodCallHandler(this)
eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, EVENT_CHANNEL_NAME)
eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
streamSink = events
}
override fun onCancel(arguments: Any?) {
streamSink = null
}
})
otaManager = OTAManager(context)
// otaManager.getBluetoothOption().setFirmwareFilePath(filePath);
// otaManager.startOTA(new IUpgradeCallback() {
// }
}
override fun onMethodCall(call: MethodCall, result: Result) {
if (call.method == "getPlatformVersion") {
val bluetoothOption = BluetoothOTAConfigure()
//选择通讯方式
bluetoothOption.priority = BluetoothOTAConfigure.PREFER_BLE
//是否需要自定义回连方式(默认不需要,如需要自定义回连方式,需要客户自行实现)
bluetoothOption.isUseReconnect = true
//是否启用设备认证流程(与固件工程师确认)
bluetoothOption.isUseAuthDevice = true
//设置BLE的MTU
bluetoothOption.mtu = 500
//是否需要改变BLE的MTU
bluetoothOption.isNeedChangeMtu = false
//是否启用杰理服务器(暂时不支持)
bluetoothOption.isUseJLServer = false
Log.d(TAG, "bluetoothOTAManager初始化成功")
//配置OTA参数
otaManager?.configure(bluetoothOption)
var filePath= "/storage/emulated/0/Android/data/com.yunqiinnovation.deepsound/files/downloads/ota_upgrades/1.ufw"
otaManager?.bluetoothOption?.firmwareFilePath = filePath
Log.d(TAG, "OpusManager路径成功${filePath}")
// Add ?. after bluetoothOption ^
otaManager?.startOTA(
CustomUpdateCallback(
context, null, this
)
)
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else {
result.notImplemented()
}
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}
class OTAManager(context: Context) : BluetoothOTAManager(context) {
override fun getConnectedDevice(): BluetoothDevice? {
// 这里可以添加空实现的注释说明
return null
}
override fun getConnectedBluetoothGatt(): BluetoothGatt? {
// 这里可以添加空实现的注释说明
return null
}
override fun connectBluetoothDevice(bluetoothDevice: BluetoothDevice?) {
// 这里可以添加空实现的注释说明
}
override fun disconnectBluetoothDevice
(bluetoothDevice: BluetoothDevice?) {
// 这里可以添加空实现的注释说明
}
/** 数据发送方法(协议无关) */
override fun sendDataToDevice(
bluetoothDevice: BluetoothDevice?,
bytes: ByteArray?
): Boolean {return true}
}
private class CustomUpdateCallback(
val context: Context,
val device: BluetoothDevice?,
val viewModel: JlOtaPlugin
) : IUpgradeCallback{
companion object {
private const val TAG = "JlOtaPlugin"
private const val CHANNEL_NAME = "com.yunqiinnovation.jl_ota"
private const val EVENT_CHANNEL_NAME = "com.yunqiinnovation.jl_ota/events"
}
override fun onStartOTA() {
Log.d(TAG, "onStartOTA开始升级")
// viewModel.otaStateMLD.value = OTAStart(device)
}
override fun onNeedReconnect(addr: String?, isNewReconnectWay: Boolean) {
Log.d(TAG, "onNeedReconnect需要重新链接")
// viewModel.otaStateMLD.value = OTAReconnect(device, addr, isNewReconnectWay)
// if (viewModel.isUseReconnectWay()) {
// viewModel.reconnectDev(addr, isNewReconnectWay)
// }
}
override fun onProgress(type: Int, progress: Float) {
Log.d(TAG, "onProgress正在进展")
//viewModel.otaStateMLD.value = OTAWorking(device, type, progress)
}
override fun onStopOTA() {
Log.d(TAG, "onStartOTA停止升级")
// viewModel.otaStateMLD.value =
// OTAEnd(device, ErrorCode.ERR_NONE, context.getString(R.string.ota_complete))
// val handler = Handler(Looper.getMainLooper())
// handler.postDelayed({
// // Log.d("ZHM", "onStopOTA: disconnectDevice connected : "+viewModel.bluetoothHelper.isConnected()+ " getConnectedDevice: "+viewModel.bluetoothHelper.getConnectedDevice())
// //变地址导致无法判断device是否一致
// if (viewModel.bluetoothHelper.isConnected()/* && BluetoothUtil.deviceEquals(
// device,
// viewModel.bluetoothHelper.getConnectedDevice()
// )*/
// ) {
// Log.d("ZHM", "onStopOTA: disconnectDevice")
// viewModel.bluetoothHelper.disconnectDevice(viewModel.otaManager.connectedDevice)
// }
// }, 500)
}
override fun onCancelOTA() {
Log.d(TAG, "onCancelOTA取消升级")
// viewModel.otaStateMLD.value =
// OTAEnd(
// device,
// ErrorCode.ERR_UNKNOWN,
// context.getString(R.string.ota_upgrade_cancel)
// )
}
override fun onError(error: BaseError?) {
Log.d(TAG, "onError升级错误")
// error?.let {
// viewModel.otaStateMLD.value = OTAEnd(device, it.subCode, it.message)
// }
// viewModel.bluetoothHelper.disconnectDevice(viewModel.otaManager.connectedDevice)//升级失败,断开设备,方便自动化继续升级
}
}
// class OtaDemo {
// private val otaManager = OTAManager(MainApplication.getInstance())
// fun initOTA(context: Context, firmwarePath: String) {
// val otaManager = JlOtaPlugin()
// val bluetoothOption = BluetoothOTAConfigure.createDefault().apply {
// priority = BluetoothOTAConfigure.PREFER_BLE
// useAuthDevice = true
// bleIntervalMs = 500
// timeoutMs = 3000
// mtu = 500
// needChangeMtu = false
// useReconnect = false
// setFirmwareFilePath(firmwarePath)
// }
// otaManager.configure(bluetoothOption)
// }
// fun otaFirmware(filePath: String) { // 方法名改为小写开头
// val otaManager = JlOtaPlugin()
// otaManager.registerBluetoothCallback(object : BtEventCallback {
// override fun onConnection(device: BluetoothDevice, status: Int) {
// if (status == StateCode.CONNECTION_OK) {
// if (otaManager.isOTA) return
// otaManager.queryMandatoryUpdate(object : IActionCallback<TargetInfoResponse> {
// override fun onSuccess(deviceInfo: TargetInfoResponse) {
// // 处理设备信息
// deviceInfo.versionCode
// deviceInfo.versionName
// deviceInfo.projectCode
// deviceInfo.uid
// deviceInfo.pid
// }
// override fun onError(baseError: BaseError) {
// if (baseError.code == ErrorCode.ERR_NONE &&
// baseError.subCode == ErrorCode.ERR_NONE) {
// val deviceInfo = otaManager.deviceInfo
// deviceInfo?.let {
// it.versionCode
// it.versionName
// it.projectCode
// it.uid
// it.pid
// }
// }
// }
// })
// }
// }
// })
// otaManager.bluetoothOption?.setFirmwareFilePath(filePath)
// otaManager.startOTA(object : IUpgradeCallback {
// override fun onStartOTA() = Unit
// override fun onNeedReconnect(addr: String, isNewReconnectWay: Boolean) {
// if (otaManager.bluetoothOption?.useReconnect == true) {
// // 处理自定义回连
// }
// }
// override fun onProgress(type: Int, progress: Float) = Unit
// override fun onStopOTA() = Unit
// override fun onCancelOTA() = Unit
// override fun onError(error: BaseError) = Unit
// })
// otaManager.release()
// }
//}

38
local_plugins/jl_ota/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/jl_ota/ios/Assets/.gitkeep

19
local_plugins/jl_ota/ios/Classes/JlOtaPlugin.swift

@ -0,0 +1,19 @@
import Flutter
import UIKit
public class JlOtaPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "jl_ota", binaryMessenger: registrar.messenger())
let instance = JlOtaPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getPlatformVersion":
result("iOS " + UIDevice.current.systemVersion)
default:
result(FlutterMethodNotImplemented)
}
}
}

14
local_plugins/jl_ota/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/jl_ota/ios/jl_ota.podspec

@ -0,0 +1,29 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint jl_ota.podspec` to validate before publishing.
#
Pod::Spec.new do |s|
s.name = 'jl_ota'
s.version = '0.0.1'
s.summary = 'A new Flutter project.'
s.description = <<-DESC
A new Flutter 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.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' }
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 = {'jl_ota_privacy' => ['Resources/PrivacyInfo.xcprivacy']}
end

8
local_plugins/jl_ota/lib/jl_ota.dart

@ -0,0 +1,8 @@
import 'jl_ota_platform_interface.dart';
class JlOta {
Future<String?> getPlatformVersion() {
return JlOtaPlatform.instance.getPlatformVersion();
}
}

17
local_plugins/jl_ota/lib/jl_ota_method_channel.dart

@ -0,0 +1,17 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'jl_ota_platform_interface.dart';
/// An implementation of [JlOtaPlatform] that uses method channels.
class MethodChannelJlOta extends JlOtaPlatform {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('jl_ota');
@override
Future<String?> getPlatformVersion() async {
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
return version;
}
}

29
local_plugins/jl_ota/lib/jl_ota_platform_interface.dart

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

72
local_plugins/jl_ota/pubspec.yaml

@ -0,0 +1,72 @@
name: jl_ota
description: "A new Flutter 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.yunqiinnovation.jl_ota
pluginClass: JlOtaPlugin
ios:
pluginClass: JlOtaPlugin
# 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

2
pubspec.yaml

@ -79,6 +79,8 @@ dependencies:
path: amap_flutter
jl_opus:
path: local_plugins/jl_opus
jl_ota:
path: local_plugins/jl_ota
ble_service:
path: local_plugins/ble_service
deepvoice_utils:

Loading…
Cancel
Save