瞄准器,地图
BIN
images/navi_pointer.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
images/satellite.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
162
lib/appbar/appbar.dart
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
import '../login_in/connect/bluetooth_page.dart';
|
||||||
|
import '../login_in/connect/connect_type.dart';
|
||||||
|
import '../login_in/getx/real_data.dart';
|
||||||
|
import '../main.dart';
|
||||||
|
import '../screens/aimpoint_page.dart';
|
||||||
|
|
||||||
|
final RealController controller1 = Get.find();
|
||||||
|
|
||||||
|
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
|
final double appBarHeight;
|
||||||
|
final RxInt _currentIndex;
|
||||||
|
|
||||||
|
CustomAppBar({required this.appBarHeight, required RxInt currentIndex})
|
||||||
|
: _currentIndex = currentIndex;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
const textStyle = TextStyle(fontSize: 16);
|
||||||
|
return AppBar(
|
||||||
|
toolbarHeight: 40,
|
||||||
|
centerTitle: true, // 标题居中
|
||||||
|
title: Obx(() {
|
||||||
|
if (_currentIndex.value == 0) {
|
||||||
|
return const Text("桩点:", style: textStyle);
|
||||||
|
} else if (_currentIndex.value == 1) {
|
||||||
|
return const Text("设备:", style: textStyle);
|
||||||
|
} else {
|
||||||
|
return const Text("系统:", style: textStyle);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
// title: _currentIndex.value == 0
|
||||||
|
// ? Obx(() => Text("桩点:${realController.pileId.value}",
|
||||||
|
// style: textStyle))
|
||||||
|
// : const Text("设备:", style: textStyle),
|
||||||
|
// // const Center(
|
||||||
|
// // child: Obx(() => Text("设备:", style: textStyle)),
|
||||||
|
// // ),
|
||||||
|
// title: Obx(() {
|
||||||
|
// if (_currentIndex.value == 1) {
|
||||||
|
// return const Text("设备:", style: textStyle);
|
||||||
|
// } else if (_currentIndex.value == 0) {
|
||||||
|
// return Text("桩点:${realController.pileId.value}",
|
||||||
|
// style: textStyle);
|
||||||
|
// } else {
|
||||||
|
// return const Text("系统:", style: textStyle);
|
||||||
|
// }
|
||||||
|
// }),
|
||||||
|
|
||||||
|
actions: [
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
MyApp.isDarkMode.value = !MyApp.isDarkMode.value;
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
MyApp.isDarkMode.value ? Icons.dark_mode : Icons.sunny,
|
||||||
|
size: 35,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () => Navigator.push(context,
|
||||||
|
MaterialPageRoute(builder: (context) => const ScenceMap())),
|
||||||
|
child: Image(
|
||||||
|
image: const AssetImage('images/satellite.png'),
|
||||||
|
// width: 40,
|
||||||
|
color: MyApp.isDarkMode.value
|
||||||
|
? Colors.white70
|
||||||
|
: const Color.fromARGB(200, 29, 28, 28),
|
||||||
|
height: 40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
_currentIndex.value == 1
|
||||||
|
? Row(
|
||||||
|
children: [
|
||||||
|
Obx(
|
||||||
|
() => InkWell(
|
||||||
|
onTap: () {
|
||||||
|
// 点击图标时显示对点卡片
|
||||||
|
sight.isCardVisible.value =
|
||||||
|
!sight.isCardVisible.value; // 确保控制器已定义
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
Icons.my_location_sharp,
|
||||||
|
size: 35,
|
||||||
|
color: sight.isCardVisible.value
|
||||||
|
? Colors.blue
|
||||||
|
// : const Color.fromARGB(200, 29, 28, 28),
|
||||||
|
: (MyApp.isDarkMode.value
|
||||||
|
? Colors.white70
|
||||||
|
: const Color.fromARGB(200, 29, 28, 28)),
|
||||||
|
), // 新增图标
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
Obx(
|
||||||
|
() => InkWell(
|
||||||
|
onTap: () {
|
||||||
|
final RealController controller1 = Get.find(); // 获取控制器
|
||||||
|
controller1.isDataVisible.value =
|
||||||
|
!controller1.isDataVisible.value; // 更新控制器中的值
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
Icons.date_range_rounded,
|
||||||
|
size: 35,
|
||||||
|
color: controller1.isDataVisible.value
|
||||||
|
? Colors.blue
|
||||||
|
: (MyApp.isDarkMode.value
|
||||||
|
? Colors.white70
|
||||||
|
: const Color.fromARGB(200, 29, 28, 28)),
|
||||||
|
), // 新增图标
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
UnconstrainedBox(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 30,
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) => InkWell(
|
||||||
|
child: Icon(
|
||||||
|
Icons.settings_outlined,
|
||||||
|
size: 35,
|
||||||
|
color: MyApp.isDarkMode.value
|
||||||
|
? Colors.white70
|
||||||
|
: const Color.fromARGB(200, 29, 28, 28),
|
||||||
|
),
|
||||||
|
onTap: () => Scaffold.of(context).openEndDrawer(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: const Text(""),
|
||||||
|
Obx(() => Icon(
|
||||||
|
blueToothController.connectedType.value == ConnectType.wifi
|
||||||
|
? Icons.wifi
|
||||||
|
: (blueToothController.connectedType.value ==
|
||||||
|
ConnectType.bluetooth
|
||||||
|
? Icons.bluetooth
|
||||||
|
: Icons.close),
|
||||||
|
color: Colors.green,
|
||||||
|
size: 30,
|
||||||
|
))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Size get preferredSize => Size.fromHeight(appBarHeight);
|
||||||
|
}
|
59
lib/login_in/connect/blue_tooth.dart
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:quick_blue/quick_blue.dart';
|
||||||
|
|
||||||
|
import 'bluetooth_page.dart';
|
||||||
|
import 'connect_type.dart';
|
||||||
|
|
||||||
|
class BlueParams {
|
||||||
|
String serviceId = "00FF";
|
||||||
|
// 七参数
|
||||||
|
// Map<String, String> characteristicId = {
|
||||||
|
// "coorTransId": "FF01", //七参数
|
||||||
|
// "realId": "FF02", //实时
|
||||||
|
// };
|
||||||
|
String characteristicId = "FF01";
|
||||||
|
String gssUuid(String code) => '0000$code-0000-1000-8000-00805f9b34fb';
|
||||||
|
}
|
||||||
|
|
||||||
|
class BlueSetting {
|
||||||
|
BlueParams blueParams = BlueParams();
|
||||||
|
connect(String deviceId) async {
|
||||||
|
blueToothController.connectedType.value = ConnectType.bluetooth;
|
||||||
|
blueToothController.connectedDeviceID.value = deviceId;
|
||||||
|
blueToothController.isConnected.value = true;
|
||||||
|
QuickBlue.connect(deviceId);
|
||||||
|
QuickBlue.discoverServices(deviceId);
|
||||||
|
String service = blueParams.gssUuid(blueParams.serviceId);
|
||||||
|
String characteristic = blueParams.gssUuid("FF01");
|
||||||
|
// 订阅实时数据
|
||||||
|
Future.delayed(const Duration(milliseconds: 2000), () {
|
||||||
|
QuickBlue.writeValue(deviceId, service, characteristic,
|
||||||
|
Uint8List.fromList([0xf2]), BleOutputProperty.withResponse);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
disConnect(deviceId) {
|
||||||
|
blueToothController.isConnected.value = false;
|
||||||
|
QuickBlue.disconnect(deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
write(String deviceId, Uint8List bytes) {
|
||||||
|
QuickBlue.writeValue(
|
||||||
|
deviceId,
|
||||||
|
blueParams.gssUuid(blueParams.serviceId),
|
||||||
|
blueParams.gssUuid(blueParams.characteristicId),
|
||||||
|
bytes,
|
||||||
|
BleOutputProperty.withResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Coord {
|
||||||
|
BJ54,
|
||||||
|
XA80,
|
||||||
|
WGS84,
|
||||||
|
WGS2000,
|
||||||
|
}
|
344
lib/login_in/connect/bluetooth_page.dart
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:open_settings/open_settings.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
import 'package:quick_blue/quick_blue.dart';
|
||||||
|
|
||||||
|
import 'blue_tooth.dart';
|
||||||
|
import 'config.dart';
|
||||||
|
import '../getx/blue_tooth.dart';
|
||||||
|
import '../getx/real_data.dart';
|
||||||
|
|
||||||
|
BlueParams blueParams = BlueParams();
|
||||||
|
BlueSetting blueSetting = BlueSetting();
|
||||||
|
//字体
|
||||||
|
TextStyle textStyle = const TextStyle(fontSize: 15);
|
||||||
|
//扫描的数据
|
||||||
|
final _scanResults = <BlueScanResult>[];
|
||||||
|
//蓝牙是否开启
|
||||||
|
bool isBlueToothOpen = false;
|
||||||
|
//选中的蓝牙
|
||||||
|
int connetedIndex = -1;
|
||||||
|
//当前id
|
||||||
|
String selectDeviceId = "";
|
||||||
|
//是否是第一次询问权限
|
||||||
|
bool isFirstAsk = true;
|
||||||
|
final BlueToothController blueToothController = Get.put(BlueToothController());
|
||||||
|
|
||||||
|
class BlueTooth extends StatefulWidget {
|
||||||
|
const BlueTooth({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BlueTooth> createState() => _BlueToothState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BlueToothState extends State<BlueTooth> {
|
||||||
|
StreamSubscription<BlueScanResult>? _scanResultSubscription;
|
||||||
|
// StreamSubscription<AvailabilityState>? _availabilitySubscription;
|
||||||
|
|
||||||
|
Widget _buildListView(BuildContext context) {
|
||||||
|
return ListView.separated(
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
String text = "连接";
|
||||||
|
if (_scanResults[index].deviceId == selectDeviceId) {
|
||||||
|
text = "断开连接";
|
||||||
|
}
|
||||||
|
return Container(
|
||||||
|
height: 50,
|
||||||
|
margin: const EdgeInsets.only(bottom: 10),
|
||||||
|
// decoration:
|
||||||
|
// BoxDecoration(color: isDarkMode ? Colors.black : Colors.white),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ListTile(
|
||||||
|
title: Text(
|
||||||
|
'${_scanResults[index].name}(${_scanResults[index].rssi})'), //rssi信号强度指示
|
||||||
|
subtitle: Text(_scanResults[index].deviceId),
|
||||||
|
onTap: () async {
|
||||||
|
setState(() {
|
||||||
|
connetedIndex = index;
|
||||||
|
selectDeviceId = _scanResults[connetedIndex].deviceId;
|
||||||
|
AppConfig.updateConfig("deviceId", selectDeviceId);
|
||||||
|
});
|
||||||
|
bool tt = await QuickBlue.isBluetoothAvailable();
|
||||||
|
if (tt) {
|
||||||
|
blueSetting.connect(selectDeviceId);
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
settingBluetoothDialog();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.only(right: 5),
|
||||||
|
// width: 80,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
if (text == "断开连接") {
|
||||||
|
QuickBlue.disconnect(selectDeviceId);
|
||||||
|
selectDeviceId = "";
|
||||||
|
blueToothController.connectedDeviceID.value = "";
|
||||||
|
// blueToothController.connectedType.value = "";
|
||||||
|
setState(() {
|
||||||
|
text = "连接";
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
if (_scanResults.isNotEmpty) {
|
||||||
|
connetedIndex = index;
|
||||||
|
selectDeviceId = _scanResults[connetedIndex].deviceId;
|
||||||
|
AppConfig.updateConfig("deviceId", selectDeviceId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
bool tt = await QuickBlue.isBluetoothAvailable();
|
||||||
|
if (tt) {
|
||||||
|
blueSetting.connect(selectDeviceId);
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
settingBluetoothDialog();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: textStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder: (context, index) => const Divider(),
|
||||||
|
itemCount: _scanResults.length,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void startBluetoothScan() {
|
||||||
|
_scanResults.length = 0;
|
||||||
|
//监视蓝牙是否开启
|
||||||
|
QuickBlue.availabilityChangeStream.listen((AvailabilityState state) {
|
||||||
|
debugPrint('Bluetooth state: ${state.toString()}');
|
||||||
|
});
|
||||||
|
|
||||||
|
//扫描蓝牙设备
|
||||||
|
_scanResultSubscription =
|
||||||
|
QuickBlue.scanResultStream.listen((BlueScanResult result) {
|
||||||
|
if (!_scanResults.any((r) => r.deviceId == result.deviceId)) {
|
||||||
|
setState(() {
|
||||||
|
_scanResults.add(result);
|
||||||
|
if (result.deviceId == blueToothController.connectedDeviceID.value) {
|
||||||
|
connetedIndex = _scanResults.length - 1;
|
||||||
|
selectDeviceId = result.deviceId;
|
||||||
|
blueSetting.connect(selectDeviceId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//开始扫描
|
||||||
|
QuickBlue.startScan();
|
||||||
|
QuickBlue.setConnectionHandler(_handleConnectionChange);
|
||||||
|
QuickBlue.setServiceHandler(_handleServiceDiscovery);
|
||||||
|
QuickBlue.setValueHandler(_handleValueChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> openBluetoothDialog() {
|
||||||
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text("提示"),
|
||||||
|
content: const Text("无法获取到开启蓝牙和位置权限,请到系统设置中开启蓝牙和位置权限。"),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text("确定"))
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> settingBluetoothDialog() {
|
||||||
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text("提示"),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: ListBody(
|
||||||
|
children: [
|
||||||
|
const Center(
|
||||||
|
child: Text("蓝牙暂未开启"),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
OpenSettings.openBluetoothSetting();
|
||||||
|
},
|
||||||
|
child: const Text("去打开"))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
child: const Text("确定"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表示应用程序请求的权限已被用户拒绝。
|
||||||
|
PermissionStatus denied = PermissionStatus.denied;
|
||||||
|
// 表示应用程序请求的权限已被用户授予。
|
||||||
|
PermissionStatus granted = PermissionStatus.granted;
|
||||||
|
final RealController realController = Get.put(RealController());
|
||||||
|
Uint8List bleList = Uint8List(0);
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
SchedulerBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
//先查看蓝牙是否开启
|
||||||
|
if (await QuickBlue.isBluetoothAvailable() == true) {
|
||||||
|
//获取手机权限
|
||||||
|
if (await Permission.location.status == granted &&
|
||||||
|
await Permission.bluetoothScan.status == granted &&
|
||||||
|
await Permission.bluetoothConnect.status == granted) {
|
||||||
|
//蓝牙部分
|
||||||
|
startBluetoothScan();
|
||||||
|
} else {
|
||||||
|
if (isFirstAsk) {
|
||||||
|
if (await Permission.location.status == denied) {
|
||||||
|
await Permission.location.request();
|
||||||
|
}
|
||||||
|
if (await Permission.bluetoothScan.status == denied) {
|
||||||
|
await Permission.bluetoothScan.request();
|
||||||
|
}
|
||||||
|
if (await Permission.bluetoothConnect.status == denied) {
|
||||||
|
await Permission.bluetoothConnect.request();
|
||||||
|
}
|
||||||
|
isFirstAsk = false;
|
||||||
|
if (await Permission.location.status == granted &&
|
||||||
|
await Permission.bluetoothScan.status == granted &&
|
||||||
|
await Permission.bluetoothConnect.status == granted) {
|
||||||
|
startBluetoothScan();
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
openBluetoothDialog();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
openBluetoothDialog();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
settingBluetoothDialog();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleConnectionChange(String deviceId, BlueConnectionState state) {
|
||||||
|
if (state == BlueConnectionState.connected) {
|
||||||
|
QuickBlue.discoverServices(selectDeviceId);
|
||||||
|
}
|
||||||
|
if (state == BlueConnectionState.connected) {
|
||||||
|
QuickBlue.discoverServices(selectDeviceId);
|
||||||
|
//显示蓝牙连接成功
|
||||||
|
|
||||||
|
// //传入当前蓝牙id
|
||||||
|
// settingController.changeID(bluetoothId);
|
||||||
|
} else if (state == BlueConnectionState.disconnected) {
|
||||||
|
// blueToothController.connectedType.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleServiceDiscovery(
|
||||||
|
String deviceId, String serviceId, List<String> characteristicIds) {
|
||||||
|
if (characteristicIds.contains(blueParams.gssUuid("FF02").toLowerCase())) {
|
||||||
|
QuickBlue.setNotifiable(selectDeviceId, serviceId,
|
||||||
|
blueParams.gssUuid("FF02"), BleInputProperty.indication);
|
||||||
|
}
|
||||||
|
|
||||||
|
print('_handleServiceDiscovery $deviceId, $serviceId, $characteristicIds');
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Uint8List> originalList = [];
|
||||||
|
void _handleValueChange(
|
||||||
|
String deviceId, String characteristicId, Uint8List value) {
|
||||||
|
ByteData byteData = value.buffer.asByteData();
|
||||||
|
int dataType = byteData.getInt8(2);
|
||||||
|
originalList.add(value);
|
||||||
|
if (dataType == 4) {
|
||||||
|
cropAndConcatUint8List(originalList);
|
||||||
|
Uint8List list = cropAndConcatUint8List(originalList);
|
||||||
|
realController.onBleData(list);
|
||||||
|
originalList.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8List cropAndConcatUint8List(List<Uint8List> originalList) {
|
||||||
|
List<int> concatenatedList = [];
|
||||||
|
|
||||||
|
for (int i = 0; i < originalList.length; i++) {
|
||||||
|
Uint8List currentUint8List = originalList[i];
|
||||||
|
|
||||||
|
if (currentUint8List.length >= 3) {
|
||||||
|
List<int> croppedList = currentUint8List.sublist(3);
|
||||||
|
concatenatedList.addAll(croppedList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Uint8List.fromList(concatenatedList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
|
||||||
|
QuickBlue.stopScan();
|
||||||
|
// QuickBlue.setValueHandler(null);
|
||||||
|
// QuickBlue.setServiceHandler(null);
|
||||||
|
// QuickBlue.setConnectionHandler(null);
|
||||||
|
_scanResultSubscription?.cancel();
|
||||||
|
// _availabilitySubscription?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text("蓝牙设备"),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
startBluetoothScan();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.refresh))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: _buildListView(context),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
69
lib/login_in/connect/config.dart
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
|
import 'connect_type.dart';
|
||||||
|
|
||||||
|
class AppConfig {
|
||||||
|
static late final Map<String, dynamic> config;
|
||||||
|
|
||||||
|
static Future<void> load() async {
|
||||||
|
String configString = await _readConfigFile();
|
||||||
|
if (configString.isEmpty) {
|
||||||
|
config = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
config = json.decode(configString);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<String> _readConfigFile() async {
|
||||||
|
Directory directory = await getApplicationDocumentsDirectory();
|
||||||
|
File file = File('${directory.path}/config.json');
|
||||||
|
if (await file.exists()) {
|
||||||
|
} else {
|
||||||
|
Map content = {};
|
||||||
|
String fileContent = json.encode(content);
|
||||||
|
file.writeAsStringSync(fileContent);
|
||||||
|
}
|
||||||
|
return await file.readAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> _writeConfigFile(String content) async {
|
||||||
|
Directory directory = await getApplicationDocumentsDirectory();
|
||||||
|
File file = File('${directory.path}//config.json');
|
||||||
|
await file.writeAsString(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void updateConfig(String key, dynamic value) {
|
||||||
|
config[key] = value;
|
||||||
|
_writeConfigFile(json.encode(config));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// config 参数内容
|
||||||
|
|
||||||
|
class Config {
|
||||||
|
// 连接方式 wifi 或蓝牙
|
||||||
|
ConnectType? connectType;
|
||||||
|
// 已连接蓝牙id
|
||||||
|
String? deviceId;
|
||||||
|
// 连接wifi 的固定地址
|
||||||
|
String? ip;
|
||||||
|
int? port;
|
||||||
|
Config({
|
||||||
|
this.connectType,
|
||||||
|
this.deviceId,
|
||||||
|
this.ip,
|
||||||
|
this.port,
|
||||||
|
});
|
||||||
|
factory Config.fromJson(Map<dynamic, dynamic> data) {
|
||||||
|
return Config(
|
||||||
|
connectType: data['connectType'] == "wifi"
|
||||||
|
? ConnectType.wifi
|
||||||
|
: ConnectType.bluetooth,
|
||||||
|
deviceId: data['deviceId'] ?? "",
|
||||||
|
ip: data['ip'] ?? "192.168.4.1",
|
||||||
|
port: data['port'] == null ? 6000 : data["port"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
6
lib/login_in/connect/connect_type.dart
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
enum ConnectType {
|
||||||
|
wifi,
|
||||||
|
bluetooth,
|
||||||
|
none
|
||||||
|
}
|
184
lib/login_in/connect/index.dart
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../service/coor_trans.dart';
|
||||||
|
import 'bluetooth_page.dart';
|
||||||
|
|
||||||
|
import 'config.dart';
|
||||||
|
import 'connect_type.dart';
|
||||||
|
import 'blue_tooth.dart';
|
||||||
|
import 'wifi.dart';
|
||||||
|
|
||||||
|
class Connect {
|
||||||
|
SocketSetting socket = SocketSetting();
|
||||||
|
BlueSetting blueSetting = BlueSetting();
|
||||||
|
Config config = Config();
|
||||||
|
init() async {
|
||||||
|
await AppConfig.load();
|
||||||
|
config = Config.fromJson(AppConfig.config);
|
||||||
|
if (config.connectType != null) {
|
||||||
|
// if (config.connectType == "wifi") {
|
||||||
|
// config.connectType = ConnectType.wifi;
|
||||||
|
// } else {
|
||||||
|
// config.connectType = ConnectType.bluetooth;
|
||||||
|
// }
|
||||||
|
blueToothController.connectedType.value =
|
||||||
|
config.connectType ?? ConnectType.none;
|
||||||
|
if (config.connectType == ConnectType.wifi) {
|
||||||
|
if (config.ip != null) {
|
||||||
|
socket.ip = config.ip ?? "192.168.4.1";
|
||||||
|
socket.port = config.port ?? 6000;
|
||||||
|
await socket.connect();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (config.deviceId != "") {
|
||||||
|
blueToothController.connectedDeviceID.value = config.deviceId!;
|
||||||
|
//蓝牙
|
||||||
|
await blueSetting.connect(config.deviceId!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect(ConnectType type) {
|
||||||
|
if (type == ConnectType.wifi) {
|
||||||
|
socket.disConnect();
|
||||||
|
} else {
|
||||||
|
blueSetting.disConnect(blueToothController.connectedDeviceID.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实时读写
|
||||||
|
write(Uint8List value) {
|
||||||
|
if (blueToothController.connectedType.value == ConnectType.wifi) {
|
||||||
|
socket.write(value);
|
||||||
|
} else if (blueToothController.connectedType.value ==
|
||||||
|
ConnectType.bluetooth) {
|
||||||
|
blueSetting.write(blueToothController.connectedDeviceID.value, value);
|
||||||
|
} else {
|
||||||
|
print("暂无连接");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCoorTransValue(String deviceId, CoorTransModel coorTrans) {
|
||||||
|
Uint8List bytes = Uint8List(20);
|
||||||
|
ByteData data = ByteData.view(bytes.buffer);
|
||||||
|
// 0xF0 + 2字节地址 + 1字节数据长度(以字节为单位)+ 数据
|
||||||
|
data.setInt8(0, 0xF0);
|
||||||
|
data.setInt16(1, 608, Endian.little);
|
||||||
|
data.setInt8(3, 16);
|
||||||
|
// data.setFloat64(4, coorTrans.L0);
|
||||||
|
|
||||||
|
// 椭球基准 601 2
|
||||||
|
sendData(
|
||||||
|
bytes, 601, 2, [toCoordString(coorTrans.dstEllipsoid).index], deviceId);
|
||||||
|
|
||||||
|
// L0,elevation 608 16
|
||||||
|
Future.delayed(const Duration(milliseconds: 2000), () {
|
||||||
|
sendData(
|
||||||
|
bytes,
|
||||||
|
608,
|
||||||
|
16,
|
||||||
|
[(coorTrans.L0 * 1e8).toInt(), (coorTrans.elevation * 1000).toInt()],
|
||||||
|
deviceId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// x 616 16
|
||||||
|
Future.delayed(const Duration(milliseconds: 3000), () {
|
||||||
|
sendData(
|
||||||
|
bytes,
|
||||||
|
616,
|
||||||
|
16,
|
||||||
|
[(coorTrans.dx * 1000).toInt(), (coorTrans.wx * 1000).toInt()],
|
||||||
|
deviceId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// y 624 16
|
||||||
|
Future.delayed(const Duration(milliseconds: 4000), () {
|
||||||
|
sendData(
|
||||||
|
bytes,
|
||||||
|
624,
|
||||||
|
16,
|
||||||
|
[(coorTrans.dy * 1000).toInt(), (coorTrans.wy * 1000).toInt()],
|
||||||
|
deviceId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// z 632 16
|
||||||
|
Future.delayed(const Duration(milliseconds: 4000), () {
|
||||||
|
sendData(
|
||||||
|
bytes,
|
||||||
|
632,
|
||||||
|
16,
|
||||||
|
[(coorTrans.dz * 1000).toInt(), (coorTrans.wz * 1000).toInt()],
|
||||||
|
deviceId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// k 640 8
|
||||||
|
Future.delayed(const Duration(milliseconds: 5000), () {
|
||||||
|
ByteData data = ByteData.view(bytes.buffer);
|
||||||
|
data.setInt8(0, 0xF0);
|
||||||
|
data.setInt16(1, 640, Endian.little);
|
||||||
|
data.setInt8(3, 8);
|
||||||
|
data.setInt64(4, (coorTrans.k * 1000).toInt(), Endian.little);
|
||||||
|
write(bytes);
|
||||||
|
});
|
||||||
|
|
||||||
|
Future.delayed(const Duration(milliseconds: 7000), () {
|
||||||
|
save(deviceId, bytes, data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendData(Uint8List bytes, int code, int length, List<int> values,
|
||||||
|
String deviceId) {
|
||||||
|
ByteData data = ByteData.view(bytes.buffer);
|
||||||
|
data.setInt8(0, 0xF0);
|
||||||
|
data.setInt16(1, code, Endian.little);
|
||||||
|
data.setInt8(3, length);
|
||||||
|
for (int i = 0; i < values.length; i++) {
|
||||||
|
data.setInt64(4 + i * 8, (values[i] * 1000).toInt(), Endian.little);
|
||||||
|
}
|
||||||
|
write(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
save(String deviceId, Uint8List bytes, ByteData data) {
|
||||||
|
// 采用 0X55BB 600 2
|
||||||
|
data.setInt16(1, 600, Endian.little);
|
||||||
|
data.setInt8(3, 2);
|
||||||
|
data.setUint16(4, 0x55AA, Endian.little);
|
||||||
|
write(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
Coord toCoordString(String coord) {
|
||||||
|
switch (coord) {
|
||||||
|
case "BJ54":
|
||||||
|
return Coord.BJ54;
|
||||||
|
case "XA80":
|
||||||
|
return Coord.XA80;
|
||||||
|
case "WGS84":
|
||||||
|
return Coord.WGS84;
|
||||||
|
case "WGS2000":
|
||||||
|
return Coord.WGS2000;
|
||||||
|
default:
|
||||||
|
return Coord.WGS2000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeAntenna(Offset center, Offset right, String deviceId) {
|
||||||
|
Uint8List bytes = Uint8List(20);
|
||||||
|
ByteData data = ByteData.view(bytes.buffer);
|
||||||
|
|
||||||
|
// 604 8 X0右侧天线 X1 中心点
|
||||||
|
data.setInt8(0, 0xF0);
|
||||||
|
data.setInt16(1, 604, Endian.little);
|
||||||
|
data.setInt8(3, 8);
|
||||||
|
data.setInt16(4, (right.dx * 1000).toInt(), Endian.little);
|
||||||
|
data.setInt16(6, (right.dy * 1000).toInt(), Endian.little);
|
||||||
|
data.setInt16(8, (center.dx * 1000).toInt(), Endian.little);
|
||||||
|
data.setInt16(10, (center.dy * 1000).toInt(), Endian.little);
|
||||||
|
write(bytes);
|
||||||
|
Future.delayed(const Duration(milliseconds: 1000), () {
|
||||||
|
save(deviceId, bytes, data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
49
lib/login_in/connect/my_routes.dart
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
//配置路由
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
|
||||||
|
import '../../main.dart';
|
||||||
|
import '../../screens/login_page.dart';
|
||||||
|
import '../user/loginprefs.dart';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 这个方法是固定写法,功能就像是一个拦截器。
|
||||||
|
*/
|
||||||
|
Route<dynamic>? onGenerateRoute(RouteSettings settings) {
|
||||||
|
Map<String, Widget> routes = {
|
||||||
|
'home': const MyApp(), //定义app路径
|
||||||
|
'login': const Login(), //定义login路径
|
||||||
|
};
|
||||||
|
|
||||||
|
String routerName = routeBeforeHook(settings);
|
||||||
|
bool mathMap = false;
|
||||||
|
Route<dynamic>? mathWidget;
|
||||||
|
routes.forEach((key, v) {
|
||||||
|
if (key == routerName) {
|
||||||
|
mathMap = true;
|
||||||
|
mathWidget = MaterialPageRoute(builder: (BuildContext context) => v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mathMap) {
|
||||||
|
return mathWidget;
|
||||||
|
}
|
||||||
|
return MaterialPageRoute(
|
||||||
|
builder: (BuildContext context) => const Text('404'));
|
||||||
|
}
|
||||||
|
|
||||||
|
String routeBeforeHook(RouteSettings settings) {
|
||||||
|
if (checkToken() == false) {
|
||||||
|
return 'login';
|
||||||
|
} else {
|
||||||
|
return settings.name!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool checkToken() {
|
||||||
|
LoginPrefs loginPrefs = LoginPrefs();
|
||||||
|
String token = loginPrefs.getToken();
|
||||||
|
if ('' != token) return true;
|
||||||
|
return false;
|
||||||
|
}
|
92
lib/login_in/connect/wifi.dart
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
import 'bluetooth_page.dart';
|
||||||
|
import '../getx/real_data.dart';
|
||||||
|
|
||||||
|
final RealController realController = Get.find();
|
||||||
|
|
||||||
|
class SocketSetting {
|
||||||
|
int port = 6000;
|
||||||
|
String ip = "192.168.4.1";
|
||||||
|
bool isConnected = false;
|
||||||
|
int times = 0;
|
||||||
|
RawDatagramSocket? socket;
|
||||||
|
connect() async {
|
||||||
|
// tcp连接
|
||||||
|
// try {
|
||||||
|
// socket = await Socket.connect(ip, port);
|
||||||
|
// // 订阅
|
||||||
|
// socket!.add([0xf2]);
|
||||||
|
|
||||||
|
// // 监听服务器回复
|
||||||
|
// socket!.listen(
|
||||||
|
// (Uint8List data) {
|
||||||
|
// realController.onBleData(data);
|
||||||
|
// // print('收到服务器回复:$data');
|
||||||
|
// },
|
||||||
|
// onError: (error) async {
|
||||||
|
// disConnect();
|
||||||
|
|
||||||
|
// print('与服务器的连接发生错误:$error');
|
||||||
|
// socket = await Socket.connect(ip, port);
|
||||||
|
// return;
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
|
||||||
|
// // 监听服务器断开连接
|
||||||
|
// socket!.done.then((_) {
|
||||||
|
// print('与服务器的连接已断开');
|
||||||
|
|
||||||
|
// disConnect();
|
||||||
|
// });
|
||||||
|
// } catch (e) {
|
||||||
|
// print("----------$e");
|
||||||
|
// disConnect();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// udp连接
|
||||||
|
|
||||||
|
socket = await RawDatagramSocket.bind(
|
||||||
|
InternetAddress.anyIPv4, 6000); //InternetAddress(ip)
|
||||||
|
|
||||||
|
// // 订阅
|
||||||
|
socket!.send([0xf2], InternetAddress(ip), 6000);
|
||||||
|
|
||||||
|
socket!.listen((RawSocketEvent e) {
|
||||||
|
times++;
|
||||||
|
if (times == 20) {
|
||||||
|
times = 0;
|
||||||
|
socket!.send([0xf2], InternetAddress(ip), 6000);
|
||||||
|
}
|
||||||
|
Datagram? dg = socket!.receive();
|
||||||
|
if (dg != null) {
|
||||||
|
realController.onBleData(dg.data);
|
||||||
|
// print('从 ${dg.address.address}:${dg.port} 接收:');
|
||||||
|
// 发送响应给客户端
|
||||||
|
// socket.send("已收到".codeUnits, dg.address, dg.port);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
write(Uint8List data) async {
|
||||||
|
try {
|
||||||
|
// socket ??= await Socket.connect(ip, port);
|
||||||
|
Datagram? dg = socket!.receive();
|
||||||
|
socket!.send(data, dg!.address, dg.port);
|
||||||
|
// socket!.add(data);
|
||||||
|
// ignore: empty_catches
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
disConnect() async {
|
||||||
|
blueToothController.isConnectedWifi.value = false;
|
||||||
|
try {
|
||||||
|
socket!.close();
|
||||||
|
// await socket!.close();
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
lib/login_in/getx/blue_tooth.dart
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
import '../connect/connect_type.dart';
|
||||||
|
|
||||||
|
class BlueToothController extends GetxController {
|
||||||
|
//是否连接蓝牙
|
||||||
|
var connectedType = ConnectType.none.obs; //蓝牙 或wifi
|
||||||
|
//连接的设备id,在蓝牙页面默认选中
|
||||||
|
RxString connectedDeviceID = "".obs;
|
||||||
|
var isConnected = false.obs;
|
||||||
|
var isConnectedWifi = false.obs;
|
||||||
|
}
|
17
lib/login_in/getx/index.dart
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:scence_map/controller.dart';
|
||||||
|
|
||||||
|
|
||||||
|
import 'real_data.dart';
|
||||||
|
|
||||||
|
class GexRegister {
|
||||||
|
GexRegister();
|
||||||
|
void registerDependencies() {
|
||||||
|
Get.lazyPut(() => ScenceMapController());
|
||||||
|
Get.lazyPut(() => RealController());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
155
lib/login_in/getx/real_data.dart
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
import '../../map_pointer/service/process.dart';
|
||||||
|
import 'package:flutter_webrtc/flutter_webrtc.dart';
|
||||||
|
|
||||||
|
class RealController extends GetxController {
|
||||||
|
var sightOffset1 = const Offset(3, 3).obs;
|
||||||
|
var sightOffsetInit1 = const Offset(0, 0).obs;
|
||||||
|
var sightInit1 = const Offset(0, 0).obs;
|
||||||
|
var initDx = 0.0.obs;
|
||||||
|
reset() {
|
||||||
|
sightOffset1.value = const Offset(3, 3);
|
||||||
|
sightOffsetInit1.value = const Offset(0, 0);
|
||||||
|
sightInit1.value = const Offset(0, 0);
|
||||||
|
|
||||||
|
initDx.value = 0.0;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
RTCPeerConnection? peerConnection;
|
||||||
|
RTCDataChannel? dataChannel;
|
||||||
|
final speed = 0.0.obs;
|
||||||
|
final depth = 0.0.obs;
|
||||||
|
final tiltX = 0.0.obs;
|
||||||
|
final tiltY = 0.0.obs;
|
||||||
|
final centerX = 0.0.obs;
|
||||||
|
final centerY = 0.0.obs;
|
||||||
|
final current1 = 0.0.obs;
|
||||||
|
final current2 = 0.0.obs;
|
||||||
|
final totalFlow2 = 0.0.obs;
|
||||||
|
final totalFlow1 = 0.0.obs;
|
||||||
|
final flow10cm1 = 0.0.obs;
|
||||||
|
final flow10cm2 = 0.0.obs;
|
||||||
|
final subtotalFlow2 = 0.0.obs;
|
||||||
|
final subtotalFlow1 = 0.0.obs;
|
||||||
|
final recvTime = ''.obs;
|
||||||
|
final alt = 0.0.obs;
|
||||||
|
final lng = 0.0.obs;
|
||||||
|
final lat = 0.0.obs;
|
||||||
|
final pileId = ''.obs;
|
||||||
|
final time = "0".obs;
|
||||||
|
final startIndex = 0.obs;
|
||||||
|
// 折线数组
|
||||||
|
|
||||||
|
final processList = <ProcessEntity>[].obs;
|
||||||
|
var isDataVisible = false.obs;
|
||||||
|
|
||||||
|
void updateProcessList(List<ProcessEntity> newList) {
|
||||||
|
processList.assignAll(newList); // 使用 assignAll 方法更新 RxList
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime? sliderTime;
|
||||||
|
updateSlider(double newValue, [isHand = true]) {
|
||||||
|
if (isHand) {
|
||||||
|
sliderTime = DateTime.now();
|
||||||
|
}
|
||||||
|
startIndex.value = newValue.toInt();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
// 定时器每秒更新时间
|
||||||
|
// Timer.periodic(const Duration(milliseconds: 1000), (_) {
|
||||||
|
// time.value = DateTime.now();
|
||||||
|
// });
|
||||||
|
initializePeerConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
void initializePeerConnection() async {
|
||||||
|
final configuration = <String, dynamic>{
|
||||||
|
'iceServers': [
|
||||||
|
{'url': 'stun:stun.l.google.com:19302'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
peerConnection = await createPeerConnection(configuration);
|
||||||
|
initializeDataChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void initializeDataChannel() async {
|
||||||
|
// 创建数据通道
|
||||||
|
dataChannel = await peerConnection!
|
||||||
|
.createDataChannel('dataChannel', RTCDataChannelInit()..id = 1);
|
||||||
|
|
||||||
|
// 监听数据通道的消息事件
|
||||||
|
dataChannel!.onMessage = (RTCDataChannelMessage message) {
|
||||||
|
// 处理接收到的消息
|
||||||
|
if (message.isBinary) {
|
||||||
|
onBleData(message.binary);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onBleData(Uint8List data) {
|
||||||
|
ByteData byteData = data.buffer.asByteData();
|
||||||
|
int tag = byteData.getUint8(0);
|
||||||
|
// int length = byteData.getUint8(1);
|
||||||
|
if (tag == 161) {
|
||||||
|
speed.value = byteData.getInt16(2, Endian.little) * 0.1;
|
||||||
|
int timeInt = byteData.getUint16(4, Endian.little);
|
||||||
|
// print(timeInt);
|
||||||
|
time.value = convertSecondsToMinSec(timeInt);
|
||||||
|
depth.value = byteData.getInt16(6, Endian.little) * 0.001;
|
||||||
|
pileId.value = byteData.getUint16(8, Endian.little).toString();
|
||||||
|
subtotalFlow1.value = byteData.getInt16(10, Endian.little) * 0.01;
|
||||||
|
flow10cm1.value = byteData.getUint16(12, Endian.little) * 0.01;
|
||||||
|
totalFlow1.value = byteData.getInt32(14, Endian.little) * 0.01;
|
||||||
|
subtotalFlow2.value = byteData.getInt16(18, Endian.little) * 0.01;
|
||||||
|
flow10cm2.value = byteData.getUint16(20, Endian.little) * 0.01;
|
||||||
|
totalFlow2.value = byteData.getInt32(22, Endian.little) * 0.01;
|
||||||
|
current1.value = byteData.getUint16(26, Endian.little) * 0.01;
|
||||||
|
current2.value = byteData.getUint16(28, Endian.little) * 0.01;
|
||||||
|
// current3.value = byteData.getUint16(30, Endian.little) * 0.01;
|
||||||
|
int tag1 = byteData.getUint8(34);
|
||||||
|
// int length = byteData.getUint8(35);
|
||||||
|
if (tag1 == 162) {
|
||||||
|
// var gpsStatus = byteData.getUint8(36);
|
||||||
|
// var gpsView = byteData.getUint8(37);
|
||||||
|
|
||||||
|
// var gpsUse = byteData.getUint8(38);
|
||||||
|
// var reg = byteData.getUint8(39);
|
||||||
|
// var utc = byteData.getUint32(40, Endian.little);
|
||||||
|
// 中心坐标
|
||||||
|
centerX.value = byteData.getFloat64(44, Endian.little);
|
||||||
|
centerY.value = byteData.getFloat64(52, Endian.little);
|
||||||
|
// print("中心---$centerX,$centerY");
|
||||||
|
// tiltX.value = byteData.getFloat64(44, Endian.little);
|
||||||
|
// tiltY.value = byteData.getFloat64(52, Endian.little);
|
||||||
|
// var direction = byteData.getFloat64(60, Endian.little);
|
||||||
|
// var pitch = byteData.getFloat64(68, Endian.little);
|
||||||
|
// print("$gpsUse,$gpsView,$utc,$direction,$pitch");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String convertSecondsToMinSec(int totalSeconds) {
|
||||||
|
int minutes = totalSeconds ~/ 60;
|
||||||
|
int seconds = totalSeconds % 60;
|
||||||
|
|
||||||
|
String formattedTime = '$minutes:${seconds.toString().padLeft(2, '0')}';
|
||||||
|
|
||||||
|
return formattedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
dataChannel?.close(); // 关闭数据通道
|
||||||
|
peerConnection?.close(); // 关闭对等连接
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
54
lib/login_in/service/base_service.dart
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import '../user/loginprefs.dart';
|
||||||
|
|
||||||
|
|
||||||
|
LoginPrefs loginPrefs = LoginPrefs();
|
||||||
|
|
||||||
|
class BaseService {
|
||||||
|
//创建client实例
|
||||||
|
final _client = http.Client();
|
||||||
|
|
||||||
|
Map<String, String> headers = {
|
||||||
|
HttpHeaders.contentTypeHeader: "application/json",
|
||||||
|
HttpHeaders.authorizationHeader: loginPrefs.getToken(),
|
||||||
|
};
|
||||||
|
// String baseUrl = "http://192.168.1.189:8001";//本地
|
||||||
|
String baseUrl = "http://1.82.251.83:8001"; //线上
|
||||||
|
String loginstatus = "登录失效";
|
||||||
|
//发送GET请求
|
||||||
|
getClient(String url, [bool useBaseUrl = true]) async {
|
||||||
|
try {
|
||||||
|
http.Response response = await _client
|
||||||
|
.get(Uri.parse(useBaseUrl ? baseUrl + url : url), headers: headers);
|
||||||
|
var res = json.decode(response.body);
|
||||||
|
if (res['message'].contains(loginstatus)) {
|
||||||
|
loginPrefs.clearLogin();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//发送POST请求
|
||||||
|
postClient(String url, body) async {
|
||||||
|
try {
|
||||||
|
var data = json.encode(body);
|
||||||
|
http.Response response = await _client.post(Uri.parse(baseUrl + url),
|
||||||
|
headers: headers, body: data);
|
||||||
|
var res = json.decode(response.body);
|
||||||
|
if (res['message'].contains(loginstatus)) {
|
||||||
|
loginPrefs.clearLogin();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
lib/login_in/service/coor_trans.dart
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
class CoorTransModel {
|
||||||
|
int id;
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
double L0;
|
||||||
|
String belt;
|
||||||
|
String? calibs;
|
||||||
|
String dstEllipsoid;
|
||||||
|
double dx;
|
||||||
|
double dy;
|
||||||
|
double dz;
|
||||||
|
double wx;
|
||||||
|
double wy;
|
||||||
|
double wz;
|
||||||
|
double k;
|
||||||
|
double elevation;
|
||||||
|
int isMain;
|
||||||
|
String name;
|
||||||
|
double rota;
|
||||||
|
String srcEllipsoid;
|
||||||
|
CoorTransModel({
|
||||||
|
required this.name,
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
required this.L0,
|
||||||
|
required this.belt,
|
||||||
|
this.calibs,
|
||||||
|
required this.dstEllipsoid,
|
||||||
|
required this.dx,
|
||||||
|
required this.dy,
|
||||||
|
required this.dz,
|
||||||
|
required this.id,
|
||||||
|
required this.wx,
|
||||||
|
required this.wy,
|
||||||
|
required this.wz,
|
||||||
|
required this.k,
|
||||||
|
required this.elevation,
|
||||||
|
required this.isMain,
|
||||||
|
required this.rota,
|
||||||
|
required this.srcEllipsoid,
|
||||||
|
});
|
||||||
|
factory CoorTransModel.fromJson(Map<dynamic, dynamic> data) {
|
||||||
|
return CoorTransModel(
|
||||||
|
name: data['name'],
|
||||||
|
id: data['id'],
|
||||||
|
L0: data['L0'].toDouble(),
|
||||||
|
belt: data['belt'],
|
||||||
|
calibs: data['calibs'],
|
||||||
|
dstEllipsoid: data['dstEllipsoid'],
|
||||||
|
dx: data['dx'].toDouble(),
|
||||||
|
dy: data['dy'].toDouble(),
|
||||||
|
dz: data['dz'].toDouble(),
|
||||||
|
wx: data['wx'].toDouble(),
|
||||||
|
wy: data['wy'].toDouble(),
|
||||||
|
wz: data['wz'].toDouble(),
|
||||||
|
rota: data['rota'].toDouble(),
|
||||||
|
k: data['k'].toDouble(),
|
||||||
|
isMain: data['is_main'],
|
||||||
|
elevation: data['elevation'].toDouble(),
|
||||||
|
srcEllipsoid: data['srcEllipsoid'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void forEach(Null Function(dynamic key, dynamic value) param0) {}
|
||||||
|
}
|
244
lib/login_in/service/service.dart
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'base_service.dart';
|
||||||
|
|
||||||
|
// String projCode = 'CJGKJEBYYB';
|
||||||
|
// String tid = "109";
|
||||||
|
// String projType = "hydraulic_tamping";
|
||||||
|
String projCode = 'TEST';
|
||||||
|
String tid = "1000";
|
||||||
|
String projType = "pile_cm";
|
||||||
|
BaseService service = BaseService();
|
||||||
|
|
||||||
|
class ProjController extends GetxController {
|
||||||
|
ProjController();
|
||||||
|
// 道路边线
|
||||||
|
Future getSideLine() async {
|
||||||
|
Map res = await service.getClient(
|
||||||
|
'/api/comm/side_line/list?proj_code=$projCode&proj_type=$projType');
|
||||||
|
return res['data'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设备绑定 --- 获取设备
|
||||||
|
Future getDeviceBind() async {
|
||||||
|
Map res = await service.getClient(
|
||||||
|
'/api/sys/device_bind/list?proj_type=$projType&proj_code=$projCode');
|
||||||
|
return res['data'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 项目列表
|
||||||
|
getproject() async {
|
||||||
|
Map res = await service.getClient(
|
||||||
|
'/api/sys/project/list?org_code=a&proj_type=$projType&proj_code=$projCode');
|
||||||
|
return res['data'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoginController extends GetxController {
|
||||||
|
// 验证码
|
||||||
|
getsmsCode(String phone) async {
|
||||||
|
Map res =
|
||||||
|
await service.postClient("/admin/base/open/smsCode", {"phone": phone});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 手机号登录
|
||||||
|
phoneLogin(String phone, String smsCode) async {
|
||||||
|
Map res = await service.postClient(
|
||||||
|
"/admin/base/open/phone", {"phone": phone, "smsCode": smsCode});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
getPerson() async {
|
||||||
|
Map res = await service.getClient("/admin/base/comm/person");
|
||||||
|
return res['data'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证码
|
||||||
|
getCaptcha() async {
|
||||||
|
Map res =
|
||||||
|
await service.getClient("/admin/base/open/captcha?height=40&width=150");
|
||||||
|
return res['data'];
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccountLogin(String captchaId, String password, String username,
|
||||||
|
String verifyCode) async {
|
||||||
|
Map res = await service.postClient("/admin/base/open/login", {
|
||||||
|
"captchaId": captchaId,
|
||||||
|
"password": password,
|
||||||
|
"username": username,
|
||||||
|
"verifyCode": verifyCode
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PileCmController extends GetxController {
|
||||||
|
//获取水泥搅拌桩点数据
|
||||||
|
getRcordData(int page, int size, String date,
|
||||||
|
[String sort = "desc", String order = "pile_id"]) async {
|
||||||
|
Map res = await service.getClient(
|
||||||
|
"/api/$projType/record/page?page=$page&size=$size&org_code=a&proj_code=$projCode&tid=$tid&date=$date&sort=$sort&order=$order");
|
||||||
|
return res['data'];
|
||||||
|
}
|
||||||
|
|
||||||
|
getRcordList(String date, String? dateEnd) async {
|
||||||
|
dateEnd ??= date;
|
||||||
|
Map res = await service.getClient(
|
||||||
|
"/api/$projType/record/list?org_code=a&proj_code=$projCode&tid=$tid&date=$date&dateEnd=$dateEnd");
|
||||||
|
return res['data'];
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取施工记录的日期
|
||||||
|
getworkDateData() async {
|
||||||
|
Map res = await service.getClient(
|
||||||
|
"/api/$projType/record/work_date?org_code=a&proj_code=$projCode&tid=$tid");
|
||||||
|
if (res['code'] == 1000) {
|
||||||
|
return res['data'] ?? [];
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//施工详细记录
|
||||||
|
getProcessData(int pileId) async {
|
||||||
|
Map res = await service.getClient(
|
||||||
|
"/api/$projType/process/list?pile_id=$pileId&proj_code=$projCode&tid=$tid");
|
||||||
|
return res['data'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetServices {
|
||||||
|
BaseService service = BaseService();
|
||||||
|
// String projCode = 'CJGKJEBYYB';
|
||||||
|
// int tid = 109;
|
||||||
|
// String projType = "hydraulic_tamping";
|
||||||
|
String projCode = 'TEST';
|
||||||
|
int tid = 1000;
|
||||||
|
String projType = "pile_cm";
|
||||||
|
// 道路边线
|
||||||
|
Future getSideLine() async {
|
||||||
|
try {
|
||||||
|
Map res = await service.getClient(
|
||||||
|
'/api/comm/side_line/list?proj_code=$projCode&proj_type=$projType');
|
||||||
|
return res['data'];
|
||||||
|
} catch (e) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设备绑定 --- 获取设备
|
||||||
|
Future getDeviceBind() async {
|
||||||
|
Map res = await service.getClient(
|
||||||
|
'/api/sys/device_bind/list?proj_type=$projType&proj_code=$projCode');
|
||||||
|
return res['data'];
|
||||||
|
}
|
||||||
|
// 项目列表
|
||||||
|
getproject() async {
|
||||||
|
Map res = await service
|
||||||
|
.getClient('/api/sys/project/list?org_code=a&proj_type=$projType');
|
||||||
|
return res['data'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 液压夯
|
||||||
|
getRcordData(int page, int size, String date,
|
||||||
|
[String sort = "desc", String order = "tp_id"]) async {
|
||||||
|
Map res = await service.getClient(
|
||||||
|
"/api/$projType/record/page?page=$page&size=$size&org_code=a&proj_code=$projCode&tid=$tid&date=$date&sort=$sort&order=$order");
|
||||||
|
return res['data'];
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取水泥搅拌桩点数据
|
||||||
|
// getRcordData(int page, int size, String date,
|
||||||
|
// [String sort = "desc", String order = "pile_id"]) async {
|
||||||
|
// Map res = await service.getClient(
|
||||||
|
// "/api/$projType/record/page?page=$page&size=$size&org_code=a&proj_code=$projCode&tid=$tid&date=$date&sort=$sort&order=$order");
|
||||||
|
// return res['data'];
|
||||||
|
// }
|
||||||
|
|
||||||
|
// getRcordList(String date, String? dateEnd) async {
|
||||||
|
// dateEnd ??= date;
|
||||||
|
// Map res = await service.getClient(
|
||||||
|
// "/api/$projType/record/list?org_code=a&proj_code=$projCode&tid=$tid&date=$date&dateEnd=$dateEnd");
|
||||||
|
// return res['data'];
|
||||||
|
// }
|
||||||
|
getRcordList(String date, [String? dateEnd]) async {
|
||||||
|
dateEnd ??= date;
|
||||||
|
Map res = await service.getClient(
|
||||||
|
"/api/$projType/record/list?org_code=a&proj_code=$projCode&tid=$tid&date=$date&dateEnd=$dateEnd");
|
||||||
|
if (res['code'] == 1000) {
|
||||||
|
return res['data'];
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取施工记录的日期
|
||||||
|
getworkDateData() async {
|
||||||
|
Map res = await service.getClient(
|
||||||
|
"/api/$projType/record/work_date?org_code=a&proj_code=$projCode&tid=$tid");
|
||||||
|
if (res['code'] == 1000) {
|
||||||
|
return res['data'] ?? [];
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//施工详细记录
|
||||||
|
getProcessData(int pileId) async {
|
||||||
|
Map res = await service.getClient(
|
||||||
|
"/api/$projType/process/list?pile_id=$pileId&proj_code=$projCode&tid=$tid");
|
||||||
|
return res['data'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证码
|
||||||
|
getsmsCode(String phone) async {
|
||||||
|
Map res =
|
||||||
|
await service.postClient("/admin/base/open/smsCode", {"phone": phone});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 手机号登录
|
||||||
|
phoneLogin(String phone, String smsCode) async {
|
||||||
|
Map res = await service.postClient(
|
||||||
|
"/admin/base/open/phone", {"phone": phone, "smsCode": smsCode});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
getPerson() async {
|
||||||
|
Map res = await service.getClient("/admin/base/comm/person");
|
||||||
|
return res['data'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证码
|
||||||
|
getCaptcha() async {
|
||||||
|
Map res =
|
||||||
|
await service.getClient("/admin/base/open/captcha?height=40&width=150");
|
||||||
|
return res['data'];
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccountLogin(String captchaId, String password, String username,
|
||||||
|
String verifyCode) async {
|
||||||
|
Map res = await service.postClient("/admin/base/open/login", {
|
||||||
|
"captchaId": captchaId,
|
||||||
|
"password": password,
|
||||||
|
"username": username,
|
||||||
|
"verifyCode": verifyCode
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRtuLast() async {
|
||||||
|
Map res = await service.getClient(
|
||||||
|
"/api/t2n/rtu/rtu_last?proj_type=$projType&proj_code=$projCode");
|
||||||
|
return res['data'];
|
||||||
|
}
|
||||||
|
|
||||||
|
getCoordTrans() async {
|
||||||
|
Map res = await service.getClient(
|
||||||
|
"/api/comm/coord_trans/list?proj_type=$projType&proj_code=$projCode");
|
||||||
|
return res['data'];
|
||||||
|
}
|
||||||
|
}
|
75
lib/login_in/user/loginprefs.dart
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:get_storage/get_storage.dart';
|
||||||
|
|
||||||
|
import 'userModel.dart';
|
||||||
|
|
||||||
|
class LoginPrefs extends GetxController {
|
||||||
|
static const String expireStr = "expire"; //用户名
|
||||||
|
static const String tokenStr = "token"; //token
|
||||||
|
static const String phoneStr = "phone";
|
||||||
|
final box = GetStorage(); // 实例化 GetStorage
|
||||||
|
|
||||||
|
Future<String> init() async {
|
||||||
|
await GetStorage.init(); // 初始化 GetStorage
|
||||||
|
return 'ok';
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveExpire(int expire) {
|
||||||
|
box.write(expireStr, expire);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
int getExpire() {
|
||||||
|
return box.read(expireStr) ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveToken(String token) {
|
||||||
|
box.write(tokenStr, token);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
String getToken() {
|
||||||
|
return box.read(tokenStr) ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeExpire() {
|
||||||
|
box.remove(expireStr);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeToken() {
|
||||||
|
box.remove(tokenStr);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void savePhone(String phone) {
|
||||||
|
box.write(phoneStr, phone);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
String getPhone() {
|
||||||
|
return box.read(phoneStr) ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearLogin() {
|
||||||
|
box.erase(); // 清除所有数据
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserController extends GetxController {
|
||||||
|
final box = GetStorage(); // 实例化 GetStorage
|
||||||
|
|
||||||
|
UserModel? getUser() {
|
||||||
|
final userMap = box.read('user');
|
||||||
|
if (userMap != null) {
|
||||||
|
return UserModel.fromJson(userMap);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setUser(Map user) {
|
||||||
|
box.write('user', user);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
53
lib/login_in/user/userModel.dart
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
class UserModel {
|
||||||
|
int id;
|
||||||
|
String updateTime;
|
||||||
|
String departmentId;
|
||||||
|
String name;
|
||||||
|
int passwordV;
|
||||||
|
String nickName;
|
||||||
|
String headImg;
|
||||||
|
String phone;
|
||||||
|
String email;
|
||||||
|
int status;
|
||||||
|
String remark;
|
||||||
|
String orgCode;
|
||||||
|
String title;
|
||||||
|
String titleImg;
|
||||||
|
String username;
|
||||||
|
UserModel({
|
||||||
|
required this.name,
|
||||||
|
required this.departmentId,
|
||||||
|
required this.email,
|
||||||
|
required this.headImg,
|
||||||
|
required this.nickName,
|
||||||
|
required this.phone,
|
||||||
|
required this.title,
|
||||||
|
required this.titleImg,
|
||||||
|
required this.id,
|
||||||
|
required this.updateTime,
|
||||||
|
required this.orgCode,
|
||||||
|
required this.status,
|
||||||
|
required this.passwordV,
|
||||||
|
required this.remark,
|
||||||
|
required this.username,
|
||||||
|
});
|
||||||
|
factory UserModel.fromJson(Map<dynamic, dynamic> data) {
|
||||||
|
return UserModel(
|
||||||
|
name: data['name'],
|
||||||
|
departmentId: data['departmentId'],
|
||||||
|
email: data['email'] ?? "",
|
||||||
|
headImg: data['headImg'] ?? "",
|
||||||
|
nickName: data['nickName'] ?? "",
|
||||||
|
passwordV: data['passwordV'],
|
||||||
|
phone: data['phone'] ?? "",
|
||||||
|
title: data['title'] ?? "",
|
||||||
|
titleImg: data['title_img'] ?? "",
|
||||||
|
id: data['id'],
|
||||||
|
updateTime: data['updateTime'],
|
||||||
|
orgCode: data['org_code'],
|
||||||
|
status: data['status'],
|
||||||
|
remark: data['remark'] ?? "",
|
||||||
|
username: data['username'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
451
lib/main.dart
@ -1,54 +1,445 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:flutter_app_update/flutter_app_update.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
// import 'controller/scence_map.dart';
|
// import 'controller/scence_map.dart';
|
||||||
|
import 'appbar/appbar.dart';
|
||||||
|
import 'login_in/connect/index.dart';
|
||||||
|
import 'login_in/connect/my_routes.dart';
|
||||||
|
import 'login_in/getx/index.dart';
|
||||||
|
import 'login_in/getx/real_data.dart';
|
||||||
|
import 'login_in/user/loginprefs.dart';
|
||||||
import 'screens/aimpoint_page.dart';
|
import 'screens/aimpoint_page.dart';
|
||||||
import 'package:scence_map/scence_map.dart';
|
import 'screens/setting_page.dart';
|
||||||
import 'package:scence_map/controller.dart';
|
|
||||||
|
|
||||||
void main() {
|
Connect connect = Connect();
|
||||||
Get.lazyPut(() => ScenceMapController());
|
void main() async {
|
||||||
runApp(const MyApp());
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); //设置全屏
|
||||||
|
Get.put(RealController());
|
||||||
|
LoginPrefs loginPrefs = LoginPrefs();
|
||||||
|
String value =
|
||||||
|
await loginPrefs.init(); // await 关键字必须用在异步方法中 await等待异步方法执行完毕 异步方法必须用变量接收
|
||||||
|
if ('ok' == value) {
|
||||||
|
connect.init();
|
||||||
|
runApp(const EntryPage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class EntryPage extends StatefulWidget {
|
||||||
const MyApp({super.key});
|
const EntryPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<EntryPage> createState() => _EntryPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EntryPageState extends State<EntryPage> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
// This widget is the root of your application.
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
return ValueListenableBuilder<bool>(
|
||||||
|
valueListenable: MyApp.isDarkMode,
|
||||||
|
builder: (context, isDarkMode, child) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Flutter Demo',
|
debugShowCheckedModeBanner: false,
|
||||||
theme: ThemeData(
|
home: const MyApp(),
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
initialRoute: "home",
|
||||||
useMaterial3: true,
|
onGenerateRoute: onGenerateRoute,
|
||||||
),
|
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||||
home: MyHomePage(title: 'Flutter Demo Home Page'),
|
supportedLocales: const [
|
||||||
|
Locale('en', ''),
|
||||||
|
Locale('zh', ''),
|
||||||
|
Locale('he', ''),
|
||||||
|
Locale('es', ''),
|
||||||
|
Locale('ru', ''),
|
||||||
|
Locale('ko', ''),
|
||||||
|
Locale('hi', ''),
|
||||||
|
],
|
||||||
|
theme: isDarkMode ? ThemeData.dark() : ThemeData.light(),
|
||||||
|
// home: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Text(""),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyHomePage extends StatefulWidget {
|
TextStyle textStyle = const TextStyle(fontSize: 15);
|
||||||
const MyHomePage({super.key, required this.title});
|
|
||||||
|
|
||||||
final String title;
|
|
||||||
|
|
||||||
|
class MyApp extends StatefulWidget {
|
||||||
|
const MyApp({super.key});
|
||||||
|
static ValueNotifier<bool> isDarkMode = ValueNotifier<bool>(false);
|
||||||
@override
|
@override
|
||||||
State<MyHomePage> createState() => _MyHomePageState();
|
State<MyApp> createState() => _MyAppState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MyHomePageState extends State<MyHomePage> {
|
//是否是竖屏,用于修改appbar 的高度
|
||||||
@override
|
late bool isPortrait;
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
class _MyAppState extends State<MyApp> {
|
||||||
// body: CounterPointer(),
|
//当前底部导航栏索引
|
||||||
body: ScenceMapView(
|
var _currentIndex = 1.obs;
|
||||||
children: const [
|
|
||||||
AimPointer(),
|
late double appBarHeight;
|
||||||
],
|
var url = 'http://v5.rdc.pub/apk/';
|
||||||
onUpdate: (Offset offset, double scale, double rotation) {
|
|
||||||
print("onScale: scale: $scale, offset: $offset,rotation: $rotation");
|
///Flutter侧处理升级对话框
|
||||||
|
///[forcedUpgrade] 是否强制升级
|
||||||
|
_showUpdateDialog(bool forcedUpgrade, String version) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: !forcedUpgrade,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return PopScope(
|
||||||
|
child: AlertDialog(
|
||||||
|
title: const Text('发现新版本'),
|
||||||
|
content: Text(
|
||||||
|
version,
|
||||||
|
style: const TextStyle(fontSize: 20),
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
if (!forcedUpgrade)
|
||||||
|
TextButton(
|
||||||
|
child: const Text('稍后升级'),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: const Text('升级'),
|
||||||
|
onPressed: () {
|
||||||
|
_appUpdate();
|
||||||
|
if (!forcedUpgrade) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_appUpdate() {
|
||||||
|
UpdateModel model = UpdateModel(
|
||||||
|
"${url}app-release.apk",
|
||||||
|
"app-release.apk",
|
||||||
|
"ic_launcher",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
AzhonAppUpdate.update(model).then((value) => debugPrint('//////$value'));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool comarableVersion(String version1, String version2) {
|
||||||
|
List<int> v1 = version1.split('.').map(int.parse).toList();
|
||||||
|
List<int> v2 = version2.split('.').map(int.parse).toList();
|
||||||
|
|
||||||
|
bool result = true;
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (v1[i] > v2[i]) {
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
} else if (v1[i] < v2[i]) {
|
||||||
|
result = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
GexRegister().registerDependencies();
|
||||||
|
super.initState();
|
||||||
|
SchedulerBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
// 当前版本
|
||||||
|
// PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||||
|
// String version = packageInfo.version;
|
||||||
|
// String packageName = packageInfo.packageName;
|
||||||
|
// print("---$packageName,$version");
|
||||||
|
// var response =
|
||||||
|
// await http.Client().get(Uri.parse("${url}output-metadata.json"));
|
||||||
|
// Map json = jsonDecode(response.body);
|
||||||
|
|
||||||
|
// String onLineVersion = json["elements"][0]["versionName"] ?? "";
|
||||||
|
// // 获取线上版本
|
||||||
|
// if (comarableVersion(version, onLineVersion) &&
|
||||||
|
// version != onLineVersion) {
|
||||||
|
// //升级弹窗
|
||||||
|
// _showUpdateDialog(false, onLineVersion);
|
||||||
|
// }
|
||||||
|
// AzhonAppUpdate.listener((map) {
|
||||||
|
// debugPrint(map['type']);
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final RealController controller1 = Get.find();
|
||||||
|
final List<Widget> _pages = [
|
||||||
|
ScenceMap(),
|
||||||
|
ScenceMap(),
|
||||||
|
ScenceMap(),
|
||||||
|
ScenceMap(),
|
||||||
|
SettingPortrait()
|
||||||
|
];
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final size = MediaQuery.of(context).size;
|
||||||
|
|
||||||
|
return OrientationBuilder(builder: ((context, orientation) {
|
||||||
|
isPortrait = Orientation.portrait == orientation ? true : false;
|
||||||
|
appBarHeight = Orientation.portrait == orientation ? 56.0 : 34.0;
|
||||||
|
return Scaffold(
|
||||||
|
resizeToAvoidBottomInset: false,
|
||||||
|
drawerEdgeDragWidth: 0.0, // 禁止通过滑动打开drawer
|
||||||
|
endDrawer: _currentIndex.value == 1
|
||||||
|
? Drawer(
|
||||||
|
width: size.width * .8,
|
||||||
|
child: const AimPointer(),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
appBar: PreferredSize(
|
||||||
|
preferredSize: Size.fromHeight(appBarHeight),
|
||||||
|
child: CustomAppBar(appBarHeight: 56, currentIndex: _currentIndex),
|
||||||
|
),
|
||||||
|
body: OrientationBuilder(
|
||||||
|
builder: (context, orientation) {
|
||||||
|
if (!isPortrait) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _pages[_currentIndex.value],
|
||||||
|
),
|
||||||
|
const VerticalDivider(
|
||||||
|
width: 1, // 设置高度为1
|
||||||
|
thickness: 1,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 48,
|
||||||
|
child: SafeArea(
|
||||||
|
child: LayoutBuilder(builder: (context, constraints) {
|
||||||
|
// 根据屏幕宽度判断横屏或竖屏
|
||||||
|
// if (!isPortrait) {
|
||||||
|
// // 横屏布局
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceEvenly,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_currentIndex.value = 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.date_range_rounded),
|
||||||
|
color: _currentIndex.value == 0
|
||||||
|
? const Color.fromARGB(
|
||||||
|
255, 60, 95, 123)
|
||||||
|
: Colors.grey,
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
'实时',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: _currentIndex.value == 0
|
||||||
|
? const Color.fromARGB(
|
||||||
|
255, 60, 95, 123)
|
||||||
|
: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(
|
||||||
|
height: 1, // 设置高度为1
|
||||||
|
thickness: 1,
|
||||||
|
),
|
||||||
|
//
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_currentIndex.value = 1;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.map_outlined),
|
||||||
|
color: _currentIndex.value == 1
|
||||||
|
? const Color.fromARGB(
|
||||||
|
255, 60, 95, 123)
|
||||||
|
: Colors.grey,
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
'桩点',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: _currentIndex.value == 1
|
||||||
|
? const Color.fromARGB(
|
||||||
|
255, 60, 95, 123)
|
||||||
|
: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_currentIndex.value = 2;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon:
|
||||||
|
const Icon(Icons.my_location_sharp),
|
||||||
|
color: _currentIndex.value == 2
|
||||||
|
? const Color.fromARGB(
|
||||||
|
255, 60, 95, 123)
|
||||||
|
: Colors.grey,
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
'任务',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: _currentIndex.value == 2
|
||||||
|
? const Color.fromARGB(
|
||||||
|
255, 60, 95, 123)
|
||||||
|
: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_currentIndex.value = 3;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.table_chart_outlined),
|
||||||
|
color: _currentIndex.value == 3
|
||||||
|
? const Color.fromARGB(
|
||||||
|
255, 60, 95, 123)
|
||||||
|
: Colors.grey,
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
'历史',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: _currentIndex.value == 3
|
||||||
|
? const Color.fromARGB(
|
||||||
|
255, 60, 95, 123)
|
||||||
|
: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_currentIndex.value = 4;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.settings),
|
||||||
|
color: _currentIndex.value == 4
|
||||||
|
? const Color.fromARGB(
|
||||||
|
255, 60, 95, 123)
|
||||||
|
: Colors.grey,
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
'设置',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: _currentIndex.value == 4
|
||||||
|
? const Color.fromARGB(
|
||||||
|
255, 60, 95, 123)
|
||||||
|
: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
//
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
} //
|
||||||
|
|
||||||
|
else {
|
||||||
|
// 竖屏布局,保持原有底部导航栏样式
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 12,
|
||||||
|
child: _pages[_currentIndex.value],
|
||||||
|
),
|
||||||
|
// VerticalDivider(),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 58,
|
||||||
|
child: BottomNavigationBar(
|
||||||
|
type: BottomNavigationBarType.fixed,
|
||||||
|
currentIndex: _currentIndex.value,
|
||||||
|
onTap: (index) {
|
||||||
|
setState(() {
|
||||||
|
_currentIndex.value = index;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
items: const [
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.date_range_rounded),
|
||||||
|
label: "实时"),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.map_outlined),
|
||||||
|
label: "桩点"),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.my_location_sharp),
|
||||||
|
label: "对点"),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.table_chart_outlined),
|
||||||
|
label: "历史"),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.settings), label: "设置"),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import 'package:flutter/widgets.dart';
|
|||||||
import 'package:scence_map/controller.dart';
|
import 'package:scence_map/controller.dart';
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import '../modules/device_type.dart';
|
import '../modules/common/device_type.dart';
|
||||||
|
|
||||||
final ScenceMapController mapcontroller = Get.put(ScenceMapController());
|
final ScenceMapController mapcontroller = Get.put(ScenceMapController());
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ class SightController extends GetxController {
|
|||||||
var sightOffsetInit = const Offset(0, 0).obs;
|
var sightOffsetInit = const Offset(0, 0).obs;
|
||||||
var sightInit = const Offset(0, 0).obs;
|
var sightInit = const Offset(0, 0).obs;
|
||||||
// var lastSightOffset = const Offset(0, 0).obs; // 新增变量,用于存储上一次手指离开时的位置
|
// var lastSightOffset = const Offset(0, 0).obs; // 新增变量,用于存储上一次手指离开时的位置
|
||||||
// var isCardVisible = false.obs; // 控制卡片是否可见
|
var isCardVisible = true.obs; // 控制卡片是否可见
|
||||||
var selectedRecord = Offset.zero.obs;
|
var selectedRecord = Offset.zero.obs;
|
||||||
|
|
||||||
var initDx = 0.0.obs;
|
var initDx = 0.0.obs;
|
||||||
@ -35,8 +35,6 @@ class SightController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final SightController sight = Get.put(SightController());
|
|
||||||
|
|
||||||
class SightView extends GetView<SightController> {
|
class SightView extends GetView<SightController> {
|
||||||
const SightView({super.key});
|
const SightView({super.key});
|
||||||
|
|
||||||
@ -48,7 +46,7 @@ class SightView extends GetView<SightController> {
|
|||||||
final orientation = mediaQueryData.orientation; //获得设备方向
|
final orientation = mediaQueryData.orientation; //获得设备方向
|
||||||
final deviceType = getDeviceType(context);
|
final deviceType = getDeviceType(context);
|
||||||
double rectWidth = size.height / 2;
|
double rectWidth = size.height / 2;
|
||||||
if (!sight.isNomal.value) {
|
if (!controller.isNomal.value) {
|
||||||
rectWidth = size.width;
|
rectWidth = size.width;
|
||||||
} else {
|
} else {
|
||||||
if (orientation == Orientation.landscape) {
|
if (orientation == Orientation.landscape) {
|
||||||
@ -71,7 +69,7 @@ class SightView extends GetView<SightController> {
|
|||||||
CustomPaint(
|
CustomPaint(
|
||||||
//绘制瞄准器
|
//绘制瞄准器
|
||||||
size: Size(rectWidth, rectWidth),
|
size: Size(rectWidth, rectWidth),
|
||||||
painter: DrawCicle(sight, 0, 0, isDarkMode),
|
painter: DrawCicle(controller, 0, 0, isDarkMode),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -195,11 +193,9 @@ class DrawCicle extends CustomPainter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class DevicePointer extends StatelessWidget {
|
class DevicePointer extends StatelessWidget {
|
||||||
final DeviceItemController item;
|
// final DeviceItemController item;
|
||||||
final double maprotation;
|
// DevicePointer(this.item);
|
||||||
final Offset targetPoint;
|
DevicePointer();
|
||||||
|
|
||||||
DevicePointer(this.item, this.maprotation, this.targetPoint);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -207,30 +203,19 @@ class DevicePointer extends StatelessWidget {
|
|||||||
// double carHeight = item.height / controller.pixel2MeterRatio;
|
// double carHeight = item.height / controller.pixel2MeterRatio;
|
||||||
|
|
||||||
return Stack(
|
return Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
Obx(() {
|
SizedBox(
|
||||||
// Offset offset = controller.xy2Screen(
|
width: 60.0, // 设置图像的宽度
|
||||||
// gnsscontroller.pilerCenter.X, gnsscontroller.pilerCenter.Y);
|
height: 60.0, // 设置图像的高度
|
||||||
var dx = item.x - targetPoint.dx;
|
|
||||||
var dy = item.y - targetPoint.dy;
|
|
||||||
var left = dx * cos(maprotation) + dy * sin(maprotation);
|
|
||||||
var top = -dx * sin(maprotation) + dy * cos(maprotation);
|
|
||||||
|
|
||||||
return Positioned(
|
|
||||||
left: left / mapcontroller.pixel2MeterRatio,
|
|
||||||
top: top / mapcontroller.pixel2MeterRatio,
|
|
||||||
child: Transform(
|
|
||||||
transform: Matrix4.identity()..rotateZ(item.rotation.value),
|
|
||||||
alignment: FractionalOffset.center,
|
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
"images/pilerCar.png",
|
"images/navi_pointer.png",
|
||||||
errorBuilder: (context, error, stackTrace) {
|
errorBuilder: (context, error, stackTrace) {
|
||||||
return Text('无法加载图片');
|
return Text('无法加载图片');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
}),
|
|
||||||
// Obx(() {
|
// Obx(() {
|
||||||
// Offset offset = controller.xy2Screen(
|
// Offset offset = controller.xy2Screen(
|
||||||
// gnsscontroller.pilerCenter.X, gnsscontroller.pilerCenter.Y);
|
// gnsscontroller.pilerCenter.X, gnsscontroller.pilerCenter.Y);
|
@ -1,683 +0,0 @@
|
|||||||
// ignore_for_file: non_constant_identifier_names
|
|
||||||
|
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
class CoordGPS {
|
|
||||||
double LAT; //纬度
|
|
||||||
double LNG; //经度
|
|
||||||
double? ALT; //高度
|
|
||||||
CoordGPS({this.ALT, required this.LAT, required this.LNG});
|
|
||||||
}
|
|
||||||
|
|
||||||
class CoordXYH {
|
|
||||||
double X;
|
|
||||||
double Y;
|
|
||||||
double H;
|
|
||||||
CoordXYH({required this.X, required this.Y, required this.H});
|
|
||||||
}
|
|
||||||
|
|
||||||
class CoordBLH {
|
|
||||||
double B;
|
|
||||||
double L;
|
|
||||||
double H;
|
|
||||||
CoordBLH({required this.B, required this.H, required this.L});
|
|
||||||
}
|
|
||||||
|
|
||||||
class CoordXYZ {
|
|
||||||
double X;
|
|
||||||
double Y;
|
|
||||||
double Z;
|
|
||||||
CoordXYZ({required this.X, required this.Y, required this.Z});
|
|
||||||
}
|
|
||||||
|
|
||||||
class PointXY {
|
|
||||||
double X;
|
|
||||||
double Y;
|
|
||||||
PointXY({required this.X, required this.Y});
|
|
||||||
}
|
|
||||||
|
|
||||||
class Region {
|
|
||||||
double X;
|
|
||||||
double Y;
|
|
||||||
double? width;
|
|
||||||
double? heigth;
|
|
||||||
double? D;
|
|
||||||
Region({this.D, required this.X, required this.Y, this.heigth, this.width});
|
|
||||||
}
|
|
||||||
|
|
||||||
class Ellipsoid {
|
|
||||||
late double A;
|
|
||||||
late double F;
|
|
||||||
late double B;
|
|
||||||
late double E12;
|
|
||||||
late double E22;
|
|
||||||
Ellipsoid(String name) {
|
|
||||||
const Map<String, Map<String, double>> datums = {
|
|
||||||
'BJ54': {'a': 6378245, 'f': 1 / 298.3},
|
|
||||||
'XA80': {'a': 6378140, 'f': 1 / 298.257},
|
|
||||||
'WGS84': {'a': 6378137, 'f': 1 / 298.257223563},
|
|
||||||
'WGS2000': {'a': 6378137, 'f': 1 / 298.257222101}
|
|
||||||
};
|
|
||||||
var Datum = datums[name];
|
|
||||||
if (Datum != null) {
|
|
||||||
double a = Datum['a']!;
|
|
||||||
double f = Datum['f']!;
|
|
||||||
double b = a - f * a;
|
|
||||||
double e12 = (a * a - b * b) / (a * a); // 第一偏心率平方
|
|
||||||
double e22 = (a * a - b * b) / (b * b); // 第二偏心率平方
|
|
||||||
A = a;
|
|
||||||
F = f;
|
|
||||||
B = b;
|
|
||||||
E12 = e12;
|
|
||||||
E22 = e22;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TransParm {
|
|
||||||
late double dx;
|
|
||||||
late double dy;
|
|
||||||
late double dz;
|
|
||||||
late double rx;
|
|
||||||
late double ry;
|
|
||||||
late double rz;
|
|
||||||
late double rota;
|
|
||||||
late double k;
|
|
||||||
|
|
||||||
TransParm(TransOptions options) {
|
|
||||||
dx = options.dx ?? 0;
|
|
||||||
dy = options.dy ?? 0;
|
|
||||||
dz = options.dz ?? 0;
|
|
||||||
rx = options.rx ?? 0;
|
|
||||||
ry = options.ry ?? 0;
|
|
||||||
rz = options.rz ?? 0;
|
|
||||||
k = options.k ?? 0;
|
|
||||||
rota = options.rota ?? 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TransOptions {
|
|
||||||
double? dx; //X平移(M)
|
|
||||||
double? dy; //Y平移(M)
|
|
||||||
double? dz; //z平移(M)
|
|
||||||
double? rx; //X旋转(°)
|
|
||||||
double? ry; //Y旋转(°)
|
|
||||||
double? rz; //Z旋转(°)
|
|
||||||
double? rota; //平面旋转,用于4参数
|
|
||||||
double? k; //尺度(ppm)
|
|
||||||
double? L0; //中央子午线(经度)
|
|
||||||
String? srcEllipsoid; //源椭球名称
|
|
||||||
String? dstEllipsoid; //目标椭球名称
|
|
||||||
double? zoneWidth; //带宽度,用于未指定中央子午线时自动计算
|
|
||||||
double? elevation; //投影高程
|
|
||||||
TransOptions({
|
|
||||||
this.L0,
|
|
||||||
this.dstEllipsoid,
|
|
||||||
this.dx,
|
|
||||||
this.dy,
|
|
||||||
this.dz,
|
|
||||||
this.elevation,
|
|
||||||
this.k,
|
|
||||||
this.rota,
|
|
||||||
this.rx,
|
|
||||||
this.ry,
|
|
||||||
this.rz,
|
|
||||||
this.srcEllipsoid,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//主天线
|
|
||||||
class CoordTrans {
|
|
||||||
late double L0; //中央子午线
|
|
||||||
late double zoneWidth; //带宽度
|
|
||||||
late double elevation; //投影高程
|
|
||||||
late double rota; //平面旋转,用于4参数
|
|
||||||
late Ellipsoid srcEllipsoid; //源椭球
|
|
||||||
late Ellipsoid dstEllipsoid; //目标椭球
|
|
||||||
late TransParm transParm; //7参数
|
|
||||||
CoordTrans(TransOptions options) {
|
|
||||||
transParm = TransParm(options);
|
|
||||||
L0 = options.L0 ?? 0;
|
|
||||||
elevation = options.elevation ?? 0;
|
|
||||||
srcEllipsoid = Ellipsoid(options.srcEllipsoid ?? 'WGS84');
|
|
||||||
dstEllipsoid = Ellipsoid(options.dstEllipsoid ?? 'WGS2000');
|
|
||||||
zoneWidth = options.zoneWidth ?? 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
CoordXYH GPS2XY(CoordGPS gps) {
|
|
||||||
return d2p(CoordBLH(H: gps.ALT ?? elevation, B: gps.LAT, L: gps.LNG));
|
|
||||||
}
|
|
||||||
|
|
||||||
//经纬度转平面坐标
|
|
||||||
CoordXYH d2p(CoordBLH blh) {
|
|
||||||
CoordXYZ zj = BLH2XYZ(blh);
|
|
||||||
CoordXYZ xzj = XYZ2XYZ(zj);
|
|
||||||
CoordBLH xdd = XYZ2BLH(xzj);
|
|
||||||
CoordXYH xpm = BL2XY(xdd);
|
|
||||||
return xpm;
|
|
||||||
}
|
|
||||||
|
|
||||||
//平面坐标转大地坐标
|
|
||||||
CoordBLH p2d(CoordXYH xyh) {
|
|
||||||
CoordBLH bl = XY2BL(xyh, true);
|
|
||||||
CoordXYZ zj = BLH2XYZ(bl, true);
|
|
||||||
CoordXYZ xzj = XYZ2XYZ(zj, true);
|
|
||||||
CoordBLH xdd = XYZ2BLH(xzj, true);
|
|
||||||
return xdd;
|
|
||||||
}
|
|
||||||
|
|
||||||
setZoneWidth(width) {
|
|
||||||
if (width != 6 && width != 3) {
|
|
||||||
// print('带宽应当为6或者为3');
|
|
||||||
} else {
|
|
||||||
zoneWidth = width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setL0(l0) {
|
|
||||||
L0 = l0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getZoneNo(double lng) {
|
|
||||||
// 6度带
|
|
||||||
// 这里应为向下取整
|
|
||||||
if (zoneWidth == 6) {
|
|
||||||
return ((lng + 6) / 6).floor();
|
|
||||||
}
|
|
||||||
// 3度带
|
|
||||||
return ((lng + 1.5) / 3).floor();
|
|
||||||
}
|
|
||||||
|
|
||||||
PointXY xy2xyLocal(PointXY source) {
|
|
||||||
double x = source.X;
|
|
||||||
double y = source.Y;
|
|
||||||
double destx = x * transParm.k * cos(rota) -
|
|
||||||
y * transParm.k * sin(rota) +
|
|
||||||
transParm.dx;
|
|
||||||
double desty = x * transParm.k * sin(rota) +
|
|
||||||
y * transParm.k * cos(rota) +
|
|
||||||
transParm.dy;
|
|
||||||
|
|
||||||
return PointXY(X: destx, Y: desty);
|
|
||||||
}
|
|
||||||
|
|
||||||
CoordXYZ BLH2XYZ(CoordBLH pointBLH, [srcDst = false]) {
|
|
||||||
Ellipsoid ellipsoid = srcDst ? srcEllipsoid : dstEllipsoid;
|
|
||||||
double a = ellipsoid.A;
|
|
||||||
double e12 = ellipsoid.E12;
|
|
||||||
|
|
||||||
double radB = pointBLH.B / 180 * pi;
|
|
||||||
double radL = pointBLH.L / 180 * pi;
|
|
||||||
double H = pointBLH.H;
|
|
||||||
|
|
||||||
double N = a / sqrt(1 - e12 * sin(radB) * sin(radB)); // 卯酉圈半径
|
|
||||||
|
|
||||||
double X = (N + H) * cos(radB) * cos(radL);
|
|
||||||
double Y = (N + H) * cos(radB) * sin(radL);
|
|
||||||
double Z = (N * (1 - e12) + H) * sin(radB);
|
|
||||||
|
|
||||||
return CoordXYZ(X: X, Y: Y, Z: Z);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
七参数转换
|
|
||||||
不同椭球参数下, 地心直角坐标系之间转换
|
|
||||||
dX, dY, dZ: 三个坐标方向的平移参数
|
|
||||||
wX, wY, wZ: 三个方向的旋转角参数(单位为弧度)
|
|
||||||
Kppm: 尺度参数, 单位是ppm,如果是以米为单位, 需要在传参前 除以1000000
|
|
||||||
*/
|
|
||||||
CoordXYZ XYZ2XYZ(CoordXYZ xyz, [srcDst = false]) {
|
|
||||||
double X = xyz.X;
|
|
||||||
double Y = xyz.Y;
|
|
||||||
double Z = xyz.Z;
|
|
||||||
double dX = transParm.dx;
|
|
||||||
double dY = transParm.dy;
|
|
||||||
double dZ = transParm.dz;
|
|
||||||
double rX = transParm.rx / 3600 / 180 * pi;
|
|
||||||
double rY = transParm.ry / 3600 / 180 * pi;
|
|
||||||
double rZ = transParm.rz / 3600 / 180 * pi;
|
|
||||||
double Kppm = transParm.k / 1000000;
|
|
||||||
CoordXYZ result = CoordXYZ(X: 0, Y: 0, Z: 0);
|
|
||||||
if (srcDst) {
|
|
||||||
result.X = X - dX - Kppm * X + rY * Z - rZ * Y;
|
|
||||||
result.Y = Y - dY - Kppm * Y - rX * Z + rZ * X;
|
|
||||||
result.Z = Z - dZ - Kppm * Z + rX * Y - rY * X;
|
|
||||||
} else {
|
|
||||||
result.X = X + dX + Kppm * X - rY * Z + rZ * Y;
|
|
||||||
result.Y = Y + dY + Kppm * Y + rX * Z - rZ * X;
|
|
||||||
result.Z = Z + dZ + Kppm * Z - rX * Y + rY * X;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
地心直角坐标系 转换到 地心大地坐标系
|
|
||||||
用直接法2
|
|
||||||
https://wenku.baidu.com/view/30a08f9ddd88d0d233d46a50.html
|
|
||||||
*/
|
|
||||||
CoordBLH XYZ2BLH(CoordXYZ XYZ, [srcDst = false]) {
|
|
||||||
double X = XYZ.X;
|
|
||||||
double Y = XYZ.Y;
|
|
||||||
double Z = XYZ.Z;
|
|
||||||
Ellipsoid ellipsoid = srcDst ? srcEllipsoid : dstEllipsoid;
|
|
||||||
double a = ellipsoid.A;
|
|
||||||
double b = ellipsoid.B;
|
|
||||||
double e12 = ellipsoid.E12;
|
|
||||||
double e22 = ellipsoid.E22;
|
|
||||||
double L = atan(Y / X);
|
|
||||||
// 弧度转角度
|
|
||||||
double degL = L * 180 / pi;
|
|
||||||
// Y值为正, 东半球, 否则西半球
|
|
||||||
if (Y > 0) {
|
|
||||||
while (degL < 0) {
|
|
||||||
degL += 180;
|
|
||||||
}
|
|
||||||
while (degL > 180) {
|
|
||||||
degL -= 180;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
while (degL > 0) {
|
|
||||||
degL -= 180;
|
|
||||||
}
|
|
||||||
while (degL < -180) {
|
|
||||||
degL += 180;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
double tgU = Z / (sqrt(X * X + Y * Y) * sqrt(1 - e12));
|
|
||||||
double U = atan(tgU);
|
|
||||||
|
|
||||||
double tgB = (Z + b * e22 * pow(sin(U), 3)) /
|
|
||||||
(sqrt(X * X + Y * Y) - a * e12 * pow(cos(U), 3));
|
|
||||||
double B = atan(tgB);
|
|
||||||
double degB = B * 180 / pi; // 弧度转角度
|
|
||||||
if (Z > 0) {
|
|
||||||
// Z值为正, 北半球, 否则南半球
|
|
||||||
while (degB < 0) {
|
|
||||||
degB += 90;
|
|
||||||
}
|
|
||||||
while (degB > 90) {
|
|
||||||
degB -= 90;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
while (degB > 0) {
|
|
||||||
degB -= 90;
|
|
||||||
}
|
|
||||||
while (degB < -90) {
|
|
||||||
degB += 90;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (degB < 0) {
|
|
||||||
degB += 360;
|
|
||||||
}
|
|
||||||
while (degB > 360) {
|
|
||||||
degB -= 360;
|
|
||||||
}
|
|
||||||
|
|
||||||
double N = a / sqrt(1 - e12 * sin(B) * sin(B)); // 卯酉圈半径
|
|
||||||
double H = 0;
|
|
||||||
// B接近极区, 在±90°附近
|
|
||||||
if ((degB).abs() > 80) {
|
|
||||||
H = Z / sin(B) - N * (1 - e12);
|
|
||||||
} else {
|
|
||||||
H = sqrt(X * X + Y * Y) / cos(B) - N;
|
|
||||||
}
|
|
||||||
return CoordBLH(B: degB, L: degL, H: H);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
地心大地坐标系 转换到 大地平面坐标系
|
|
||||||
prjHeight: 投影面高程
|
|
||||||
http://www.cnblogs.com/imeiba/p/5696967.html
|
|
||||||
*/
|
|
||||||
CoordXYH BL2XY(CoordBLH BLH,
|
|
||||||
[srcDst = false, offsetY = 500000, offsetX = 0]) {
|
|
||||||
Ellipsoid ellipsoid = srcDst ? srcEllipsoid : dstEllipsoid;
|
|
||||||
double a = ellipsoid.A;
|
|
||||||
double b = ellipsoid.B;
|
|
||||||
double e12 = ellipsoid.E12;
|
|
||||||
double e22 = ellipsoid.E22;
|
|
||||||
if (L0 == 0) {
|
|
||||||
int zoneNo = getZoneNo(BLH.L);
|
|
||||||
L0 = (zoneNo - 0.5) * zoneWidth;
|
|
||||||
}
|
|
||||||
double radL0 = L0 / 180 * pi;
|
|
||||||
|
|
||||||
double radB = BLH.B / 180 * pi;
|
|
||||||
double radL = BLH.L / 180 * pi;
|
|
||||||
|
|
||||||
double N = a / sqrt(1 - e12 * sin(radB) * sin(radB)); // 卯酉圈半径
|
|
||||||
double T = tan(radB) * tan(radB);
|
|
||||||
double C = e22 * cos(radB) * cos(radB);
|
|
||||||
double A = (radL - radL0) * cos(radB);
|
|
||||||
double M = a *
|
|
||||||
((1 - e12 / 4 - 3 * e12 * e12 / 64 - 5 * e12 * e12 * e12 / 256) * radB -
|
|
||||||
(3 * e12 / 8 + 3 * e12 * e12 / 32 + 45 * e12 * e12 * e12 / 1024) *
|
|
||||||
sin(2 * radB) +
|
|
||||||
(15 * e12 * e12 / 256 + 45 * e12 * e12 * e12 / 1024) *
|
|
||||||
sin(4 * radB) -
|
|
||||||
(35 * e12 * e12 * e12 / 3072) * sin(6 * radB));
|
|
||||||
|
|
||||||
//x,y的计算公式见孔祥元等主编武汉大学出版社2002年出版的《控制测量学》的第72页
|
|
||||||
//书的的括号有问题,( 和 [ 应该交换
|
|
||||||
|
|
||||||
double x = M +
|
|
||||||
N *
|
|
||||||
tan(radB) *
|
|
||||||
(A * A / 2 +
|
|
||||||
(5 - T + 9 * C + 4 * C * C) * A * A * A * A / 24 +
|
|
||||||
(61 - 58 * T + T * T + 600 * C - 330 * e22) *
|
|
||||||
A *
|
|
||||||
A *
|
|
||||||
A *
|
|
||||||
A *
|
|
||||||
A *
|
|
||||||
A /
|
|
||||||
720);
|
|
||||||
double y = N *
|
|
||||||
(A +
|
|
||||||
(1 - T + C) * A * A * A / 6 +
|
|
||||||
(5 - 18 * T * T * T + 72 * C - 58 * e22) * A * A * A * A * A / 120);
|
|
||||||
|
|
||||||
x = offsetX + x * (b + elevation) / b;
|
|
||||||
y = offsetY + y * (b + elevation) / b;
|
|
||||||
|
|
||||||
return CoordXYH(
|
|
||||||
X: (x * 1000).round() / 1000, Y: (y * 1000).round() / 1000, H: BLH.H);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
大地平面坐标系 转换到 地心大地坐标系
|
|
||||||
prjHeight: 投影面高程
|
|
||||||
http://www.cnblogs.com/imeiba/p/5696967.html
|
|
||||||
*/
|
|
||||||
CoordBLH XY2BL(CoordXYH xyh,
|
|
||||||
[srcDst = false, offsetY = 500000, offsetX = 0]) {
|
|
||||||
Ellipsoid ellipsoid = srcDst ? srcEllipsoid : dstEllipsoid;
|
|
||||||
double a = ellipsoid.A;
|
|
||||||
double b = ellipsoid.B;
|
|
||||||
double e12 = ellipsoid.E12;
|
|
||||||
double e22 = ellipsoid.E22;
|
|
||||||
|
|
||||||
double e1 = (1 - sqrt(1 - e12)) / (1 + sqrt(1 - e12));
|
|
||||||
double radL0 = L0 / 180 * pi;
|
|
||||||
// 带内大地坐标
|
|
||||||
double Y = xyh.Y % 1000000;
|
|
||||||
double x = (xyh.X - offsetX) * b / (b + elevation);
|
|
||||||
double y = (Y - offsetY) * b / (b + elevation);
|
|
||||||
|
|
||||||
double u = x /
|
|
||||||
(a * (1 - e12 / 4 - 3 * e12 * e12 / 64 - 5 * e12 * e12 * e12 / 256));
|
|
||||||
double fai = u +
|
|
||||||
(3 * e1 / 2 - 27 * e1 * e1 * e1 / 32) * sin(2 * u) +
|
|
||||||
(21 * e1 * e1 / 16 - 55 * e1 * e1 * e1 * e1 / 32) * sin(4 * u) +
|
|
||||||
(151 * e1 * e1 * e1 / 96) * sin(6 * u) +
|
|
||||||
(1097 * e1 * e1 * e1 * e1 / 512) * sin(8 * u);
|
|
||||||
double C = e22 * cos(fai) * cos(fai);
|
|
||||||
double T = tan(fai) * tan(fai);
|
|
||||||
double N = a / sqrt(1 - e12 * sin(fai) * sin(fai));
|
|
||||||
double R = a *
|
|
||||||
(1 - e12) /
|
|
||||||
sqrt((1 - e12 * sin(fai) * sin(fai)) *
|
|
||||||
(1 - e12 * sin(fai) * sin(fai)) *
|
|
||||||
(1 - e12 * sin(fai) * sin(fai)));
|
|
||||||
double D = y / N;
|
|
||||||
|
|
||||||
double L = radL0 +
|
|
||||||
(D -
|
|
||||||
(1 + 2 * T + C) * D * D * D / 6 +
|
|
||||||
(5 - 2 * C + 28 * T - 3 * C * C + 8 * e22 + 24 * T * T) *
|
|
||||||
D *
|
|
||||||
D *
|
|
||||||
D *
|
|
||||||
D *
|
|
||||||
D /
|
|
||||||
120) /
|
|
||||||
cos(fai);
|
|
||||||
double B = fai -
|
|
||||||
(N * tan(fai) / R) *
|
|
||||||
(D * D / 2 -
|
|
||||||
(5 + 3 * T + 10 * C - 4 * C * C - 9 * e22) *
|
|
||||||
D *
|
|
||||||
D *
|
|
||||||
D *
|
|
||||||
D /
|
|
||||||
24 +
|
|
||||||
(61 + 90 * T + 298 * C + 45 * T * T - 256 * e22 - 3 * C * C) *
|
|
||||||
D *
|
|
||||||
D *
|
|
||||||
D *
|
|
||||||
D *
|
|
||||||
D *
|
|
||||||
D /
|
|
||||||
720);
|
|
||||||
|
|
||||||
B = B * 180 / pi;
|
|
||||||
L = L * 180 / pi;
|
|
||||||
return CoordBLH(B: B, L: L, H: xyh.H);
|
|
||||||
}
|
|
||||||
|
|
||||||
//两点之间的距离公式
|
|
||||||
dist(PointXY p1, PointXY p2) {
|
|
||||||
double d = sqrt(pow((p2.X - p1.X), 2) + pow((p2.Y - p1.Y), 2));
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
//求坐标方位角
|
|
||||||
double fwj(PointXY p1, PointXY p2) {
|
|
||||||
double dx = p2.X - p1.X;
|
|
||||||
double dy = p2.Y - p1.Y;
|
|
||||||
return pi - sign(dy) - atan(dx / dy);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算三参数
|
|
||||||
TransOptions calcThree(CoordBLH p1, CoordXYH p2) {
|
|
||||||
CoordXYZ source = BLH2XYZ(p1);
|
|
||||||
CoordBLH bl = XY2BL(p2, true);
|
|
||||||
CoordXYZ dest = BLH2XYZ(bl, true);
|
|
||||||
|
|
||||||
double dX = dest.X - source.X;
|
|
||||||
double dY = dest.Y - source.Y;
|
|
||||||
double dZ = dest.Z - source.Z;
|
|
||||||
return TransOptions(dx: dX, dy: dY, dz: dZ);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TransOptions calcFour(List p1, List p2, int PointCount) {
|
|
||||||
// double rota = 0;
|
|
||||||
// double scale = 0;
|
|
||||||
// double dx = 0;
|
|
||||||
// double dy = 0;
|
|
||||||
// if (PointCount == 2) {
|
|
||||||
// rota = fwj(p2[0] as PointXY, p2[1]) - fwj(p1[0], p1[1]);
|
|
||||||
// scale = dist(p2[0], p2[1]) / dist(p1[0], p1[1]);
|
|
||||||
// dx = p2[0].X - scale * cos(rota) * p1[0].X + scale * sin(rota) * p1[0].Y;
|
|
||||||
// dy = p2[0].Y - scale * sin(rota) * p1[0].X - scale * cos(rota) * p1[0].Y;
|
|
||||||
// } else if (PointCount > 2) {
|
|
||||||
// double u = 1.0, v = 0, Dx = 0.0, Dy = 0.0;
|
|
||||||
// int intCount = PointCount;
|
|
||||||
|
|
||||||
// Matrix4 dx1 = Matrix.zeros(4, 1);
|
|
||||||
// // Matrix4 B1 = Matrix.zeros(2 * intCount, 4);
|
|
||||||
// Matrix4 B1 = Matrix4.zero();
|
|
||||||
// for (int i = 0; i < 2 * intCount; i++) {
|
|
||||||
// for (int j = 0; j < 4; j++) {
|
|
||||||
// B1.setEntry(i, j, 0);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// Matrix4 W1 = Matrix.zeros(2 * intCount, 1);
|
|
||||||
// // Matrix4 BT = Matrix.zeros(4, 2 * intCount);
|
|
||||||
// Matrix4 BT = Matrix4.zero();
|
|
||||||
// for (int i = 0; i < 4; i++) {
|
|
||||||
// for (int j = 0; j < 2 * intCount; j++) {
|
|
||||||
// BT.setEntry(i, j, 0);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// Matrix4 N = Matrix4(
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// );
|
|
||||||
// Matrix4 InvN = Matrix4(
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// );
|
|
||||||
// Matrix4 BTW = Matrix.zeros(4, 1);
|
|
||||||
// for (int i = 0; i < intCount; i++) {
|
|
||||||
// //计算误差方程系数
|
|
||||||
// B1.setEntry(2 * i, 0, 1);
|
|
||||||
// B1.setEntry(2 * i, 1, 0);
|
|
||||||
// B1.setEntry(2 * i, 2, p1[i].X);
|
|
||||||
// B1.setEntry(2 * i, 3, -p1[i].Y);
|
|
||||||
|
|
||||||
// B1.setEntry(2 * i + 1, 0, 0);
|
|
||||||
// B1.setEntry(2 * i + 1, 1, 1);
|
|
||||||
// B1.setEntry(2 * i + 1, 2, p1[i].Y);
|
|
||||||
// B1.setEntry(2 * i + 1, 3, p1[i].X);
|
|
||||||
// // B1[2 * i][0] = 1;
|
|
||||||
// // B1[2 * i][1] = 0;
|
|
||||||
// // B1[2 * i][2] = p1[i].X;
|
|
||||||
// // B1[2 * i][3] = -p1[i].Y;
|
|
||||||
|
|
||||||
// // B1[2 * i + 1][0] = 0;
|
|
||||||
// // B1[2 * i + 1][1] = 1;
|
|
||||||
// // B1[2 * i + 1][2] = p1[i].Y;
|
|
||||||
// // B1[2 * i + 1][3] = p1[i].X;
|
|
||||||
// }
|
|
||||||
// for (int i = 0; i < intCount; i++) {
|
|
||||||
// //计算误差方程系常数
|
|
||||||
// // W1[2 * i][0] = p2[i].X - u * p1[i].X + v * p1[i].Y - Dx;
|
|
||||||
// // W1[2 * i + 1][0] = p2[i].Y - u * p1[i].Y - v * p1[i].X - Dy;
|
|
||||||
// W1.setEntry(2 * i, 0, p2[i].X - u * p1[i].X + v * p1[i].Y - Dx);
|
|
||||||
// W1.setEntry(2 * i + 1, 0, p2[i].Y - u * p1[i].Y - v * p1[i].X - Dy);
|
|
||||||
// }
|
|
||||||
// // 最小二乘求解
|
|
||||||
// Matrix4 b1 = Matrix4.identity();
|
|
||||||
// // 矩阵 B1 的转置
|
|
||||||
// BT = Matrix4.copy(b1);
|
|
||||||
// BT.transpose();
|
|
||||||
// // 矩阵乘法
|
|
||||||
// N = BT * B1;
|
|
||||||
// // 逆矩阵
|
|
||||||
// InvN = N.clone();
|
|
||||||
// InvN.invert();
|
|
||||||
// BTW = BT * (W1);
|
|
||||||
// dx1 = InvN * (BTW);
|
|
||||||
|
|
||||||
// Dx = Dx + dx1[0][0];
|
|
||||||
// Dy = Dy + dx1[1][0];
|
|
||||||
// u = u + dx1[2][0];
|
|
||||||
// v = v + dx1[3][0];
|
|
||||||
// dx = Dx;
|
|
||||||
// dy = Dy;
|
|
||||||
// rota = atan(v / u);
|
|
||||||
// scale = u / cos(rota);
|
|
||||||
// }
|
|
||||||
// return TransOptions(rota: rota, k: scale, dx: dx, dy: dy);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 计算七参数
|
|
||||||
// TransOptions calcBuras(List p1, List p2, pointCount) {
|
|
||||||
// let B1 = Matrix.zeros(pointCount * 3, 7);
|
|
||||||
// let dx1 = Matrix.zeros(7, 1);
|
|
||||||
// let L = Matrix.zeros(pointCount * 3, 1);
|
|
||||||
// let BT = Matrix.zeros(7, pointCount * 3);
|
|
||||||
// let N = Matrix.zeros(7, 7);
|
|
||||||
// let InvN = Matrix.zeros(7, 7);
|
|
||||||
// let BTL = Matrix.zeros(7, 1);
|
|
||||||
// for (int i = 0; i < pointCount * 3; i++) {
|
|
||||||
// if (i % 3 == 0) {
|
|
||||||
// L[i][0] = p2[(i / 3).floor()].X;
|
|
||||||
// } else if (i % 3 == 1) {
|
|
||||||
// L[i][0] = p2[(i / 3).floor()].Y;
|
|
||||||
// } else if (i % 3 == 2) {
|
|
||||||
// L[i][0] = p2[(i / 3).floor()].Z;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// for (int i = 0; i < pointCount * 3; i++) {
|
|
||||||
// if (i % 3 == 0) {
|
|
||||||
// int index = (i / 3) as int;
|
|
||||||
// B1[i][0] = 1;
|
|
||||||
// B1[i][1] = 0;
|
|
||||||
// B1[i][2] = 0;
|
|
||||||
// B1[i][3] = p1[index].X;
|
|
||||||
// B1[i][4] = 0;
|
|
||||||
// B1[i][5] = -p1[index].Z;
|
|
||||||
// B1[i][6] = p1[index].Y;
|
|
||||||
// } else if (i % 3 == 1) {
|
|
||||||
// int index = ((i - 1) / 3) as int;
|
|
||||||
// B1[i][0] = 0;
|
|
||||||
// B1[i][1] = 1;
|
|
||||||
// B1[i][2] = 0;
|
|
||||||
// B1[i][3] = p1[index].Y;
|
|
||||||
// B1[i][4] = p1[index].Z;
|
|
||||||
// B1[i][5] = 0;
|
|
||||||
// B1[i][6] = -p1[index].X;
|
|
||||||
// } else if (i % 3 == 2) {
|
|
||||||
// int index = ((i - 2) / 3) as int;
|
|
||||||
// B1[i][0] = 0;
|
|
||||||
// B1[i][1] = 0;
|
|
||||||
// B1[i][2] = 1;
|
|
||||||
// B1[i][3] = p1[index].Z;
|
|
||||||
// B1[i][4] = -p1[index].Y;
|
|
||||||
// B1[i][5] = p1[index].X;
|
|
||||||
// B1[i][6] = 0;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// Matrix4 b1 = Matrix4.identity();
|
|
||||||
// BT = B1.transpose();
|
|
||||||
// N = BT.mmul(B1);
|
|
||||||
// InvN = inverse(N);
|
|
||||||
// BTL = BT.mmul(L);
|
|
||||||
// dx1 = InvN.mmul(BTL);
|
|
||||||
|
|
||||||
// double dx = dx1[0][0];
|
|
||||||
// double dy = dx1[1][0];
|
|
||||||
// double dz = dx1[2][0];
|
|
||||||
// double scale = dx1[3][0];
|
|
||||||
// double rotax = dx1[4][0] / dx1[3][0];
|
|
||||||
// double rotay = dx1[5][0] / dx1[3][0];
|
|
||||||
// double rotaz = dx1[6][0] / dx1[3][0];
|
|
||||||
|
|
||||||
// return TransOptions(
|
|
||||||
// dx: dx, dy: dy, dz: dz, k: scale, rx: rotax, ry: rotay, rz: rotaz);
|
|
||||||
// }
|
|
||||||
|
|
||||||
double sign(num number) {
|
|
||||||
if (number < 0) {
|
|
||||||
return -1.0;
|
|
||||||
} else if (number > 0) {
|
|
||||||
return 1.0;
|
|
||||||
} else {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +1,15 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:math';
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:scence_map/controller.dart';
|
import 'package:scence_map/controller.dart';
|
||||||
|
import 'package:scence_map/scence_map.dart';
|
||||||
|
|
||||||
import '../modules/device_type.dart';
|
import '../map_pointer/modules/common/device_type.dart';
|
||||||
|
|
||||||
import '../draws/aim_pointer.dart';
|
import '../map_pointer/draws/aim_pointer.dart';
|
||||||
|
|
||||||
final SightController sight = Get.put(SightController());
|
final SightController sight = Get.put(SightController());
|
||||||
|
|
||||||
@ -23,6 +25,10 @@ class AimPointer extends StatefulWidget {
|
|||||||
//类的实例,用于管理 CounterPointer 的状态和生命周期。
|
//类的实例,用于管理 CounterPointer 的状态和生命周期。
|
||||||
|
|
||||||
class _AimPointerState extends State<AimPointer> {
|
class _AimPointerState extends State<AimPointer> {
|
||||||
|
// late final DeviceItemController item;
|
||||||
|
// late final double maprotation;
|
||||||
|
// late final Offset targetPoint;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -41,6 +47,11 @@ class _AimPointerState extends State<AimPointer> {
|
|||||||
bool isPortrait = Orientation.portrait == orientation ? true : false;
|
bool isPortrait = Orientation.portrait == orientation ? true : false;
|
||||||
double rectWidth = size.width;
|
double rectWidth = size.width;
|
||||||
final deviceType = getDeviceType(context);
|
final deviceType = getDeviceType(context);
|
||||||
|
|
||||||
|
// var dx = item.x - targetPoint.dx;
|
||||||
|
// var dy = item.y - targetPoint.dy;
|
||||||
|
// var left = dx * cos(maprotation) + dy * sin(maprotation);
|
||||||
|
// var top = -dx * sin(maprotation) + dy * cos(maprotation);
|
||||||
if (orientation == Orientation.landscape) {
|
if (orientation == Orientation.landscape) {
|
||||||
rectWidth = size.width / 2 - 60;
|
rectWidth = size.width / 2 - 60;
|
||||||
if (deviceType == DeviceType.mobile) {
|
if (deviceType == DeviceType.mobile) {
|
||||||
@ -58,6 +69,10 @@ class _AimPointerState extends State<AimPointer> {
|
|||||||
width: rectWidth,
|
width: rectWidth,
|
||||||
height: rectWidth,
|
height: rectWidth,
|
||||||
child: Card(
|
child: Card(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
side: BorderSide(color: Colors.transparent, width: 0),
|
||||||
|
borderRadius: BorderRadius.circular(4.0), // 可以根据需要调整圆角半径
|
||||||
|
),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
@ -92,7 +107,7 @@ class _AimPointerState extends State<AimPointer> {
|
|||||||
);
|
);
|
||||||
})),
|
})),
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 10,
|
top: 25,
|
||||||
right: 10,
|
right: 10,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -101,6 +116,33 @@ class _AimPointerState extends State<AimPointer> {
|
|||||||
),
|
),
|
||||||
onPressed: () {},
|
onPressed: () {},
|
||||||
)),
|
)),
|
||||||
|
Positioned(
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
child: IconButton(
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
onPressed: () {
|
||||||
|
// 关闭按钮的回调函数
|
||||||
|
sight.isCardVisible.value = false;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
// left: rectWidth / 2 + left / mapcontroller.pixel2MeterRatio,
|
||||||
|
// top: rectWidth / 2 - top / mapcontroller.pixel2MeterRatio,
|
||||||
|
left: 70,
|
||||||
|
top: 70,
|
||||||
|
// child: Obx(() {
|
||||||
|
// return Transform(
|
||||||
|
// // transform: Matrix4.identity()..rotateZ(item.rotation.value),
|
||||||
|
// transform: Matrix4.identity()..rotateZ(0),
|
||||||
|
// alignment: FractionalOffset.center,
|
||||||
|
// child: DevicePointer(item),
|
||||||
|
// );
|
||||||
|
// }),
|
||||||
|
|
||||||
|
child: DevicePointer(),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -125,75 +167,79 @@ class _AimPointerState extends State<AimPointer> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// List<Widget> createDevice(ScenceMapController controller) {
|
class ScenceMap extends StatelessWidget {
|
||||||
// //用于生成设备的视图小部件列表
|
const ScenceMap();
|
||||||
// if (controller.isDrag) {
|
@override
|
||||||
// return [];
|
Widget build(BuildContext context) {
|
||||||
// }
|
// 获取屏幕尺寸
|
||||||
// List<Widget> devices = [];
|
final Size screenSize = MediaQuery.of(context).size;
|
||||||
// controller.deviceList.forEach((key, item) {
|
|
||||||
// devices.add(Obx(() {
|
|
||||||
// Offset offset = controller.xy2Screen(
|
|
||||||
// gnsscontroller.pilerCenter.X, gnsscontroller.pilerCenter.Y);
|
|
||||||
|
|
||||||
// double carWidth = item.width / controller.pixel2MeterRatio;
|
return Container(
|
||||||
// double carHeight = item.height / controller.pixel2MeterRatio;
|
width: screenSize.width,
|
||||||
// print("${offset.dx - carHeight * 2},${offset.dy - carWidth}");
|
height: screenSize.height,
|
||||||
// return Stack(
|
child: ScenceMapView(
|
||||||
// children: [
|
children: const [
|
||||||
// Obx(() {
|
SightGview(),
|
||||||
// // item.update.value;
|
],
|
||||||
// gnsscontroller.locationUpdate.value;
|
onUpdate: (Offset offset, double scale, double rotation) {
|
||||||
|
print("onScale: scale: $scale, offset: $offset,rotation: $rotation");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Offset offset = controller.xy2Screen(
|
// 瞄准器卡片
|
||||||
// gnsscontroller.pilerCenter.X, gnsscontroller.pilerCenter.Y);
|
class SightGview extends StatelessWidget {
|
||||||
// return Positioned(
|
const SightGview();
|
||||||
// left: offset.dx - carHeight * 3.5,
|
|
||||||
// top: offset.dy - carWidth / 2,
|
|
||||||
// // width: ,
|
|
||||||
// height: carWidth,
|
|
||||||
// child: Transform(
|
|
||||||
// transform: Matrix4.identity()
|
|
||||||
// // ..scale(item.scale)
|
|
||||||
|
|
||||||
// ..rotateZ(gnsscontroller.pilerCenter.rotation),
|
@override
|
||||||
// alignment: FractionalOffset.center,
|
Widget build(BuildContext context) {
|
||||||
// // child: Image.network(
|
final Size size = MediaQuery.of(context).size;
|
||||||
// // "http://v5.rdc.pub${item.image[item.status.value]}",
|
MediaQueryData mediaQueryData =
|
||||||
// // errorBuilder: (context, error, stackTrace) {
|
MediaQueryData.fromView(WidgetsBinding.instance.window); //获取当前屏幕信息
|
||||||
// // return Image.asset("")
|
final orientation = mediaQueryData.orientation; //获得设备方向
|
||||||
|
bool isPortrait = Orientation.portrait == orientation ? true : false;
|
||||||
// // child: Image.asset("images/pilerCar.png",
|
double rectWidth = size.width;
|
||||||
// // errorBuilder: (context, error, stackTrace) {
|
final deviceType = getDeviceType(context);
|
||||||
// // return Text('无法加载图片');
|
if (orientation == Orientation.landscape) {
|
||||||
// // }
|
rectWidth = size.width / 2 - 60;
|
||||||
// // // width: item.width,
|
if (deviceType == DeviceType.mobile) {
|
||||||
// // // height: item.height,
|
rectWidth = size.height - 130;
|
||||||
// // )
|
}
|
||||||
|
} else {
|
||||||
// child: Image.asset(
|
rectWidth = size.height / 2;
|
||||||
// "images/pilerCar.png",
|
if (deviceType == DeviceType.mobile) {
|
||||||
// errorBuilder: (context, error, stackTrace) {
|
rectWidth = size.width - 5;
|
||||||
// return Text('无法加载图片');
|
}
|
||||||
// },
|
}
|
||||||
// // width: item.width,
|
return Obx(() => Positioned(
|
||||||
// // height: item.height,
|
left: sight.sightOffset.value.dx,
|
||||||
// ),
|
top: sight.sightOffset.value.dy,
|
||||||
// ));
|
width: rectWidth + 15,
|
||||||
// }),
|
height: rectWidth + 15,
|
||||||
// Positioned(
|
child: GestureDetector(
|
||||||
// left: offset.dx,
|
onScaleStart: (details) {
|
||||||
// top: offset.dy,
|
// 正确计算初始偏移量:当前手指位置与卡片当前位置之间的差值
|
||||||
// child: Transform(
|
sight.sightInit.value =
|
||||||
// transform: Matrix4.identity()
|
details.localFocalPoint - sight.sightOffset.value;
|
||||||
// ..rotateZ(-controller.rotation.value),
|
},
|
||||||
// child: Column(
|
onScaleUpdate: (details) {
|
||||||
// children: [Text("设备:${item.name}")],
|
// 使用初始偏移量来更新卡片的位置
|
||||||
// ),
|
sight.sightOffset.value =
|
||||||
// ))
|
details.localFocalPoint - sight.sightInit.value;
|
||||||
// ],
|
},
|
||||||
// );
|
child: Visibility(
|
||||||
// }));
|
visible: sight.isCardVisible.value, // 控制可见性
|
||||||
// });
|
child: Container(
|
||||||
// return devices;
|
decoration: const BoxDecoration(color: Colors.transparent),
|
||||||
// }
|
child: const Stack(children: [
|
||||||
|
Card(
|
||||||
|
color: Colors.transparent,
|
||||||
|
elevation: 5.0,
|
||||||
|
child: AimPointer(),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
)))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
392
lib/screens/login_page.dart
Normal file
@ -0,0 +1,392 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import '../login_in/user/loginprefs.dart';
|
||||||
|
import '../login_in/service/service.dart';
|
||||||
|
|
||||||
|
class Login extends StatefulWidget {
|
||||||
|
const Login({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Login> createState() => _LoginState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginState extends State<Login> with SingleTickerProviderStateMixin {
|
||||||
|
//with SingleTickerProviderStateMixin 使this 不报错
|
||||||
|
late TabController _tabController;
|
||||||
|
//初始化实例
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_tabController = TabController(length: 2, vsync: this);
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_tabController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('欢迎登录'),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
TabBar(
|
||||||
|
labelColor: Colors.black, // 设置选中标签的字体颜色为黑色
|
||||||
|
unselectedLabelColor: Colors.grey, // 设置未选中标签的字体颜色为灰色
|
||||||
|
labelStyle: const TextStyle(color: Colors.black), // 设置选中标签的字体颜色为黑色
|
||||||
|
unselectedLabelStyle:
|
||||||
|
const TextStyle(color: Colors.grey), // 设置未选中标签的字体颜色为灰色
|
||||||
|
controller: _tabController,
|
||||||
|
tabs: const [
|
||||||
|
Tab(text: '手机号登录', icon: Icon(Icons.phone_android)),
|
||||||
|
Tab(text: '账号登录', icon: Icon(Icons.account_box_outlined)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TabBarView(
|
||||||
|
controller: _tabController,
|
||||||
|
children: const [
|
||||||
|
PhoneLoginPage(),
|
||||||
|
AccountLoginPage(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccountLoginPage extends StatefulWidget {
|
||||||
|
const AccountLoginPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AccountLoginPage> createState() => _AccountLoginPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccountLoginPageState extends State<AccountLoginPage> {
|
||||||
|
//初始化FormState
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
String username = "";
|
||||||
|
String password = "";
|
||||||
|
String verifyCode = "";
|
||||||
|
String svgString = "";
|
||||||
|
String captchaId = "";
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
SchedulerBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
await changeCaptcha();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
changeCaptcha() async {
|
||||||
|
var captcha = await GetServices().getCaptcha();
|
||||||
|
setState(() {
|
||||||
|
svgString = captcha['data'];
|
||||||
|
captchaId = captcha['captchaId'];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showErrorSnackbar(BuildContext context, String text) {
|
||||||
|
final snackBar = SnackBar(
|
||||||
|
content: Text(text),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
);
|
||||||
|
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
LoginPrefs loginPrefs = LoginPrefs();
|
||||||
|
bool _obscureText = false;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// 账号输入框
|
||||||
|
TextFormField(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: '用户名:',
|
||||||
|
),
|
||||||
|
onSaved: (String? value) => username = value!,
|
||||||
|
validator: (String? value) {
|
||||||
|
String str = "";
|
||||||
|
|
||||||
|
RegExp regex = RegExp(r'^[a-zA-Z0-9]+$');
|
||||||
|
if (value!.isEmpty) {
|
||||||
|
str = '用户名不能为空';
|
||||||
|
} else if (!regex.hasMatch(value)) {
|
||||||
|
str = '请输入有效的账号';
|
||||||
|
}
|
||||||
|
|
||||||
|
return str == "" ? null : str;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16.0),
|
||||||
|
|
||||||
|
// 密码输入框
|
||||||
|
TextFormField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: '密码:',
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
_obscureText
|
||||||
|
? Icons.visibility
|
||||||
|
: Icons.visibility_off,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_obscureText = !_obscureText; // 切换密码可见状态
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
obscureText: _obscureText,
|
||||||
|
onSaved: (String? value) => password = value!,
|
||||||
|
validator: (String? value) {
|
||||||
|
String str = "";
|
||||||
|
RegExp regex = RegExp(r'^[a-zA-Z0-9@#$]+$');
|
||||||
|
|
||||||
|
if (value!.isEmpty) {
|
||||||
|
str = '密码不能为空';
|
||||||
|
} else if (!regex.hasMatch(value)) {
|
||||||
|
str = '请输入有效的密码';
|
||||||
|
}
|
||||||
|
|
||||||
|
return str == "" ? null : str;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24.0),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: '验证码:',
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.phone,
|
||||||
|
validator: (String? value) {
|
||||||
|
String str = "";
|
||||||
|
|
||||||
|
if (value!.isEmpty) {
|
||||||
|
str = '验证码不能为空';
|
||||||
|
}
|
||||||
|
|
||||||
|
return str == "" ? null : str;
|
||||||
|
},
|
||||||
|
onSaved: (String? value) => verifyCode = value!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
changeCaptcha();
|
||||||
|
},
|
||||||
|
child: svgString.isNotEmpty
|
||||||
|
? SvgPicture.string(
|
||||||
|
svgString,
|
||||||
|
width: 70, // 设置宽度
|
||||||
|
height: 40, // 设置高度
|
||||||
|
color: Colors.black,
|
||||||
|
)
|
||||||
|
: const Text(""))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
// 登录按钮
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
_formKey.currentState!.save(); //必须
|
||||||
|
var res = await GetServices().getAccountLogin(
|
||||||
|
captchaId, password, username, verifyCode);
|
||||||
|
setState(() {
|
||||||
|
if (res['code'] != 1000) {
|
||||||
|
changeCaptcha();
|
||||||
|
showErrorSnackbar(context, res["message"]);
|
||||||
|
} else {
|
||||||
|
loginPrefs.saveExpire((res["data"]["expire"]));
|
||||||
|
loginPrefs.saveToken((res["data"]["token"]));
|
||||||
|
Navigator.pushNamed(context, 'home'); //跳转至首页
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('登录'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PhoneLoginPage extends StatefulWidget {
|
||||||
|
const PhoneLoginPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PhoneLoginPage> createState() => _PhoneLoginPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PhoneLoginPageState extends State<PhoneLoginPage> {
|
||||||
|
String phoneNumber = "";
|
||||||
|
String smsCode = "";
|
||||||
|
bool issmsCode = false;
|
||||||
|
String smsText = "获取验证码";
|
||||||
|
int _seconds = 60;
|
||||||
|
late Timer _timer;
|
||||||
|
//初始化FormState
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
LoginPrefs loginPrefs = LoginPrefs();
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
phoneNumber = loginPrefs.getPhone();
|
||||||
|
}
|
||||||
|
|
||||||
|
void startTimer() {
|
||||||
|
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||||
|
if (_seconds > 0) {
|
||||||
|
_seconds -= 1;
|
||||||
|
smsText = "重新获取(${_seconds}s)";
|
||||||
|
} else {
|
||||||
|
_timer.cancel(); // 到0时关闭定时器
|
||||||
|
smsText = "获取验证码";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showErrorSnackbar(BuildContext context, String text) {
|
||||||
|
final snackBar = SnackBar(
|
||||||
|
content: Text(text),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
);
|
||||||
|
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
key: _scaffoldKey,
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Form(
|
||||||
|
key: _formKey,
|
||||||
|
child:
|
||||||
|
// 手机号输入框
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
maxLength: 11,
|
||||||
|
initialValue: phoneNumber,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: '手机号',
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.phone,
|
||||||
|
validator: (String? value) {
|
||||||
|
String str = "";
|
||||||
|
final RegExp phoneRegex = RegExp(r'^[1-9]\d{10}$');
|
||||||
|
if (value!.isEmpty) {
|
||||||
|
str = '手机号不能为空';
|
||||||
|
} else if (!phoneRegex.hasMatch(value)) {
|
||||||
|
str = '请输入有效的手机号';
|
||||||
|
}
|
||||||
|
|
||||||
|
return str == "" ? null : str;
|
||||||
|
},
|
||||||
|
onSaved: (String? value) => phoneNumber = value!,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
maxLength: 6,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: '验证码',
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.phone,
|
||||||
|
validator: (String? value) {
|
||||||
|
String str = "";
|
||||||
|
if (value!.isEmpty) {
|
||||||
|
str = '验证码不能为空';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (issmsCode) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return str == "" ? null : str;
|
||||||
|
},
|
||||||
|
onSaved: (val) => smsCode = val!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 获取验证码按钮
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
issmsCode = true;
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
_formKey.currentState!.save();
|
||||||
|
// 收回键盘
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
await GetServices().getsmsCode(phoneNumber);
|
||||||
|
startTimer(); // 开始定时器
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(smsText),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16.0),
|
||||||
|
|
||||||
|
// 登录按钮
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
issmsCode = false;
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
_formKey.currentState!.save();
|
||||||
|
// 表单校验通过,执行提交逻辑...
|
||||||
|
// 收回键盘
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
var res = await GetServices()
|
||||||
|
.phoneLogin(phoneNumber, smsCode);
|
||||||
|
setState(() {
|
||||||
|
if (res['code'] != 1000) {
|
||||||
|
showErrorSnackbar(
|
||||||
|
_scaffoldKey.currentContext!, res["message"]);
|
||||||
|
} else {
|
||||||
|
loginPrefs.savePhone(phoneNumber);
|
||||||
|
loginPrefs.saveExpire(res["data"]["expire"]);
|
||||||
|
loginPrefs.saveToken(res["data"]["token"]);
|
||||||
|
Navigator.pushNamed(
|
||||||
|
_scaffoldKey.currentContext!, 'home'); //跳转至首页
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('登录'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
162
lib/screens/setting_page.dart
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
//字体
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
import '../login_in/connect/bluetooth_page.dart';
|
||||||
|
import '../login_in/connect/config.dart';
|
||||||
|
import '../login_in/connect/connect_type.dart';
|
||||||
|
import '../login_in/getx/blue_tooth.dart';
|
||||||
|
import '../setting/antenna_setting.dart';
|
||||||
|
import '../setting/person_details.dart';
|
||||||
|
import '../setting/wifi_page.dart';
|
||||||
|
import '../setting/xy_change.dart';
|
||||||
|
|
||||||
|
class ListTileButton extends StatelessWidget {
|
||||||
|
final ListItem item;
|
||||||
|
|
||||||
|
const ListTileButton({super.key, required this.item, isInput = false});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => item.onPressed(),
|
||||||
|
child: ListTile(
|
||||||
|
leading: Icon(item.icon),
|
||||||
|
title: Text(item.text),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ListItem {
|
||||||
|
String text;
|
||||||
|
final IconData icon;
|
||||||
|
final Function onPressed;
|
||||||
|
|
||||||
|
ListItem(this.text, this.icon, this.onPressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextStyle textStyle = const TextStyle(fontSize: 15);
|
||||||
|
|
||||||
|
// //是否有获取地址权限
|
||||||
|
// PermissionStatus? _status;
|
||||||
|
|
||||||
|
final BlueToothController blueToothController = Get.put(BlueToothController());
|
||||||
|
String connectedID = "";
|
||||||
|
|
||||||
|
//竖屏
|
||||||
|
class SettingPortrait extends StatefulWidget {
|
||||||
|
const SettingPortrait({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SettingPortrait> createState() => _SettingPortraitState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SettingPortraitState extends State<SettingPortrait> {
|
||||||
|
String userName = "";
|
||||||
|
List<ListItem> items = [];
|
||||||
|
void readID() {
|
||||||
|
setState(() {
|
||||||
|
connectedID = blueToothController.connectedDeviceID.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
items = [
|
||||||
|
ListItem('连接设置', Icons.change_circle_outlined, () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text("选择连接方式"),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
ListTile(
|
||||||
|
title: const Text("蓝牙连接"),
|
||||||
|
onTap: () {
|
||||||
|
// items[0].text = "连接设置(蓝牙)";
|
||||||
|
AppConfig.updateConfig("connectType", "bluetooth");
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const BlueTooth()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: const Text("WiFi连接"),
|
||||||
|
onTap: () {
|
||||||
|
blueToothController.connectedType.value =
|
||||||
|
ConnectType.wifi;
|
||||||
|
AppConfig.updateConfig("connectType", "wifi");
|
||||||
|
// items[0].text = "连接设置(WiFi)";
|
||||||
|
|
||||||
|
// Connectivity()
|
||||||
|
// .checkConnectivity()
|
||||||
|
// .then((connectivityResult) {
|
||||||
|
// if (connectivityResult != ConnectivityResult.wifi) {
|
||||||
|
// OpenSettings.openWIFISetting();
|
||||||
|
// } else {
|
||||||
|
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const WifiPage()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
// ListItem('规划点', Icons.download_outlined, () {
|
||||||
|
// Navigator.push(context,
|
||||||
|
// MaterialPageRoute(builder: (context) => const PlanPoint()));
|
||||||
|
// }),
|
||||||
|
ListItem('坐标转换', Icons.settings, () {
|
||||||
|
Navigator.push(context,
|
||||||
|
MaterialPageRoute(builder: (context) => const XyChangeView()));
|
||||||
|
}),
|
||||||
|
ListItem('个人中心', Icons.person_outline_outlined, () {
|
||||||
|
Navigator.push(context,
|
||||||
|
MaterialPageRoute(builder: (context) => const PersonDetails()));
|
||||||
|
}),
|
||||||
|
ListItem('天线位置设置', Icons.settings_input_antenna, () {
|
||||||
|
Navigator.push(context,
|
||||||
|
MaterialPageRoute(builder: (context) => const AntennaSetting()));
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
SchedulerBinding.instance.addPostFrameCallback((_) async {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: items.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Obx(() {
|
||||||
|
blueToothController.connectedType.value;
|
||||||
|
return SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: ListTileButton(
|
||||||
|
item: items[index],
|
||||||
|
));
|
||||||
|
}),
|
||||||
|
const Divider(), // 在每个列表项下方添加一条线
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
290
lib/setting/antenna_setting.dart
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
import '../login_in/connect/blue_tooth.dart';
|
||||||
|
import '../login_in/connect/index.dart';
|
||||||
|
import '../login_in/getx/blue_tooth.dart';
|
||||||
|
|
||||||
|
final BlueToothController blueToothController = Get.put(BlueToothController());
|
||||||
|
BlueSetting blueSetting = BlueSetting();
|
||||||
|
|
||||||
|
class AntennaSetting extends GetView<AntennaController> {
|
||||||
|
const AntennaSetting({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final size = MediaQuery.of(context).size;
|
||||||
|
final isPortrait =
|
||||||
|
MediaQuery.of(context).orientation == Orientation.portrait;
|
||||||
|
|
||||||
|
// 获取当前的brightness信息
|
||||||
|
Brightness currentBrightness = Theme.of(context).brightness;
|
||||||
|
bool isDarkMode = currentBrightness == Brightness.dark;
|
||||||
|
Widget simpleDevice = Card(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
child: CustomPaint(
|
||||||
|
size: isPortrait
|
||||||
|
? Size(size.width, size.height / 2)
|
||||||
|
: Size(size.width / 2, size.height - 80),
|
||||||
|
painter: SimpleDevice(controller, isDarkMode),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
List<Widget> inputList = [
|
||||||
|
// const Text("中心点位置(m):"),
|
||||||
|
// Row(
|
||||||
|
// children: [
|
||||||
|
// Expanded(
|
||||||
|
// child: TextFormField(
|
||||||
|
// enabled: false, // 设置为false以禁止输入
|
||||||
|
// initialValue:
|
||||||
|
// controller.centerPoint.value.dx.toStringAsFixed(3),
|
||||||
|
// keyboardType: TextInputType.number,
|
||||||
|
// decoration: const InputDecoration(prefixText: 'x:'),
|
||||||
|
// validator: (value) {
|
||||||
|
// if (value == null || value.isEmpty) {
|
||||||
|
// return '请输入';
|
||||||
|
// }
|
||||||
|
// return null;
|
||||||
|
// },
|
||||||
|
// onChanged: (String value) {
|
||||||
|
// if (value != "") {
|
||||||
|
// double point = double.parse(value);
|
||||||
|
// if (point != 0) {
|
||||||
|
// controller.centerPoint.value =
|
||||||
|
// Offset(point, controller.centerPoint.value.dy);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }),
|
||||||
|
// ),
|
||||||
|
// const SizedBox(
|
||||||
|
// width: 5,
|
||||||
|
// ),
|
||||||
|
// Expanded(
|
||||||
|
// child: TextFormField(
|
||||||
|
// enabled: false, // 设置为false以禁止输入
|
||||||
|
// initialValue:
|
||||||
|
// controller.centerPoint.value.dy.toStringAsFixed(3),
|
||||||
|
// keyboardType: TextInputType.number,
|
||||||
|
// decoration: const InputDecoration(prefixText: 'y:'),
|
||||||
|
// validator: (value) {
|
||||||
|
// if (value == null || value.isEmpty) {
|
||||||
|
// return '请输入';
|
||||||
|
// }
|
||||||
|
// return null;
|
||||||
|
// },
|
||||||
|
// onChanged: (String value) {
|
||||||
|
// if (value != "") {
|
||||||
|
// double point = double.parse(value);
|
||||||
|
// if (point != 0) {
|
||||||
|
// controller.centerPoint.value =
|
||||||
|
// Offset(controller.centerPoint.value.dx, point);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }),
|
||||||
|
// )
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
|
||||||
|
const Text("左侧位置(m):"),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
initialValue: controller.leftPoint.value.dx.toStringAsFixed(3),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
decoration: const InputDecoration(prefixText: 'x:'),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return '请输入值';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onChanged: (String value) {
|
||||||
|
if (value != "") {
|
||||||
|
double point = double.parse(value);
|
||||||
|
if (point != 0) {
|
||||||
|
controller.leftPoint.value =
|
||||||
|
Offset(point, controller.leftPoint.value.dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 5,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
initialValue: controller.leftPoint.value.dy.toStringAsFixed(3),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
decoration: const InputDecoration(prefixText: 'y:'),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return '请输入值';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onChanged: (String value) {
|
||||||
|
if (value != "") {
|
||||||
|
double point = double.parse(value);
|
||||||
|
if (point != 0) {
|
||||||
|
controller.leftPoint.value =
|
||||||
|
Offset(controller.leftPoint.value.dx, point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Text("右侧位置(m):"),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
initialValue: controller.rightPoint.value.dx.toStringAsFixed(3),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
decoration: const InputDecoration(prefixText: 'x:'),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return '请输入值';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onChanged: (String value) {
|
||||||
|
if (value != "") {
|
||||||
|
double point = double.parse(value);
|
||||||
|
if (point != 0) {
|
||||||
|
controller.rightPoint.value =
|
||||||
|
Offset(point, controller.rightPoint.value.dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 5,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
initialValue: controller.rightPoint.value.dy.toStringAsFixed(3),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
decoration: const InputDecoration(prefixText: 'y:'),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return '请输入值';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onChanged: (String value) {
|
||||||
|
if (value != "") {
|
||||||
|
double point = double.parse(value);
|
||||||
|
if (point != 0) {
|
||||||
|
controller.rightPoint.value =
|
||||||
|
Offset(controller.rightPoint.value.dx, point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
Widget button = TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Offset center = controller.leftPoint.value;
|
||||||
|
Offset right =
|
||||||
|
controller.rightPoint.value - controller.leftPoint.value;
|
||||||
|
|
||||||
|
Connect().writeAntenna(
|
||||||
|
Offset(-center.dx, center.dy),
|
||||||
|
Offset(right.dx, -right.dy),
|
||||||
|
blueToothController.connectedDeviceID.value);
|
||||||
|
},
|
||||||
|
child: const Text("位置更新"));
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('天线位置设置'),
|
||||||
|
toolbarHeight: 40,
|
||||||
|
),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: isPortrait
|
||||||
|
? Column(
|
||||||
|
children: [
|
||||||
|
simpleDevice,
|
||||||
|
Card(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [...inputList, button],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
simpleDevice,
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [...inputList, button],
|
||||||
|
),
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 矩形图
|
||||||
|
class SimpleDevice extends CustomPainter {
|
||||||
|
final AntennaController controller;
|
||||||
|
final bool isDarkMode;
|
||||||
|
SimpleDevice(this.controller, this.isDarkMode) : super();
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final Paint paint = Paint()
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 2.0;
|
||||||
|
|
||||||
|
paint.color = isDarkMode ? Colors.white : Colors.black;
|
||||||
|
|
||||||
|
final Path path = Path();
|
||||||
|
double w = size.width;
|
||||||
|
if (size.width > size.height) {
|
||||||
|
w = size.height;
|
||||||
|
}
|
||||||
|
double unit = w * .1;
|
||||||
|
double width = w * .2;
|
||||||
|
double height = w * .8;
|
||||||
|
double left = (size.width - width) / 2;
|
||||||
|
double top = (size.height - height) / 2;
|
||||||
|
canvas.translate(left, top);
|
||||||
|
canvas.drawRect(Rect.fromLTWH(0, 0, width, height), paint);
|
||||||
|
Offset center = Offset(controller.centerPoint.value.dx * unit,
|
||||||
|
controller.centerPoint.value.dy * unit);
|
||||||
|
double radius = 10;
|
||||||
|
canvas.drawCircle(center, radius, paint);
|
||||||
|
canvas.translate(center.dx, center.dy);
|
||||||
|
|
||||||
|
Offset leftPoint = Offset(controller.leftPoint.value.dx * unit,
|
||||||
|
controller.leftPoint.value.dy * unit);
|
||||||
|
Offset rightPoint = Offset(controller.rightPoint.value.dx * unit,
|
||||||
|
controller.rightPoint.value.dy * unit);
|
||||||
|
canvas.drawLine(const Offset(10, 0), rightPoint, paint);
|
||||||
|
canvas.drawLine(const Offset(-10, 0), leftPoint, paint);
|
||||||
|
canvas.drawCircle(rightPoint, 10, paint);
|
||||||
|
canvas.drawCircle(leftPoint, 10, paint);
|
||||||
|
canvas.translate(-center.dx, -center.dy);
|
||||||
|
canvas.drawPath(path, paint);
|
||||||
|
canvas.translate(-left, -top);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AntennaController extends GetxController {
|
||||||
|
var leftPoint = const Offset(-1, 3).obs;
|
||||||
|
var rightPoint = const Offset(1, 3).obs;
|
||||||
|
var centerPoint = const Offset(1.0, 0.5).obs;
|
||||||
|
}
|
79
lib/setting/person_details.dart
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
|
||||||
|
import '../login_in/service/service.dart';
|
||||||
|
import '../login_in/user/loginprefs.dart';
|
||||||
|
import '../login_in/user/userModel.dart';
|
||||||
|
import '../screens/setting_page.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PersonDetails extends StatefulWidget {
|
||||||
|
const PersonDetails({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PersonDetails> createState() => _PersonDetailsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PersonDetailsState extends State<PersonDetails> {
|
||||||
|
String userName = '';
|
||||||
|
List<ListItem> items = [];
|
||||||
|
late UserModel user;
|
||||||
|
LoginPrefs loginPrefs = LoginPrefs();
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
SchedulerBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
UserModel? getUser = UserController().getUser();
|
||||||
|
if (getUser == null) {
|
||||||
|
var person = await GetServices().getPerson();
|
||||||
|
UserController().setUser(person);
|
||||||
|
user = UserModel.fromJson(person);
|
||||||
|
} else {}
|
||||||
|
setState(() {
|
||||||
|
user = UserController().getUser()!;
|
||||||
|
});
|
||||||
|
emptyFunc() => {};
|
||||||
|
items = [
|
||||||
|
// '用户名'
|
||||||
|
ListItem(user.username, Icons.home, emptyFunc),
|
||||||
|
// "姓名"
|
||||||
|
ListItem(user.name, Icons.person, emptyFunc),
|
||||||
|
// "昵称"
|
||||||
|
ListItem(user.nickName, Icons.person_add_rounded, emptyFunc),
|
||||||
|
// "手机号"
|
||||||
|
ListItem(user.phone, Icons.phone_android, emptyFunc),
|
||||||
|
// "邮箱"
|
||||||
|
ListItem(user.email, Icons.email, emptyFunc),
|
||||||
|
// "备注"
|
||||||
|
ListItem(user.remark, Icons.info_outline, emptyFunc),
|
||||||
|
ListItem('退出登录', Icons.logout, () {
|
||||||
|
loginPrefs.clearLogin();
|
||||||
|
Navigator.pushNamed(context, 'login');
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text("个人中心")),
|
||||||
|
body: ListView.builder(
|
||||||
|
itemCount: items.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: ListTileButton(
|
||||||
|
item: items[index],
|
||||||
|
)),
|
||||||
|
|
||||||
|
const Divider(), // 在每个列表项下方添加一条线
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
45
lib/setting/plan_point.dart
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../screens/setting_page.dart';
|
||||||
|
|
||||||
|
class PlanPoint extends StatefulWidget {
|
||||||
|
const PlanPoint({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PlanPoint> createState() => _PlanPointState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlanPointState extends State<PlanPoint> {
|
||||||
|
List<ListItem> items = [];
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
items = [
|
||||||
|
ListItem('下载', Icons.download, () {}),
|
||||||
|
ListItem('删除', Icons.delete, () {}),
|
||||||
|
];
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
toolbarHeight: 40,
|
||||||
|
title: const Text("规划点"),
|
||||||
|
),
|
||||||
|
body: ListView.builder(
|
||||||
|
itemCount: items.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
ListTileButton(
|
||||||
|
item: items[index],
|
||||||
|
),
|
||||||
|
const Divider(), // 在每个列表项下方添加一条线
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
252
lib/setting/wifi_page.dart
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
|
||||||
|
import 'package:wifi_iot/wifi_iot.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:wifi_scan/wifi_scan.dart';
|
||||||
|
|
||||||
|
import '../login_in/connect/wifi.dart';
|
||||||
|
|
||||||
|
SocketSetting socket = SocketSetting();
|
||||||
|
|
||||||
|
class WifiPage extends StatefulWidget {
|
||||||
|
const WifiPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<WifiPage> createState() => _WifiPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WifiPageState extends State<WifiPage> {
|
||||||
|
String ip = "192.168.4.1";
|
||||||
|
int port = 6000;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
SchedulerBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
await _getScannedResults(context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
reconnectToWifi(currentTitle, password, withInternet) async {
|
||||||
|
String ssid = currentTitle;
|
||||||
|
bool isConnected = await WiFiForIoTPlugin.isConnected();
|
||||||
|
if (isConnected) {
|
||||||
|
// 断开当前WiFi连接
|
||||||
|
await WiFiForIoTPlugin.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接到指定WiFi网络
|
||||||
|
await WiFiForIoTPlugin.connect(ssid,
|
||||||
|
password: password,
|
||||||
|
security: NetworkSecurity.WPA,
|
||||||
|
withInternet: withInternet);
|
||||||
|
|
||||||
|
// 检查连接状态
|
||||||
|
isConnected = await WiFiForIoTPlugin.isConnected();
|
||||||
|
if (isConnected) {
|
||||||
|
connectedssid = ssid;
|
||||||
|
|
||||||
|
setState(() {});
|
||||||
|
print("Successfully reconnected to WiFi network: $ssid");
|
||||||
|
} else {
|
||||||
|
print("Failed to reconnect to WiFi network: $ssid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<WiFiAccessPoint> accessPoints = <WiFiAccessPoint>[];
|
||||||
|
StreamSubscription<List<WiFiAccessPoint>>? subscription;
|
||||||
|
bool shouldCheckCan = true;
|
||||||
|
Future<bool> _canGetScannedResults(BuildContext context) async {
|
||||||
|
if (shouldCheckCan) {
|
||||||
|
// check if can-getScannedResults
|
||||||
|
final can = await WiFiScan.instance.canGetScannedResults();
|
||||||
|
// if can-not, then show error
|
||||||
|
if (can != CanGetScannedResults.yes) {
|
||||||
|
if (mounted) kShowSnackBar(context, "Cannot get scanned results: $can");
|
||||||
|
accessPoints = <WiFiAccessPoint>[];
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _getScannedResults(BuildContext context) async {
|
||||||
|
if (await _canGetScannedResults(context)) {
|
||||||
|
// get scanned results
|
||||||
|
final results = await WiFiScan.instance.getScannedResults();
|
||||||
|
setState(() => accessPoints = results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool withInternet = false;
|
||||||
|
String password = "01010101";
|
||||||
|
String currentTitle = "";
|
||||||
|
String connectedssid = "";
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final size = MediaQuery.of(context).size;
|
||||||
|
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
TextEditingController passWordController =
|
||||||
|
TextEditingController(text: password);
|
||||||
|
|
||||||
|
List<Widget> children = [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Text("是否允许上网:"),
|
||||||
|
Switch(
|
||||||
|
value: withInternet,
|
||||||
|
onChanged: ((value) {
|
||||||
|
setState(() {
|
||||||
|
withInternet = value;
|
||||||
|
});
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
TextFormField(
|
||||||
|
controller: passWordController,
|
||||||
|
obscureText: true,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
prefixText: '密码:',
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
onChanged: (String value) {
|
||||||
|
password = value;
|
||||||
|
},
|
||||||
|
)
|
||||||
|
];
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
toolbarHeight: 40,
|
||||||
|
title: const Text('WiFi列表'),
|
||||||
|
actions: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () async => _getScannedResults(context),
|
||||||
|
icon: const Icon(Icons.refresh)),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Builder(
|
||||||
|
builder: (context) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 20),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Divider(),
|
||||||
|
Flexible(
|
||||||
|
child: Center(
|
||||||
|
child: accessPoints.isEmpty
|
||||||
|
? const Text("空")
|
||||||
|
: ListView.builder(
|
||||||
|
itemCount: accessPoints.length,
|
||||||
|
itemBuilder: (context, i) {
|
||||||
|
var accessPoint = accessPoints[i];
|
||||||
|
var signalIcon = accessPoint.level >= -80
|
||||||
|
? Icons.signal_wifi_4_bar
|
||||||
|
: Icons.signal_wifi_0_bar;
|
||||||
|
int level = accessPoint.level;
|
||||||
|
if (level <= -80) {
|
||||||
|
signalIcon = Icons.signal_wifi_0_bar; // 弱信号
|
||||||
|
} else if (level > -80 && level <= -70) {
|
||||||
|
signalIcon = Icons.network_wifi_2_bar_sharp; // 较弱
|
||||||
|
} else if (level > -70 && level <= -60) {
|
||||||
|
signalIcon = Icons.network_wifi_3_bar_sharp; // 较好
|
||||||
|
} else if (level > -60 && level <= -50) {
|
||||||
|
signalIcon = Icons.network_wifi; // 较强
|
||||||
|
} else {
|
||||||
|
signalIcon = Icons.signal_wifi_4_bar; // 强信号
|
||||||
|
}
|
||||||
|
|
||||||
|
final title = accessPoint.ssid.isNotEmpty
|
||||||
|
? accessPoint.ssid
|
||||||
|
: "";
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
leading: Icon(signalIcon),
|
||||||
|
title: Text(title),
|
||||||
|
subtitle: Text(accessPoint.bssid),
|
||||||
|
trailing: ElevatedButton(
|
||||||
|
child: Text(
|
||||||
|
connectedssid == accessPoint.ssid &&
|
||||||
|
connectedssid != ""
|
||||||
|
? "已连接"
|
||||||
|
: '连接'),
|
||||||
|
onPressed: () {
|
||||||
|
currentTitle = title;
|
||||||
|
Scaffold.of(context).openEndDrawer();
|
||||||
|
}),
|
||||||
|
onTap: () {
|
||||||
|
currentTitle = title;
|
||||||
|
Scaffold.of(context).openEndDrawer();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
endDrawer: Drawer(
|
||||||
|
backgroundColor: isDarkMode ? Colors.black : Colors.white,
|
||||||
|
width: size.width * .8,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Container(
|
||||||
|
height: size.height,
|
||||||
|
padding: const EdgeInsets.fromLTRB(5, 20, 5, 20),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
children: children,
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 20,
|
||||||
|
left: 10,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text("取消")),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
await reconnectToWifi(
|
||||||
|
currentTitle, password, withInternet);
|
||||||
|
},
|
||||||
|
child: const Text("确认"),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
await socket.connect();
|
||||||
|
},
|
||||||
|
child: const Text("连接"),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void kShowSnackBar(BuildContext context, String message) {
|
||||||
|
if (kDebugMode) print(message);
|
||||||
|
ScaffoldMessenger.of(context)
|
||||||
|
..hideCurrentSnackBar()
|
||||||
|
..showSnackBar(SnackBar(content: Text(message)));
|
||||||
|
}
|
253
lib/setting/xy_change.dart
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
import '../login_in/connect/blue_tooth.dart';
|
||||||
|
import '../login_in/connect/index.dart';
|
||||||
|
import '../login_in/getx/blue_tooth.dart';
|
||||||
|
import '../login_in/service/coor_trans.dart';
|
||||||
|
import '../login_in/service/service.dart';
|
||||||
|
import '../map_pointer/modules/common/device_type.dart';
|
||||||
|
|
||||||
|
|
||||||
|
final BlueToothController blueToothController = Get.put(BlueToothController());
|
||||||
|
BlueSetting blueSetting = BlueSetting();
|
||||||
|
|
||||||
|
class XyChangeView extends StatefulWidget {
|
||||||
|
const XyChangeView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<XyChangeView> createState() => _XyChangeViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _XyChangeViewState extends State<XyChangeView> {
|
||||||
|
List<Widget> list = [];
|
||||||
|
CoorTransModel coorTrans = CoorTransModel(
|
||||||
|
name: "",
|
||||||
|
L0: 0,
|
||||||
|
belt: "",
|
||||||
|
dstEllipsoid: "",
|
||||||
|
dx: 0,
|
||||||
|
dy: 0,
|
||||||
|
dz: 0,
|
||||||
|
id: 0,
|
||||||
|
wx: 0,
|
||||||
|
wy: 0,
|
||||||
|
wz: 0,
|
||||||
|
k: 0,
|
||||||
|
elevation: 0,
|
||||||
|
isMain: 0,
|
||||||
|
rota: 0,
|
||||||
|
srcEllipsoid: "");
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
SchedulerBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
List coordTransmap = await GetServices().getCoordTrans();
|
||||||
|
if (coordTransmap.isNotEmpty) {
|
||||||
|
var mianBelt =
|
||||||
|
coordTransmap.firstWhere((element) => element['belt'] == 'main');
|
||||||
|
coorTrans = CoorTransModel.fromJson(mianBelt);
|
||||||
|
setState(() {});
|
||||||
|
} else {
|
||||||
|
print("当前项目没有设置中央子午线");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final deviceType = getDeviceType(context);
|
||||||
|
final size = MediaQuery.of(context).size;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text("坐标转换"),
|
||||||
|
toolbarHeight: 40,
|
||||||
|
),
|
||||||
|
body: Container(
|
||||||
|
margin: const EdgeInsets.all(5),
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: 1,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
TextEditingController l0Controller =
|
||||||
|
TextEditingController(text: coorTrans.L0.toString());
|
||||||
|
TextEditingController elevationController =
|
||||||
|
TextEditingController(text: coorTrans.elevation.toString());
|
||||||
|
TextEditingController dxController =
|
||||||
|
TextEditingController(text: coorTrans.dx.toString());
|
||||||
|
TextEditingController dyController =
|
||||||
|
TextEditingController(text: coorTrans.dy.toString());
|
||||||
|
TextEditingController dzController =
|
||||||
|
TextEditingController(text: coorTrans.dz.toString());
|
||||||
|
TextEditingController wxController =
|
||||||
|
TextEditingController(text: coorTrans.wx.toString());
|
||||||
|
TextEditingController wyController =
|
||||||
|
TextEditingController(text: coorTrans.wy.toString());
|
||||||
|
TextEditingController wzController =
|
||||||
|
TextEditingController(text: coorTrans.wz.toString());
|
||||||
|
TextEditingController kController =
|
||||||
|
TextEditingController(text: coorTrans.k.toString());
|
||||||
|
List<Widget> list1 = [
|
||||||
|
Flexible(
|
||||||
|
child: TextFormField(
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
controller: l0Controller,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
prefixText: '中央子午线:',
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
onChanged: (String value) {
|
||||||
|
// coorTrans.L0 =(value);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
const Divider(),
|
||||||
|
Flexible(
|
||||||
|
child: TextFormField(
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
controller: elevationController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
prefixText: '投影高程:',
|
||||||
|
),
|
||||||
|
onChanged: (String value) {
|
||||||
|
// coorTrans.L0 =(value);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
];
|
||||||
|
List<Widget> list2 = [
|
||||||
|
Flexible(
|
||||||
|
child: TextFormField(
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
controller: dxController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
prefixText: 'x平移(m):',
|
||||||
|
),
|
||||||
|
onChanged: (String value) {
|
||||||
|
// coorTrans.L0 =(value);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
const Divider(),
|
||||||
|
Flexible(
|
||||||
|
child: TextFormField(
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
controller: dyController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
prefixText: 'Y平移(米):',
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
onChanged: (String value) {
|
||||||
|
// coorTrans.L0 =(value);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
const Divider(),
|
||||||
|
Flexible(
|
||||||
|
child: TextFormField(
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
controller: dzController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
prefixText: 'Z平移(米):',
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
onChanged: (String value) {
|
||||||
|
// coorTrans.L0 =(value);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
const Divider(),
|
||||||
|
];
|
||||||
|
List<Widget> list3 = [
|
||||||
|
Flexible(
|
||||||
|
child: TextFormField(
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
controller: wxController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
prefixText: 'X轴旋转(秒):',
|
||||||
|
),
|
||||||
|
onChanged: (String value) {
|
||||||
|
// coorTrans.L0 =(value);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
const Divider(),
|
||||||
|
Flexible(
|
||||||
|
child: TextFormField(
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
controller: wyController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
prefixText: 'Y轴旋转(秒):',
|
||||||
|
),
|
||||||
|
onChanged: (String value) {
|
||||||
|
// coorTrans.L0 =(value);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
const Divider(),
|
||||||
|
Flexible(
|
||||||
|
child: TextFormField(
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
controller: wzController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
prefixText: 'Z轴旋转(秒):',
|
||||||
|
),
|
||||||
|
onChanged: (String value) {
|
||||||
|
// coorTrans.L0 =(value);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
const Divider(),
|
||||||
|
];
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
deviceType == DeviceType.mobile && size.width < 350
|
||||||
|
? Column(
|
||||||
|
children: list1,
|
||||||
|
)
|
||||||
|
: Row(
|
||||||
|
children: list1,
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
deviceType == DeviceType.mobile && size.width < 350
|
||||||
|
? Column(
|
||||||
|
children: list2,
|
||||||
|
)
|
||||||
|
: Row(children: list2),
|
||||||
|
const Divider(),
|
||||||
|
deviceType == DeviceType.mobile && size.width < 350
|
||||||
|
? Column(
|
||||||
|
children: list3,
|
||||||
|
)
|
||||||
|
: Row(
|
||||||
|
children: list3,
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
SizedBox(
|
||||||
|
child: TextFormField(
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
controller: kController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
prefixText: '尺度:',
|
||||||
|
),
|
||||||
|
onChanged: (String value) {
|
||||||
|
// coorTrans.L0 =(value);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
const Divider(),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Connect().writeCoorTransValue(
|
||||||
|
blueToothController.connectedDeviceID.value,
|
||||||
|
coorTrans);
|
||||||
|
},
|
||||||
|
child: const Text("更新设置"))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
@ -7,8 +7,10 @@ import Foundation
|
|||||||
|
|
||||||
import flutter_webrtc
|
import flutter_webrtc
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
|
import quick_blue
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
|
QuickBluePlugin.register(with: registry.registrar(forPlugin: "QuickBluePlugin"))
|
||||||
}
|
}
|
||||||
|
29
plugins/quick_blue/.gitignore
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
|
||||||
|
# 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/
|
||||||
|
.packages
|
||||||
|
build/
|
10
plugins/quick_blue/.metadata
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# 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: 77d935af4db863f6abd0b9c31c7e6df2a13de57b
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
project_type: plugin
|
3
plugins/quick_blue/CHANGELOG.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
## 0.0.1
|
||||||
|
|
||||||
|
* TODO: Describe initial release.
|
1
plugins/quick_blue/LICENSE
Normal file
@ -0,0 +1 @@
|
|||||||
|
TODO: Add your license here.
|
109
plugins/quick_blue/README.md
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
# quick_blue
|
||||||
|
|
||||||
|
A cross-platform (Android/iOS/macOS/Windows/Linux) BluetoothLE plugin for Flutter
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
- [Receive BLE availability changes](#receive-ble-availability-changes)
|
||||||
|
- [Scan BLE peripheral](#scan-ble-peripheral)
|
||||||
|
- [Connect BLE peripheral](#connect-ble-peripheral)
|
||||||
|
- [Discover services of BLE peripheral](#discover-services-of-ble-peripheral)
|
||||||
|
- [Transfer data between BLE central & peripheral](#transfer-data-between-ble-central--peripheral)
|
||||||
|
|
||||||
|
| API | Android | iOS | macOS | Windows | Linux |
|
||||||
|
| :--- | :---: | :---: | :---: | :---: | :---: |
|
||||||
|
| availabilityChangeStream | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
|
| isBluetoothAvailable | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
|
| startScan/stopScan | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
|
| connect/disconnect | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
|
| discoverServices | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
|
| setNotifiable | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
|
| readValue | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
|
| writeValue | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
|
| requestMtu | ✔️ | ✔️ | ✔️ | ✔️ | |
|
||||||
|
|
||||||
|
> * Windows' APIs are little different on `discoverServices`: https://github.com/woodemi/quick_blue/issues/76
|
||||||
|
|
||||||
|
## Receive BLE availability changes
|
||||||
|
|
||||||
|
iOS/macOS
|
||||||
|
```dart
|
||||||
|
QuickBlue.availabilityChangeStream.listen((state) {
|
||||||
|
debugPrint('Bluetooth state: ${state.toString()}');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Scan BLE peripheral
|
||||||
|
|
||||||
|
Android/iOS/macOS/Windows/Linux
|
||||||
|
|
||||||
|
```dart
|
||||||
|
QuickBlue.scanResultStream.listen((result) {
|
||||||
|
print('onScanResult $result');
|
||||||
|
});
|
||||||
|
|
||||||
|
QuickBlue.startScan();
|
||||||
|
// ...
|
||||||
|
QuickBlue.stopScan();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Connect BLE peripheral
|
||||||
|
|
||||||
|
Connect to `deviceId`, received from `QuickBlue.scanResultStream`
|
||||||
|
|
||||||
|
```dart
|
||||||
|
QuickBlue.setConnectionHandler(_handleConnectionChange);
|
||||||
|
|
||||||
|
void _handleConnectionChange(String deviceId, BlueConnectionState state) {
|
||||||
|
print('_handleConnectionChange $deviceId, $state');
|
||||||
|
}
|
||||||
|
|
||||||
|
QuickBlue.connect(deviceId);
|
||||||
|
// ...
|
||||||
|
QuickBlue.disconnect(deviceId);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Discover services of BLE peripheral
|
||||||
|
|
||||||
|
Discover services od `deviceId`
|
||||||
|
|
||||||
|
```dart
|
||||||
|
QuickBlue.setServiceHandler(_handleServiceDiscovery);
|
||||||
|
|
||||||
|
void _handleServiceDiscovery(String deviceId, String serviceId) {
|
||||||
|
print('_handleServiceDiscovery $deviceId, $serviceId');
|
||||||
|
}
|
||||||
|
|
||||||
|
QuickBlue.discoverServices(deviceId);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Transfer data between BLE central & peripheral
|
||||||
|
|
||||||
|
- Pull data from peripheral of `deviceId`
|
||||||
|
|
||||||
|
> Data would receive within value handler of `QuickBlue.setValueHandler`
|
||||||
|
> Because it is how [peripheral(_:didUpdateValueFor:error:)](https://developer.apple.com/documentation/corebluetooth/cbperipheraldelegate/1518708-peripheral) work on iOS/macOS
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Data would receive from value handler of `QuickBlue.setValueHandler`
|
||||||
|
QuickBlue.readValue(deviceId, serviceId, characteristicId);
|
||||||
|
```
|
||||||
|
|
||||||
|
- Send data to peripheral of `deviceId`
|
||||||
|
|
||||||
|
```dart
|
||||||
|
QuickBlue.writeValue(deviceId, serviceId, characteristicId, value);
|
||||||
|
```
|
||||||
|
|
||||||
|
- Receive data from peripheral of `deviceId`
|
||||||
|
|
||||||
|
```dart
|
||||||
|
QuickBlue.setValueHandler(_handleValueChange);
|
||||||
|
|
||||||
|
void _handleValueChange(String deviceId, String characteristicId, Uint8List value) {
|
||||||
|
print('_handleValueChange $deviceId, $characteristicId, ${hex.encode(value)}');
|
||||||
|
}
|
||||||
|
|
||||||
|
QuickBlue.setNotifiable(deviceId, serviceId, characteristicId, true);
|
||||||
|
```
|
4
plugins/quick_blue/analysis_options.yaml
Normal file
@ -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
|
8
plugins/quick_blue/android/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
*.iml
|
||||||
|
.gradle
|
||||||
|
/local.properties
|
||||||
|
/.idea/workspace.xml
|
||||||
|
/.idea/libraries
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
50
plugins/quick_blue/android/build.gradle
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
group 'com.example.quick_blue'
|
||||||
|
version '1.0-SNAPSHOT'
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.5.30'
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 30
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 21
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
}
|
1
plugins/quick_blue/android/settings.gradle
Normal file
@ -0,0 +1 @@
|
|||||||
|
rootProject.name = 'quick_blue'
|
12
plugins/quick_blue/android/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.quick_blue">
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||||
|
|
||||||
|
<!-- legacy for Android 11 or lower -->
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<!-- legacy for Android 9 or lower -->
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
</manifest>
|
@ -0,0 +1,349 @@
|
|||||||
|
package com.example.quick_blue
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.bluetooth.*
|
||||||
|
import android.bluetooth.le.ScanCallback
|
||||||
|
import android.bluetooth.le.ScanResult
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.annotation.NonNull
|
||||||
|
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||||
|
import io.flutter.plugin.common.*
|
||||||
|
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||||
|
import io.flutter.plugin.common.MethodChannel.Result
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.ByteOrder
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
private const val TAG = "QuickBluePlugin"
|
||||||
|
|
||||||
|
/** QuickBluePlugin */
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
class QuickBluePlugin: FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler {
|
||||||
|
/// 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 method : MethodChannel
|
||||||
|
private lateinit var eventAvailabilityChange : EventChannel
|
||||||
|
private lateinit var eventScanResult : EventChannel
|
||||||
|
private lateinit var messageConnector: BasicMessageChannel<Any>
|
||||||
|
|
||||||
|
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
|
method = MethodChannel(flutterPluginBinding.binaryMessenger, "quick_blue/method")
|
||||||
|
eventAvailabilityChange = EventChannel(flutterPluginBinding.binaryMessenger, "quick_blue/event.availabilityChange")
|
||||||
|
eventScanResult = EventChannel(flutterPluginBinding.binaryMessenger, "quick_blue/event.scanResult")
|
||||||
|
messageConnector = BasicMessageChannel(flutterPluginBinding.binaryMessenger, "quick_blue/message.connector", StandardMessageCodec.INSTANCE)
|
||||||
|
method.setMethodCallHandler(this)
|
||||||
|
eventAvailabilityChange.setStreamHandler(this)
|
||||||
|
eventScanResult.setStreamHandler(this)
|
||||||
|
|
||||||
|
context = flutterPluginBinding.applicationContext
|
||||||
|
mainThreadHandler = Handler(Looper.getMainLooper())
|
||||||
|
bluetoothManager = flutterPluginBinding.applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
||||||
|
|
||||||
|
context.registerReceiver(
|
||||||
|
broadcastReceiver,
|
||||||
|
IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
|
bluetoothManager.adapter.bluetoothLeScanner?.stopScan(scanCallback)
|
||||||
|
|
||||||
|
context.unregisterReceiver(broadcastReceiver)
|
||||||
|
eventAvailabilityChange.setStreamHandler(null)
|
||||||
|
eventScanResult.setStreamHandler(null)
|
||||||
|
method.setMethodCallHandler(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var context: Context
|
||||||
|
private lateinit var mainThreadHandler: Handler
|
||||||
|
private lateinit var bluetoothManager: BluetoothManager
|
||||||
|
|
||||||
|
private val knownGatts = mutableListOf<BluetoothGatt>()
|
||||||
|
|
||||||
|
private fun sendMessage(messageChannel: BasicMessageChannel<Any>, message: Map<String, Any>) {
|
||||||
|
mainThreadHandler.post { messageChannel.send(message) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
|
||||||
|
when (call.method) {
|
||||||
|
"isBluetoothAvailable" -> {
|
||||||
|
result.success(bluetoothManager.adapter.isEnabled)
|
||||||
|
}
|
||||||
|
"startScan" -> {
|
||||||
|
bluetoothManager.adapter.bluetoothLeScanner?.startScan(scanCallback)
|
||||||
|
result.success(null)
|
||||||
|
}
|
||||||
|
"stopScan" -> {
|
||||||
|
bluetoothManager.adapter.bluetoothLeScanner?.stopScan(scanCallback)
|
||||||
|
result.success(null)
|
||||||
|
}
|
||||||
|
"connect" -> {
|
||||||
|
val deviceId = call.argument<String>("deviceId")!!
|
||||||
|
if (knownGatts.find { it.device.address == deviceId } != null) {
|
||||||
|
return result.success(null)
|
||||||
|
}
|
||||||
|
val remoteDevice = bluetoothManager.adapter.getRemoteDevice(deviceId)
|
||||||
|
val gatt = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
remoteDevice.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
|
||||||
|
} else {
|
||||||
|
remoteDevice.connectGatt(context, false, gattCallback)
|
||||||
|
}
|
||||||
|
knownGatts.add(gatt)
|
||||||
|
result.success(null)
|
||||||
|
// TODO connecting
|
||||||
|
}
|
||||||
|
"disconnect" -> {
|
||||||
|
val deviceId = call.argument<String>("deviceId")!!
|
||||||
|
val gatt = knownGatts.find { it.device.address == deviceId }
|
||||||
|
?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", null)
|
||||||
|
cleanConnection(gatt)
|
||||||
|
result.success(null)
|
||||||
|
//FIXME If `disconnect` is called before BluetoothGatt.STATE_CONNECTED
|
||||||
|
// there will be no `disconnected` message any more
|
||||||
|
}
|
||||||
|
"discoverServices" -> {
|
||||||
|
val deviceId = call.argument<String>("deviceId")!!
|
||||||
|
val gatt = knownGatts.find { it.device.address == deviceId }
|
||||||
|
?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", null)
|
||||||
|
gatt.discoverServices()
|
||||||
|
result.success(null)
|
||||||
|
}
|
||||||
|
"setNotifiable" -> {
|
||||||
|
val deviceId = call.argument<String>("deviceId")!!
|
||||||
|
val service = call.argument<String>("service")!!
|
||||||
|
val characteristic = call.argument<String>("characteristic")!!
|
||||||
|
val bleInputProperty = call.argument<String>("bleInputProperty")!!
|
||||||
|
val gatt = knownGatts.find { it.device.address == deviceId }
|
||||||
|
?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", null)
|
||||||
|
val c = gatt.getCharacteristic(service, characteristic)
|
||||||
|
?: return result.error("IllegalArgument", "Unknown characteristic: $characteristic", null)
|
||||||
|
gatt.setNotifiable(c, bleInputProperty)
|
||||||
|
result.success(null)
|
||||||
|
}
|
||||||
|
"readValue" -> {
|
||||||
|
val deviceId = call.argument<String>("deviceId")!!
|
||||||
|
val service = call.argument<String>("service")!!
|
||||||
|
val characteristic = call.argument<String>("characteristic")!!
|
||||||
|
val gatt = knownGatts.find { it.device.address == deviceId }
|
||||||
|
?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", null)
|
||||||
|
val c = gatt.getCharacteristic(service, characteristic)
|
||||||
|
?: return result.error("IllegalArgument", "Unknown characteristic: $characteristic", null)
|
||||||
|
if (gatt.readCharacteristic(c))
|
||||||
|
result.success(null)
|
||||||
|
else
|
||||||
|
result.error("Characteristic unavailable", null, null)
|
||||||
|
}
|
||||||
|
"writeValue" -> {
|
||||||
|
val deviceId = call.argument<String>("deviceId")!!
|
||||||
|
val service = call.argument<String>("service")!!
|
||||||
|
val characteristic = call.argument<String>("characteristic")!!
|
||||||
|
val value = call.argument<ByteArray>("value")!!
|
||||||
|
val gatt = knownGatts.find { it.device.address == deviceId }
|
||||||
|
?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", null)
|
||||||
|
val c = gatt.getCharacteristic(service, characteristic)
|
||||||
|
?: return result.error("IllegalArgument", "Unknown characteristic: $characteristic", null)
|
||||||
|
c.value = value
|
||||||
|
if (gatt.writeCharacteristic(c))
|
||||||
|
result.success(null)
|
||||||
|
else
|
||||||
|
result.error("Characteristic unavailable", null, null)
|
||||||
|
}
|
||||||
|
"requestMtu" -> {
|
||||||
|
val deviceId = call.argument<String>("deviceId")!!
|
||||||
|
val expectedMtu = call.argument<Int>("expectedMtu")!!
|
||||||
|
val gatt = knownGatts.find { it.device.address == deviceId }
|
||||||
|
?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", null)
|
||||||
|
gatt.requestMtu(expectedMtu)
|
||||||
|
result.success(null)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cleanConnection(gatt: BluetoothGatt) {
|
||||||
|
knownGatts.remove(gatt)
|
||||||
|
gatt.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class AvailabilityState(val value: Int) {
|
||||||
|
unknown(0),
|
||||||
|
resetting(1),
|
||||||
|
unsupported(2),
|
||||||
|
unauthorized(3),
|
||||||
|
poweredOff(4),
|
||||||
|
poweredOn(5),
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BluetoothManager.getAvailabilityState(): AvailabilityState {
|
||||||
|
val state = adapter?.state ?: return AvailabilityState.unsupported
|
||||||
|
return when(state) {
|
||||||
|
BluetoothAdapter.STATE_OFF -> AvailabilityState.poweredOff
|
||||||
|
BluetoothAdapter.STATE_ON -> AvailabilityState.poweredOn
|
||||||
|
BluetoothAdapter.STATE_TURNING_ON -> AvailabilityState.resetting
|
||||||
|
BluetoothAdapter.STATE_TURNING_OFF -> AvailabilityState.resetting
|
||||||
|
else -> AvailabilityState.unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val broadcastReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
if (intent?.action == BluetoothAdapter.ACTION_STATE_CHANGED) {
|
||||||
|
availabilityChangeSink?.success(bluetoothManager.getAvailabilityState().value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val scanCallback = object : ScanCallback() {
|
||||||
|
override fun onScanFailed(errorCode: Int) {
|
||||||
|
Log.v(TAG, "onScanFailed: $errorCode")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
||||||
|
// Log.v(TAG, "onScanResult: $callbackType + $result")
|
||||||
|
scanResultSink?.success(mapOf<String, Any>(
|
||||||
|
"name" to (result.device.name ?: ""),
|
||||||
|
"deviceId" to result.device.address,
|
||||||
|
"manufacturerDataHead" to (result.manufacturerDataHead ?: byteArrayOf()),
|
||||||
|
"rssi" to result.rssi
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBatchScanResults(results: MutableList<ScanResult>?) {
|
||||||
|
Log.v(TAG, "onBatchScanResults: $results")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var availabilityChangeSink: EventChannel.EventSink? = null
|
||||||
|
private var scanResultSink: EventChannel.EventSink? = null
|
||||||
|
|
||||||
|
override fun onListen(args: Any?, eventSink: EventChannel.EventSink?) {
|
||||||
|
val map = args as? Map<String, Any> ?: return
|
||||||
|
when (map["name"]) {
|
||||||
|
"availabilityChange" -> {
|
||||||
|
availabilityChangeSink = eventSink
|
||||||
|
availabilityChangeSink?.success(bluetoothManager.getAvailabilityState().value)
|
||||||
|
}
|
||||||
|
"scanResult" -> scanResultSink = eventSink
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel(args: Any?) {
|
||||||
|
val map = args as? Map<String, Any> ?: return
|
||||||
|
when (map["name"]) {
|
||||||
|
"availabilityChange" -> availabilityChangeSink = null
|
||||||
|
"scanResult" -> scanResultSink = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val gattCallback = object : BluetoothGattCallback() {
|
||||||
|
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
|
||||||
|
Log.v(TAG, "onConnectionStateChange: device(${gatt.device.address}) status($status), newState($newState)")
|
||||||
|
if (newState == BluetoothGatt.STATE_CONNECTED && status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
|
sendMessage(messageConnector, mapOf(
|
||||||
|
"deviceId" to gatt.device.address,
|
||||||
|
"ConnectionState" to "connected"
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
cleanConnection(gatt)
|
||||||
|
sendMessage(messageConnector, mapOf(
|
||||||
|
"deviceId" to gatt.device.address,
|
||||||
|
"ConnectionState" to "disconnected"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
|
||||||
|
Log.v(TAG, "onServicesDiscovered ${gatt.device.address} $status")
|
||||||
|
if (status != BluetoothGatt.GATT_SUCCESS) return
|
||||||
|
|
||||||
|
gatt.services?.forEach { service ->
|
||||||
|
Log.v(TAG, "Service " + service.uuid)
|
||||||
|
service.characteristics.forEach { characteristic ->
|
||||||
|
Log.v(TAG, " Characteristic ${characteristic.uuid}")
|
||||||
|
characteristic.descriptors.forEach {
|
||||||
|
Log.v(TAG, " Descriptor ${it.uuid}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage(messageConnector, mapOf(
|
||||||
|
"deviceId" to gatt.device.address,
|
||||||
|
"ServiceState" to "discovered",
|
||||||
|
"service" to service.uuid.toString(),
|
||||||
|
"characteristics" to service.characteristics.map { it.uuid.toString() }
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) {
|
||||||
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
|
sendMessage(messageConnector, mapOf(
|
||||||
|
"mtuConfig" to mtu
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
|
||||||
|
Log.v(TAG, "onCharacteristicRead ${characteristic.uuid}, ${characteristic.value.contentToString()}")
|
||||||
|
sendMessage(messageConnector, mapOf(
|
||||||
|
"deviceId" to gatt.device.address,
|
||||||
|
"characteristicValue" to mapOf(
|
||||||
|
"characteristic" to characteristic.uuid.toString(),
|
||||||
|
"value" to characteristic.value
|
||||||
|
)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCharacteristicWrite(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic, status: Int) {
|
||||||
|
Log.v(TAG, "onCharacteristicWrite ${characteristic.uuid}, ${characteristic.value.contentToString()} $status")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
|
||||||
|
Log.v(TAG, "onCharacteristicChanged ${characteristic.uuid}, ${characteristic.value.contentToString()}")
|
||||||
|
sendMessage(messageConnector, mapOf(
|
||||||
|
"deviceId" to gatt.device.address,
|
||||||
|
"characteristicValue" to mapOf(
|
||||||
|
"characteristic" to characteristic.uuid.toString(),
|
||||||
|
"value" to characteristic.value
|
||||||
|
)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val ScanResult.manufacturerDataHead: ByteArray?
|
||||||
|
get() {
|
||||||
|
val sparseArray = scanRecord?.manufacturerSpecificData ?: return null
|
||||||
|
if (sparseArray.size() == 0) return null
|
||||||
|
|
||||||
|
return sparseArray.keyAt(0).toShort().toByteArray() + sparseArray.valueAt(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Short.toByteArray(byteOrder: ByteOrder = ByteOrder.LITTLE_ENDIAN): ByteArray =
|
||||||
|
ByteBuffer.allocate(2 /*Short.SIZE_BYTES*/).order(byteOrder).putShort(this).array()
|
||||||
|
|
||||||
|
fun BluetoothGatt.getCharacteristic(service: String, characteristic: String): BluetoothGattCharacteristic? =
|
||||||
|
getService(UUID.fromString(service)).getCharacteristic(UUID.fromString(characteristic))
|
||||||
|
|
||||||
|
private val DESC__CLIENT_CHAR_CONFIGURATION = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
|
||||||
|
|
||||||
|
fun BluetoothGatt.setNotifiable(gattCharacteristic: BluetoothGattCharacteristic, bleInputProperty: String) {
|
||||||
|
val descriptor = gattCharacteristic.getDescriptor(DESC__CLIENT_CHAR_CONFIGURATION)
|
||||||
|
val (value, enable) = when (bleInputProperty) {
|
||||||
|
"notification" -> BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE to true
|
||||||
|
"indication" -> BluetoothGattDescriptor.ENABLE_INDICATION_VALUE to true
|
||||||
|
else -> BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE to false
|
||||||
|
}
|
||||||
|
descriptor.value = value
|
||||||
|
setCharacteristicNotification(descriptor.characteristic, enable) && writeDescriptor(descriptor)
|
||||||
|
}
|
281
plugins/quick_blue/darwin/QuickBlueDarwin.swift
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
import CoreBluetooth
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
#elseif os(OSX)
|
||||||
|
import Cocoa
|
||||||
|
import FlutterMacOS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
let GATT_HEADER_LENGTH = 3
|
||||||
|
|
||||||
|
let GSS_SUFFIX = "0000-1000-8000-00805f9b34fb"
|
||||||
|
|
||||||
|
extension CBUUID {
|
||||||
|
public var uuidStr: String {
|
||||||
|
get {
|
||||||
|
uuidString.lowercased()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CBPeripheral {
|
||||||
|
// FIXME https://forums.developer.apple.com/thread/84375
|
||||||
|
public var uuid: UUID {
|
||||||
|
get {
|
||||||
|
value(forKey: "identifier") as! NSUUID as UUID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getCharacteristic(_ characteristic: String, of service: String) -> CBCharacteristic? {
|
||||||
|
let s = self.services?.first {
|
||||||
|
$0.uuid.uuidStr == service || "0000\($0.uuid.uuidStr)-\(GSS_SUFFIX)" == service
|
||||||
|
}
|
||||||
|
let c = s?.characteristics?.first {
|
||||||
|
$0.uuid.uuidStr == characteristic || "0000\($0.uuid.uuidStr)-\(GSS_SUFFIX)" == characteristic
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setNotifiable(_ bleInputProperty: String, for characteristic: String, of service: String) {
|
||||||
|
guard let characteristic = getCharacteristic(characteristic, of: service) else{
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setNotifyValue(bleInputProperty != "disabled", for: characteristic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class QuickBlueDarwin: NSObject, FlutterPlugin {
|
||||||
|
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||||
|
#if os(iOS)
|
||||||
|
let messenger = registrar.messenger()
|
||||||
|
#elseif os(OSX)
|
||||||
|
let messenger = registrar.messenger
|
||||||
|
#endif
|
||||||
|
let method = FlutterMethodChannel(name: "quick_blue/method", binaryMessenger: messenger)
|
||||||
|
let eventAvailabilityChange = FlutterEventChannel(name: "quick_blue/event.availabilityChange", binaryMessenger: messenger)
|
||||||
|
let eventScanResult = FlutterEventChannel(name: "quick_blue/event.scanResult", binaryMessenger: messenger)
|
||||||
|
let messageConnector = FlutterBasicMessageChannel(name: "quick_blue/message.connector", binaryMessenger: messenger)
|
||||||
|
|
||||||
|
let instance = QuickBlueDarwin()
|
||||||
|
registrar.addMethodCallDelegate(instance, channel: method)
|
||||||
|
eventAvailabilityChange.setStreamHandler(instance)
|
||||||
|
eventScanResult.setStreamHandler(instance)
|
||||||
|
instance.messageConnector = messageConnector
|
||||||
|
}
|
||||||
|
|
||||||
|
private lazy var manager: CBCentralManager = { CBCentralManager(delegate: self, queue: nil) }()
|
||||||
|
private var discoveredPeripherals = Dictionary<String, CBPeripheral>()
|
||||||
|
|
||||||
|
private var availabilityChangeSink: FlutterEventSink?
|
||||||
|
private var scanResultSink: FlutterEventSink?
|
||||||
|
private var messageConnector: FlutterBasicMessageChannel!
|
||||||
|
|
||||||
|
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
switch call.method {
|
||||||
|
case "isBluetoothAvailable":
|
||||||
|
result(manager.state == .poweredOn)
|
||||||
|
case "startScan":
|
||||||
|
manager.scanForPeripherals(withServices: nil)
|
||||||
|
result(nil)
|
||||||
|
case "stopScan":
|
||||||
|
manager.stopScan()
|
||||||
|
result(nil)
|
||||||
|
case "connect":
|
||||||
|
let arguments = call.arguments as! Dictionary<String, Any>
|
||||||
|
let deviceId = arguments["deviceId"] as! String
|
||||||
|
guard let peripheral = discoveredPeripherals[deviceId] else {
|
||||||
|
result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
peripheral.delegate = self
|
||||||
|
manager.connect(peripheral)
|
||||||
|
result(nil)
|
||||||
|
case "disconnect":
|
||||||
|
let arguments = call.arguments as! Dictionary<String, Any>
|
||||||
|
let deviceId = arguments["deviceId"] as! String
|
||||||
|
guard let peripheral = discoveredPeripherals[deviceId] else {
|
||||||
|
result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (peripheral.state != .disconnected) {
|
||||||
|
manager.cancelPeripheralConnection(peripheral)
|
||||||
|
}
|
||||||
|
result(nil)
|
||||||
|
case "discoverServices":
|
||||||
|
let arguments = call.arguments as! Dictionary<String, Any>
|
||||||
|
let deviceId = arguments["deviceId"] as! String
|
||||||
|
guard let peripheral = discoveredPeripherals[deviceId] else {
|
||||||
|
result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
peripheral.discoverServices(nil)
|
||||||
|
result(nil)
|
||||||
|
case "setNotifiable":
|
||||||
|
let arguments = call.arguments as! Dictionary<String, Any>
|
||||||
|
let deviceId = arguments["deviceId"] as! String
|
||||||
|
let service = arguments["service"] as! String
|
||||||
|
let characteristic = arguments["characteristic"] as! String
|
||||||
|
let bleInputProperty = arguments["bleInputProperty"] as! String
|
||||||
|
guard let peripheral = discoveredPeripherals[deviceId] else {
|
||||||
|
result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let c = peripheral.getCharacteristic(characteristic, of: service) else {
|
||||||
|
result(FlutterError(code: "IllegalArgument", message: "Unknown characteristic:\(characteristic)", details: nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
peripheral.setNotifyValue(bleInputProperty != "disabled", for: c)
|
||||||
|
result(nil)
|
||||||
|
case "readValue":
|
||||||
|
let arguments = call.arguments as! Dictionary<String, Any>
|
||||||
|
let deviceId = arguments["deviceId"] as! String
|
||||||
|
let service = arguments["service"] as! String
|
||||||
|
let characteristic = arguments["characteristic"] as! String
|
||||||
|
guard let peripheral = discoveredPeripherals[deviceId] else {
|
||||||
|
result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let c = peripheral.getCharacteristic(characteristic, of: service) else {
|
||||||
|
result(FlutterError(code: "IllegalArgument", message: "Unknown characteristic:\(characteristic)", details: nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
peripheral.readValue(for: c)
|
||||||
|
result(nil)
|
||||||
|
case "writeValue":
|
||||||
|
let arguments = call.arguments as! Dictionary<String, Any>
|
||||||
|
let deviceId = arguments["deviceId"] as! String
|
||||||
|
let service = arguments["service"] as! String
|
||||||
|
let characteristic = arguments["characteristic"] as! String
|
||||||
|
let value = arguments["value"] as! FlutterStandardTypedData
|
||||||
|
let bleOutputProperty = arguments["bleOutputProperty"] as! String
|
||||||
|
guard let peripheral = discoveredPeripherals[deviceId] else {
|
||||||
|
result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let type = bleOutputProperty == "withoutResponse" ? CBCharacteristicWriteType.withoutResponse : CBCharacteristicWriteType.withResponse
|
||||||
|
guard let c = peripheral.getCharacteristic(characteristic, of: service) else {
|
||||||
|
result(FlutterError(code: "IllegalArgument", message: "Unknown characteristic:\(characteristic)", details: nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
peripheral.writeValue(value.data, for: c, type: type)
|
||||||
|
result(nil)
|
||||||
|
case "requestMtu":
|
||||||
|
let arguments = call.arguments as! Dictionary<String, Any>
|
||||||
|
let deviceId = arguments["deviceId"] as! String
|
||||||
|
guard let peripheral = discoveredPeripherals[deviceId] else {
|
||||||
|
result(FlutterError(code: "IllegalArgument", message: "Unknown deviceId:\(deviceId)", details: nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result(nil)
|
||||||
|
let mtu = peripheral.maximumWriteValueLength(for: .withoutResponse)
|
||||||
|
print("peripheral.maximumWriteValueLengthForType:CBCharacteristicWriteWithoutResponse \(mtu)")
|
||||||
|
messageConnector.sendMessage(["mtuConfig": mtu + GATT_HEADER_LENGTH])
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension QuickBlueDarwin: CBCentralManagerDelegate {
|
||||||
|
public func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
||||||
|
availabilityChangeSink?(central.state.rawValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
|
||||||
|
print("centralManager:didDiscoverPeripheral \(peripheral.name ?? "nil") \(peripheral.uuid.uuidString)")
|
||||||
|
discoveredPeripherals[peripheral.uuid.uuidString] = peripheral
|
||||||
|
|
||||||
|
let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
|
||||||
|
scanResultSink?([
|
||||||
|
"name": peripheral.name ?? "",
|
||||||
|
"deviceId": peripheral.uuid.uuidString,
|
||||||
|
"manufacturerData": FlutterStandardTypedData(bytes: manufacturerData ?? Data()),
|
||||||
|
"rssi": RSSI,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
|
||||||
|
print("centralManager:didConnect \(peripheral.uuid.uuidString)")
|
||||||
|
messageConnector.sendMessage([
|
||||||
|
"deviceId": peripheral.uuid.uuidString,
|
||||||
|
"ConnectionState": "connected",
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
|
||||||
|
print("centralManager:didDisconnectPeripheral: \(peripheral.uuid.uuidString) error: \(String(describing: error))")
|
||||||
|
messageConnector.sendMessage([
|
||||||
|
"deviceId": peripheral.uuid.uuidString,
|
||||||
|
"ConnectionState": "disconnected",
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension QuickBlueDarwin: FlutterStreamHandler {
|
||||||
|
open func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
|
||||||
|
guard let args = arguments as? Dictionary<String, Any>, let name = args["name"] as? String else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
print("QuickBlueDarwin onListenWithArguments: \(name)")
|
||||||
|
if name == "availabilityChange" {
|
||||||
|
availabilityChangeSink = events
|
||||||
|
availabilityChangeSink?(manager.state.rawValue) // Initializes CBCentralManager and returns the current state when hot restarting
|
||||||
|
} else if name == "scanResult" {
|
||||||
|
scanResultSink = events
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
open func onCancel(withArguments arguments: Any?) -> FlutterError? {
|
||||||
|
guard let args = arguments as? Dictionary<String, Any>, let name = args["name"] as? String else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
print("QuickBlueDarwin onCancelWithArguments: \(name)")
|
||||||
|
if name == "availabilityChange" {
|
||||||
|
availabilityChangeSink = nil
|
||||||
|
} else if name == "scanResult" {
|
||||||
|
scanResultSink = nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension QuickBlueDarwin: CBPeripheralDelegate {
|
||||||
|
public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
|
||||||
|
print("peripheral: \(peripheral.uuid.uuidString) didDiscoverServices error: \(String(describing: error))")
|
||||||
|
for service in peripheral.services! {
|
||||||
|
peripheral.discoverCharacteristics(nil, for: service)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
|
||||||
|
for characteristic in service.characteristics! {
|
||||||
|
print("peripheral:didDiscoverCharacteristicsForService (\(service.uuid.uuidStr), \(characteristic.uuid.uuidStr)")
|
||||||
|
}
|
||||||
|
self.messageConnector.sendMessage([
|
||||||
|
"deviceId": peripheral.uuid.uuidString,
|
||||||
|
"ServiceState": "discovered",
|
||||||
|
"service": service.uuid.uuidStr,
|
||||||
|
"characteristics": service.characteristics!.map { $0.uuid.uuidStr }
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
|
||||||
|
let data = characteristic.value as NSData?
|
||||||
|
print("peripheral:didWriteValueForCharacteristic \(characteristic.uuid.uuidStr) \(String(describing: data)) error: \(String(describing: error))")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
|
||||||
|
let data = characteristic.value as NSData?
|
||||||
|
print("peripheral:didUpdateValueForCharacteristic \(characteristic.uuid) \(String(describing: data)) error: \(String(describing: error))")
|
||||||
|
self.messageConnector.sendMessage([
|
||||||
|
"deviceId": peripheral.uuid.uuidString,
|
||||||
|
"characteristicValue": [
|
||||||
|
"characteristic": characteristic.uuid.uuidStr,
|
||||||
|
"value": FlutterStandardTypedData(bytes: characteristic.value!)
|
||||||
|
]
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
46
plugins/quick_blue/example/.gitignore
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.packages
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
|
||||||
|
# Web related
|
||||||
|
lib/generated_plugin_registrant.dart
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Android Studio will place build artifacts here
|
||||||
|
/android/app/debug
|
||||||
|
/android/app/profile
|
||||||
|
/android/app/release
|
10
plugins/quick_blue/example/.metadata
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# 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: 77d935af4db863f6abd0b9c31c7e6df2a13de57b
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
project_type: app
|
16
plugins/quick_blue/example/README.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# quick_blue_example
|
||||||
|
|
||||||
|
Demonstrates how to use the quick_blue plugin.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
This project is a starting point for a Flutter application.
|
||||||
|
|
||||||
|
A few resources to get you started if this is your first Flutter project:
|
||||||
|
|
||||||
|
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
|
||||||
|
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
|
||||||
|
|
||||||
|
For help getting started with Flutter, view our
|
||||||
|
[online documentation](https://flutter.dev/docs), which offers tutorials,
|
||||||
|
samples, guidance on mobile development, and a full API reference.
|
29
plugins/quick_blue/example/analysis_options.yaml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# This file configures the analyzer, which statically analyzes Dart code to
|
||||||
|
# check for errors, warnings, and lints.
|
||||||
|
#
|
||||||
|
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||||
|
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||||
|
# invoked from the command line by running `flutter analyze`.
|
||||||
|
|
||||||
|
# The following line activates a set of recommended lints for Flutter apps,
|
||||||
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
# The lint rules applied to this project can be customized in the
|
||||||
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
# included above or to enable additional rules. A list of all available lints
|
||||||
|
# and their documentation is published at
|
||||||
|
# https://dart-lang.github.io/linter/lints/index.html.
|
||||||
|
#
|
||||||
|
# Instead of disabling a lint rule for the entire project in the
|
||||||
|
# section below, it can also be suppressed for a single line of code
|
||||||
|
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||||
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
|
# producing the lint.
|
||||||
|
rules:
|
||||||
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
13
plugins/quick_blue/example/android/.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||||
|
key.properties
|
||||||
|
**/*.keystore
|
||||||
|
**/*.jks
|
68
plugins/quick_blue/example/android/app/build.gradle
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
def localProperties = new Properties()
|
||||||
|
def localPropertiesFile = rootProject.file('local.properties')
|
||||||
|
if (localPropertiesFile.exists()) {
|
||||||
|
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||||
|
localProperties.load(reader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||||
|
if (flutterRoot == null) {
|
||||||
|
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
|
if (flutterVersionCode == null) {
|
||||||
|
flutterVersionCode = '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||||
|
if (flutterVersionName == null) {
|
||||||
|
flutterVersionName = '1.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion flutter.compileSdkVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
|
applicationId "com.example.quick_blue_example"
|
||||||
|
minSdkVersion 21
|
||||||
|
targetSdkVersion flutter.targetSdkVersion
|
||||||
|
versionCode flutterVersionCode.toInteger()
|
||||||
|
versionName flutterVersionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
// TODO: Add your own signing config for the release build.
|
||||||
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source '../..'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.quick_blue_example">
|
||||||
|
<!-- Flutter needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
@ -0,0 +1,36 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.quick_blue_example">
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
<application
|
||||||
|
android:label="quick_blue_example"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
|
the Android process has started. This theme is visible to the user
|
||||||
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
to determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
</application>
|
||||||
|
</manifest>
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.example.quick_blue_example
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity: FlutterActivity() {
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
After Width: | Height: | Size: 544 B |
After Width: | Height: | Size: 442 B |
After Width: | Height: | Size: 721 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
Flutter draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
Flutter draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.quick_blue_example">
|
||||||
|
<!-- Flutter needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
31
plugins/quick_blue/example/android/build.gradle
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.5.30'
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.buildDir = '../build'
|
||||||
|
subprojects {
|
||||||
|
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
|
project.evaluationDependsOn(':app')
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("clean", Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
3
plugins/quick_blue/example/android/gradle.properties
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx1536M
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
6
plugins/quick_blue/example/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#Fri Jun 23 08:50:38 CEST 2017
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip
|
11
plugins/quick_blue/example/android/settings.gradle
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
include ':app'
|
||||||
|
|
||||||
|
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
||||||
|
def properties = new Properties()
|
||||||
|
|
||||||
|
assert localPropertiesFile.exists()
|
||||||
|
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
||||||
|
|
||||||
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
|
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
34
plugins/quick_blue/example/ios/.gitignore
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
**/dgph
|
||||||
|
*.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
*.moved-aside
|
||||||
|
*.pbxuser
|
||||||
|
*.perspectivev3
|
||||||
|
**/*sync/
|
||||||
|
.sconsign.dblite
|
||||||
|
.tags*
|
||||||
|
**/.vagrant/
|
||||||
|
**/DerivedData/
|
||||||
|
Icon?
|
||||||
|
**/Pods/
|
||||||
|
**/.symlinks/
|
||||||
|
profile
|
||||||
|
xcuserdata
|
||||||
|
**/.generated/
|
||||||
|
Flutter/App.framework
|
||||||
|
Flutter/Flutter.framework
|
||||||
|
Flutter/Flutter.podspec
|
||||||
|
Flutter/Generated.xcconfig
|
||||||
|
Flutter/ephemeral/
|
||||||
|
Flutter/app.flx
|
||||||
|
Flutter/app.zip
|
||||||
|
Flutter/flutter_assets/
|
||||||
|
Flutter/flutter_export_environment.sh
|
||||||
|
ServiceDefinitions.json
|
||||||
|
Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!default.mode1v3
|
||||||
|
!default.mode2v3
|
||||||
|
!default.pbxuser
|
||||||
|
!default.perspectivev3
|
@ -0,0 +1,26 @@
|
|||||||
|
<?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>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>io.flutter.flutter.app</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>MinimumOSVersion</key>
|
||||||
|
<string>9.0</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
2
plugins/quick_blue/example/ios/Flutter/Debug.xcconfig
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
2
plugins/quick_blue/example/ios/Flutter/Release.xcconfig
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
41
plugins/quick_blue/example/ios/Podfile
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# Uncomment this line to define a global platform for your project
|
||||||
|
# platform :ios, '9.0'
|
||||||
|
|
||||||
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
|
project 'Runner', {
|
||||||
|
'Debug' => :debug,
|
||||||
|
'Profile' => :release,
|
||||||
|
'Release' => :release,
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutter_root
|
||||||
|
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||||
|
unless File.exist?(generated_xcode_build_settings_path)
|
||||||
|
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||||
|
end
|
||||||
|
|
||||||
|
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||||
|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||||
|
return matches[1].strip if matches
|
||||||
|
end
|
||||||
|
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||||
|
|
||||||
|
flutter_ios_podfile_setup
|
||||||
|
|
||||||
|
target 'Runner' do
|
||||||
|
use_frameworks!
|
||||||
|
use_modular_headers!
|
||||||
|
|
||||||
|
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
flutter_additional_ios_build_settings(target)
|
||||||
|
end
|
||||||
|
end
|
552
plugins/quick_blue/example/ios/Runner.xcodeproj/project.pbxproj
Normal file
@ -0,0 +1,552 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 50;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
B9073778C60E98C353E95558 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 267704C746F7481BE2FD075F /* Pods_Runner.framework */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 10;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
name = "Embed Frameworks";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
267704C746F7481BE2FD075F /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
|
62DFED7748A45B2C8AB309AD /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
8D98FEFF83B7209CB5E85C69 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
928BCE08B84E5EF4963AE926 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
B9073778C60E98C353E95558 /* Pods_Runner.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
2B5912B4332ACFF87993A81F /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
267704C746F7481BE2FD075F /* Pods_Runner.framework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
8E9EBB99EC94F35D1070368A /* Pods */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
8D98FEFF83B7209CB5E85C69 /* Pods-Runner.debug.xcconfig */,
|
||||||
|
62DFED7748A45B2C8AB309AD /* Pods-Runner.release.xcconfig */,
|
||||||
|
928BCE08B84E5EF4963AE926 /* Pods-Runner.profile.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Pods;
|
||||||
|
path = Pods;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Flutter;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146E51CF9000F007C117D = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
|
8E9EBB99EC94F35D1070368A /* Pods */,
|
||||||
|
2B5912B4332ACFF87993A81F /* Frameworks */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146EF1CF9000F007C117D /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */,
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
|
);
|
||||||
|
path = Runner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
|
buildPhases = (
|
||||||
|
1A400B5A75970CECD3788202 /* [CP] Check Pods Manifest.lock */,
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
4360CC4FBC1B3F978A10BABA /* [CP] Embed Pods Frameworks */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = Runner;
|
||||||
|
productName = Runner;
|
||||||
|
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
LastUpgradeCheck = 1300;
|
||||||
|
ORGANIZATIONNAME = "";
|
||||||
|
TargetAttributes = {
|
||||||
|
97C146ED1CF9000F007C117D = {
|
||||||
|
CreatedOnToolsVersion = 7.3.1;
|
||||||
|
LastSwiftMigration = 1100;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||||
|
compatibilityVersion = "Xcode 9.3";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 97C146E51CF9000F007C117D;
|
||||||
|
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
1A400B5A75970CECD3788202 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Thin Binary";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||||
|
};
|
||||||
|
4360CC4FBC1B3F978A10BABA /* [CP] Embed Pods Frameworks */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
name = "[CP] Embed Pods Frameworks";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Run Script";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C146FB1CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = Main.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C147001CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = LaunchScreen.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = X637F4W694;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.quickBlueExample;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
97C147031CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147041CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
97C147061CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = X637F4W694;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.quickBlueExample;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147071CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = X637F4W694;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.quickBlueExample;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147031CF9000F007C117D /* Debug */,
|
||||||
|
97C147041CF9000F007C117D /* Release */,
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147061CF9000F007C117D /* Debug */,
|
||||||
|
97C147071CF9000F007C117D /* Release */,
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
}
|
7
plugins/quick_blue/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
@ -0,0 +1,8 @@
|
|||||||
|
<?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>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -0,0 +1,8 @@
|
|||||||
|
<?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>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -0,0 +1,87 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1300"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Profile"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
10
plugins/quick_blue/example/ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:Runner.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
@ -0,0 +1,8 @@
|
|||||||
|
<?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>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -0,0 +1,8 @@
|
|||||||
|
<?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>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
13
plugins/quick_blue/example/ios/Runner/AppDelegate.swift
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import UIKit
|
||||||
|
import Flutter
|
||||||
|
|
||||||
|
@UIApplicationMain
|
||||||
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
|
override func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
|
) -> Bool {
|
||||||
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,122 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "83.5x83.5",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "1024x1024",
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"filename" : "Icon-App-1024x1024@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 564 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.5 KiB |
23
plugins/quick_blue/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
BIN
plugins/quick_blue/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
After Width: | Height: | Size: 68 B |
BIN
plugins/quick_blue/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
After Width: | Height: | Size: 68 B |