import 'dart:io'; import 'package:flutter/material.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:file_picker/file_picker.dart'; import 'package:sense_the_rhythm/utils/esense_input.dart'; import 'package:sense_the_rhythm/utils/simfile.dart'; import 'package:sense_the_rhythm/widgets/connection_status_button.dart'; import 'package:sense_the_rhythm/widgets/level_list_entry.dart'; class LevelSelection extends StatefulWidget { const LevelSelection({super.key}); @override State createState() => _LevelSelectionState(); } class _LevelSelectionState extends State { String? _stepmaniaCoursesPath; List _stepmaniaCoursesFolders = []; List _stepmaniaCoursesFoldersFiltered = []; @override void initState() { super.initState(); _loadFolderPath(); } /// gets folder path from persistent storage and updates state with loaded simfiles Future _loadFolderPath() async { SharedPreferences prefs = await SharedPreferences.getInstance(); final String? stepmaniaCoursesPathSetting = prefs.getString('stepmania_courses'); if (stepmaniaCoursesPathSetting == null) return; List stepmaniaCoursesFoldersFuture = await _listFilesAndFolders(stepmaniaCoursesPathSetting); setState(() { _stepmaniaCoursesPath = stepmaniaCoursesPathSetting; _stepmaniaCoursesFolders = stepmaniaCoursesFoldersFuture; _stepmaniaCoursesFoldersFiltered = stepmaniaCoursesFoldersFuture; }); } /// open folder selection dialog and save selected folder in persistent storage Future _selectFolder() async { await Permission.manageExternalStorage.request(); 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(); } } /// load all simfiles from a [directoryPath] Future> _listFilesAndFolders(String directoryPath) async { final directory = Directory(directoryPath); try { // List all files and folders in the directory List simfiles = directory .listSync(recursive: true) .where((entity) => entity.path.endsWith('.sm')) .map((entity) => Simfile(entity.path)) .toList(); List successfullLoads = await Future.wait(simfiles.map((simfile) => simfile.load())); List simfilesFiltered = []; for (int i = 0; i < simfiles.length; i++) { if (successfullLoads[i]) { simfilesFiltered.add(simfiles[i]); } } simfilesFiltered .sort((a, b) => a.tags['TITLE']!.compareTo(b.tags['TITLE']!)); return simfilesFiltered; } catch (e) { print("Error reading directory: $e"); return []; } } /// filter stepmaniaCoursesFolders based on [input] void _filterLevels(String input) { setState(() { _stepmaniaCoursesFoldersFiltered = _stepmaniaCoursesFolders .where((simfile) => simfile.tags["TITLE"]! .toLowerCase() .contains(input.toLowerCase())) .toList(); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Sense the Rhythm'), actions: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: ValueListenableBuilder( valueListenable: ESenseInput.instance.deviceStatus, builder: (BuildContext context, String deviceStatus, Widget? child) { return ConnectionStatusButton(deviceStatus); }, ), ) ], ), 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 Column( children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 0.0), child: TextField( onChanged: _filterLevels, decoration: InputDecoration( // icon: Icon(Icons.search), hintText: 'Search'), ), ), Expanded( child: ListView.separated( itemCount: _stepmaniaCoursesFoldersFiltered.length, separatorBuilder: (BuildContext context, int index) => const Divider(), itemBuilder: (context, index) { Simfile simfile = _stepmaniaCoursesFoldersFiltered[index]; return LevelListEntry(simfile: simfile); }, ), ), ], ); } }), floatingActionButton: FloatingActionButton( onPressed: () { _selectFolder(); }, child: Icon(Icons.add)), ); } }