mhunt_launcher/lib/home.dart
2024-07-17 16:31:39 +05:30

857 lines
30 KiB
Dart

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<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Map<int, List<FileHashEntry>> downloadQueue = {};
Map<int, double> totalDownloadSize = {};
List<FileHashEntry> missingFilesForSelectedGame = [];
List<LeaderboardEntry> leaderboardForSelectedGame = [];
Map<String, dynamic> myStatsForSelectedGame = {};
List<Map<String, dynamic>> newsForSelectedGame = [];
bool calculatingFiles = false;
Map<int, Map<FileHashEntry, num>> 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<FileHashEntry> files) {
int doneSize = 0;
if (downloadProgress.containsKey(gameId)) {
Map<FileHashEntry, num> _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<int, Future<List<ProcessResult>>> 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 + " username ${Backend.UserJson['username']} password ${Backend.UserJson['passwd']}"));
setState(() {
});
await runningProcs[id];
runningProcs.remove(id);
setState(() {
});
}
void StartDownload(int gameId, List<FileHashEntry> 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<void> 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<void> 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<int> _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<DataRow> 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>[
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<String>(
iconColor: Colors.blueGrey,
onSelected: (val) {
if (val
.toLowerCase()
.contains("uninstall")) {
UninstallGame(dashboardSelectedGameIndex);
}
},
itemBuilder: (BuildContext context) {
return {'Uninstall'}.map((String choice) {
return PopupMenuItem<String>(
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,
)
],
)
],
)),
);
}
}