import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:audioplayers/audioplayers.dart'; class Level extends StatefulWidget { const Level({super.key, required this.stepmaniaFolderPath}); final String stepmaniaFolderPath; @override State createState() => _LevelState(); } class _LevelState extends State { final player = AudioPlayer(); bool _isPlaying = true; Duration? _duration; Duration? _position; StreamSubscription? _durationSubscription; StreamSubscription? _positionSubscription; @override void setState(VoidCallback fn) { // Subscriptions only can be closed asynchronously, // therefore events can occur after widget has been disposed. if (mounted) { super.setState(fn); } } @override void initState() { super.initState(); // Use initial values from player player.getDuration().then( (value) => setState(() { _duration = value; }), ); player.getCurrentPosition().then( (value) => setState(() { _position = value; }), ); _durationSubscription = player.onDurationChanged.listen((duration) { setState(() => _duration = duration); }); _positionSubscription = player.onPositionChanged.listen( (p) => setState(() => _position = p), ); } @override Widget build(BuildContext context) { player.onDurationChanged.listen((Duration d) { // print('Max duration: $d'); setState(() => _duration = d); }); player.onPositionChanged.listen((Duration p) { // print('Current position: $p'); setState(() => _position = p); }); String audioPath = Directory(widget.stepmaniaFolderPath) .listSync() .firstWhere((entity) => entity.path.endsWith('.ogg'), orElse: () => File('')) .path; player.play(DeviceFileSource(audioPath)); 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: [ Arrow( position: -100.0, ), Arrow( position: 00.0, ), Arrow( position: 100.0, ), Arrow( position: 200.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, // color: Colors.blue, border: Border.all(color: Colors.black, width: 10)), ), ), ])); } @override void dispose() { _durationSubscription?.cancel(); _positionSubscription?.cancel(); player.dispose(); super.dispose(); } } class Arrow extends StatelessWidget { final double position; const Arrow({super.key, required this.position}); @override Widget build(BuildContext context) { return Positioned( left: MediaQuery.of(context).size.width / 2 - 25, // Center the arrow top: position, child: Icon(size: 100, Icons.arrow_forward), ); } }