Added modern progress bar, real-time install/OBB progress updates using AdvancedSharpAdbClient, updated sideload confirmation messages, and a new icon based on the VRP server icon.
Introduce ModernProgressBar, a custom control with gradient fill, rounded corners, indeterminate animation, and optional status text. Integrated AdvancedSharpAdbClient to provide real-time progress updates for APK installs and OBB copies, updating the UI to reflect the current operation and its progress. Refactored ADB methods to support async progress reporting, updated MainForm to use the new progress bar features, improved user feedback with clearer confirmation messages during and after sideloading, added a new icon based on the VRP server icon and enabled it in the window title bar.
This commit is contained in:
507
ADB.cs
507
ADB.cs
@@ -1,8 +1,14 @@
|
||||
using AndroidSideloader.Utilities;
|
||||
using AdvancedSharpAdbClient;
|
||||
using AdvancedSharpAdbClient.DeviceCommands;
|
||||
using AdvancedSharpAdbClient.Models;
|
||||
using AdvancedSharpAdbClient.Receivers;
|
||||
using AndroidSideloader.Utilities;
|
||||
using JR.Utils.GUI.Forms;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace AndroidSideloader
|
||||
@@ -10,32 +16,82 @@ namespace AndroidSideloader
|
||||
internal class ADB
|
||||
{
|
||||
private static readonly SettingsManager settings = SettingsManager.Instance;
|
||||
private static readonly Process adb = new Process();
|
||||
public static string adbFolderPath = Path.Combine(Environment.CurrentDirectory, "platform-tools");
|
||||
public static string adbFilePath = Path.Combine(adbFolderPath, "adb.exe");
|
||||
public static string DeviceID = "";
|
||||
public static string package = "";
|
||||
public static bool wirelessadbON;
|
||||
|
||||
// AdbClient for direct protocol communication
|
||||
private static AdbClient _adbClient;
|
||||
private static DeviceData _currentDevice;
|
||||
|
||||
// Gets or initializes the AdbClient instance
|
||||
private static AdbClient GetAdbClient()
|
||||
{
|
||||
if (_adbClient == null)
|
||||
{
|
||||
// Ensure ADB server is started
|
||||
if (!AdbServer.Instance.GetStatus().IsRunning)
|
||||
{
|
||||
var server = new AdbServer();
|
||||
var result = server.StartServer(adbFilePath, false);
|
||||
Logger.Log($"ADB server start result: {result}");
|
||||
}
|
||||
|
||||
_adbClient = new AdbClient();
|
||||
}
|
||||
return _adbClient;
|
||||
}
|
||||
|
||||
// Gets the current device for AdbClient operations
|
||||
private static DeviceData GetCurrentDevice()
|
||||
{
|
||||
var client = GetAdbClient();
|
||||
var devices = client.GetDevices();
|
||||
|
||||
if (devices == null || !devices.Any())
|
||||
{
|
||||
Logger.Log("No devices found via AdbClient", LogLevel.WARNING);
|
||||
return default;
|
||||
}
|
||||
|
||||
// If DeviceID is set, find that specific device
|
||||
if (!string.IsNullOrEmpty(DeviceID) && DeviceID.Length > 1)
|
||||
{
|
||||
var device = devices.FirstOrDefault(d => d.Serial == DeviceID || d.Serial.StartsWith(DeviceID));
|
||||
if (device.Serial != null)
|
||||
{
|
||||
_currentDevice = device;
|
||||
return device;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise return the first available device
|
||||
_currentDevice = devices.First();
|
||||
return _currentDevice;
|
||||
}
|
||||
|
||||
public static ProcessOutput RunAdbCommandToString(string command)
|
||||
{
|
||||
// Replacing "adb" from command if the user added it
|
||||
command = command.Replace("adb", "");
|
||||
|
||||
settings.ADBFolder = adbFolderPath;
|
||||
settings.ADBPath = adbFilePath;
|
||||
settings.Save();
|
||||
|
||||
if (DeviceID.Length > 1)
|
||||
{
|
||||
command = $" -s {DeviceID} {command}";
|
||||
}
|
||||
|
||||
if (!command.Contains("dumpsys") && !command.Contains("shell pm list packages") && !command.Contains("KEYCODE_WAKEUP"))
|
||||
{
|
||||
string logcmd = command;
|
||||
|
||||
if (logcmd.Contains(Environment.CurrentDirectory))
|
||||
{
|
||||
logcmd = logcmd.Replace($"{Environment.CurrentDirectory}", $"CurrentDirectory");
|
||||
}
|
||||
|
||||
_ = Logger.Log($"Running command: {logcmd}");
|
||||
}
|
||||
|
||||
@@ -88,6 +144,264 @@ namespace AndroidSideloader
|
||||
}
|
||||
}
|
||||
|
||||
// Executes a shell command on the device.
|
||||
private static void ExecuteShellCommand(AdbClient client, DeviceData device, string command)
|
||||
{
|
||||
var receiver = new ConsoleOutputReceiver();
|
||||
client.ExecuteRemoteCommand(command, device, receiver);
|
||||
}
|
||||
|
||||
// Copies and installs an APK with real-time progress reporting using AdvancedSharpAdbClient
|
||||
public static async Task<ProcessOutput> SideloadWithProgressAsync(
|
||||
string path,
|
||||
Action<int> progressCallback = null,
|
||||
Action<string> statusCallback = null,
|
||||
string packagename = "",
|
||||
string gameName = "")
|
||||
{
|
||||
statusCallback?.Invoke("Installing APK...");
|
||||
progressCallback?.Invoke(0);
|
||||
|
||||
try
|
||||
{
|
||||
var device = GetCurrentDevice();
|
||||
if (device.Serial == null)
|
||||
{
|
||||
return new ProcessOutput("", "No device connected");
|
||||
}
|
||||
|
||||
var client = GetAdbClient();
|
||||
var packageManager = new PackageManager(client, device);
|
||||
|
||||
statusCallback?.Invoke("Installing APK...");
|
||||
|
||||
// Create install progress handler
|
||||
Action<InstallProgressEventArgs> installProgress = (args) =>
|
||||
{
|
||||
// Map PackageInstallProgressState to percentage
|
||||
int percent = 0;
|
||||
switch (args.State)
|
||||
{
|
||||
case PackageInstallProgressState.Preparing:
|
||||
percent = 0;
|
||||
statusCallback?.Invoke("Preparing...");
|
||||
break;
|
||||
case PackageInstallProgressState.Uploading:
|
||||
percent = (int)Math.Round(args.UploadProgress);
|
||||
statusCallback?.Invoke($"Installing · {args.UploadProgress:F0}%");
|
||||
break;
|
||||
case PackageInstallProgressState.Installing:
|
||||
percent = 100;
|
||||
statusCallback?.Invoke("Completing Installation...");
|
||||
break;
|
||||
case PackageInstallProgressState.Finished:
|
||||
percent = 100;
|
||||
statusCallback?.Invoke("");
|
||||
break;
|
||||
default:
|
||||
percent = 50;
|
||||
break;
|
||||
}
|
||||
progressCallback?.Invoke(percent);
|
||||
};
|
||||
|
||||
// Install the package with progress
|
||||
await Task.Run(() =>
|
||||
{
|
||||
packageManager.InstallPackage(path, installProgress);
|
||||
});
|
||||
|
||||
progressCallback?.Invoke(100);
|
||||
statusCallback?.Invoke("");
|
||||
|
||||
return new ProcessOutput($"{gameName}: Success\n");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log($"SideloadWithProgressAsync error: {ex.Message}", LogLevel.ERROR);
|
||||
|
||||
// Check for signature mismatch errors
|
||||
if (ex.Message.Contains("INSTALL_FAILED") ||
|
||||
ex.Message.Contains("signatures do not match"))
|
||||
{
|
||||
bool cancelClicked = false;
|
||||
|
||||
if (!settings.AutoReinstall)
|
||||
{
|
||||
Program.form.Invoke(() =>
|
||||
{
|
||||
DialogResult dialogResult1 = FlexibleMessageBox.Show(Program.form,
|
||||
"In place upgrade has failed. Rookie can attempt to backup your save data and reinstall the game automatically, however some games do not store their saves in an accessible location (less than 5%). Continue with reinstall?",
|
||||
"In place upgrade failed.", MessageBoxButtons.OKCancel);
|
||||
if (dialogResult1 == DialogResult.Cancel)
|
||||
cancelClicked = true;
|
||||
});
|
||||
}
|
||||
|
||||
if (cancelClicked)
|
||||
return new ProcessOutput("", "Installation cancelled by user");
|
||||
|
||||
// Perform reinstall
|
||||
statusCallback?.Invoke("Performing reinstall...");
|
||||
|
||||
try
|
||||
{
|
||||
var device = GetCurrentDevice();
|
||||
var client = GetAdbClient();
|
||||
var packageManager = new PackageManager(client, device);
|
||||
|
||||
// Backup save data
|
||||
statusCallback?.Invoke("Backing up save data...");
|
||||
_ = RunAdbCommandToString($"pull \"/sdcard/Android/data/{MainForm.CurrPCKG}\" \"{Environment.CurrentDirectory}\"");
|
||||
|
||||
// Uninstall
|
||||
statusCallback?.Invoke("Uninstalling old version...");
|
||||
packageManager.UninstallPackage(packagename);
|
||||
|
||||
// Reinstall with progress
|
||||
statusCallback?.Invoke("Reinstalling game...");
|
||||
Action<InstallProgressEventArgs> reinstallProgress = (args) =>
|
||||
{
|
||||
if (args.State == PackageInstallProgressState.Uploading)
|
||||
{
|
||||
progressCallback?.Invoke((int)Math.Round(args.UploadProgress));
|
||||
}
|
||||
};
|
||||
packageManager.InstallPackage(path, reinstallProgress);
|
||||
|
||||
// Restore save data
|
||||
statusCallback?.Invoke("Restoring save data...");
|
||||
_ = RunAdbCommandToString($"push \"{Environment.CurrentDirectory}\\{MainForm.CurrPCKG}\" /sdcard/Android/data/");
|
||||
|
||||
string directoryToDelete = Path.Combine(Environment.CurrentDirectory, MainForm.CurrPCKG);
|
||||
if (Directory.Exists(directoryToDelete) && directoryToDelete != Environment.CurrentDirectory)
|
||||
{
|
||||
Directory.Delete(directoryToDelete, true);
|
||||
}
|
||||
|
||||
progressCallback?.Invoke(100);
|
||||
return new ProcessOutput($"{gameName}: Reinstall: Success\n", "");
|
||||
}
|
||||
catch (Exception reinstallEx)
|
||||
{
|
||||
return new ProcessOutput($"{gameName}: Reinstall: Failed: {reinstallEx.Message}\n");
|
||||
}
|
||||
}
|
||||
|
||||
return new ProcessOutput("", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
// Copies OBB folder with real-time progress reporting using AdvancedSharpAdbClient
|
||||
public static async Task<ProcessOutput> CopyOBBWithProgressAsync(
|
||||
string localPath,
|
||||
Action<int> progressCallback = null,
|
||||
Action<string> statusCallback = null,
|
||||
string gameName = "")
|
||||
{
|
||||
string folderName = Path.GetFileName(localPath);
|
||||
|
||||
if (!folderName.Contains("."))
|
||||
{
|
||||
return new ProcessOutput("No OBB Folder found");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var device = GetCurrentDevice();
|
||||
if (device.Serial == null)
|
||||
{
|
||||
return new ProcessOutput("", "No device connected");
|
||||
}
|
||||
|
||||
var client = GetAdbClient();
|
||||
string remotePath = $"/sdcard/Android/obb/{folderName}";
|
||||
|
||||
statusCallback?.Invoke($"Preparing: {folderName}");
|
||||
progressCallback?.Invoke(0);
|
||||
|
||||
// Delete existing OBB folder and create new one
|
||||
ExecuteShellCommand(client, device, $"rm -rf \"{remotePath}\"");
|
||||
ExecuteShellCommand(client, device, $"mkdir -p \"{remotePath}\"");
|
||||
|
||||
// Get all files to push and calculate total size
|
||||
var files = Directory.GetFiles(localPath, "*", SearchOption.AllDirectories);
|
||||
long totalBytes = files.Sum(f => new FileInfo(f).Length);
|
||||
long transferredBytes = 0;
|
||||
|
||||
statusCallback?.Invoke($"Copying: {folderName}");
|
||||
|
||||
using (var syncService = new SyncService(client, device))
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
string relativePath = file.Substring(localPath.Length)
|
||||
.TrimStart('\\', '/')
|
||||
.Replace('\\', '/');
|
||||
string remoteFilePath = $"{remotePath}/{relativePath}";
|
||||
string fileName = Path.GetFileName(file);
|
||||
|
||||
// Let UI know which file we're currently on
|
||||
statusCallback?.Invoke(fileName);
|
||||
|
||||
// Ensure remote directory exists
|
||||
string remoteDir = remoteFilePath.Substring(0, remoteFilePath.LastIndexOf('/'));
|
||||
ExecuteShellCommand(client, device, $"mkdir -p \"{remoteDir}\"");
|
||||
|
||||
var fileInfo = new FileInfo(file);
|
||||
long fileSize = fileInfo.Length;
|
||||
long capturedTransferredBytes = transferredBytes;
|
||||
|
||||
// Progress handler for this file
|
||||
Action<SyncProgressChangedEventArgs> progressHandler = (args) =>
|
||||
{
|
||||
long totalProgressBytes = capturedTransferredBytes + args.ReceivedBytesSize;
|
||||
|
||||
double overallPercent = totalBytes > 0
|
||||
? (totalProgressBytes * 100.0) / totalBytes
|
||||
: 0.0;
|
||||
|
||||
int overallPercentInt = (int)Math.Round(overallPercent);
|
||||
overallPercentInt = Math.Max(0, Math.Min(100, overallPercentInt));
|
||||
|
||||
// Single source of truth for UI (bar + label + text)
|
||||
progressCallback?.Invoke(overallPercentInt);
|
||||
};
|
||||
|
||||
// Push the file with progress
|
||||
using (var stream = File.OpenRead(file))
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
syncService.Push(
|
||||
stream,
|
||||
remoteFilePath,
|
||||
UnixFileStatus.DefaultFileMode,
|
||||
DateTime.Now,
|
||||
progressHandler,
|
||||
false);
|
||||
});
|
||||
}
|
||||
|
||||
// Mark this file as fully transferred
|
||||
transferredBytes += fileSize;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure final 100% and clear status
|
||||
progressCallback?.Invoke(100);
|
||||
statusCallback?.Invoke("");
|
||||
|
||||
return new ProcessOutput($"{gameName}: OBB transfer: Success\n", "");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log($"CopyOBBWithProgressAsync error: {ex.Message}", LogLevel.ERROR);
|
||||
|
||||
return new ProcessOutput("", $"{gameName}: OBB transfer: Failed: {ex.Message}\n");
|
||||
}
|
||||
}
|
||||
|
||||
public static ProcessOutput RunAdbCommandToStringWOADB(string result, string path)
|
||||
{
|
||||
string command = result;
|
||||
@@ -99,54 +413,51 @@ namespace AndroidSideloader
|
||||
|
||||
_ = Logger.Log($"Running command: {logcmd}");
|
||||
|
||||
adb.StartInfo.FileName = "cmd.exe";
|
||||
adb.StartInfo.RedirectStandardError = true;
|
||||
adb.StartInfo.RedirectStandardInput = true;
|
||||
adb.StartInfo.RedirectStandardOutput = true;
|
||||
adb.StartInfo.CreateNoWindow = true;
|
||||
adb.StartInfo.UseShellExecute = false;
|
||||
adb.StartInfo.WorkingDirectory = Path.GetDirectoryName(path);
|
||||
_ = adb.Start();
|
||||
adb.StandardInput.WriteLine(command);
|
||||
adb.StandardInput.Flush();
|
||||
adb.StandardInput.Close();
|
||||
|
||||
|
||||
string output = "";
|
||||
string error = "";
|
||||
|
||||
try
|
||||
using (var adb = new Process())
|
||||
{
|
||||
output += adb.StandardOutput.ReadToEnd();
|
||||
error += adb.StandardError.ReadToEnd();
|
||||
}
|
||||
catch { }
|
||||
if (command.Contains("connect"))
|
||||
{
|
||||
bool graceful = adb.WaitForExit(3000);
|
||||
if (!graceful)
|
||||
adb.StartInfo.FileName = "cmd.exe";
|
||||
adb.StartInfo.RedirectStandardError = true;
|
||||
adb.StartInfo.RedirectStandardInput = true;
|
||||
adb.StartInfo.RedirectStandardOutput = true;
|
||||
adb.StartInfo.CreateNoWindow = true;
|
||||
adb.StartInfo.UseShellExecute = false;
|
||||
adb.StartInfo.WorkingDirectory = Path.GetDirectoryName(path);
|
||||
_ = adb.Start();
|
||||
adb.StandardInput.WriteLine(command);
|
||||
adb.StandardInput.Flush();
|
||||
adb.StandardInput.Close();
|
||||
|
||||
string output = "";
|
||||
string error = "";
|
||||
|
||||
try
|
||||
{
|
||||
adb.Kill();
|
||||
adb.WaitForExit();
|
||||
output += adb.StandardOutput.ReadToEnd();
|
||||
error += adb.StandardError.ReadToEnd();
|
||||
}
|
||||
}
|
||||
else if (command.Contains("connect"))
|
||||
{
|
||||
bool graceful = adb.WaitForExit(3000);
|
||||
if (!graceful)
|
||||
catch { }
|
||||
|
||||
if (command.Contains("connect"))
|
||||
{
|
||||
adb.Kill();
|
||||
adb.WaitForExit();
|
||||
bool graceful = adb.WaitForExit(3000);
|
||||
if (!graceful)
|
||||
{
|
||||
adb.Kill();
|
||||
adb.WaitForExit();
|
||||
}
|
||||
}
|
||||
|
||||
if (error.Contains("ADB_VENDOR_KEYS") && settings.AdbDebugWarned)
|
||||
{
|
||||
ADBDebugWarning();
|
||||
}
|
||||
|
||||
_ = Logger.Log(output);
|
||||
_ = Logger.Log(error, LogLevel.ERROR);
|
||||
return new ProcessOutput(output, error);
|
||||
}
|
||||
if (error.Contains("ADB_VENDOR_KEYS") && settings.AdbDebugWarned)
|
||||
{
|
||||
ADBDebugWarning();
|
||||
}
|
||||
_ = Logger.Log(output);
|
||||
_ = Logger.Log(error, LogLevel.ERROR);
|
||||
return new ProcessOutput(output, error);
|
||||
}
|
||||
|
||||
public static ProcessOutput RunCommandToString(string result, string path = "")
|
||||
{
|
||||
string command = result;
|
||||
@@ -160,37 +471,37 @@ namespace AndroidSideloader
|
||||
|
||||
try
|
||||
{
|
||||
using (var adb = new Process())
|
||||
using (var proc = new Process())
|
||||
{
|
||||
adb.StartInfo.FileName = $@"{Path.GetPathRoot(Environment.SystemDirectory)}\Windows\System32\cmd.exe";
|
||||
adb.StartInfo.Arguments = command;
|
||||
adb.StartInfo.RedirectStandardError = true;
|
||||
adb.StartInfo.RedirectStandardInput = true;
|
||||
adb.StartInfo.RedirectStandardOutput = true;
|
||||
adb.StartInfo.CreateNoWindow = true;
|
||||
adb.StartInfo.UseShellExecute = false;
|
||||
adb.StartInfo.WorkingDirectory = Path.GetDirectoryName(path);
|
||||
proc.StartInfo.FileName = $@"{Path.GetPathRoot(Environment.SystemDirectory)}\Windows\System32\cmd.exe";
|
||||
proc.StartInfo.Arguments = command;
|
||||
proc.StartInfo.RedirectStandardError = true;
|
||||
proc.StartInfo.RedirectStandardInput = true;
|
||||
proc.StartInfo.RedirectStandardOutput = true;
|
||||
proc.StartInfo.CreateNoWindow = true;
|
||||
proc.StartInfo.UseShellExecute = false;
|
||||
proc.StartInfo.WorkingDirectory = Path.GetDirectoryName(path);
|
||||
|
||||
adb.Start();
|
||||
adb.StandardInput.WriteLine(command);
|
||||
adb.StandardInput.Flush();
|
||||
adb.StandardInput.Close();
|
||||
proc.Start();
|
||||
proc.StandardInput.WriteLine(command);
|
||||
proc.StandardInput.Flush();
|
||||
proc.StandardInput.Close();
|
||||
|
||||
string output = adb.StandardOutput.ReadToEnd();
|
||||
string error = adb.StandardError.ReadToEnd();
|
||||
string output = proc.StandardOutput.ReadToEnd();
|
||||
string error = proc.StandardError.ReadToEnd();
|
||||
|
||||
if (command.Contains("connect"))
|
||||
{
|
||||
bool graceful = adb.WaitForExit(3000);
|
||||
bool graceful = proc.WaitForExit(3000);
|
||||
if (!graceful)
|
||||
{
|
||||
adb.Kill();
|
||||
adb.WaitForExit();
|
||||
proc.Kill();
|
||||
proc.WaitForExit();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
adb.WaitForExit();
|
||||
proc.WaitForExit();
|
||||
}
|
||||
|
||||
if (error.Contains("ADB_VENDOR_KEYS") && settings.AdbDebugWarned)
|
||||
@@ -198,12 +509,6 @@ namespace AndroidSideloader
|
||||
ADBDebugWarning();
|
||||
}
|
||||
|
||||
if (error.Contains("Asset path") && error.Contains("is neither a directory nor file"))
|
||||
{
|
||||
Logger.Log("Asset path error detected. The specified path might not exist or be accessible.", LogLevel.WARNING);
|
||||
// You might want to handle this specific error differently
|
||||
}
|
||||
|
||||
Logger.Log(output);
|
||||
Logger.Log(error, LogLevel.ERROR);
|
||||
|
||||
@@ -221,10 +526,11 @@ namespace AndroidSideloader
|
||||
{
|
||||
Program.form.Invoke(() =>
|
||||
{
|
||||
DialogResult dialogResult = FlexibleMessageBox.Show(Program.form, "On your headset, click on the Notifications Bell, and then select the USB Detected notification to enable Connections.", "ADB Debugging not enabled.", MessageBoxButtons.OKCancel);
|
||||
DialogResult dialogResult = FlexibleMessageBox.Show(Program.form,
|
||||
"On your headset, click on the Notifications Bell, and then select the USB Detected notification to enable Connections.",
|
||||
"ADB Debugging not enabled.", MessageBoxButtons.OKCancel);
|
||||
if (dialogResult == DialogResult.Cancel)
|
||||
{
|
||||
// settings.adbdebugwarned = true;
|
||||
settings.Save();
|
||||
}
|
||||
});
|
||||
@@ -234,6 +540,20 @@ namespace AndroidSideloader
|
||||
{
|
||||
ProcessOutput output = new ProcessOutput("", "");
|
||||
output += RunAdbCommandToString($"shell pm uninstall {package}");
|
||||
|
||||
// Prefix the output with the simple game name
|
||||
string label = Sideloader.gameNameToSimpleName(Sideloader.PackageNametoGameName(package));
|
||||
|
||||
if (!string.IsNullOrEmpty(output.Output))
|
||||
{
|
||||
output.Output = $"{label}: {output.Output}";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(output.Error))
|
||||
{
|
||||
output.Error = $"{label}: {output.Error}";
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -255,7 +575,7 @@ namespace AndroidSideloader
|
||||
totalSize = long.Parse(foo[1]) / 1000;
|
||||
usedSize = long.Parse(foo[2]) / 1000;
|
||||
freeSize = long.Parse(foo[3]) / 1000;
|
||||
break; // Assuming we only need the first matching line
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,51 +583,52 @@ namespace AndroidSideloader
|
||||
return $"Total space: {string.Format("{0:0.00}", (double)totalSize / 1000)}GB\nUsed space: {string.Format("{0:0.00}", (double)usedSize / 1000)}GB\nFree space: {string.Format("{0:0.00}", (double)freeSize / 1000)}GB";
|
||||
}
|
||||
|
||||
public static bool wirelessadbON;
|
||||
public static ProcessOutput Sideload(string path, string packagename = "")
|
||||
{
|
||||
ProcessOutput ret = new ProcessOutput();
|
||||
ret += RunAdbCommandToString($"install -g \"{path}\"");
|
||||
string out2 = ret.Output + ret.Error;
|
||||
|
||||
if (out2.Contains("failed"))
|
||||
{
|
||||
_ = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), $"Rookie Backups");
|
||||
_ = Logger.Log(out2);
|
||||
|
||||
if (out2.Contains("offline") && !settings.NodeviceMode)
|
||||
{
|
||||
DialogResult dialogResult2 = FlexibleMessageBox.Show(Program.form, "Device is offline. Press Yes to reconnect, or if you don't wish to connect and just want to download the game (requires unchecking \"Delete games after install\" from settings menu) then press No.", "Device offline.", MessageBoxButtons.YesNoCancel);
|
||||
}
|
||||
|
||||
if (out2.Contains($"signatures do not match previously") || out2.Contains("INSTALL_FAILED_VERSION_DOWNGRADE") || out2.Contains("signatures do not match") || out2.Contains("failed to install"))
|
||||
{
|
||||
ret.Error = string.Empty;
|
||||
ret.Output = string.Empty;
|
||||
|
||||
bool cancelClicked = false;
|
||||
|
||||
if (!settings.AutoReinstall)
|
||||
{
|
||||
bool cancelClicked = false;
|
||||
|
||||
if (!settings.AutoReinstall)
|
||||
Program.form.Invoke((MethodInvoker)(() =>
|
||||
{
|
||||
Program.form.Invoke((MethodInvoker)(() =>
|
||||
{
|
||||
DialogResult dialogResult1 = FlexibleMessageBox.Show(Program.form, "In place upgrade has failed. Rookie can attempt to backup your save data and reinstall the game automatically, however some games do not store their saves in an accessible location (less than 5%). Continue with reinstall?", "In place upgrade failed.", MessageBoxButtons.OKCancel);
|
||||
if (dialogResult1 == DialogResult.Cancel)
|
||||
cancelClicked = true;
|
||||
}));
|
||||
}
|
||||
|
||||
if (cancelClicked)
|
||||
return ret;
|
||||
DialogResult dialogResult1 = FlexibleMessageBox.Show(Program.form, "In place upgrade has failed. Rookie can attempt to backup your save data and reinstall the game automatically, however some games do not store their saves in an accessible location (less than 5%). Continue with reinstall?", "In place upgrade failed.", MessageBoxButtons.OKCancel);
|
||||
if (dialogResult1 == DialogResult.Cancel)
|
||||
cancelClicked = true;
|
||||
}));
|
||||
}
|
||||
|
||||
if (cancelClicked)
|
||||
return ret;
|
||||
|
||||
Program.form.changeTitle("Performing reinstall, please wait...");
|
||||
_ = ADB.RunAdbCommandToString("kill-server");
|
||||
_ = ADB.RunAdbCommandToString("devices");
|
||||
_ = ADB.RunAdbCommandToString($"pull \"/sdcard/Android/data/{MainForm.CurrPCKG}\" \"{Environment.CurrentDirectory}\"");
|
||||
_ = RunAdbCommandToString("kill-server");
|
||||
_ = RunAdbCommandToString("devices");
|
||||
_ = RunAdbCommandToString($"pull \"/sdcard/Android/data/{MainForm.CurrPCKG}\" \"{Environment.CurrentDirectory}\"");
|
||||
Program.form.changeTitle("Uninstalling game...");
|
||||
_ = Sideloader.UninstallGame(MainForm.CurrPCKG);
|
||||
Program.form.changeTitle("Reinstalling game...");
|
||||
ret += ADB.RunAdbCommandToString($"install -g \"{path}\"");
|
||||
_ = ADB.RunAdbCommandToString($"push \"{Environment.CurrentDirectory}\\{MainForm.CurrPCKG}\" /sdcard/Android/data/");
|
||||
ret += RunAdbCommandToString($"install -g \"{path}\"");
|
||||
_ = RunAdbCommandToString($"push \"{Environment.CurrentDirectory}\\{MainForm.CurrPCKG}\" /sdcard/Android/data/");
|
||||
|
||||
string directoryToDelete = Path.Combine(Environment.CurrentDirectory, MainForm.CurrPCKG);
|
||||
if (Directory.Exists(directoryToDelete))
|
||||
{
|
||||
|
||||
@@ -136,6 +136,9 @@
|
||||
<ManifestKeyFile>AndroidSideloader_TemporaryKey.pfx</ManifestKeyFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="AdvancedSharpAdbClient, Version=3.5.15.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>packages\AdvancedSharpAdbClient.3.5.15\lib\net45\AdvancedSharpAdbClient.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Costura, Version=5.7.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>packages\Costura.Fody.5.7.0\lib\netstandard1.0\Costura.dll</HintPath>
|
||||
</Reference>
|
||||
@@ -187,6 +190,9 @@
|
||||
<Compile Include="GalleryView.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="ModernProgessBar.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
|
||||
@@ -305,7 +305,9 @@ namespace AndroidSideloader
|
||||
|
||||
File.WriteAllText(blacklistPath, Newtonsoft.Json.JsonConvert.SerializeObject(existingBlacklist.ToArray(), Newtonsoft.Json.Formatting.Indented));
|
||||
Logger.Log($"Added {appsToBlacklist.Count} apps to local blacklist");
|
||||
MessageBox.Show($"{appsToBlacklist.Count} app(s) added to blacklist.", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
MessageBox.Show($"{appsToBlacklist.Count} {(appsToBlacklist.Count == 1 ? "app" : "apps")} added to blacklist.",
|
||||
"Success",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
103
MainForm.Designer.cs
generated
103
MainForm.Designer.cs
generated
@@ -34,7 +34,7 @@ namespace AndroidSideloader
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.m_combo = new SergeUtils.EasyCompletionComboBox();
|
||||
this.progressBar = new System.Windows.Forms.ProgressBar();
|
||||
this.progressBar = new AndroidSideloader.ModernProgressBar();
|
||||
this.speedLabel = new System.Windows.Forms.Label();
|
||||
this.etaLabel = new System.Windows.Forms.Label();
|
||||
this.freeDisclaimer = new System.Windows.Forms.Label();
|
||||
@@ -117,9 +117,9 @@ namespace AndroidSideloader
|
||||
this.leftNavContainer = new System.Windows.Forms.Panel();
|
||||
this.statusInfoPanel = new System.Windows.Forms.Panel();
|
||||
this.sideloadingStatusLabel = new System.Windows.Forms.Label();
|
||||
this.rookieStatusLabel = new System.Windows.Forms.Label();
|
||||
this.activeMirrorLabel = new System.Windows.Forms.Label();
|
||||
this.deviceIdLabel = new System.Windows.Forms.Label();
|
||||
this.rookieStatusLabel = new System.Windows.Forms.Label();
|
||||
this.sidebarMediaPanel = new System.Windows.Forms.Panel();
|
||||
this.downloadInstallGameButton = new AndroidSideloader.RoundButton();
|
||||
this.selectedGameLabel = new System.Windows.Forms.Label();
|
||||
@@ -176,14 +176,26 @@ namespace AndroidSideloader
|
||||
//
|
||||
this.progressBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.progressBar.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.progressBar.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(32)))), ((int)(((byte)(35)))), ((int)(((byte)(45)))));
|
||||
this.progressBar.BackgroundColor = System.Drawing.Color.FromArgb(((int)(((byte)(28)))), ((int)(((byte)(32)))), ((int)(((byte)(38)))));
|
||||
this.progressBar.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
|
||||
this.progressBar.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(93)))), ((int)(((byte)(203)))), ((int)(((byte)(173)))));
|
||||
this.progressBar.IndeterminateColor = System.Drawing.Color.FromArgb(((int)(((byte)(93)))), ((int)(((byte)(203)))), ((int)(((byte)(173)))));
|
||||
this.progressBar.IsIndeterminate = false;
|
||||
this.progressBar.Location = new System.Drawing.Point(1, 18);
|
||||
this.progressBar.Maximum = 100;
|
||||
this.progressBar.Minimum = 0;
|
||||
this.progressBar.MinimumSize = new System.Drawing.Size(200, 13);
|
||||
this.progressBar.Name = "progressBar";
|
||||
this.progressBar.OperationType = "";
|
||||
this.progressBar.ProgressEndColor = System.Drawing.Color.FromArgb(((int)(((byte)(50)))), ((int)(((byte)(160)))), ((int)(((byte)(130)))));
|
||||
this.progressBar.ProgressStartColor = System.Drawing.Color.FromArgb(((int)(((byte)(120)))), ((int)(((byte)(220)))), ((int)(((byte)(190)))));
|
||||
this.progressBar.Radius = 6;
|
||||
this.progressBar.Size = new System.Drawing.Size(983, 13);
|
||||
this.progressBar.Style = System.Windows.Forms.ProgressBarStyle.Continuous;
|
||||
this.progressBar.StatusText = "";
|
||||
this.progressBar.TabIndex = 7;
|
||||
this.progressBar.TextColor = System.Drawing.Color.FromArgb(((int)(((byte)(230)))), ((int)(((byte)(230)))), ((int)(((byte)(230)))));
|
||||
this.progressBar.Value = 0;
|
||||
//
|
||||
// speedLabel
|
||||
//
|
||||
@@ -1200,7 +1212,6 @@ namespace AndroidSideloader
|
||||
this.statusInfoPanel.Controls.Add(this.activeMirrorLabel);
|
||||
this.statusInfoPanel.Controls.Add(this.deviceIdLabel);
|
||||
this.statusInfoPanel.Controls.Add(this.rookieStatusLabel);
|
||||
this.statusInfoPanel.AutoSize = false;
|
||||
this.statusInfoPanel.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
this.statusInfoPanel.Location = new System.Drawing.Point(0, 1019);
|
||||
this.statusInfoPanel.Name = "statusInfoPanel";
|
||||
@@ -1208,58 +1219,54 @@ namespace AndroidSideloader
|
||||
this.statusInfoPanel.Size = new System.Drawing.Size(233, 81);
|
||||
this.statusInfoPanel.TabIndex = 102;
|
||||
//
|
||||
// rookieStatusLabel
|
||||
//
|
||||
this.rookieStatusLabel.AutoSize = false;
|
||||
this.rookieStatusLabel.Font = new System.Drawing.Font("Segoe UI", 8F, System.Drawing.FontStyle.Bold);
|
||||
this.rookieStatusLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(93)))), ((int)(((byte)(203)))), ((int)(((byte)(173)))));
|
||||
this.rookieStatusLabel.Location = new System.Drawing.Point(8, 4);
|
||||
this.rookieStatusLabel.Name = "rookieStatusLabel";
|
||||
this.rookieStatusLabel.Padding = new System.Windows.Forms.Padding(0, 2, 0, 2);
|
||||
this.rookieStatusLabel.Size = new System.Drawing.Size(225, 17);
|
||||
this.rookieStatusLabel.AutoEllipsis = true;
|
||||
this.rookieStatusLabel.TabIndex = 0;
|
||||
this.rookieStatusLabel.Text = "Status";
|
||||
//
|
||||
// deviceIdLabel
|
||||
//
|
||||
this.deviceIdLabel.AutoSize = false;
|
||||
this.deviceIdLabel.Font = new System.Drawing.Font("Segoe UI", 8F);
|
||||
this.deviceIdLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(140)))), ((int)(((byte)(145)))), ((int)(((byte)(150)))));
|
||||
this.deviceIdLabel.Location = new System.Drawing.Point(8, 21);
|
||||
this.deviceIdLabel.Name = "deviceIdLabel";
|
||||
this.deviceIdLabel.Padding = new System.Windows.Forms.Padding(0, 2, 0, 2);
|
||||
this.deviceIdLabel.Size = new System.Drawing.Size(225, 17);
|
||||
this.deviceIdLabel.AutoEllipsis = true;
|
||||
this.deviceIdLabel.TabIndex = 1;
|
||||
this.deviceIdLabel.Text = "Device: Not connected";
|
||||
//
|
||||
// activeMirrorLabel
|
||||
//
|
||||
this.activeMirrorLabel.AutoSize = false;
|
||||
this.activeMirrorLabel.Font = new System.Drawing.Font("Segoe UI", 8F);
|
||||
this.activeMirrorLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(140)))), ((int)(((byte)(145)))), ((int)(((byte)(150)))));
|
||||
this.activeMirrorLabel.Location = new System.Drawing.Point(8, 38);
|
||||
this.activeMirrorLabel.Name = "activeMirrorLabel";
|
||||
this.activeMirrorLabel.Padding = new System.Windows.Forms.Padding(0, 2, 0, 2);
|
||||
this.activeMirrorLabel.Size = new System.Drawing.Size(225, 17);
|
||||
this.activeMirrorLabel.AutoEllipsis = true;
|
||||
this.activeMirrorLabel.TabIndex = 2;
|
||||
this.activeMirrorLabel.Text = "Mirror: None";
|
||||
//
|
||||
// sideloadingStatusLabel
|
||||
//
|
||||
this.sideloadingStatusLabel.AutoSize = false;
|
||||
this.sideloadingStatusLabel.AutoEllipsis = true;
|
||||
this.sideloadingStatusLabel.Font = new System.Drawing.Font("Segoe UI", 8F, System.Drawing.FontStyle.Bold);
|
||||
this.sideloadingStatusLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(93)))), ((int)(((byte)(203)))), ((int)(((byte)(173)))));
|
||||
this.sideloadingStatusLabel.Location = new System.Drawing.Point(8, 55);
|
||||
this.sideloadingStatusLabel.Name = "sideloadingStatusLabel";
|
||||
this.sideloadingStatusLabel.Padding = new System.Windows.Forms.Padding(0, 2, 0, 2);
|
||||
this.sideloadingStatusLabel.Size = new System.Drawing.Size(225, 17);
|
||||
this.sideloadingStatusLabel.AutoEllipsis = true;
|
||||
this.sideloadingStatusLabel.TabIndex = 3;
|
||||
this.sideloadingStatusLabel.Text = "Sideloading: Enabled";
|
||||
//
|
||||
// activeMirrorLabel
|
||||
//
|
||||
this.activeMirrorLabel.AutoEllipsis = true;
|
||||
this.activeMirrorLabel.Font = new System.Drawing.Font("Segoe UI", 8F);
|
||||
this.activeMirrorLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(140)))), ((int)(((byte)(145)))), ((int)(((byte)(150)))));
|
||||
this.activeMirrorLabel.Location = new System.Drawing.Point(8, 38);
|
||||
this.activeMirrorLabel.Name = "activeMirrorLabel";
|
||||
this.activeMirrorLabel.Padding = new System.Windows.Forms.Padding(0, 2, 0, 2);
|
||||
this.activeMirrorLabel.Size = new System.Drawing.Size(225, 17);
|
||||
this.activeMirrorLabel.TabIndex = 2;
|
||||
this.activeMirrorLabel.Text = "Mirror: None";
|
||||
//
|
||||
// deviceIdLabel
|
||||
//
|
||||
this.deviceIdLabel.AutoEllipsis = true;
|
||||
this.deviceIdLabel.Font = new System.Drawing.Font("Segoe UI", 8F);
|
||||
this.deviceIdLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(140)))), ((int)(((byte)(145)))), ((int)(((byte)(150)))));
|
||||
this.deviceIdLabel.Location = new System.Drawing.Point(8, 21);
|
||||
this.deviceIdLabel.Name = "deviceIdLabel";
|
||||
this.deviceIdLabel.Padding = new System.Windows.Forms.Padding(0, 2, 0, 2);
|
||||
this.deviceIdLabel.Size = new System.Drawing.Size(225, 17);
|
||||
this.deviceIdLabel.TabIndex = 1;
|
||||
this.deviceIdLabel.Text = "Device: Not connected";
|
||||
//
|
||||
// rookieStatusLabel
|
||||
//
|
||||
this.rookieStatusLabel.AutoEllipsis = true;
|
||||
this.rookieStatusLabel.Font = new System.Drawing.Font("Segoe UI", 8F, System.Drawing.FontStyle.Bold);
|
||||
this.rookieStatusLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(93)))), ((int)(((byte)(203)))), ((int)(((byte)(173)))));
|
||||
this.rookieStatusLabel.Location = new System.Drawing.Point(8, 4);
|
||||
this.rookieStatusLabel.Name = "rookieStatusLabel";
|
||||
this.rookieStatusLabel.Padding = new System.Windows.Forms.Padding(0, 2, 0, 2);
|
||||
this.rookieStatusLabel.Size = new System.Drawing.Size(225, 17);
|
||||
this.rookieStatusLabel.TabIndex = 0;
|
||||
this.rookieStatusLabel.Text = "Status";
|
||||
//
|
||||
// sidebarMediaPanel
|
||||
//
|
||||
this.sidebarMediaPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(20)))), ((int)(((byte)(24)))), ((int)(((byte)(29)))));
|
||||
@@ -1600,7 +1607,6 @@ namespace AndroidSideloader
|
||||
this.HelpButton = true;
|
||||
this.MinimumSize = new System.Drawing.Size(1048, 760);
|
||||
this.Name = "MainForm";
|
||||
this.ShowIcon = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.Text = "Rookie Sideloader";
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Form1_FormClosing);
|
||||
@@ -1623,7 +1629,6 @@ namespace AndroidSideloader
|
||||
this.leftNavContainer.ResumeLayout(false);
|
||||
this.leftNavContainer.PerformLayout();
|
||||
this.statusInfoPanel.ResumeLayout(false);
|
||||
this.statusInfoPanel.PerformLayout();
|
||||
this.sidebarMediaPanel.ResumeLayout(false);
|
||||
this.tableLayoutPanel1.ResumeLayout(false);
|
||||
this.searchPanel.ResumeLayout(false);
|
||||
@@ -1638,7 +1643,7 @@ namespace AndroidSideloader
|
||||
|
||||
#endregion
|
||||
private SergeUtils.EasyCompletionComboBox m_combo;
|
||||
private System.Windows.Forms.ProgressBar progressBar;
|
||||
private ModernProgressBar progressBar;
|
||||
private System.Windows.Forms.Label etaLabel;
|
||||
private System.Windows.Forms.Label speedLabel;
|
||||
private System.Windows.Forms.Label freeDisclaimer;
|
||||
|
||||
235
MainForm.cs
235
MainForm.cs
@@ -105,6 +105,9 @@ namespace AndroidSideloader
|
||||
InitializeTimeReferences();
|
||||
CheckCommandLineArguments();
|
||||
|
||||
// Use same icon as the executable
|
||||
this.Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
|
||||
|
||||
// Load user's preferred view from settings
|
||||
isGalleryView = settings.UseGalleryView;
|
||||
|
||||
@@ -597,7 +600,8 @@ namespace AndroidSideloader
|
||||
btnNoDevice.Text = "ENABLE SIDELOADING";
|
||||
}
|
||||
|
||||
progressBar.Style = ProgressBarStyle.Marquee;
|
||||
progressBar.IsIndeterminate = true;
|
||||
progressBar.OperationType = "Loading";
|
||||
|
||||
// Update check
|
||||
if (!debugMode && settings.CheckForUpdates && !isOffline)
|
||||
@@ -729,7 +733,8 @@ namespace AndroidSideloader
|
||||
await Task.WhenAll(tasksToWait);
|
||||
}
|
||||
|
||||
progressBar.Style = ProgressBarStyle.Marquee;
|
||||
progressBar.IsIndeterminate = true;
|
||||
progressBar.OperationType = "Loading";
|
||||
changeTitle("Populating Game List...");
|
||||
|
||||
_ = await CheckForDevice();
|
||||
@@ -950,27 +955,34 @@ namespace AndroidSideloader
|
||||
|
||||
if (dialog.Show(Handle))
|
||||
{
|
||||
progressBar.Style = ProgressBarStyle.Marquee;
|
||||
string path = dialog.FileName;
|
||||
changeTitle($"Copying {path} OBB to device...");
|
||||
Thread t1 = new Thread(() =>
|
||||
{
|
||||
output += output += ADB.CopyOBB(path);
|
||||
})
|
||||
{
|
||||
IsBackground = true
|
||||
};
|
||||
t1.Start();
|
||||
string folderName = Path.GetFileName(path);
|
||||
|
||||
while (t1.IsAlive)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
changeTitle($"Copying {folderName} OBB to device...");
|
||||
progressBar.IsIndeterminate = false;
|
||||
progressBar.Value = 0;
|
||||
progressBar.OperationType = "Copying OBB";
|
||||
|
||||
output = await ADB.CopyOBBWithProgressAsync(
|
||||
path,
|
||||
progress => this.Invoke(() => {
|
||||
progressBar.Value = progress;
|
||||
speedLabel.Text = $"Progress: {progress}%";
|
||||
}),
|
||||
status => this.Invoke(() => {
|
||||
progressBar.StatusText = status;
|
||||
etaLabel.Text = status;
|
||||
}),
|
||||
folderName);
|
||||
|
||||
progressBar.Value = 100;
|
||||
changeTitle("Done.");
|
||||
showAvailableSpace();
|
||||
|
||||
ShowPrcOutput(output);
|
||||
changeTitle("");
|
||||
speedLabel.Text = "";
|
||||
etaLabel.Text = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1356,7 +1368,8 @@ namespace AndroidSideloader
|
||||
if (!isworking)
|
||||
{
|
||||
isworking = true;
|
||||
progressBar.Style = ProgressBarStyle.Marquee;
|
||||
progressBar.IsIndeterminate = true;
|
||||
progressBar.OperationType = "Loading";
|
||||
string HWID = SideloaderUtilities.UUID();
|
||||
string GameName = selectedApp;
|
||||
string packageName = Sideloader.gameNameToPackageName(GameName);
|
||||
@@ -1418,7 +1431,7 @@ namespace AndroidSideloader
|
||||
changeTitle("Zipping extracted application...");
|
||||
string cmd = $"7z a -mx1 \"{gameZipName}\" .\\{packageName}\\*";
|
||||
string path = $"{settings.MainDir}\\7z.exe";
|
||||
progressBar.Style = ProgressBarStyle.Continuous;
|
||||
progressBar.IsIndeterminate = false;
|
||||
Thread t4 = new Thread(() =>
|
||||
{
|
||||
_ = ADB.RunCommandToString(cmd, path);
|
||||
@@ -1513,7 +1526,8 @@ namespace AndroidSideloader
|
||||
Sideloader.BackupGame(packagename);
|
||||
}
|
||||
ProcessOutput output = new ProcessOutput("", "");
|
||||
progressBar.Style = ProgressBarStyle.Marquee;
|
||||
progressBar.IsIndeterminate = true;
|
||||
progressBar.OperationType = "Loading";
|
||||
Thread t1 = new Thread(() =>
|
||||
{
|
||||
output += Sideloader.UninstallGame(packagename);
|
||||
@@ -1527,7 +1541,7 @@ namespace AndroidSideloader
|
||||
|
||||
ShowPrcOutput(output);
|
||||
showAvailableSpace();
|
||||
progressBar.Style = ProgressBarStyle.Continuous;
|
||||
progressBar.IsIndeterminate = false;
|
||||
}
|
||||
|
||||
private async void copyBulkObbButton_Click(object sender, EventArgs e)
|
||||
@@ -1576,7 +1590,8 @@ namespace AndroidSideloader
|
||||
DragDropLbl.Visible = false;
|
||||
ProcessOutput output = new ProcessOutput(String.Empty, String.Empty);
|
||||
ADB.DeviceID = GetDeviceID();
|
||||
progressBar.Style = ProgressBarStyle.Marquee;
|
||||
progressBar.IsIndeterminate = true;
|
||||
progressBar.OperationType = "Loading";
|
||||
CurrPCKG = String.Empty;
|
||||
string[] datas = (string[])e.Data.GetData(DataFormats.FileDrop);
|
||||
foreach (string data in datas)
|
||||
@@ -1928,7 +1943,7 @@ namespace AndroidSideloader
|
||||
}
|
||||
}
|
||||
|
||||
progressBar.Style = ProgressBarStyle.Continuous;
|
||||
progressBar.IsIndeterminate = false;
|
||||
|
||||
showAvailableSpace();
|
||||
|
||||
@@ -2050,7 +2065,8 @@ namespace AndroidSideloader
|
||||
|
||||
if (SideloaderRCLONE.games.Count > 5)
|
||||
{
|
||||
progressBar.Style = ProgressBarStyle.Marquee;
|
||||
progressBar.IsIndeterminate = true;
|
||||
progressBar.OperationType = "";
|
||||
|
||||
// Use full dumpsys to get all version codes at once
|
||||
Dictionary<string, ulong> installedVersions = new Dictionary<string, ulong>(packageList.Length, StringComparer.OrdinalIgnoreCase);
|
||||
@@ -2257,7 +2273,7 @@ namespace AndroidSideloader
|
||||
await ProcessNewApps(newGamesList, blacklistSet.ToList());
|
||||
}
|
||||
|
||||
progressBar.Style = ProgressBarStyle.Continuous;
|
||||
progressBar.IsIndeterminate = false;
|
||||
|
||||
if (either && !updatesNotified && !noAppCheck)
|
||||
{
|
||||
@@ -2572,7 +2588,8 @@ namespace AndroidSideloader
|
||||
|
||||
public async Task extractAndPrepareGameToUploadAsync(string GameName, string packagename, ulong installedVersionInt, bool isupdate)
|
||||
{
|
||||
progressBar.Style = ProgressBarStyle.Marquee;
|
||||
progressBar.IsIndeterminate = true;
|
||||
progressBar.OperationType = "";
|
||||
|
||||
Thread t1 = new Thread(() =>
|
||||
{
|
||||
@@ -2604,7 +2621,7 @@ namespace AndroidSideloader
|
||||
|
||||
string HWID = SideloaderUtilities.UUID();
|
||||
File.WriteAllText($"{settings.MainDir}\\{packagename}\\HWID.txt", HWID);
|
||||
progressBar.Style = ProgressBarStyle.Continuous;
|
||||
progressBar.IsIndeterminate = false;
|
||||
UploadGame game = new UploadGame
|
||||
{
|
||||
isUpdate = isupdate,
|
||||
@@ -2905,7 +2922,8 @@ Additional Thanks & Resources
|
||||
if (isLoading) { return; }
|
||||
isLoading = true;
|
||||
|
||||
progressBar.Style = ProgressBarStyle.Marquee;
|
||||
progressBar.IsIndeterminate = true;
|
||||
progressBar.OperationType = "Refreshing";
|
||||
devicesbutton_Click(sender, e);
|
||||
|
||||
await initMirrors();
|
||||
@@ -2919,7 +2937,8 @@ Additional Thanks & Resources
|
||||
changeTitle(titleMessage);
|
||||
if (isLoading) { return; }
|
||||
isLoading = true;
|
||||
progressBar.Style = ProgressBarStyle.Marquee;
|
||||
progressBar.IsIndeterminate = true;
|
||||
progressBar.OperationType = "Refreshing";
|
||||
|
||||
Thread t1 = new Thread(() =>
|
||||
{
|
||||
@@ -3030,7 +3049,14 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g
|
||||
|
||||
public void SetProgress(int progress)
|
||||
{
|
||||
progressBar.Value = progress;
|
||||
if (progressBar.InvokeRequired)
|
||||
{
|
||||
progressBar.Invoke(new Action(() => progressBar.Value = progress));
|
||||
}
|
||||
else
|
||||
{
|
||||
progressBar.Value = progress;
|
||||
}
|
||||
}
|
||||
|
||||
public bool isinstalling = false;
|
||||
@@ -3051,10 +3077,11 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g
|
||||
showAvailableSpace();
|
||||
listAppsBtn();
|
||||
}
|
||||
progressBar.Style = ProgressBarStyle.Marquee;
|
||||
progressBar.IsIndeterminate = true;
|
||||
progressBar.OperationType = "Downloading";
|
||||
if (gamesListView.SelectedItems.Count == 0)
|
||||
{
|
||||
progressBar.Style = ProgressBarStyle.Continuous;
|
||||
progressBar.IsIndeterminate = false;
|
||||
changeTitle("You must select a game from the game list!");
|
||||
return;
|
||||
}
|
||||
@@ -3077,7 +3104,7 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g
|
||||
}
|
||||
|
||||
progressBar.Value = 0;
|
||||
progressBar.Style = ProgressBarStyle.Continuous;
|
||||
progressBar.IsIndeterminate = false;
|
||||
string game = gamesToDownload.Length == 1 ? $"\"{gamesToDownload[0]}\"" : "the selected games";
|
||||
isinstalling = true;
|
||||
//Add games to the queue
|
||||
@@ -3279,7 +3306,7 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g
|
||||
// Logger.Log("Files: " + transfersComplete.ToString() + "/" + fileCount.ToString() + " (" + Convert.ToInt32((downloadedSize / totalSize) * 100).ToString() + "% Complete)");
|
||||
// Logger.Log("Downloaded: " + downloadedSize.ToString() + " of " + totalSize.ToString());
|
||||
|
||||
progressBar.Style = ProgressBarStyle.Continuous;
|
||||
progressBar.IsIndeterminate = false;
|
||||
progressBar.Value = Convert.ToInt32((downloadedSize / totalSize) * 100);
|
||||
|
||||
TimeSpan time = TimeSpan.FromSeconds(globalEta);
|
||||
@@ -3367,18 +3394,18 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g
|
||||
|
||||
if (UsingPublicConfig && otherError == false && gameDownloadOutput.Output != "Download skipped.")
|
||||
{
|
||||
|
||||
Thread extractionThread = new Thread(() =>
|
||||
{
|
||||
Invoke(new Action(() =>
|
||||
{
|
||||
speedLabel.Text = "Extracting..."; etaLabel.Text = "Please wait...";
|
||||
progressBar.Style = ProgressBarStyle.Continuous;
|
||||
progressBar.IsIndeterminate = false;
|
||||
progressBar.Value = 0;
|
||||
isInDownloadExtract = true;
|
||||
}));
|
||||
try
|
||||
{
|
||||
progressBar.OperationType = "Extracting";
|
||||
changeTitle("Extracting " + gameName);
|
||||
Zip.ExtractFile($"{settings.DownloadDir}\\{gameNameHash}\\{gameNameHash}.7z.001", $"{settings.DownloadDir}", PublicConfigFile.Password);
|
||||
changeTitle("");
|
||||
@@ -3415,7 +3442,7 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g
|
||||
ADB.DeviceID = GetDeviceID();
|
||||
quotaTries = 0;
|
||||
progressBar.Value = 0;
|
||||
progressBar.Style = ProgressBarStyle.Continuous;
|
||||
progressBar.IsIndeterminate = false;
|
||||
changeTitle("Installing game APK " + gameName);
|
||||
etaLabel.Text = "ETA: Wait for install...";
|
||||
speedLabel.Text = "DLS: Finished";
|
||||
@@ -3474,41 +3501,90 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g
|
||||
};
|
||||
t.Tick += new EventHandler(timer_Tick4);
|
||||
t.Start();
|
||||
Thread apkThread = new Thread(() =>
|
||||
{
|
||||
changeTitle($"Sideloading APK...");
|
||||
etaLabel.Text = "Sideloading APK...";
|
||||
output += ADB.Sideload(apkFile, packagename);
|
||||
})
|
||||
{
|
||||
IsBackground = true
|
||||
};
|
||||
apkThread.Start();
|
||||
while (apkThread.IsAlive)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
changeTitle($"Sideloading APK...");
|
||||
etaLabel.Text = "Installing APK...";
|
||||
progressBar.IsIndeterminate = false;
|
||||
progressBar.OperationType = "Installing";
|
||||
progressBar.Value = 0;
|
||||
|
||||
// Use async method with progress
|
||||
output += await ADB.SideloadWithProgressAsync(
|
||||
apkFile,
|
||||
progress => this.Invoke(() => {
|
||||
if (progress == 0)
|
||||
{
|
||||
progressBar.IsIndeterminate = true;
|
||||
progressBar.OperationType = "Installing";
|
||||
}
|
||||
else
|
||||
{
|
||||
progressBar.IsIndeterminate = false;
|
||||
progressBar.Value = progress;
|
||||
}
|
||||
}),
|
||||
status => this.Invoke(() => {
|
||||
progressBar.StatusText = status;
|
||||
etaLabel.Text = status;
|
||||
}),
|
||||
packagename,
|
||||
Sideloader.gameNameToSimpleName(gameName));
|
||||
|
||||
t.Stop();
|
||||
progressBar.IsIndeterminate = false;
|
||||
|
||||
Debug.WriteLine(wrDelimiter);
|
||||
if (Directory.Exists($"{settings.DownloadDir}\\{gameName}\\{packagename}"))
|
||||
{
|
||||
deleteOBB(packagename);
|
||||
Thread obbThread = new Thread(() =>
|
||||
{
|
||||
changeTitle($"Copying {packagename} OBB to device...");
|
||||
ADB.RunAdbCommandToString($"shell mkdir \"/sdcard/Android/obb/{packagename}\"");
|
||||
output += ADB.RunAdbCommandToString($"push \"{settings.DownloadDir}\\{gameName}\\{packagename}\" \"/sdcard/Android/obb\"");
|
||||
changeTitle("");
|
||||
})
|
||||
{
|
||||
IsBackground = true
|
||||
};
|
||||
obbThread.Start();
|
||||
while (obbThread.IsAlive)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
changeTitle($"Copying {packagename} OBB to device...");
|
||||
etaLabel.Text = "Copying OBB...";
|
||||
progressBar.Value = 0;
|
||||
progressBar.OperationType = "Copying OBB";
|
||||
|
||||
// Use async method with progress for OBB
|
||||
string currentObbStatusBase = string.Empty; // phase or filename
|
||||
|
||||
output += await ADB.CopyOBBWithProgressAsync(
|
||||
$"{settings.DownloadDir}\\{gameName}\\{packagename}",
|
||||
progress => this.Invoke(() =>
|
||||
{
|
||||
progressBar.Value = progress;
|
||||
speedLabel.Text = $"OBB: {progress}%";
|
||||
|
||||
if (!string.IsNullOrEmpty(currentObbStatusBase))
|
||||
{
|
||||
if (currentObbStatusBase.StartsWith("Preparing:", StringComparison.OrdinalIgnoreCase) ||
|
||||
currentObbStatusBase.StartsWith("Copying:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
progressBar.StatusText = currentObbStatusBase;
|
||||
}
|
||||
else
|
||||
{
|
||||
// "filename · 73%"
|
||||
progressBar.StatusText = $"{currentObbStatusBase} · {progress}%";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: just show the numeric percent in the bar
|
||||
progressBar.StatusText = $"{progress}%";
|
||||
}
|
||||
}),
|
||||
status => this.Invoke(() =>
|
||||
{
|
||||
currentObbStatusBase = status ?? string.Empty;
|
||||
if (currentObbStatusBase.StartsWith("Preparing:", StringComparison.OrdinalIgnoreCase) ||
|
||||
currentObbStatusBase.StartsWith("Copying:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
progressBar.StatusText = currentObbStatusBase;
|
||||
}
|
||||
}),
|
||||
Sideloader.gameNameToSimpleName(gameName));
|
||||
|
||||
changeTitle("");
|
||||
|
||||
if (!nodeviceonstart | DeviceConnected)
|
||||
{
|
||||
if (!output.Output.Contains("offline"))
|
||||
@@ -3564,7 +3640,7 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g
|
||||
{
|
||||
ShowPrcOutput(output);
|
||||
}
|
||||
progressBar.Style = ProgressBarStyle.Continuous;
|
||||
progressBar.IsIndeterminate = false;
|
||||
etaLabel.Text = "ETA: Finished Queue";
|
||||
speedLabel.Text = "DLS: Finished Queue";
|
||||
gamesAreDownloading = false;
|
||||
@@ -3686,7 +3762,7 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g
|
||||
{
|
||||
ShowPrcOutput(output);
|
||||
}
|
||||
progressBar.Style = ProgressBarStyle.Continuous;
|
||||
progressBar.IsIndeterminate = false;
|
||||
etaLabel.Text = "ETA: Finished Queue";
|
||||
speedLabel.Text = "DLS: Finished Queue";
|
||||
gamesAreDownloading = false;
|
||||
@@ -3849,7 +3925,8 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g
|
||||
{
|
||||
ADB.wirelessadbON = false;
|
||||
changeTitle("Disabling wireless ADB...");
|
||||
progressBar.Style = ProgressBarStyle.Marquee;
|
||||
progressBar.IsIndeterminate = true;
|
||||
progressBar.OperationType = "";
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
@@ -3866,7 +3943,7 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g
|
||||
try { File.Delete(storedIpPath); } catch { }
|
||||
}
|
||||
|
||||
progressBar.Style = ProgressBarStyle.Continuous;
|
||||
progressBar.IsIndeterminate = false;
|
||||
_ = await CheckForDevice();
|
||||
changeTitlebarToDevice();
|
||||
changeTitle("Wireless ADB disabled.", true);
|
||||
@@ -3917,12 +3994,13 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g
|
||||
|
||||
// Connect to the device
|
||||
changeTitle($"Connecting to {ipAddress}...");
|
||||
progressBar.Style = ProgressBarStyle.Marquee;
|
||||
progressBar.IsIndeterminate = true;
|
||||
progressBar.OperationType = "";
|
||||
|
||||
string ipCommand = $"connect {ipAddress}:5555";
|
||||
string connectResult = await Task.Run(() => ADB.RunAdbCommandToString(ipCommand).Output);
|
||||
|
||||
progressBar.Style = ProgressBarStyle.Continuous;
|
||||
progressBar.IsIndeterminate = false;
|
||||
|
||||
if (connectResult.Contains("cannot resolve host") ||
|
||||
connectResult.Contains("cannot connect to") ||
|
||||
@@ -4090,7 +4168,8 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g
|
||||
}
|
||||
|
||||
changeTitle("Scanning network for ADB devices...");
|
||||
progressBar.Style = ProgressBarStyle.Marquee;
|
||||
progressBar.IsIndeterminate = true;
|
||||
progressBar.OperationType = "";
|
||||
|
||||
// Scan common IP range (1-254) on port 5555
|
||||
var tasks = new List<Task<string>>();
|
||||
@@ -4104,7 +4183,7 @@ Please visit our Telegram (https://t.me/VRPirates) or Discord (https://discord.g
|
||||
var results = await Task.WhenAll(tasks);
|
||||
foundDevices.AddRange(results.Where(r => !string.IsNullOrEmpty(r)));
|
||||
|
||||
progressBar.Style = ProgressBarStyle.Continuous;
|
||||
progressBar.IsIndeterminate = false;
|
||||
changeTitle("");
|
||||
|
||||
return foundDevices;
|
||||
@@ -5100,7 +5179,8 @@ function onYouTubeIframeAPIReady() {
|
||||
if (!isworking)
|
||||
{
|
||||
isworking = true;
|
||||
progressBar.Style = ProgressBarStyle.Marquee;
|
||||
progressBar.IsIndeterminate = true;
|
||||
progressBar.OperationType = "Loading";
|
||||
string HWID = SideloaderUtilities.UUID();
|
||||
string GameName = selectedApp;
|
||||
string packageName = Sideloader.gameNameToPackageName(GameName);
|
||||
@@ -5179,7 +5259,7 @@ function onYouTubeIframeAPIReady() {
|
||||
Directory.Delete($"{settings.MainDir}\\{packageName}", true);
|
||||
isworking = false;
|
||||
changeTitle("");
|
||||
progressBar.Style = ProgressBarStyle.Continuous;
|
||||
progressBar.IsIndeterminate = false;
|
||||
_ = FlexibleMessageBox.Show(Program.form, $"{GameName} pulled to:\n\n{GameName} v{VersionInt} {packageName}.zip\n\nOn your desktop!");
|
||||
}
|
||||
}
|
||||
@@ -6894,7 +6974,8 @@ function onYouTubeIframeAPIReady() {
|
||||
|
||||
// Perform uninstall
|
||||
ProcessOutput output = new ProcessOutput("", "");
|
||||
progressBar.Style = ProgressBarStyle.Marquee;
|
||||
progressBar.IsIndeterminate = true;
|
||||
progressBar.OperationType = "";
|
||||
|
||||
await Task.Run(() => {
|
||||
output += Sideloader.UninstallGame(packageName);
|
||||
@@ -6902,7 +6983,7 @@ function onYouTubeIframeAPIReady() {
|
||||
|
||||
ShowPrcOutput(output);
|
||||
showAvailableSpace();
|
||||
progressBar.Style = ProgressBarStyle.Continuous;
|
||||
progressBar.IsIndeterminate = false;
|
||||
|
||||
// Remove from combo box
|
||||
for (int i = 0; i < m_combo.Items.Count; i++)
|
||||
|
||||
438
ModernProgessBar.cs
Normal file
438
ModernProgessBar.cs
Normal file
@@ -0,0 +1,438 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace AndroidSideloader
|
||||
{
|
||||
// A modern progress bar with rounded corners, left-to-right gradient fill,
|
||||
// animated indeterminate mode, and optional status text overlay
|
||||
[Description("Modern Themed Progress Bar")]
|
||||
public class ModernProgressBar : Control
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private int _value;
|
||||
private int _minimum;
|
||||
private int _maximum = 100;
|
||||
private int _radius = 8;
|
||||
private bool _isIndeterminate;
|
||||
private string _statusText = string.Empty;
|
||||
private string _operationType = string.Empty;
|
||||
|
||||
// Indeterminate animation
|
||||
private readonly Timer _animationTimer;
|
||||
private float _animationOffset;
|
||||
private const float AnimationSpeed = 4f;
|
||||
private const int IndeterminateBlockWidth = 80;
|
||||
|
||||
// Colors
|
||||
private Color _backgroundColor = Color.FromArgb(28, 32, 38);
|
||||
private Color _progressStartColor = Color.FromArgb(120, 220, 190); // lighter accent
|
||||
private Color _progressEndColor = Color.FromArgb(50, 160, 130); // darker accent
|
||||
private Color _indeterminateColor = Color.FromArgb(93, 203, 173); // accent
|
||||
private Color _textColor = Color.FromArgb(230, 230, 230);
|
||||
private Color _textShadowColor = Color.FromArgb(90, 0, 0, 0);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
public ModernProgressBar()
|
||||
{
|
||||
SetStyle(
|
||||
ControlStyles.AllPaintingInWmPaint |
|
||||
ControlStyles.OptimizedDoubleBuffer |
|
||||
ControlStyles.ResizeRedraw |
|
||||
ControlStyles.UserPaint |
|
||||
ControlStyles.SupportsTransparentBackColor,
|
||||
true);
|
||||
|
||||
BackColor = Color.Transparent;
|
||||
|
||||
// Size + Font
|
||||
Height = 28;
|
||||
Width = 220;
|
||||
Font = new Font("Segoe UI", 9f, FontStyle.Bold);
|
||||
|
||||
_animationTimer = new Timer { Interval = 16 }; // ~60fps
|
||||
_animationTimer.Tick += AnimationTimer_Tick;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
[Category("Progress")]
|
||||
[Description("The current value of the progress bar.")]
|
||||
public int Value
|
||||
{
|
||||
get => _value;
|
||||
set
|
||||
{
|
||||
_value = Math.Max(_minimum, Math.Min(_maximum, value));
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
[Category("Progress")]
|
||||
[Description("The minimum value of the progress bar.")]
|
||||
public int Minimum
|
||||
{
|
||||
get => _minimum;
|
||||
set
|
||||
{
|
||||
_minimum = value;
|
||||
if (_value < _minimum) _value = _minimum;
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
[Category("Progress")]
|
||||
[Description("The maximum value of the progress bar.")]
|
||||
public int Maximum
|
||||
{
|
||||
get => _maximum;
|
||||
set
|
||||
{
|
||||
_maximum = value;
|
||||
if (_value > _maximum) _value = _maximum;
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
[Category("Appearance")]
|
||||
[Description("The corner radius of the progress bar.")]
|
||||
public int Radius
|
||||
{
|
||||
get => _radius;
|
||||
set
|
||||
{
|
||||
_radius = Math.Max(0, value);
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
[Category("Progress")]
|
||||
[Description("Whether the progress bar shows indeterminate (marquee) progress.")]
|
||||
public bool IsIndeterminate
|
||||
{
|
||||
get => _isIndeterminate;
|
||||
set
|
||||
{
|
||||
// If there is no change, do nothing
|
||||
if (_isIndeterminate == value)
|
||||
return;
|
||||
|
||||
_isIndeterminate = value;
|
||||
if (_isIndeterminate)
|
||||
{
|
||||
_animationOffset = -IndeterminateBlockWidth;
|
||||
_animationTimer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
_animationTimer.Stop();
|
||||
}
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
[Category("Appearance")]
|
||||
[Description("Optional status text to display on the progress bar.")]
|
||||
public string StatusText
|
||||
{
|
||||
get => _statusText;
|
||||
set
|
||||
{
|
||||
_statusText = value ?? string.Empty;
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
[Category("Appearance")]
|
||||
[Description("Operation type label (e.g., 'Downloading', 'Installing').")]
|
||||
public string OperationType
|
||||
{
|
||||
get => _operationType;
|
||||
set
|
||||
{
|
||||
_operationType = value ?? string.Empty;
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
[Category("Appearance")]
|
||||
[Description("Background color of the progress bar track.")]
|
||||
public Color BackgroundColor
|
||||
{
|
||||
get => _backgroundColor;
|
||||
set { _backgroundColor = value; Invalidate(); }
|
||||
}
|
||||
|
||||
[Category("Appearance")]
|
||||
[Description("Start color of the progress gradient (left side).")]
|
||||
public Color ProgressStartColor
|
||||
{
|
||||
get => _progressStartColor;
|
||||
set { _progressStartColor = value; Invalidate(); }
|
||||
}
|
||||
|
||||
[Category("Appearance")]
|
||||
[Description("End color of the progress gradient (right side).")]
|
||||
public Color ProgressEndColor
|
||||
{
|
||||
get => _progressEndColor;
|
||||
set { _progressEndColor = value; Invalidate(); }
|
||||
}
|
||||
|
||||
[Category("Appearance")]
|
||||
[Description("Color used for indeterminate animation.")]
|
||||
public Color IndeterminateColor
|
||||
{
|
||||
get => _indeterminateColor;
|
||||
set { _indeterminateColor = value; Invalidate(); }
|
||||
}
|
||||
|
||||
[Category("Appearance")]
|
||||
[Description("Text color for status overlay.")]
|
||||
public Color TextColor
|
||||
{
|
||||
get => _textColor;
|
||||
set { _textColor = value; Invalidate(); }
|
||||
}
|
||||
|
||||
// Gets the progress as a percentage (0-100)
|
||||
public float ProgressPercent =>
|
||||
_maximum > _minimum ? (float)(_value - _minimum) / (_maximum - _minimum) * 100f : 0f;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Painting
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
var g = e.Graphics;
|
||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
g.Clear(BackColor);
|
||||
|
||||
int w = ClientSize.Width;
|
||||
int h = ClientSize.Height;
|
||||
if (w <= 0 || h <= 0) return;
|
||||
|
||||
var outerRect = new Rectangle(0, 0, w - 1, h - 1);
|
||||
|
||||
// Draw background track
|
||||
using (var path = CreateRoundedPath(outerRect, _radius))
|
||||
using (var bgBrush = new SolidBrush(_backgroundColor))
|
||||
{
|
||||
g.FillPath(bgBrush, path);
|
||||
}
|
||||
|
||||
// Draw progress or indeterminate animation
|
||||
if (_isIndeterminate)
|
||||
{
|
||||
DrawIndeterminate(g, outerRect);
|
||||
}
|
||||
else if (_value > _minimum)
|
||||
{
|
||||
DrawProgress(g, outerRect);
|
||||
}
|
||||
|
||||
// Draw text overlay
|
||||
DrawTextOverlay(g, outerRect);
|
||||
|
||||
base.OnPaint(e);
|
||||
}
|
||||
|
||||
private void DrawProgress(Graphics g, Rectangle outerRect)
|
||||
{
|
||||
float percent = (_maximum > _minimum)
|
||||
? (float)(_value - _minimum) / (_maximum - _minimum)
|
||||
: 0f;
|
||||
|
||||
if (percent <= 0f) return;
|
||||
if (percent > 1f) percent = 1f;
|
||||
|
||||
int progressWidth = (int)Math.Round(outerRect.Width * percent);
|
||||
if (progressWidth <= 0) return;
|
||||
if (progressWidth > outerRect.Width) progressWidth = outerRect.Width;
|
||||
|
||||
using (var outerPath = CreateRoundedPath(outerRect, _radius))
|
||||
{
|
||||
// Clip to progress area inside rounded track
|
||||
Rectangle progressRect = new Rectangle(outerRect.X, outerRect.Y, progressWidth, outerRect.Height);
|
||||
using (var progressClip = new Region(progressRect))
|
||||
using (var trackRegion = new Region(outerPath))
|
||||
{
|
||||
trackRegion.Intersect(progressClip);
|
||||
|
||||
Region prevClip = g.Clip;
|
||||
try
|
||||
{
|
||||
g.SetClip(trackRegion, CombineMode.Replace);
|
||||
|
||||
// Left-to-right gradient, based on accent color
|
||||
using (var gradientBrush = new LinearGradientBrush(
|
||||
progressRect,
|
||||
_progressStartColor,
|
||||
_progressEndColor,
|
||||
LinearGradientMode.Horizontal))
|
||||
{
|
||||
g.FillPath(gradientBrush, outerPath);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
g.Clip = prevClip;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawIndeterminate(Graphics g, Rectangle outerRect)
|
||||
{
|
||||
using (var outerPath = CreateRoundedPath(outerRect, _radius))
|
||||
{
|
||||
Region prevClip = g.Clip;
|
||||
try
|
||||
{
|
||||
g.SetClip(outerPath, CombineMode.Replace);
|
||||
|
||||
int blockWidth = Math.Min(IndeterminateBlockWidth, outerRect.Width);
|
||||
int blockX = (int)_animationOffset;
|
||||
var blockRect = new Rectangle(blockX, outerRect.Y, blockWidth, outerRect.Height);
|
||||
|
||||
// Solid bar with slight left-to-right gradient
|
||||
using (var brush = new LinearGradientBrush(
|
||||
blockRect,
|
||||
ControlPaint.Light(_indeterminateColor, 0.1f),
|
||||
ControlPaint.Dark(_indeterminateColor, 0.1f),
|
||||
LinearGradientMode.Horizontal))
|
||||
{
|
||||
g.FillRectangle(brush, blockRect);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
g.Clip = prevClip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTextOverlay(Graphics g, Rectangle outerRect)
|
||||
{
|
||||
string displayText = BuildDisplayText();
|
||||
if (string.IsNullOrEmpty(displayText)) return;
|
||||
|
||||
using (var sf = new StringFormat
|
||||
{
|
||||
Alignment = StringAlignment.Center,
|
||||
LineAlignment = StringAlignment.Center,
|
||||
Trimming = StringTrimming.EllipsisCharacter
|
||||
})
|
||||
{
|
||||
// Slight shadow for legibility on accent background
|
||||
var shadowRect = new Rectangle(outerRect.X + 1, outerRect.Y + 1, outerRect.Width, outerRect.Height);
|
||||
using (var shadowBrush = new SolidBrush(_textShadowColor))
|
||||
{
|
||||
g.DrawString(displayText, Font, shadowBrush, shadowRect, sf);
|
||||
}
|
||||
|
||||
using (var textBrush = new SolidBrush(_textColor))
|
||||
{
|
||||
g.DrawString(displayText, Font, textBrush, outerRect, sf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildDisplayText()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_statusText))
|
||||
{
|
||||
return _statusText;
|
||||
}
|
||||
|
||||
if (_isIndeterminate && !string.IsNullOrEmpty(_operationType))
|
||||
{
|
||||
// E.g. "Downloading..."
|
||||
return _operationType + "...";
|
||||
}
|
||||
|
||||
if (!_isIndeterminate && _value > _minimum)
|
||||
{
|
||||
string percentText = $"{(int)ProgressPercent}%";
|
||||
if (!string.IsNullOrEmpty(_operationType))
|
||||
{
|
||||
// E.g. "Downloading · 73%"
|
||||
return $"{_operationType} · {percentText}";
|
||||
}
|
||||
return percentText;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private GraphicsPath CreateRoundedPath(Rectangle rect, int radius)
|
||||
{
|
||||
var path = new GraphicsPath();
|
||||
|
||||
if (radius <= 0)
|
||||
{
|
||||
path.AddRectangle(rect);
|
||||
return path;
|
||||
}
|
||||
|
||||
int diameter = radius * 2;
|
||||
diameter = Math.Min(diameter, Math.Min(rect.Width, rect.Height));
|
||||
radius = diameter / 2;
|
||||
|
||||
var arcRect = new Rectangle(rect.Location, new Size(diameter, diameter));
|
||||
|
||||
path.AddArc(arcRect, 180, 90);
|
||||
arcRect.X = rect.Right - diameter;
|
||||
path.AddArc(arcRect, 270, 90);
|
||||
arcRect.Y = rect.Bottom - diameter;
|
||||
path.AddArc(arcRect, 0, 90);
|
||||
arcRect.X = rect.Left;
|
||||
path.AddArc(arcRect, 90, 90);
|
||||
|
||||
path.CloseFigure();
|
||||
return path;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Animation
|
||||
|
||||
private void AnimationTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
_animationOffset += AnimationSpeed;
|
||||
|
||||
if (_animationOffset > ClientSize.Width + IndeterminateBlockWidth)
|
||||
{
|
||||
_animationOffset = -IndeterminateBlockWidth;
|
||||
}
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cleanup
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_animationTimer?.Stop();
|
||||
_animationTimer?.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
BIN
icon.ico
BIN
icon.ico
Binary file not shown.
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="AdvancedSharpAdbClient" version="3.5.15" targetFramework="net452" />
|
||||
<package id="Costura.Fody" version="5.7.0" targetFramework="net452" developmentDependency="true" />
|
||||
<package id="Fody" version="6.8.1" targetFramework="net452" developmentDependency="true" />
|
||||
<package id="Microsoft.NETCore.Platforms" version="7.0.4" targetFramework="net452" />
|
||||
|
||||
Reference in New Issue
Block a user