android_hid_gamepad 0.0.1
android_hid_gamepad: ^0.0.1 copied to clipboard
A Flutter plugin for Android HID Gamepad using Bluetooth
import 'package:android_hid_gamepad_example/cubits/paired_devices_cubit.dart';
// import 'package:android_hid_gamepad_example/cubits/paired_devices_state.dart';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:android_hid_gamepad/android_hid_gamepad.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_joystick/flutter_joystick.dart';
import 'package:flutter/cupertino.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]).then((_) {
runApp(
BlocProvider(
create: (context) => PairedDevicesCubit(AndroidHidGamepad()),
child: const MyApp(),
),
);
});
}
// States ill have: deviceList
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(home: MainPage(), debugShowCheckedModeBanner: false);
}
}
class MainPage extends StatefulWidget {
const MainPage({super.key});
@override
State<StatefulWidget> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
final _androidHidGamepad = AndroidHidGamepad();
String? _connectedDeviceName;
String? _lastConnAddress;
List<int> inputByte = [0, 0, 0]; // Button, JoyX, JoyY
void updateInputData(int inputBit, bool isPressed) {
// setState(() {
if (isPressed) {
inputByte[0] = inputByte[0] | inputBit;
} else {
inputByte[0] = inputByte[0] & ~inputBit;
}
// });
_androidHidGamepad.sendInputData(inputByte);
debugPrint("Current Mask Sent: $inputByte");
}
void updateJoystick(double x, double y) {
// flutter_joystick gives -1.0 to 1.0. We multiply by 127 for our HID limits.
inputByte[1] = (x * 127).toInt();
inputByte[2] = (y * 127).toInt();
_androidHidGamepad.sendInputData(inputByte);
debugPrint("Current Mask Sent: $inputByte");
}
@override
void initState() {
super.initState();
context.read<PairedDevicesCubit>().initSetup();
}
Future<void> connectToPCwithAdd(String? add) async {
if (add == null) return;
try {
await _androidHidGamepad.connectToPC(add);
setState(() {
_connectedDeviceName = add;
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Connected: $add'),
backgroundColor: Colors.green,
),
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Nope No Connected'),
backgroundColor: Colors.red,
),
);
}
}
}
Future<void> verifyConnection() async {
List<String>? pcInfo = await _androidHidGamepad.getConnectedDevice();
if (pcInfo != null && pcInfo.length >= 2) {
setState(() {
_connectedDeviceName = pcInfo[0];
});
_lastConnAddress = pcInfo[1];
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Connected: $_connectedDeviceName"),
backgroundColor: Colors.green,
),
);
}
} else {
try {
connectToPCwithAdd(_lastConnAddress);
} catch (e) {
debugPrint("error in old conn");
}
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Nope Not Connected'),
backgroundColor: Colors.red,
),
);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Stack(
children: [
Positioned(
top: 10,
left: 10,
child: Text(
_connectedDeviceName != null
? "Connected: $_connectedDeviceName"
: "NotConnected",
style: TextStyle(
color: _connectedDeviceName != null
? Colors.green
: Colors.red,
fontSize: 18,
),
),
),
if (_connectedDeviceName == null)
Center(
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
"Instructions",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 15),
const Text(
"1. Turn on BT in this phone and make it discoverable.\n"
"2. Open the app on phone and turn on BT on your laptop and pair the phone.\n"
"3. Press verify to connect. Enjoy!\n"
"If any problem occurs, forget the device from PC and pair again.",
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: verifyConnection,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 40,
vertical: 15,
),
),
child: const Text(
"Verify & Connect",
style: TextStyle(fontSize: 16),
),
),
],
),
),
),
if (_connectedDeviceName != null) ...[
Positioned(
top: 10,
right: 10,
child: Row(
children: [
OutlinedButton(
onPressed: () {
_androidHidGamepad.disconnect();
setState(() {
_connectedDeviceName = null;
});
},
style: OutlinedButton.styleFrom(
side: const BorderSide(
color: CupertinoColors.systemRed,
width: 1.5,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Icon(
CupertinoIcons.power,
color: CupertinoColors.systemRed,
),
),
],
),
),
Positioned(
bottom: 80,
right: 80,
child: SizedBox(
width: 180,
height: 180,
child: Stack(
children: [
Positioned(
top: 0,
left: 60,
child: gamePadButtonXYAB(
'Y',
Colors.yellow.shade700,
8,
updateInputData,
),
),
Positioned(
top: 60,
left: 0,
child: gamePadButtonXYAB(
'X',
Colors.blue,
4,
updateInputData,
),
),
Positioned(
top: 60,
right: 0,
child: gamePadButtonXYAB(
'B',
Colors.red,
2,
updateInputData,
),
),
Positioned(
bottom: 0,
left: 60,
child: gamePadButtonXYAB(
'A',
Colors.green,
1,
updateInputData,
),
),
],
),
),
),
Positioned(
bottom: 80,
left: 80,
child: Joystick(
mode: JoystickMode.all,
base: Container(
width: 180,
height: 180,
decoration: BoxDecoration(
color: Colors.grey.shade400,
shape: BoxShape.circle,
),
),
stick: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Colors.grey,
shape: BoxShape.circle,
),
),
listener: (details) {
updateJoystick(details.x, details.y);
},
),
),
],
],
),
),
);
}
}
Widget gamePadButtonXYAB(
String name,
Color color,
int bitValue,
Function updateBit,
) {
return SizedBox(
width: 60,
height: 60,
child: Listener(
onPointerDown: (event) {
updateBit(bitValue, true);
},
onPointerUp: (event) {
updateBit(bitValue, false);
},
child: ElevatedButton(
onPressed: () {
debugPrint("PRESS: $name");
},
style: ElevatedButton.styleFrom(
shape: CircleBorder(),
padding: EdgeInsets.zero,
backgroundColor: color,
foregroundColor: Colors.white,
),
child: Text(
name,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
),
),
);
}