import 'dart:convert'; import 'dart:io'; import 'package:background_downloader/background_downloader.dart'; import 'package:filepicker_windows/filepicker_windows.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:mhunt_launcher/Backend/Backend.dart'; import 'package:mhunt_launcher/Backend/InstallHelper.dart'; import 'package:mhunt_launcher/Backend/ProcessMan.dart'; import 'package:mhunt_launcher/Widgets/CustomWidgets.dart'; import 'package:mhunt_launcher/sidebar.dart'; import 'package:process_run/process_run.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:path/path.dart' as Path; import 'Backend/DebugHelper.dart'; import 'package:http/http.dart' as http; import 'Backend/FileHashEntry.dart'; import 'Backend/Structures.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @override State createState() => _HomePageState(); } class _HomePageState extends State { Map> downloadQueue = {}; Map totalDownloadSize = {}; List missingFilesForSelectedGame = []; List leaderboardForSelectedGame = []; Map myStatsForSelectedGame = {}; List> newsForSelectedGame = []; bool calculatingFiles = false; Map> downloadProgress = {}; int selectedSidebarIndex = 0; bool downloadRunning = false; int unwaitingFiles = 0; int dashboardSelectedGameIndex = 0; @override void initState() { // TODO: implement initState super.initState(); kickstartAnimations(); SetSelectedDashboardGame(0); } void kickstartAnimations() async { await Future.delayed(const Duration(milliseconds: 500)); setState(() { op1 = 1; op2 = 1; op3 = 1; }); } @override Widget build(BuildContext context) { final screenHeight = MediaQuery.of(context).size.height; final screenWidth = MediaQuery.of(context).size.width; Widget content = Library(); if (selectedSidebarIndex == 1) { content = Dashboard(); } else if (selectedSidebarIndex == 2) { content = Downloads(); } return Scaffold( backgroundColor: Colors.transparent, body: CustomBody( context: context, onAnimEnd: () { setState(() {}); }, child: Row( children: [ SideBar( width: screenWidth * 0.2, height: screenHeight, ), SizedBox( width: screenWidth * 0.8, height: screenHeight, child: content, ) ], ), ), ); } Widget Library() { return Center( child: Wrap( spacing: 20, children: List.generate(Backend.Games.length, (index) { GameData gameData = Backend.Games[index]; return LibGameCard(gameData); }), ), ); } Widget Downloads() { if (downloadQueue.length <= 0) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.download_done_sharp, size: 100, color: Colors.white.withOpacity(0.5), ), Text( "No ongoing Downloads", style: TextStyle(fontSize: 40, color: Colors.white.withOpacity(0.4)), ), ], ), ); } return Container( child: Container( padding: EdgeInsets.all(100), child: GlassCard( child: Column( children: List.generate(downloadQueue.keys.length, (index) { int gameId = downloadQueue.keys.toList()[index]; return DownloadCard(gameId, downloadQueue[gameId]!); }), )))); } Widget DownloadCard(int gameId, List files) { int doneSize = 0; if (downloadProgress.containsKey(gameId)) { Map _progress = downloadProgress[gameId]!; _progress.forEach((key, value) { doneSize += value.toInt(); }); } double totalSize = totalDownloadSize[gameId]! / 1024 / 1024; double doneSizeMb = doneSize / 1024 / 1024; double progress = doneSizeMb / totalSize; return Padding( padding: const EdgeInsets.all(8.0), child: GlassCard( child: Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Container( decoration: BoxDecoration( image: DecorationImage( image: AssetImage(Backend.Games[gameId].imagePath)), borderRadius: BorderRadius.circular(50)), width: 160, height: 80, ), Column( children: [ Text( Backend.Games[gameId].name, style: TextStyle(fontSize: 20), ), Text((doneSizeMb).toStringAsFixed(2) + " MB" + " / " + totalSize.toStringAsFixed(2) + " MB") ], ) ], ), Expanded( child: Padding( padding: const EdgeInsets.fromLTRB(20, 50, 20, 20), child: LinearProgressIndicator( value: progress, backgroundColor: Colors.black.withOpacity(0.2), color: Colors.blue, ), ), ), InkWell( child: Icon(Icons.cancel, size: 40, color: Colors.red.withOpacity(0.6)), onTap: () { downloadQueue.remove(gameId); downloadProgress.remove(gameId); downloadRunning = false; SetSelectedDashboardGame(gameId); setState(() {}); }, ) // Icon(Icons.download,size: 40,color: Colors.green.withOpacity(0.4),) ], ) ], ), )), ); } Widget SideBar( {required double width, required double height, int selectedIndex = 0}) { return GlassContainer( child: Container( width: width, height: height, child: Column( children: [ SizedBox( height: 100, ), SidebarTitle("Library", Icons.laptop_chromebook_outlined, index: 0), SidebarTitle("Dashboard", Icons.dashboard, index: 1), SidebarTitle("Downloads", Icons.download, index: 2), ], ), ), opacity: 0.15, color: Colors.blueGrey); } Widget SidebarTitle(String title, IconData iconData, {int index = 0}) { return InkWell( onTap: () { setState(() { selectedSidebarIndex = index; }); }, child: Padding( padding: EdgeInsets.symmetric(vertical: 0), child: GlassContainer( opacity: selectedSidebarIndex == index ? 0.1 : 0, color: selectedSidebarIndex == index ? Colors.white : Colors.transparent, child: Container( height: 75, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(iconData), SizedBox( width: 20, ), Text( title, style: TextStyle(fontSize: 20), ), ], ), ), ), ), ); } String hoveringGameCard = ""; Widget LibGameCard(GameData gameData) { return InkWell( onTap: () { selectedSidebarIndex = 1; SetSelectedDashboardGame(gameData.id); }, onHover: (val) { if (val) { hoveringGameCard = gameData.name; } else if (hoveringGameCard == gameData.name) { hoveringGameCard = ""; } setState(() {}); }, child: AnimatedContainer( duration: const Duration(milliseconds: 500), decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), color: Colors.black, image: DecorationImage( image: AssetImage(gameData.imagePath), fit: BoxFit.fill)), height: 200, width: 300, child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ hoveringGameCard != gameData.name ? Container() : Expanded( child: GlassContainer( child: Center( child: Text( gameData.description, textAlign: TextAlign.center, ), ), opacity: 0.5, color: Colors.black)), GlassContainer( child: Padding( padding: const EdgeInsets.all(8.0), child: true? Container():Row( children: [ Text(gameData.name), gameData.isAvailable ? GlassButton( onTap: () {}, child: Text("Install"), width: 100, color: Colors.blue, opacity: 0.8) : Text("Coming soon") ], mainAxisAlignment: MainAxisAlignment.spaceBetween, ), ), opacity: 0.9, color: Colors.black), ], ), ), ); } void SetSelectedDashboardGame(int i) async { calculatingFiles = true; setState(() {}); dashboardSelectedGameIndex = i; if (!Backend.Games[i].isAvailable) { return; } leaderboardForSelectedGame = await Backend.GetLeaderboard(Backend.Games[i].code); myStatsForSelectedGame = await Backend.GetUserStatsForGame(i); newsForSelectedGame = await Backend.GetNewsForGame(i); setState(() {}); missingFilesForSelectedGame = await InstallHelper.GetMissingFiles(i, force: true); calculatingFiles = false; setState(() {}); } void OnGameActionButtonClicked() { if (calculatingFiles) { return; } if(isUninstalling){return;} // if(downloadRunning){ // Debug.Log("Download running"); // setState(() {selectedSidebarIndex = (2);}); // return;} if (missingFilesForSelectedGame.length <= 0) { if(runningProcs.containsKey(dashboardSelectedGameIndex)){ // runningProcs[dashboardSelectedGameIndex][0]. }else{ StartGame(dashboardSelectedGameIndex); } // ProcessMan.RunGame( // InstallHelper.GetExeFilePath(dashboardSelectedGameIndex)); } else { setState(() {selectedSidebarIndex = (2);}); if (downloadQueue.containsKey(dashboardSelectedGameIndex)) { Debug.Log("already downloading"); return; //Already downloading } StartDownload(dashboardSelectedGameIndex, missingFilesForSelectedGame); } } Map>> runningProcs = {}; void StartGame(int id)async{ if(runningProcs.containsKey(id)){return;} String exePath = InstallHelper.GetExeFilePath(dashboardSelectedGameIndex); // Process proc = await run(exePath,); runningProcs.putIfAbsent(id, ()=> run(exePath)); setState(() { }); await runningProcs[id]; runningProcs.remove(id); setState(() { }); } void StartDownload(int gameId, List missingList) { if(downloadQueue.containsKey(dashboardSelectedGameIndex)){return;} downloadQueue.putIfAbsent(dashboardSelectedGameIndex, () => []);//missingList totalDownloadSize.putIfAbsent(dashboardSelectedGameIndex, () => 0);//FileHashEntry.getTotalSizeInBytes(missingList).toDouble() downloadQueue[dashboardSelectedGameIndex]=missingList; totalDownloadSize[dashboardSelectedGameIndex] = FileHashEntry.getTotalSizeInBytes(missingList).toDouble(); CheckDownloads(); } bool isUninstalling = false; void UninstallGame(int id) async { if(runningProcs.containsKey(id)){return;} // String exeFilePath = InstallHelper.GetExeFilePath(id); // if(!File(exeFilePath).existsSync()){ // Debug.LogError("No game installed to uninstall"); // return; // } isUninstalling = true; setState(() {}); String path = InstallHelper.GetFilePath(id); Directory parent = Directory(path); if (parent.existsSync()) { await parent.delete(recursive: true); } parent.create(recursive: true); setState(() { isUninstalling = false; }); SetSelectedDashboardGame(id); } //DOWNLOAD// void CheckDownloads() async { if (downloadRunning) { return; } downloadRunning = true; while (downloadQueue.length > 0) { int gameId = downloadQueue.keys.toList()[0]; if (downloadQueue[gameId]!.length <= 0) { if (unwaitingFiles <= 0) { downloadQueue.remove(gameId); downloadProgress.remove(gameId); } await Future.delayed(const Duration(milliseconds: 500)); continue; } FileHashEntry entry = downloadQueue[gameId]![0]; double fileSizeMb = entry.bytes / 1024 / 1024; if (fileSizeMb < 20) { Debug.Log("Downloading ${entry.file} smaller file : " + fileSizeMb.toString()); if (fileSizeMb < 1 && unwaitingFiles < 10) { unwaitingFiles++; DownloadFile(entry, gameId).then((value) => unwaitingFiles--); } else { await DownloadFile(entry, gameId); } } else { Debug.Log( "Downloading ${entry.file} larger file : " + fileSizeMb.toString()); await DownloadLargeFile(entry, gameId); } // Debug.Log("${allAddedDownloads[gameId]!.length} : ${downloadQueue[gameId]!.length}" ); downloadQueue[gameId]!.removeAt(0); setState(() {}); } downloadRunning = false; } Future DownloadLargeFile(FileHashEntry entry, int gameId) async { String url = InstallHelper.GetFileUrl(entry.file, gameId); String savepath = "${InstallHelper.GetFilePath(gameId)}${entry.file}"; String filename = savepath.contains('\\') ? savepath.substring(savepath.lastIndexOf('\\') + 1) : savepath.substring(savepath.lastIndexOf('/') + 1); if (!File(savepath).parent.existsSync()) { File(savepath).parent.create(recursive: true); } // await DownloadFile(downloadQueue[gameId]![0], gameId); if (!downloadProgress.containsKey(gameId)) { downloadProgress.putIfAbsent(gameId, () => {entry: 0}); } final task = DownloadTask( url: url, filename: filename, directory: File(savepath).parent.path); await FileDownloader().download(task, onProgress: (progress) { downloadProgress[gameId]![entry] = entry.bytes * progress; setState(() {}); }); downloadProgress[gameId]![entry] = entry.bytes; } Future DownloadFile(FileHashEntry fileEntry, int gameId) async { if (!downloadProgress.containsKey(gameId)) { downloadProgress.putIfAbsent(gameId, () => {fileEntry: 0}); } String url = InstallHelper.GetFileUrl(fileEntry.file, gameId); var _response = await http.Client().send(http.Request('GET', Uri.parse(url))); // _total = _response.contentLength ?? 0; List _bytes = []; int prog = 0; _response.stream.listen((value) { setState(() { _bytes.addAll(value); prog += value.length; downloadProgress[gameId]![fileEntry] = prog; }); }).onDone(() async { File file = new File("${InstallHelper.GetFilePath(gameId)}${fileEntry.file}"); if (!file.parent.existsSync()) { await file.parent.create(recursive: true); } await file.writeAsBytes(_bytes); setState(() { // _image = file; }); }); } Widget Dashboard() { GameData selectedGameData = Backend.Games[dashboardSelectedGameIndex]; String filePath = InstallHelper.GetFilePath(dashboardSelectedGameIndex); bool folderExists = Directory(filePath).existsSync(); String ActionBtnTxt = "Install"; if (missingFilesForSelectedGame.length <= 0) { ActionBtnTxt = "Play"; } else { ActionBtnTxt = ("Download ${FileHashEntry.getTotalSizeInMbytes(missingFilesForSelectedGame).toStringAsFixed(0)} MB"); } if (calculatingFiles) { ActionBtnTxt = "Validating"; } if (downloadQueue.containsKey(dashboardSelectedGameIndex)) { ActionBtnTxt = "Downloading"; } if (isUninstalling) { ActionBtnTxt = "Uninstalling"; } if(runningProcs.containsKey(dashboardSelectedGameIndex)){ ActionBtnTxt = "Running"; } Widget ActionButton = GlassButton( onTap: OnGameActionButtonClicked, child: Text(ActionBtnTxt), width: 200, color: Colors.blue, opacity: 0.5, height: 50); final screenHeight = MediaQuery.of(context).size.height; List leaderboardList = []; // leaderboardList.add(TableRow(children: [Text("username"),Text("kills"), Text("deaths")])); // leaderboardList.add(TableRow()); leaderboardList .addAll(List.generate(leaderboardForSelectedGame.length, (i) { return DataRow(cells: [ DataCell(Text(leaderboardForSelectedGame[i].place.toString())), DataCell(Text(leaderboardForSelectedGame[i].username)), DataCell(Text(leaderboardForSelectedGame[i].kills.toString())), DataCell(Text(leaderboardForSelectedGame[i].deaths.toString())), DataCell(Text(leaderboardForSelectedGame[i].xp.toString())), ]); })); return Theme( data: ThemeData(splashColor: Colors.transparent), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( height: screenHeight * 0.18, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text( selectedGameData.name, style: TextStyle(fontSize: 25), ), Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(Backend.Games.length, (index) { bool isSelected = index == dashboardSelectedGameIndex; return InkWell( onTap: () { SetSelectedDashboardGame(index); setState(() {}); }, child: AnimatedContainer( duration: const Duration(milliseconds: 100), margin: EdgeInsets.all(9), height: 80 * (isSelected ? 1.5 : 1), width: 130 * (isSelected ? 1.5 : 1), decoration: BoxDecoration( image: DecorationImage( image: AssetImage(Backend.Games[index].imagePath), fit: BoxFit.fill), borderRadius: BorderRadius.circular(20)), ), ); }), ), ], ), ), !Backend.Games[dashboardSelectedGameIndex].isAvailable ? Center( child: Text("Coming Soon", style: TextStyle(fontSize: 40))) : Container( height: screenHeight * 0.72, child: Column( children: [ Container( padding: EdgeInsets.symmetric(horizontal: 50), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ GlassCard( color: Colors.green, child: Padding( padding: const EdgeInsets.all(8.0), child: Row( children: [ Text("My Stats", style: TextStyle(color: Colors.blueGrey)), SizedBox( width: 50, ), Padding( padding: const EdgeInsets.all(8.0), child: Text( "Kills : ${myStatsForSelectedGame['kills']}"), ), Padding( padding: const EdgeInsets.all(8.0), child: Text( "Deaths : ${myStatsForSelectedGame['deaths']}"), ), Padding( padding: const EdgeInsets.all(8.0), child: Text( "Assist : ${myStatsForSelectedGame['assists']}"), ), ], ), ), ), Row( children: [ ActionButton, PopupMenuButton( iconColor: Colors.blueGrey, onSelected: (val) { if (val .toLowerCase() .contains("uninstall")) { UninstallGame(dashboardSelectedGameIndex); } }, itemBuilder: (BuildContext context) { return {'Uninstall'}.map((String choice) { return PopupMenuItem( value: choice, child: Text(choice), ); }).toList(); }, ), ], ) ], ), ), Container( height: screenHeight * 0.6, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ SingleChildScrollView( scrollDirection: Axis.vertical, child: Column( children: [ Padding( padding: const EdgeInsets.fromLTRB( 0, 20, 0, 0), child: Text( "Leaderboard", style: TextStyle(fontSize: 20), ), ), DataTable( dataTextStyle: TextStyle(color: Colors.white), headingTextStyle: TextStyle(color: Colors.white), dividerThickness: 0, columns: [ DataColumn( label: Expanded(child: Text("Place"))), DataColumn( label: Expanded( child: Text("Username"))), DataColumn( label: Expanded(child: Text("Kills"))), DataColumn( label: Expanded( child: Text("Deaths"))), DataColumn( label: Expanded(child: Text("XP"))), ], rows: leaderboardList, ), ], ), ), SizedBox( width: 50, ), Padding( padding: const EdgeInsets.all(20.0), child: GlassCard( child: Container( padding: EdgeInsets.symmetric(horizontal: 10), width: 350, child: Column( children: [ Padding( padding: const EdgeInsets.symmetric( vertical: 12.0, horizontal: 20), child: Text( "News", style: TextStyle(fontSize: 20), ), ), Expanded( child: SingleChildScrollView( child: Column( children: List.generate( newsForSelectedGame.length, (index) { return NewsCard( newsForSelectedGame[index] ['title'], newsForSelectedGame[index] ['message']); }), ), ), ) ], ), )), ) ], )) ], ), ), Container( //Install path footer padding: EdgeInsets.symmetric(horizontal: 10), alignment: Alignment.bottomCenter, child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row(children: [Text("Install path "), Text(filePath)]), GlassButton( onTap: () { DirectoryPicker file = DirectoryPicker(); final dir = file.getDirectory(); Backend.prefs!.setString( '${selectedGameData.name}_path', dir!.path); setState(() {}); }, child: Text("Change Install Location"), width: 250) ], ), SizedBox( height: 10, ) ], ), ) ], ), ); } Widget NewsCard(String title, String message) { return Padding( padding: const EdgeInsets.all(5.0), child: GlassCard( child: Row( children: [ Padding( padding: const EdgeInsets.all(18.0), child: Icon( Icons.newspaper, color: Colors.blueGrey, ), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle(fontSize: 18), textAlign: TextAlign.left, ), Text( message, overflow: TextOverflow.clip, ) ], ) ], )), ); } }