import 'dart:io'; import 'package:esense_flutter/esense.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'level.dart'; class LevelSelection extends StatefulWidget { const LevelSelection({super.key}); @override State createState() => _LevelSelectionState(); } class _LevelSelectionState extends State { String? stepmaniaCoursesPath; List stepmaniaCoursesFolders = []; ESenseManager? eSenseManager; String _deviceStatus = ''; String eSenseDeviceName = ''; bool connected = false; bool sampling = false; @override void initState() { super.initState(); loadFolderPath(); } Future _askForPermissions() async { if (!(await Permission.bluetoothScan.request().isGranted && await Permission.bluetoothConnect.request().isGranted)) { print( 'WARNING - no permission to use Bluetooth granted. Cannot access eSense device.'); } // for some strange reason, Android requires permission to location for Bluetooth to work.....? if (Platform.isAndroid) { if (!(await Permission.locationWhenInUse.request().isGranted)) { print( 'WARNING - no permission to access location granted. Cannot access eSense device.'); } } } Future _listenToESense() async { await _askForPermissions(); // if you want to get the connection events when connecting, // set up the listener BEFORE connecting... eSenseManager!.connectionEvents.listen((event) { print('CONNECTION event: $event'); // when we're connected to the eSense device, we can start listening to events from it // if (event.type == ConnectionType.connected) _listenToESenseEvents(); setState(() { connected = false; switch (event.type) { case ConnectionType.connected: _deviceStatus = 'connected'; connected = true; break; case ConnectionType.unknown: _deviceStatus = 'unknown'; break; case ConnectionType.disconnected: _deviceStatus = 'disconnected'; sampling = false; break; case ConnectionType.device_found: _deviceStatus = 'device_found'; break; case ConnectionType.device_not_found: _deviceStatus = 'device_not_found'; break; } }); }); } Future _connectToESense() async { if (!connected) { await _askForPermissions(); print('Trying to connect to eSense device...'); print(eSenseDeviceName); eSenseManager = ESenseManager(eSenseDeviceName); connected = await eSenseManager!.connect(); print('success!'); setState(() { _deviceStatus = connected ? 'connecting...' : 'connection failed'; }); _listenToESense(); } } Future loadFolderPath() async { SharedPreferences prefs = await SharedPreferences.getInstance(); final String? stepmaniaCoursesPathSetting = prefs.getString('stepmania_courses'); if (stepmaniaCoursesPathSetting == null) return; setState(() { stepmaniaCoursesPath = stepmaniaCoursesPathSetting; }); setState(() async { stepmaniaCoursesFolders = await listFilesAndFolders(stepmaniaCoursesPathSetting); }); } Future selectFolder() async { String? selectedFolder = await FilePicker.platform.getDirectoryPath(); if (selectedFolder != null) { // Save the selected folder path SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.setString('stepmania_courses', selectedFolder); loadFolderPath(); } } Future> listFilesAndFolders( String directoryPath) async { final directory = Directory(directoryPath); try { // List all files and folders in the directory return directory .listSync() .where((entity) => FileSystemEntity.isDirectorySync(entity.path)) .toList(); } catch (e) { print("Error reading directory: $e"); return []; } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Sense the Rhythm'), actions: [ IconButton( onPressed: () => showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Connect to ESense'), content: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { return Column( mainAxisSize: MainAxisSize.min, children: [ TextField( onChanged: (input) { setState(() { eSenseDeviceName = input; }); }, decoration: InputDecoration( border: OutlineInputBorder(), hintText: 'eSense-xxxx', labelText: 'Device name', ), ), Text(eSenseDeviceName), Text(_deviceStatus) ]); }), actions: [ TextButton( onPressed: () => Navigator.pop(context, 'Cancel'), child: const Text('Discard'), ), TextButton( onPressed: () => _connectToESense(), child: const Text('Connect'), ), ], ); }, ), icon: const Icon(Icons.bluetooth)) ], ), body: Builder(builder: (context) { if (stepmaniaCoursesPath == null) { return Text('Add a Directory with Stepmania Songs on \'+\''); } else if (stepmaniaCoursesFolders.isEmpty) { return Text( 'Folder empty. Add Stepmania Songs to Folder or select a different folder on \'+\''); } else { return ListView.separated( itemCount: stepmaniaCoursesFolders.length, separatorBuilder: (BuildContext context, int index) => const Divider(), itemBuilder: (context, index) { String thumbnailPath = Directory( stepmaniaCoursesFolders[index].path) .listSync() .firstWhere( (file) => file.path.toLowerCase().endsWith('banner.png'), orElse: () => File('')) .path; return ListTile( leading: Image.file(File(thumbnailPath)), trailing: Icon(Icons.play_arrow), title: Text(stepmaniaCoursesFolders[index].path.split('/').last), subtitle: Text('3:45'), onTap: () => Navigator.push( context, MaterialPageRoute( builder: (BuildContext context) => Level( stepmaniaFolderPath: stepmaniaCoursesFolders[index].path, ))), ); }, ); } }), floatingActionButton: FloatingActionButton( onPressed: () => {selectFolder()}, child: Icon(Icons.add)), ); } }