// ignore_for_file: prefer_const_literals_to_create_immutables, avoid_print import 'package:fip_parser_ui/globals.dart'; import 'package:fip_parser_ui/providers/home.dart'; import 'package:fip_parser_ui/providers/player.dart'; import 'package:flutter/material.dart'; import 'package:fip_parser_ui/queries.dart'; import 'package:graphql/client.dart'; import 'package:kplayer/kplayer.dart'; import 'package:miniplayer/miniplayer.dart'; import 'package:provider/provider.dart'; import 'package:retry/retry.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:http/http.dart' as http; import 'package:youtube_explode_dart/youtube_explode_dart.dart'; PlayerController? player; Track? currentTrack; List trackList = []; String radio = 'groove'; class HomeScreen extends StatefulWidget { const HomeScreen({Key? key, required this.title}) : super(key: key); final String title; @override State createState() => _HomeScreenState(); } class _HomeScreenState extends State { @override Widget build(BuildContext context) { final MiniplayerController controller = MiniplayerController(); const hours = 8; return Stack( children: [ Scaffold( body: SingleChildScrollView( child: Column( children: [ Container( color: Colors.grey[900], height: 50, child: Center( child: DropdownButton( dropdownColor: Colors.grey[900], value: radio, style: TextStyle(fontSize: 15, color: Colors.grey[300]), underline: const SizedBox(), iconSize: 22, onChanged: (String? newRadio) { setState(() { radio = newRadio!; getTracks(radio, hours); }); }, items: radioList), ), ), FutureBuilder>( future: getTracks(radio, hours), builder: ( BuildContext context, AsyncSnapshot> snapshot, ) { print(snapshot.connectionState); if (snapshot.connectionState == ConnectionState.waiting) { return Stack(children: [ Container( height: 10000, width: 10000, color: const Color(0xFF121212)), Center( child: Column(children: [ const SizedBox(height: 20), SizedBox( height: 30, width: 30, child: CircularProgressIndicator( color: Colors.amber[100], ), ), ]), ), ]); } else if (snapshot.connectionState == ConnectionState.done) { if (snapshot.hasError) { return const Text('Error'); } else if (snapshot.hasData) { return Table( defaultVerticalAlignment: TableCellVerticalAlignment.middle, children: snapshot.data! .map((item) => _buildTableRow(item, context)) .toList() ..insert( 0, _buildTableRow( Track( number: -1, title: 'TITRE', artiste: 'ARTISTE', album: 'ALBUM', id: 'URL'), context), ), ); } else { return const Text('Empty data'); } } else { return Text('State: ${snapshot.connectionState}'); } }, ), ], ), ), ), Consumer(builder: (context, playerProvider, _) { TextEditingController trackTitle = TextEditingController(); TextEditingController trackArtiste = TextEditingController(); trackTitle.text = currentTrack?.title ?? ''; trackArtiste.text = currentTrack?.artiste ?? ''; return Miniplayer( controller: controller, backgroundColor: Colors.grey[900]!, minHeight: 70, maxHeight: 370, builder: (height, percentage) { return Row( // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ currentTrack?.id != null ? Image.network( 'https://img.youtube.com/vi/${currentTrack?.id}/1.jpg', width: 93, ) : const SizedBox(width: 100), Expanded( child: Column(children: [ SizedBox( height: 20, child: TextField( enabled: false, maxLines: 1, controller: trackTitle, decoration: const InputDecoration( border: InputBorder.none, focusedBorder: InputBorder.none, enabledBorder: InputBorder.none, disabledBorder: InputBorder.none, contentPadding: EdgeInsets.only(left: 15), ), style: TextStyle( fontSize: 14, color: Colors.grey[300], ), ), ), SizedBox( height: 20, child: TextField( enabled: false, maxLines: 1, controller: trackArtiste, decoration: const InputDecoration( border: InputBorder.none, focusedBorder: InputBorder.none, enabledBorder: InputBorder.none, disabledBorder: InputBorder.none, contentPadding: EdgeInsets.only(left: 15), ), style: TextStyle( fontSize: 12, color: Colors.grey[500], ), ), ), ]), ), IconButton( icon: Icon(Icons.skip_previous, color: Colors.grey[500], size: 32), onPressed: () { if (currentTrack != null && currentTrack!.number > 1) { currentTrack = trackList.firstWhere((element) => element.number == currentTrack!.number - 1); playTrack(context, currentTrack!); } }), ElevatedButton( onPressed: () { player?.playing ?? false ? player?.pause() : player?.play(); playerProvider.reload(); }, child: Icon( player?.playing ?? false ? Icons.pause : Icons.play_arrow, color: Colors.grey[900], size: 32), style: ElevatedButton.styleFrom( shape: const CircleBorder(), padding: const EdgeInsets.all(12), primary: Colors.grey[300], onPrimary: Colors.grey[900], ), ), IconButton( icon: Icon(Icons.skip_next, color: Colors.grey[500], size: 32), onPressed: () { if (currentTrack != null && currentTrack!.number < trackList.last.number) { currentTrack = trackList.firstWhere((element) => element.number == currentTrack!.number + 1); playTrack(context, currentTrack!); } }), const Spacer(), SliderTheme( data: const SliderThemeData( thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7), overlayShape: RoundSliderThumbShape(enabledThumbRadius: 8), trackHeight: 2.5, ), child: SizedBox( width: 130, child: Slider( value: player?.volume ?? 1, max: 1, onChanged: (double value) { if (player?.volume != null) { player!.volume = value; playerProvider.reload(); } }, activeColor: Colors.grey[400], inactiveColor: Colors.grey[700], ), ), ), const SizedBox(width: 20) ], ); }, ); }) ], ); } } Widget trackLine(Track track) { return SizedBox( height: 30, child: Row( children: [Text(track.title + ' - ' + track.artiste)], ), ); } class Track { final int number; final String title; final String artiste; final String? album; String? id; Duration? duration; Track( {required this.number, required this.title, required this.artiste, this.album, this.id, this.duration}); } GraphQLClient initClient() { final _httpLink = HttpLink( 'https://openapi.radiofrance.fr/v1/graphql?x-token=$token', ); final GraphQLClient client = GraphQLClient( cache: GraphQLCache(), link: _httpLink, ); return client; } Future> getTracks(String radio, int hours) async { const int queryTimeout = 8; final client = initClient(); final now = (DateTime.now().millisecondsSinceEpoch ~/ 1000); final yesterday = now - (hours * 3600); final String radioQuery; if (radio == 'fip') { radioQuery = 'FIP'; } else { radioQuery = 'FIP_${radio.toUpperCase()}'; } final QueryOptions options = QueryOptions( document: gql(getSongs), pollInterval: const Duration(milliseconds: 50), variables: { 'start': yesterday, 'end': now, 'radio': radioQuery, }, ); QueryResult result; result = await retry( () async => await client .query(options) .timeout(const Duration(seconds: queryTimeout)), onRetry: (_) => print('Timeout, retry...'), ); if (result.hasException) { print(result.exception.toString()); } final List data = result.data?['grid'] ?? []; print(data.length.toString() + ' songs'); // ####### int trackNbr = 0; trackList.clear(); final yt = YoutubeExplode(); List resultUrlPool = []; for (Map track in data) { track = track['track']; trackNbr++; final String title = track['title']; final String artiste; if (track['mainArtists'].isNotEmpty) { artiste = track['mainArtists'].first; } else { artiste = ''; } final String album = track['albumTitle']; final thisTrack = Track(number: trackNbr, title: title, artiste: artiste, album: album); trackList.add(thisTrack); final secondMatch = artiste == '' ? album : artiste; final resultUrl = yt.search.search(title + ' ' + secondMatch); resultUrlPool.add(resultUrl); resultUrl.then((value) { try { trackList[trackNbr - 1].id = value.first.id.value; } catch (e) { print( 'Error: ' + trackList[trackNbr - 1].title + ' -> ' + e.toString()); } }); } trackList.sort((a, b) => a.number.compareTo(b.number)); final secondMatch = trackList[0].artiste == '' ? trackList[0].album : trackList[0].artiste; yt.search.search(trackList[0].title + ' ' + secondMatch!).then((resultUrl) { trackList[0].id = resultUrl.first.id.value; player = Player.network( "https://invidious.fdn.fr/embed/${trackList[0].id}?raw=1&?listen=1"); currentTrack = trackList[0]; }); return trackList; } TableRow _buildTableRow(Track track, BuildContext context) { final textStyle = TextStyle( fontWeight: track.number == -1 ? FontWeight.w200 : FontWeight.normal, color: Colors.grey[500], fontSize: track.number == -1 ? 15 : 14); const rowPadding = EdgeInsets.all(10); final yt = YoutubeExplode(); return TableRow( decoration: const BoxDecoration( color: Color(0xFF121212), ), children: [ TableCell( child: Padding( padding: rowPadding, child: InkWell( onTap: () async { if (track.number != -1) { playTrack(context, track); } }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Consumer( builder: (context, playerProvider, _) { return Text(track.title, style: TextStyle( fontWeight: FontWeight.normal, color: currentTrack == track ? Colors.green : Colors.grey[350], fontSize: 14)); }), const SizedBox(height: 5), if (track.number != -1) Text(track.artiste, style: TextStyle( fontWeight: FontWeight.normal, color: Colors.grey[500], fontSize: 13)) ]), )), ), TableCell( child: Padding( padding: rowPadding, child: Text(track.album ?? '', style: textStyle), ), ), TableCell( child: Padding( padding: rowPadding, child: InkWell( onTap: () async { if (track.id == null) { final secondMatch = track.artiste == '' ? track.album : track.artiste; final resultUrl = await yt.search .search(track.title + ' ' + secondMatch!); track.id = resultUrl.first.id.value; } if (track.id != null) { final response = await http.get(Uri.parse( 'https://ytdl.p2p.legal/ytdl.sh?${track.id}')); final dlLink = response.body; if (await canLaunchUrl(Uri.parse(dlLink))) { launchUrl(Uri.parse(dlLink)); } } }, child: Text('TÉLÉCHARGER', style: textStyle), )), ), ]); } Future playTrack(BuildContext context, Track track) async { var yt = YoutubeExplode(); PlayerProvider playerProvider = Provider.of(context, listen: false); HomeProvider homeProvider = Provider.of(context, listen: false); track = trackList[track.number - 1]; if (track.id == null) { final secondMatch = track.artiste == '' ? track.album : track.artiste; final resultUrl = await yt.search.search(track.title + ' ' + secondMatch!); track.id = resultUrl.first.id.value; } player?.dispose(); Future.delayed(const Duration(milliseconds: 5)); player = Player.network( "https://invidious.fdn.fr/embed/${track.id}?raw=1&?listen=1"); print(track.id); try { player!.play(); } catch (e) { print('Play error: ' + e.toString()); } Future.delayed(const Duration(milliseconds: 50)); player!.callback = (PlayerEvent event) { if (event.name == 'status') { var nextTrack = trackList.firstWhere((element) => element.number == track.number + 1); playTrack(context, nextTrack); } }; currentTrack = track; playerProvider.reload(); homeProvider.reload(); } // final radioList = [ // 'fip', // 'electro', // 'groove', // 'rock', // 'jazz', // 'pop', // 'reggae', // 'world', // 'nouveautes', // ]; List> get radioList { List> menuItems = [ const DropdownMenuItem(child: Text("FIP"), value: "fip"), const DropdownMenuItem(child: Text("Electro"), value: "electro"), const DropdownMenuItem(child: Text("Groove"), value: "groove"), const DropdownMenuItem(child: Text("Rock"), value: "rock"), const DropdownMenuItem(child: Text("Jazz"), value: "jazz"), const DropdownMenuItem(child: Text("Pop"), value: "pop"), const DropdownMenuItem(child: Text("Reggae"), value: "reggae"), const DropdownMenuItem(child: Text("World"), value: "world"), const DropdownMenuItem(child: Text("Nouveautes"), value: "nouveautes"), ]; return menuItems; }