dtb_link 0.1.23+44
dtb_link: ^0.1.23+44 copied to clipboard
DTB Link - utility app enabling applications to connect and integrate with NFC card readers over Bluetooth
example/lib/main.dart
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:dtb_link/dtb_link.dart';
enum EasyKey { sKey, echo, saleTran, voidTran }
typedef OnDevice = Function;
void main() {
runApp(const MaterialApp(home: MyApp()));
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
DtbLink dtbLink = DtbLink();
DataInitRet? initData;
List<BluetoothDevice> deviceList = [];
TextEditingController controller = TextEditingController();
ScrollController scrollController = ScrollController();
OnDevice? onDevice;
Map<String, dynamic>? lastTran;
InitType type = InitType.production;
@override
void initState() {
super.initState();
dtbLink.dtbLibInit(const bool.fromEnvironment("dart.vm.product") ? InitType.production : InitType.staging);
WidgetsBinding.instance.addPostFrameCallback((_) {
dtbLink.terminalInitData().then((data) {
debugPrint("initData: ${data?.toJson()}");
initData = data;
});
dtbLink.onEvent((onData) {
debugPrint("onData: $onData");
Map<String, dynamic> retData = json.decode(onData);
if (retData["command"] == "bluetooth.device_found") {
String? deviceId = retData["ret"]?["device_id"];
String? deviceName = retData["ret"]?["device_name"];
if (deviceId != null && deviceName != null) {
final device = BluetoothDevice(deviceId: deviceId, deviceName: deviceName);
if (!deviceList.contains(device)) {
setState(() {
deviceList.add(device);
addMessage("${device.toJson()}");
});
onDevice?.call();
}
}
} else if (retData["command"] == "bluetooth.download_file.pregress") {
int? current = retData["ret"]?["current"];
int? total = retData["ret"]?["total"];
debugPrint("current: $current, total: $total");
}
});
initPlatformState();
});
}
Future<void> initPlatformState() async {
String platformVersion;
try {
debugPrint("platformVersion before");
platformVersion = await dtbLink.getPlatformVersion() ?? 'Unknown platform version';
debugPrint("platformVersion: $platformVersion");
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("DTB Link Flutter Demo $_platformVersion")),
body: Padding(
padding: const EdgeInsets.all(10),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
const Text("Bluetooth"),
DtbButton(
onPressed: () {
deviceList.clear();
addMessage("=>Search device");
showSearchDialog(context);
dtbLink.bluetoothStartSearch();
},
title: "Search device",
),
DtbButton(
onPressed: () {
addMessage("=>Is connected");
dtbLink.bluetoothIsConnected().then((result) {
addMessage("<=$result");
});
},
title: "Is connected",
),
DtbButton(
onPressed: () {
addMessage("=>Contactless upload");
dtbLink
.bluetoothContactlessFileUpload()
.then((result) {
addMessage("<=$result");
});
},
title: "Contactless upload",
),
DtbButton(
onPressed: () {
addMessage("=>EMV upload");
dtbLink.bluetoothEmvFileUpload().then((result) {
addMessage("<=$result");
});
},
title: "EMV upload",
),
DtbButton(
onPressed: () {
addMessage("=>Device data");
dtbLink.bluetoothDeviceData().then((device) {
addMessage("<=${device.toJson()}");
});
},
title: "Device Data",
),
DtbButton(
onPressed: () {
addMessage("=>Reconnect Device");
dtbLink.bluetoothReconnectDevice().then((ret) {
addMessage("<=$ret}");
});
},
title: "Reconnect Device",
),
DtbButton(
onPressed: () {
addMessage("=>Card read");
dtbLink.traceNo.then((traceNo) {
dtbLink
.bluetoothCardRead(
amount: "100",
tranType: 1,
traceNo: traceNo)
.then((ret) {
addMessage("<=$ret");
});
});
},
title: "Card read",
),
],
),
),
),
),
Expanded(
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Wrap(
children: [
const Text("Terminal"),
DtbSwitch(
type: type,
onChanged: (InitType initType) {
setState(() {
type = initType;
});
},
),
DtbButton(
onPressed: () {
addMessage("=>Echo test");
dtbLink.terminalEchoTest().then((ret) {
addMessage("<=$ret");
});
},
title: "Echo Test",
),
DtbButton(
onPressed: () {
addMessage("=>Init Data");
dtbLink.terminalInitData().then((ret) {
addMessage("<=${ret?.toJson()}");
});
},
title: "Init Data",
),
DtbButton(
onPressed: () {
addMessage("=>Init terminal");
showTerminalTrcDialog(context).then((ret) {
String? terminalId = ret?["terminal_id"];
String? trcCode = ret?["trc_code"];
if (terminalId != null && trcCode != null) {
dtbLink
.terminalDoInit(
terminalId: terminalId,
trc: trcCode,
)
.then((ret) {
addMessage("<=${ret.toJson()}");
});
}
});
},
title: "Init Terminal",
),
DtbButton(
onPressed: () {
addMessage("=>Sync Key");
dtbLink.syncKey().then((ret) {
addMessage("<=${ret.toJson()}");
});
},
title: "Sync Key",
),
DtbButton(
onPressed: () {
addMessage("=>Do Sale");
showNumberInputDialog(context).then((amount) {
if (mounted && amount != null) {
dtbLink
.doSale(context: context, amount: amount)
.then((ret) {
addMessage("<=$ret");
debugPrint("ret: $ret");
if (ret["code"] == 0) {
Map<String, dynamic> tranRet = ret["ret"];
if (tranRet["resp_code"] == "000") {
debugPrint("tranRet: $tranRet");
setState(() {
lastTran = tranRet;
});
}
} else {
setState(() {
lastTran = ret;
});
}
});
}
});
},
title: "Do Sale",
),
DtbButton(
onPressed: lastTran != null ? doVoid : null,
title: "Do Void",
),
DtbButton(
onPressed: () {
addMessage("=>Do User Cancel");
dtbLink.doCancelCardRead();
},
title: "Do UserCancel",
),
],
),
),
),
),
],
),
Expanded(
child: SingleChildScrollView(
controller: scrollController,
child: TextField(
maxLines: null,
enabled: false,
controller: controller,
style: const TextStyle(fontSize: 11),
),
),
),
],
),
),
);
}
doVoid() {
addMessage("=>Do Void");
debugPrint("lastTran: $lastTran");
final lastRet = lastTran?["ret"];
if (lastRet != null) {
if (lastRet["resp_code"] == "000") {
String? amountStr = lastRet?["amount"];
String? encTrack2 = lastRet?["enc_track"];
String? rrn = lastRet?["rrn"];
String? authCode = lastRet?["auth_code"];
String? traceNo = lastRet?["trace_no"];
if (amountStr != null &&
encTrack2 != null &&
rrn != null &&
authCode != null &&
traceNo != null) {
dtbLink
.doVoid(
amountStr: amountStr,
encTrack2: encTrack2,
rrn: rrn,
authCode: authCode,
traceNo: traceNo)
.then(
(ret) {
addMessage("<=$ret");
},
);
} else {
addMessage("<=Data is not valid");
}
} else {
addMessage("<=Last tran is invalid respCode: ${lastRet["resp_code"]}");
}
} else {
addMessage("<=Last tran is empty");
}
}
addMessage(String msg) {
controller.text = "${controller.text}\n$msg";
Future.delayed(const Duration(milliseconds: 200), () {
scrollController.animateTo(scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 200), curve: Curves.easeInOut);
});
}
showSearchDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder:
(BuildContext context, void Function(void Function()) setState) {
onDevice = () {
setState(() {});
};
Widget buildDevice(BluetoothDevice device) {
return DtbButton(
title: '${device.deviceName}\n${device.deviceId}',
onPressed: () {
Navigator.of(context).pop();
onDevice = null;
dtbLink.connectDevice(device);
},
);
}
List<Widget> buildList() {
List<Widget> list = [];
for (BluetoothDevice device in deviceList) {
list.add(buildDevice(device));
}
return list;
}
return AlertDialog(
title: const Text('Dialog Title'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: buildList(),
),
);
},
);
},
);
}
Future<double?> showNumberInputDialog(BuildContext context) async {
TextEditingController controller = TextEditingController();
return showDialog<double>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Гүйлгээний дүн"),
content: TextField(
controller: controller,
keyboardType: TextInputType.number,
decoration:
const InputDecoration(hintText: "Тоон утгаа оруулна уу"),
),
actions: <Widget>[
TextButton(
child: const Text("Болих"),
onPressed: () {
Navigator.of(context).pop(); // Хариуг буцаахгүй
},
),
ElevatedButton(
child: const Text("Гүйлгээ"),
onPressed: () {
final input = controller.text;
final number = double.tryParse(input);
if (number != null) {
Navigator.of(context).pop(number);
} else {
// Хэрэглэгч буруу утга оруулсан бол, тооцохгүй
Navigator.of(context).pop();
}
},
),
],
);
},
);
}
Future<Map<String, String>?> showTerminalTrcDialog(
BuildContext context,
) async {
TextEditingController terminalController = TextEditingController();
TextEditingController trcCodeController = TextEditingController();
return showDialog<Map<String, String>>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Мэдээлэл оруулах"),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: terminalController,
decoration: const InputDecoration(labelText: "Terminal дугаар"),
),
const SizedBox(height: 8),
TextField(
controller: trcCodeController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(labelText: "TRC code"),
),
],
),
actions: <Widget>[
TextButton(
child: const Text("Болих"),
onPressed: () {
Navigator.of(context).pop(); // Хариуг буцаахгүй
},
),
ElevatedButton(
child: const Text("ОК"),
onPressed: () {
String terminal = terminalController.text.trim();
String trcCode = trcCodeController.text.trim();
if (terminal.isNotEmpty && trcCode.isNotEmpty) {
Navigator.of(context).pop({
'terminal_id': terminal,
'trc_code': trcCode,
});
} else {
// Хоосон талбар байвал, утга буцаахгүй
Navigator.of(context).pop();
}
},
),
],
);
},
);
}
}
class DtbButton extends ElevatedButton {
DtbButton({
super.key,
super.onPressed,
required String title,
}) : super(
child: Text(title, style: const TextStyle(fontSize: 11)),
style: ElevatedButton.styleFrom(
elevation: 0.5,
minimumSize: const Size.fromHeight(38),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4), // <-- Radius
),
),
);
}
class DtbSwitch extends StatelessWidget {
final InitType type;
final Function(InitType) onChanged;
const DtbSwitch({super.key, required this.type, required this.onChanged});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
onChanged.call(
type == InitType.production ? InitType.staging : InitType.production,
);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(type == InitType.production ? "Production" : "Staging"),
Transform.scale(
scale: 0.7,
child: Switch(
padding: EdgeInsets.zero,
value: type == InitType.production,
onChanged: (value) {
onChanged.call(value ? InitType.production : InitType.staging);
},
),
),
],
),
);
}
}