sense_the_rythm

rythm game for ESense Earable
git clone git://source.orangerot.dev:/university/sense_the_rythm.git
Log | Files | Refs | README | LICENSE

level_selection.dart (5479B)


      1 import 'dart:io';
      2 
      3 import 'package:flutter/material.dart';
      4 import 'package:permission_handler/permission_handler.dart';
      5 import 'package:shared_preferences/shared_preferences.dart';
      6 import 'package:file_picker/file_picker.dart';
      7 import 'package:sense_the_rhythm/utils/esense_input.dart';
      8 import 'package:sense_the_rhythm/utils/simfile.dart';
      9 import 'package:sense_the_rhythm/widgets/connection_status_button.dart';
     10 import 'package:sense_the_rhythm/widgets/level_list_entry.dart';
     11 
     12 class LevelSelection extends StatefulWidget {
     13   const LevelSelection({super.key});
     14 
     15   @override
     16   State<LevelSelection> createState() => _LevelSelectionState();
     17 }
     18 
     19 class _LevelSelectionState extends State<LevelSelection> {
     20   String? _stepmaniaCoursesPath;
     21   List<Simfile> _stepmaniaCoursesFolders = [];
     22   List<Simfile> _stepmaniaCoursesFoldersFiltered = [];
     23 
     24   @override
     25   void initState() {
     26     super.initState();
     27     _loadFolderPath();
     28   }
     29 
     30   /// gets folder path from persistent storage and updates state with loaded simfiles
     31   Future<void> _loadFolderPath() async {
     32     SharedPreferences prefs = await SharedPreferences.getInstance();
     33     final String? stepmaniaCoursesPathSetting =
     34         prefs.getString('stepmania_courses');
     35 
     36     if (stepmaniaCoursesPathSetting == null) return;
     37     List<Simfile> stepmaniaCoursesFoldersFuture =
     38         await _listFilesAndFolders(stepmaniaCoursesPathSetting);
     39 
     40     setState(() {
     41       _stepmaniaCoursesPath = stepmaniaCoursesPathSetting;
     42       _stepmaniaCoursesFolders = stepmaniaCoursesFoldersFuture;
     43       _stepmaniaCoursesFoldersFiltered = stepmaniaCoursesFoldersFuture;
     44     });
     45   }
     46 
     47   /// open folder selection dialog and save selected folder in persistent storage
     48   Future<void> _selectFolder() async {
     49     await Permission.manageExternalStorage.request();
     50     String? selectedFolder = await FilePicker.platform.getDirectoryPath();
     51 
     52     if (selectedFolder != null) {
     53       // Save the selected folder path
     54       SharedPreferences prefs = await SharedPreferences.getInstance();
     55       await prefs.setString('stepmania_courses', selectedFolder);
     56 
     57       _loadFolderPath();
     58     }
     59   }
     60 
     61   /// load all simfiles from a [directoryPath]
     62   Future<List<Simfile>> _listFilesAndFolders(String directoryPath) async {
     63     final directory = Directory(directoryPath);
     64     try {
     65       // List all files and folders in the directory
     66       List<Simfile> simfiles = directory
     67           .listSync(recursive: true)
     68           .where((entity) => entity.path.endsWith('.sm'))
     69           .map((entity) => Simfile(entity.path))
     70           .toList();
     71 
     72       List<bool> successfullLoads =
     73           await Future.wait(simfiles.map((simfile) => simfile.load()));
     74       List<Simfile> simfilesFiltered = [];
     75       for (int i = 0; i < simfiles.length; i++) {
     76         if (successfullLoads[i]) {
     77           simfilesFiltered.add(simfiles[i]);
     78         }
     79       }
     80 
     81       simfilesFiltered
     82           .sort((a, b) => a.tags['TITLE']!.compareTo(b.tags['TITLE']!));
     83 
     84       return simfilesFiltered;
     85     } catch (e) {
     86       print("Error reading directory: $e");
     87       return [];
     88     }
     89   }
     90 
     91   /// filter stepmaniaCoursesFolders based on [input]
     92   void _filterLevels(String input) {
     93     setState(() {
     94       _stepmaniaCoursesFoldersFiltered = _stepmaniaCoursesFolders
     95           .where((simfile) => simfile.tags["TITLE"]!
     96               .toLowerCase()
     97               .contains(input.toLowerCase()))
     98           .toList();
     99     });
    100   }
    101 
    102   @override
    103   Widget build(BuildContext context) {
    104     return Scaffold(
    105       appBar: AppBar(
    106         title: const Text('Sense the Rhythm'),
    107         actions: [
    108           Padding(
    109             padding: const EdgeInsets.symmetric(horizontal: 8.0),
    110             child: ValueListenableBuilder(
    111               valueListenable: ESenseInput.instance.deviceStatus,
    112               builder:
    113                   (BuildContext context, String deviceStatus, Widget? child) {
    114                 return ConnectionStatusButton(deviceStatus);
    115               },
    116             ),
    117           )
    118         ],
    119       ),
    120       body: Builder(builder: (context) {
    121         if (_stepmaniaCoursesPath == null) {
    122           return Text('Add a Directory with Stepmania Songs on \'+\'');
    123         } else if (_stepmaniaCoursesFolders.isEmpty) {
    124           return Text(
    125               'Folder empty. Add Stepmania Songs to Folder or select a different folder on \'+\'');
    126         } else {
    127           return Column(
    128             children: [
    129               Padding(
    130                 padding:
    131                     const EdgeInsets.symmetric(horizontal: 16.0, vertical: 0.0),
    132                 child: TextField(
    133                   onChanged: _filterLevels,
    134                   decoration: InputDecoration(
    135                       // icon: Icon(Icons.search),
    136                       hintText: 'Search'),
    137                 ),
    138               ),
    139               Expanded(
    140                 child: ListView.separated(
    141                   itemCount: _stepmaniaCoursesFoldersFiltered.length,
    142                   separatorBuilder: (BuildContext context, int index) =>
    143                       const Divider(),
    144                   itemBuilder: (context, index) {
    145                     Simfile simfile = _stepmaniaCoursesFoldersFiltered[index];
    146                     return LevelListEntry(simfile: simfile);
    147                   },
    148                 ),
    149               ),
    150             ],
    151           );
    152         }
    153       }),
    154       floatingActionButton: FloatingActionButton(
    155           onPressed: () {
    156             _selectFolder();
    157           },
    158           child: Icon(Icons.add)),
    159     );
    160   }
    161 }