|
@@ -0,0 +1,280 @@
|
|
|
|
|
+import 'dart:async';
|
|
|
|
|
+import 'dart:isolate';
|
|
|
|
|
+import 'dart:math';
|
|
|
|
|
+import 'dart:ui';
|
|
|
|
|
+import 'package:fast_gbk/fast_gbk.dart';
|
|
|
|
|
+import 'package:flutter/material.dart';
|
|
|
|
|
+import 'package:mqtt_client/mqtt_client.dart';
|
|
|
|
|
+import 'package:mqtt_client/mqtt_server_client.dart';
|
|
|
|
|
+import 'package:flutter_notification_listener/flutter_notification_listener.dart';
|
|
|
|
|
+import 'package:uuid/uuid.dart';
|
|
|
|
|
+
|
|
|
|
|
+Future<void> main() async {
|
|
|
|
|
+ runApp(const MyApp());
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+class MyApp extends StatelessWidget {
|
|
|
|
|
+ const MyApp({super.key});
|
|
|
|
|
+
|
|
|
|
|
+ @override
|
|
|
|
|
+ Widget build(BuildContext context) {
|
|
|
|
|
+ return MaterialApp(
|
|
|
|
|
+ title: 'MokiBox',
|
|
|
|
|
+ theme: ThemeData(
|
|
|
|
|
+ primarySwatch: Colors.blue,
|
|
|
|
|
+ ),
|
|
|
|
|
+ home: const MyHomePage(title: 'Moki收款宝'),
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+class Listener {
|
|
|
|
|
+ Mqtt mqtt = Mqtt();
|
|
|
|
|
+
|
|
|
|
|
+ Listener() {
|
|
|
|
|
+ initPlatformState();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @pragma('vm:entry-point')
|
|
|
|
|
+ static void _callback(NotificationEvent evt) {
|
|
|
|
|
+ final SendPort? send =
|
|
|
|
|
+ IsolateNameServer.lookupPortByName("notifications_send_port");
|
|
|
|
|
+ if (send == null) print("can't find the sender");
|
|
|
|
|
+ send?.send(evt);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void testSendMsg() {
|
|
|
|
|
+ String msg =
|
|
|
|
|
+ "{\"type\":\"tts_dynamic\",\"msgid\":\"${Random().nextInt(999999)}\",\"txt\":\"设备已连接!\"}";
|
|
|
|
|
+ mqtt.sendMsg(msg);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void onData(NotificationEvent event) {
|
|
|
|
|
+ String? msg;
|
|
|
|
|
+ if (event.packageName == 'com.eg.android.AlipayGphone' &&
|
|
|
|
|
+ event.title == '店员通') {
|
|
|
|
|
+ msg = event.text;
|
|
|
|
|
+ if (msg == null) return;
|
|
|
|
|
+ final number = RegExp(r'\d+\.\d+').firstMatch(msg);
|
|
|
|
|
+ final orderId = Random().nextInt(999999);
|
|
|
|
|
+
|
|
|
|
|
+ if (number != null) {
|
|
|
|
|
+ final regAmount = double.parse(number.group(0)!);
|
|
|
|
|
+ String amount = regAmount.toStringAsFixed(2);
|
|
|
|
|
+ if (amount.endsWith('.00')) {
|
|
|
|
|
+ amount = amount.replaceAll('.00', '');
|
|
|
|
|
+ }
|
|
|
|
|
+ String msgPush = '866374061547503|1007-2|$orderId|2001|$amount|7571';
|
|
|
|
|
+ mqtt.sendMsg(msgPush);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (event.packageName == 'com.tencent.mm' && event.title == '微信收款助手') {
|
|
|
|
|
+ msg = event.text;
|
|
|
|
|
+ if (msg == null) return;
|
|
|
|
|
+ final number = RegExp(r'\d+\.\d+').firstMatch(msg);
|
|
|
|
|
+ final orderId = Random().nextInt(999999);
|
|
|
|
|
+
|
|
|
|
|
+ if (number != null) {
|
|
|
|
|
+ final regAmount = double.parse(number.group(0)!);
|
|
|
|
|
+ String amount = regAmount.toStringAsFixed(2);
|
|
|
|
|
+ if (amount.endsWith('.00')) {
|
|
|
|
|
+ amount = amount.replaceAll('.00', '');
|
|
|
|
|
+ }
|
|
|
|
|
+ String msgPush = '866374061547503|1007-2|$orderId|2003|$amount|7571';
|
|
|
|
|
+ mqtt.sendMsg(msgPush);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+// 监听到消息后转到处理函数
|
|
|
|
|
+ Future<void> initPlatformState() async {
|
|
|
|
|
+ NotificationsListener.initialize(callbackHandle: _callback);
|
|
|
|
|
+ // register you event handler in the ui logic.
|
|
|
|
|
+ NotificationsListener.receivePort?.listen((evt) => onData(evt));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void startListening() async {
|
|
|
|
|
+ print("start listening");
|
|
|
|
|
+ var isR = await NotificationsListener.isRunning;
|
|
|
|
|
+ if (!isR!) {
|
|
|
|
|
+ await NotificationsListener.startService();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Future<void> checkPermission() async {
|
|
|
|
|
+ var hasPermission = await NotificationsListener.hasPermission;
|
|
|
|
|
+
|
|
|
|
|
+ if (!hasPermission!) {
|
|
|
|
|
+ print("no permission, so open settings");
|
|
|
|
|
+ NotificationsListener.openPermissionSettings();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+class Mqtt {
|
|
|
|
|
+ late MqttServerClient client;
|
|
|
|
|
+
|
|
|
|
|
+ Mqtt() {
|
|
|
|
|
+ connect();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Future<MqttServerClient> connect() async {
|
|
|
|
|
+ var uuid = const Uuid();
|
|
|
|
|
+ client = MqttServerClient.withPort('47.92.50.210', uuid.v4(), 9883);
|
|
|
|
|
+ client.logging(on: true);
|
|
|
|
|
+
|
|
|
|
|
+ String willMsg =
|
|
|
|
|
+ "{\"type\":\"tts_dynamic\",\"msgid\":\"${Random().nextInt(999999)}\",\"txt\":\"lost connect!\"}";
|
|
|
|
|
+
|
|
|
|
|
+ final connMessage = MqttConnectMessage()
|
|
|
|
|
+ .authenticateAs('moki', 'Moki@886.')
|
|
|
|
|
+ .withWillTopic('866374061547503')
|
|
|
|
|
+ .withWillMessage(willMsg)
|
|
|
|
|
+ .startClean() // 清理会话
|
|
|
|
|
+ .withWillQos(MqttQos.atLeastOnce);
|
|
|
|
|
+ client.connectionMessage = connMessage;
|
|
|
|
|
+ client.autoReconnect = true;
|
|
|
|
|
+ client.keepAlivePeriod = 120;
|
|
|
|
|
+ try {
|
|
|
|
|
+ await client.connect();
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ print('Exception: $e');
|
|
|
|
|
+ client.disconnect();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return client;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Future<String> sendMsg(String msg) async {
|
|
|
|
|
+ // 发布消息
|
|
|
|
|
+ final builder = MqttClientPayloadBuilder();
|
|
|
|
|
+ // builder.addString(msg);
|
|
|
|
|
+ final bytes = gbk.encode(msg);
|
|
|
|
|
+ for (var element in bytes) {
|
|
|
|
|
+ builder.addByte(element);
|
|
|
|
|
+ }
|
|
|
|
|
+ // MqttServerClient client = await connect();
|
|
|
|
|
+ client.publishMessage(
|
|
|
|
|
+ '866374061547503', MqttQos.atLeastOnce, builder.payload!);
|
|
|
|
|
+ return 'success';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+// // 断开连接
|
|
|
|
|
+// await
|
|
|
|
|
+//
|
|
|
|
|
+// client.disconnect();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+class MyHomePage extends StatefulWidget {
|
|
|
|
|
+ const MyHomePage({Key? key, required this.title}) : super(key: key);
|
|
|
|
|
+ final String title;
|
|
|
|
|
+
|
|
|
|
|
+ @override
|
|
|
|
|
+ State<StatefulWidget> createState() => _MyHomePageState();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+class _MyHomePageState extends State<MyHomePage> {
|
|
|
|
|
+ late Listener listener = Listener();
|
|
|
|
|
+
|
|
|
|
|
+ // 弹框信息
|
|
|
|
|
+ void _showDialog(BuildContext context, String msg, bool tag) {
|
|
|
|
|
+ String title = tag ? '成功!' : '失败';
|
|
|
|
|
+ showDialog(
|
|
|
|
|
+ context: context,
|
|
|
|
|
+ builder: (BuildContext context) {
|
|
|
|
|
+ return Theme(
|
|
|
|
|
+ data: Theme.of(context).copyWith(
|
|
|
|
|
+ dialogTheme: DialogTheme(
|
|
|
|
|
+ shape: RoundedRectangleBorder(
|
|
|
|
|
+ borderRadius: BorderRadius.circular(5.0)))),
|
|
|
|
|
+ child: AlertDialog(
|
|
|
|
|
+ title: Text(title),
|
|
|
|
|
+ content: Text(msg),
|
|
|
|
|
+ actions: <Widget>[
|
|
|
|
|
+ TextButton(
|
|
|
|
|
+ child: const Center(child: Text('确定')),
|
|
|
|
|
+ onPressed: () {
|
|
|
|
|
+ Navigator.of(context).pop();
|
|
|
|
|
+ },
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ));
|
|
|
|
|
+ },
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @override
|
|
|
|
|
+ Widget build(BuildContext context) {
|
|
|
|
|
+ return Scaffold(
|
|
|
|
|
+ appBar: AppBar(
|
|
|
|
|
+ title: Text(widget.title),
|
|
|
|
|
+ backgroundColor: Colors.cyan,
|
|
|
|
|
+ ),
|
|
|
|
|
+ body: Center(
|
|
|
|
|
+ child: Column(
|
|
|
|
|
+ mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
+ children: <Widget>[
|
|
|
|
|
+ const Text(
|
|
|
|
|
+ '欢迎使用魔基盒收款宝',
|
|
|
|
|
+ style: TextStyle(
|
|
|
|
|
+ fontSize: 22, // 设置字体大小为20
|
|
|
|
|
+ ),
|
|
|
|
|
+ textAlign: TextAlign.center,
|
|
|
|
|
+ ),
|
|
|
|
|
+ const Text(
|
|
|
|
|
+ '请依次点击如下按钮!',
|
|
|
|
|
+ style: TextStyle(
|
|
|
|
|
+ fontSize: 16, // 设置字体大小为20
|
|
|
|
|
+ ),
|
|
|
|
|
+ textAlign: TextAlign.center,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ElevatedButton.icon(
|
|
|
|
|
+ icon: const Icon(Icons.ad_units_outlined),
|
|
|
|
|
+ label: const Text("申请权限"),
|
|
|
|
|
+ style: ElevatedButton.styleFrom(
|
|
|
|
|
+ foregroundColor: Colors.white,
|
|
|
|
|
+ backgroundColor: Colors.cyan,
|
|
|
|
|
+ shape: RoundedRectangleBorder(
|
|
|
|
|
+ borderRadius: BorderRadius.circular(5), // 设置按钮的圆角半径为10
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ onPressed: () async {
|
|
|
|
|
+ listener.checkPermission();
|
|
|
|
|
+ },
|
|
|
|
|
+ ),
|
|
|
|
|
+ ElevatedButton.icon(
|
|
|
|
|
+ icon: const Icon(Icons.add_chart_rounded),
|
|
|
|
|
+ onPressed: () async {
|
|
|
|
|
+ listener.startListening();
|
|
|
|
|
+ _showDialog(context, '服务已成功启动!', true);
|
|
|
|
|
+ },
|
|
|
|
|
+ label: const Text('开启监听'),
|
|
|
|
|
+ style: ElevatedButton.styleFrom(
|
|
|
|
|
+ foregroundColor: Colors.white,
|
|
|
|
|
+ backgroundColor: Colors.greenAccent,
|
|
|
|
|
+ shape: RoundedRectangleBorder(
|
|
|
|
|
+ borderRadius: BorderRadius.circular(5), // 设置按钮的圆角半径为10
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ElevatedButton.icon(
|
|
|
|
|
+ icon: const Icon(Icons.add_alert_outlined),
|
|
|
|
|
+ onPressed: () async {
|
|
|
|
|
+ listener.testSendMsg();
|
|
|
|
|
+ },
|
|
|
|
|
+ label: const Text('检测设备'),
|
|
|
|
|
+ style: ElevatedButton.styleFrom(
|
|
|
|
|
+ foregroundColor: Colors.white,
|
|
|
|
|
+ backgroundColor: Colors.grey,
|
|
|
|
|
+ shape: RoundedRectangleBorder(
|
|
|
|
|
+ borderRadius: BorderRadius.circular(5), // 设置按钮的圆角半径为10
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ )
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+}
|