summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/screens/level.dart6
-rw-r--r--lib/screens/level_selection.dart28
-rw-r--r--lib/utils/simfile.dart61
-rw-r--r--lib/widgets/arrows.dart3
-rw-r--r--lib/widgets/level_info_chip.dart37
-rw-r--r--lib/widgets/level_list_entry.dart24
6 files changed, 115 insertions, 44 deletions
diff --git a/lib/screens/level.dart b/lib/screens/level.dart
index e2b2195..9c5823b 100644
--- a/lib/screens/level.dart
+++ b/lib/screens/level.dart
@@ -244,11 +244,7 @@ class _LevelState extends State<Level> with SingleTickerProviderStateMixin {
)),
),
body: Stack(children: [
- Arrows(
- notes: notes,
- position: _position != null
- ? _position!.inMilliseconds.toDouble()
- : 0.0),
+ Arrows(notes: notes),
Positioned(
top: 50,
width: MediaQuery.of(context).size.width,
diff --git a/lib/screens/level_selection.dart b/lib/screens/level_selection.dart
index 41eb4f7..7241ad7 100644
--- a/lib/screens/level_selection.dart
+++ b/lib/screens/level_selection.dart
@@ -62,16 +62,24 @@ class _LevelSelectionState extends State<LevelSelection> {
try {
// List all files and folders in the directory
List<Simfile> simfiles = directory
- .listSync()
- .where((entity) => FileSystemEntity.isDirectorySync(entity.path))
- .map((entity) {
- Simfile simfile = Simfile(entity.path);
- simfile.load();
- return simfile;
- }).toList();
- simfiles.sort((a, b) => a.tags['TITLE']!.compareTo(b.tags['TITLE']!));
-
- return simfiles;
+ .listSync(recursive: true)
+ .where((entity) => entity.path.endsWith('.sm'))
+ .map((entity) => Simfile(entity.path))
+ .toList();
+
+ List<bool> successfullLoads =
+ await Future.wait(simfiles.map((simfile) => simfile.load()));
+ List<Simfile> 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 [];
diff --git a/lib/utils/simfile.dart b/lib/utils/simfile.dart
index 0af734f..c528084 100644
--- a/lib/utils/simfile.dart
+++ b/lib/utils/simfile.dart
@@ -1,5 +1,7 @@
import 'dart:io';
+import 'package:audioplayers/audioplayers.dart';
+
enum Difficulty { Beginner, Easy, Medium, Hard, Challenge, Edit }
// These are the standard note values:
@@ -36,12 +38,13 @@ class Chart {
}
class Simfile {
- String directoryPath;
- String? simfilePath;
+ String? directoryPath;
+ String simfilePath;
String? audioPath;
String? bannerPath;
String? lines;
+ Duration? duration;
// tags of simfile
Map<String, String> tags = {};
@@ -50,7 +53,7 @@ class Simfile {
Map<double, double> bpms = {};
double offset = 0;
- Simfile(this.directoryPath);
+ Simfile(this.simfilePath);
void _parseChart({required List<String> keys, required String value}) {
Chart chart = Chart();
@@ -119,33 +122,41 @@ class Simfile {
_parseChart(keys: keys, value: value);
}
- void load() {
- simfilePath = Directory(directoryPath)
- .listSync()
- .firstWhere((entity) => entity.path.endsWith('.sm'),
- orElse: () => File(''))
- .path;
-
- audioPath = Directory(directoryPath)
- .listSync()
- .firstWhere((entity) => entity.path.endsWith('.ogg'),
- orElse: () => File(''))
- .path;
-
- bannerPath = Directory(directoryPath)
- .listSync()
- .firstWhere((file) => file.path.toLowerCase().endsWith('banner.png'),
- orElse: () => File(''))
- .path;
-
- lines = File(simfilePath!).readAsStringSync();
-
+ Future<bool> load() async {
+ directoryPath = File(simfilePath).parent.path;
+ lines = File(simfilePath).readAsStringSync();
+
RegExp commentsRegExp = RegExp(r'//.*$');
lines = lines?.replaceAll(commentsRegExp, '');
RegExp fieldDataRegExp = RegExp(r'#([^;]+):([^;]*);');
for (final fieldData in fieldDataRegExp.allMatches(lines!)) {
- _parseTag(fieldData);
+ try {
+ _parseTag(fieldData);
+ } catch (err) {
+ return false;
+ }
}
+
+ String? musicFileName = tags["MUSIC"];
+ if (musicFileName == null) return false;
+ String? bannerFileName = tags["BANNER"];
+ if (bannerFileName == null) return false;
+
+ for (FileSystemEntity entity in Directory(directoryPath!).listSync()) {
+ if (entity.path.endsWith('.ogg')) {
+ audioPath = entity.path;
+ }
+ if (entity.path.endsWith('anner.png')) {
+ bannerPath = entity.path;
+ }
+ }
+
+ AudioPlayer audioplayer = AudioPlayer();
+ await audioplayer.setSource(DeviceFileSource(audioPath!));
+ duration = await audioplayer.getDuration();
+ audioplayer.dispose();
+
+ return true;
}
}
diff --git a/lib/widgets/arrows.dart b/lib/widgets/arrows.dart
index cf04e59..162f0f3 100644
--- a/lib/widgets/arrows.dart
+++ b/lib/widgets/arrows.dart
@@ -4,9 +4,8 @@ import 'package:sense_the_rhythm/widgets/arrow.dart';
class Arrows extends StatelessWidget {
final List<Note> notes;
- final double position;
- const Arrows({super.key, required this.notes, required this.position});
+ const Arrows({super.key, required this.notes});
@override
Widget build(BuildContext context) {
diff --git a/lib/widgets/level_info_chip.dart b/lib/widgets/level_info_chip.dart
new file mode 100644
index 0000000..8e4146c
--- /dev/null
+++ b/lib/widgets/level_info_chip.dart
@@ -0,0 +1,37 @@
+import 'package:flutter/material.dart';
+
+class LevelInfoChip extends StatelessWidget {
+ final String label;
+ final IconData icon;
+
+ const LevelInfoChip({super.key, required this.label, required this.icon});
+
+ @override
+ Widget build(BuildContext context) {
+ return OutlinedButton(
+ style: ButtonStyle(
+ shape: WidgetStateProperty.all(RoundedRectangleBorder(
+ borderRadius: BorderRadius.all(Radius.circular(5)))),
+ minimumSize: WidgetStateProperty.all(Size(10, 10)),
+ tapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ padding: WidgetStateProperty.all(
+ EdgeInsets.symmetric(vertical: 4.0, horizontal: 5.0))
+ ),
+ onPressed: () {},
+ child: Row(children: [
+ Icon(
+ icon,
+ size: 16,
+ ),
+ SizedBox(width: 4),
+ Text(
+ label,
+ style: TextStyle(
+ fontSize: 14,
+ fontWeight:
+ FontWeight.w200), // Adjust font size for smaller appearance
+ ),
+ ]),
+ );
+ }
+}
diff --git a/lib/widgets/level_list_entry.dart b/lib/widgets/level_list_entry.dart
index 832186f..abb4784 100644
--- a/lib/widgets/level_list_entry.dart
+++ b/lib/widgets/level_list_entry.dart
@@ -6,6 +6,7 @@ import 'package:sense_the_rhythm/utils/simfile.dart';
import 'package:sense_the_rhythm/screens/level.dart';
import 'package:sense_the_rhythm/widgets/esense_connect_dialog.dart';
import 'package:sense_the_rhythm/widgets/esense_not_connected_dialog.dart';
+import 'package:sense_the_rhythm/widgets/level_info_chip.dart';
class LevelListEntry extends StatelessWidget {
const LevelListEntry({
@@ -62,8 +63,27 @@ class LevelListEntry extends StatelessWidget {
return ListTile(
leading: Image.file(File(simfile.bannerPath!)),
trailing: Icon(Icons.play_arrow),
- title: Text(simfile.tags["TITLE"]!),
- subtitle: Text('3:45'),
+ title: Text(
+ simfile.tags["TITLE"]!,
+ style: TextStyle(fontWeight: FontWeight.bold),
+ ),
+ subtitle: Padding(
+ padding: const EdgeInsets.only(bottom: 2),
+ child: Row(
+ spacing: 2,
+ children: [
+ LevelInfoChip(
+ label:
+ '${simfile.duration!.inMinutes}:${simfile.duration!.inSeconds.remainder(60).toString().padLeft(2, "0")}',
+ icon: Icons.timer_outlined,
+ ),
+ LevelInfoChip(
+ label: '${simfile.bpms.entries.first.value.toInt()} BPM',
+ icon: Icons.graphic_eq,
+ ),
+ ],
+ ),
+ ),
onTap: () {
tapHandler(context);
},