42 changed files with 1604 additions and 182 deletions
@ -1,4 +1,5 @@ |
|||||
class NWApi { |
class NWApi { |
||||
// static const baseApi = "https://www.deapsound.com"; |
// static const baseApi = "https://www.deapsound.com"; |
||||
static const baseApi = "https://api.deapsound.com"; |
static const baseApi = "https://api.deapsound.com"; |
||||
|
// static const baseApi = "https://api.ideapsound.com"; |
||||
} |
} |
||||
|
|||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
@ -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/ |
||||
@ -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' |
||||
@ -0,0 +1,3 @@ |
|||||
|
## 0.0.1 |
||||
|
|
||||
|
* TODO: Describe initial release. |
||||
@ -0,0 +1 @@ |
|||||
|
TODO: Add your license here. |
||||
@ -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. |
||||
|
|
||||
@ -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 |
||||
@ -0,0 +1,9 @@ |
|||||
|
*.iml |
||||
|
.gradle |
||||
|
/local.properties |
||||
|
/.idea/workspace.xml |
||||
|
/.idea/libraries |
||||
|
.DS_Store |
||||
|
/build |
||||
|
/captures |
||||
|
.cxx |
||||
@ -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 |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@ |
|||||
|
rootProject.name = 'jl_ota' |
||||
@ -0,0 +1,3 @@ |
|||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||
|
package="com.yunqiinnovation.jl_ota"> |
||||
|
</manifest> |
||||
@ -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() |
||||
|
// } |
||||
|
//} |
||||
@ -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,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) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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> |
||||
@ -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 |
||||
@ -0,0 +1,8 @@ |
|||||
|
|
||||
|
import 'jl_ota_platform_interface.dart'; |
||||
|
|
||||
|
class JlOta { |
||||
|
Future<String?> getPlatformVersion() { |
||||
|
return JlOtaPlatform.instance.getPlatformVersion(); |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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.'); |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
Loading…
Reference in new issue