style: refactored arrows in own file
This commit is contained in:
parent
e9ba842e21
commit
5a763a5de2
57
lib/arrows.dart
Normal file
57
lib/arrows.dart
Normal file
|
@ -0,0 +1,57 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
enum ArrowDirection {
|
||||
left(Icons.arrow_back),
|
||||
down(Icons.arrow_downward),
|
||||
up(Icons.arrow_upward),
|
||||
right(Icons.arrow_forward);
|
||||
|
||||
const ArrowDirection(this.icon);
|
||||
|
||||
final IconData icon;
|
||||
}
|
||||
|
||||
class Note {
|
||||
final double time;
|
||||
final ArrowDirection direction;
|
||||
double position = 0;
|
||||
bool wasHit = false;
|
||||
|
||||
Note({required this.time, required this.direction});
|
||||
}
|
||||
|
||||
class Arrows extends StatelessWidget {
|
||||
final List<Note> notes;
|
||||
final double position;
|
||||
|
||||
const Arrows({super.key, required this.notes, required this.position});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: notes.map((note) {
|
||||
double position = note.position * 10000; // * 20 * MediaQuery.of(context).size.height;
|
||||
|
||||
return Arrow(
|
||||
position: position,
|
||||
direction: note.direction,
|
||||
);
|
||||
}).toList());
|
||||
}
|
||||
}
|
||||
|
||||
class Arrow extends StatelessWidget {
|
||||
final double position;
|
||||
final ArrowDirection direction;
|
||||
|
||||
const Arrow({super.key, required this.position, required this.direction});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Positioned(
|
||||
left: MediaQuery.of(context).size.width / 2 - 50, // Center the arrow
|
||||
bottom: position + 50,
|
||||
child: Icon(size: 100, color: Colors.redAccent.shade400, direction.icon),
|
||||
);
|
||||
}
|
||||
}
|
238
lib/level.dart
238
lib/level.dart
|
@ -3,6 +3,8 @@ import 'dart:io';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:sense_the_rhythm/arrows.dart';
|
||||
import 'package:sense_the_rhythm/simfile.dart';
|
||||
|
||||
class Level extends StatefulWidget {
|
||||
|
@ -13,22 +15,11 @@ class Level extends StatefulWidget {
|
|||
State<Level> createState() => _LevelState();
|
||||
}
|
||||
|
||||
enum ArrowDirection {
|
||||
left(Icons.arrow_back),
|
||||
down(Icons.arrow_downward),
|
||||
up(Icons.arrow_upward),
|
||||
right(Icons.arrow_forward);
|
||||
|
||||
const ArrowDirection(this.icon);
|
||||
|
||||
final IconData icon;
|
||||
}
|
||||
|
||||
class Note {
|
||||
final double time;
|
||||
final ArrowDirection direction;
|
||||
|
||||
const Note({required this.time, required this.direction});
|
||||
class InputDirection {
|
||||
bool up = false;
|
||||
bool down = false;
|
||||
bool left = false;
|
||||
bool right = false;
|
||||
}
|
||||
|
||||
class _LevelState extends State<Level> {
|
||||
|
@ -41,6 +32,9 @@ class _LevelState extends State<Level> {
|
|||
StreamSubscription? _durationSubscription;
|
||||
StreamSubscription? _positionSubscription;
|
||||
|
||||
final FocusNode _focusNode = FocusNode();
|
||||
InputDirection inputDirection = InputDirection();
|
||||
|
||||
List<Note> notes = [];
|
||||
|
||||
@override
|
||||
|
@ -82,6 +76,38 @@ class _LevelState extends State<Level> {
|
|||
player.onPositionChanged.listen((Duration p) {
|
||||
// print('Current position: $p');
|
||||
setState(() => _position = p);
|
||||
for (final note in notes) {
|
||||
note.position = note.time - p.inMilliseconds / 60000.0;
|
||||
|
||||
if (!note.wasHit && note.position.abs() < 0.5 * 1.0 / 60.0) {
|
||||
bool keypressCorrect = false;
|
||||
switch (note.direction) {
|
||||
case ArrowDirection.up:
|
||||
keypressCorrect = inputDirection.up;
|
||||
break;
|
||||
case ArrowDirection.down:
|
||||
keypressCorrect = inputDirection.down;
|
||||
break;
|
||||
case ArrowDirection.right:
|
||||
keypressCorrect = inputDirection.right;
|
||||
break;
|
||||
case ArrowDirection.left:
|
||||
keypressCorrect = inputDirection.left;
|
||||
break;
|
||||
}
|
||||
if (keypressCorrect) {
|
||||
print("you hit!");
|
||||
note.wasHit = true;
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('This is a toast message'),
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
String simfilePath = Directory(widget.stepmaniaFolderPath)
|
||||
|
@ -108,12 +134,12 @@ class _LevelState extends State<Level> {
|
|||
if (arrowIndex < 0 || arrowIndex > 3) {
|
||||
continue;
|
||||
}
|
||||
double beat =
|
||||
measureIndex * 4.0 + (noteIndex.toDouble() / measure.length) * 4.0;
|
||||
double minutesPerBeat = 1.0 / bpm;
|
||||
double offsetMinutes = simfile!.offset / 60.0;
|
||||
notes.add(Note(
|
||||
time: (measureIndex * 4.0 +
|
||||
(noteIndex.toDouble() / measure.length) * 4.0) *
|
||||
1.0 /
|
||||
bpm +
|
||||
simfile!.offset / 60.0,
|
||||
time: beat * minutesPerBeat + offsetMinutes,
|
||||
direction: ArrowDirection.values[arrowIndex]));
|
||||
}
|
||||
}
|
||||
|
@ -125,75 +151,97 @@ class _LevelState extends State<Level> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
icon: Icon(_isPlaying ? Icons.pause : Icons.play_arrow),
|
||||
onPressed: () {
|
||||
if (_isPlaying) {
|
||||
player.pause();
|
||||
setState(() {
|
||||
_isPlaying = false;
|
||||
});
|
||||
} else {
|
||||
player.resume();
|
||||
setState(() {
|
||||
_isPlaying = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
title: Text(widget.stepmaniaFolderPath.split('/').last),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.close),
|
||||
onPressed: () => Navigator.pop(context))
|
||||
],
|
||||
bottom: PreferredSize(
|
||||
preferredSize: Size(double.infinity, 1.0),
|
||||
child: LinearProgressIndicator(
|
||||
value: (_duration != null &&
|
||||
_position != null &&
|
||||
_position!.inMilliseconds > 0 &&
|
||||
_position!.inMilliseconds < _duration!.inMilliseconds)
|
||||
? _position!.inMilliseconds / _duration!.inMilliseconds
|
||||
: 0.0,
|
||||
)),
|
||||
),
|
||||
body: Stack(children: [
|
||||
...notes.map((note) {
|
||||
return Arrow(
|
||||
position: _position != null
|
||||
? (note.time - _position!.inMilliseconds / 60000.0) *
|
||||
20 *
|
||||
MediaQuery.of(context).size.height
|
||||
: 0.0,
|
||||
direction: note.direction,
|
||||
);
|
||||
}),
|
||||
Positioned(
|
||||
top: 50,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
left: 0,
|
||||
child: Text(
|
||||
"Great!",
|
||||
textScaler: TextScaler.linear(4),
|
||||
textAlign: TextAlign.center,
|
||||
return KeyboardListener(
|
||||
focusNode: _focusNode,
|
||||
autofocus: true,
|
||||
onKeyEvent: (event) {
|
||||
bool isDown = false;
|
||||
if (event is KeyDownEvent) {
|
||||
isDown = true;
|
||||
} else if (event is KeyUpEvent) {
|
||||
isDown = false;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
switch (event.logicalKey) {
|
||||
case LogicalKeyboardKey.arrowUp:
|
||||
inputDirection.up = isDown;
|
||||
break;
|
||||
case LogicalKeyboardKey.arrowDown:
|
||||
inputDirection.down = isDown;
|
||||
break;
|
||||
case LogicalKeyboardKey.arrowLeft:
|
||||
inputDirection.left = isDown;
|
||||
break;
|
||||
case LogicalKeyboardKey.arrowRight:
|
||||
inputDirection.right = isDown;
|
||||
break;
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
icon: Icon(_isPlaying ? Icons.pause : Icons.play_arrow),
|
||||
onPressed: () {
|
||||
if (_isPlaying) {
|
||||
player.pause();
|
||||
setState(() {
|
||||
_isPlaying = false;
|
||||
});
|
||||
} else {
|
||||
player.resume();
|
||||
setState(() {
|
||||
_isPlaying = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
title: Text(widget.stepmaniaFolderPath.split('/').last),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.close),
|
||||
onPressed: () => Navigator.pop(context))
|
||||
],
|
||||
bottom: PreferredSize(
|
||||
preferredSize: Size(double.infinity, 1.0),
|
||||
child: LinearProgressIndicator(
|
||||
value: (_duration != null &&
|
||||
_position != null &&
|
||||
_position!.inMilliseconds > 0 &&
|
||||
_position!.inMilliseconds < _duration!.inMilliseconds)
|
||||
? _position!.inMilliseconds / _duration!.inMilliseconds
|
||||
: 0.0,
|
||||
)),
|
||||
),
|
||||
Positioned(
|
||||
left: MediaQuery.of(context).size.width / 2 - 50,
|
||||
bottom: 50,
|
||||
child: Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
// color: Colors.blue,
|
||||
border: Border.all(color: Colors.black, width: 10)),
|
||||
body: Stack(children: [
|
||||
Arrows(
|
||||
notes: notes,
|
||||
position: _position != null
|
||||
? _position!.inMilliseconds.toDouble()
|
||||
: 0.0),
|
||||
Positioned(
|
||||
top: 50,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
left: 0,
|
||||
child: Text(
|
||||
"Great!",
|
||||
textScaler: TextScaler.linear(4),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
]));
|
||||
Positioned(
|
||||
left: MediaQuery.of(context).size.width / 2 - 50,
|
||||
bottom: 50,
|
||||
child: Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.black, width: 10)),
|
||||
),
|
||||
),
|
||||
])),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -204,19 +252,3 @@ class _LevelState extends State<Level> {
|
|||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class Arrow extends StatelessWidget {
|
||||
final double position;
|
||||
final ArrowDirection direction;
|
||||
|
||||
const Arrow({super.key, required this.position, required this.direction});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Positioned(
|
||||
left: MediaQuery.of(context).size.width / 2 - 50, // Center the arrow
|
||||
bottom: position + 50,
|
||||
child: Icon(size: 100, color: Colors.redAccent.shade400, direction.icon),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue