Files
Flyoobe/Flyoobe/Views/AppsControlView.cs
T
2025-09-11 17:13:10 +02:00

356 lines
13 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Windows.Foundation;
using Windows.Management.Deployment;
namespace Flyoobe
{
public partial class AppsControlView : UserControl, IView
{
public string ViewTitle => "Manage Installed Apps";
private Dictionary<string, string> _appDirectory = new Dictionary<string, string>();
private string currentSearchTerm = string.Empty;
private string activePatternFile = "Flyoobe_Profile_Full.txt";
public AppsControlView()
{
InitializeComponent();
}
private void AppControlView_Load(object sender, EventArgs e)
{
InitializeProfileDropdown();
RefreshView();
assetViewInfo.Text = "\uE773";
}
private void InitializeProfileDropdown()
{
profileDropdown.Items.Clear();
profileDropdown.Items.Add("Full Microsoft Experience – everything included");
profileDropdown.Items.Add("Balanced – essentials plus Store (recommended)");
profileDropdown.Items.Add("Minimal Windows – only essentials, zero bloat");
profileDropdown.Items.Add("Community (from GitHub)");
profileDropdown.SelectedIndex = 1; // Default to Balanced
profileDropdown.SelectedIndexChanged += async (s, e) => await ApplyProfileChange();
}
/// <summary>
/// Called when profile dropdown is changed.
/// Updates label + sets correct pattern file + reloads app list.
/// </summary>
private async Task ApplyProfileChange()
{
string baseText = "Select the apps you want to uninstall. Use the dropdown to pick a cleanup profile. ";
switch (profileDropdown.SelectedIndex)
{
case 0: // Full
lblStatus.Text = baseText + "Currently showing: Full Microsoft Experience.";
activePatternFile = "Flyoobe_Profile_Full.txt";
await LoadAndDisplayApps();
break;
case 1: // Balanced
lblStatus.Text = baseText + "Currently showing: Balanced profile.";
activePatternFile = "Flyoobe_Profile_Balanced.txt";
await LoadAndDisplayApps();
break;
case 2: // Minimal
lblStatus.Text = baseText + "Currently showing: Minimal Windows profile.";
activePatternFile = "Flyoobe_Profile_Minimal.txt";
await LoadAndDisplayApps();
break;
case 3: // Community
await LoadCommunityProfileAsync();
break;
}
}
/// <summary>
/// Downloads and applies the community profile from GitHub.
/// Always overwrites the local copy.
/// </summary>
private async Task LoadCommunityProfileAsync()
{
string url = "https://raw.githubusercontent.com/builtbybel/Flyoobe/main/assets/Flyoobe_Profile_Community.txt";
string appDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "app");
string localPath = Path.Combine(appDir, "Flyoobe_Profile_Community.txt");
try
{
var result = MessageBox.Show(
"A Community Cleanup Profile is available online.\n\n" +
"This profile contains the most commonly removed apps, curated by the community.\n\n" +
"Do you want to download and apply it now?",
"Community Profile",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question);
if (result == DialogResult.Yes)
{
Directory.CreateDirectory(appDir);
using (var client = new System.Net.WebClient())
{
await client.DownloadFileTaskAsync(new Uri(url), localPath);
}
activePatternFile = Path.GetFileName(localPath);
lblStatus.Text = "Community Profile active – showing most commonly removed apps.";
await LoadAndDisplayApps();
}
else
{
lblStatus.Text = "Community Profile was not applied.";
}
}
catch (Exception ex)
{
MessageBox.Show(
"⚠️ Failed to load the Community Profile.\n\n" + ex.Message,
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
// Loads all installed apps and displays matches in the checked list
private async Task LoadAndDisplayApps()
{
checkedListBoxApps.Items.Clear();
var (bloatware, whitelist, scanAll) = LoadNativeAppPatterns();
await LoadInstalledAppsAsync();
foreach (var app in _appDirectory)
{
string appNameLower = app.Key.ToLower();
// Skip whitelisted apps
if (whitelist.Any(w => appNameLower.Contains(w)))
continue;
// Apply search filter
if (!string.IsNullOrEmpty(currentSearchTerm) &&
!appNameLower.Contains(currentSearchTerm))
continue;
// Show all apps if wildcard is enabled, or match by pattern
if (scanAll || bloatware.Any(b => appNameLower.Contains(b)))
{
checkedListBoxApps.Items.Add(app.Key, false);
}
}
}
// Loads installed UWP apps into the internal dictionary
private async Task LoadInstalledAppsAsync()
{
_appDirectory.Clear();
var pm = new PackageManager();
var packages = await Task.Run(() =>
pm.FindPackagesForUserWithPackageTypes(string.Empty, PackageTypes.Main));
foreach (var p in packages)
{
string name = p.Id.Name;
string fullName = p.Id.FullName;
if (!_appDirectory.ContainsKey(name))
{
_appDirectory[name] = fullName;
}
}
}
// Loads bloatware patterns and whitelist from text file
private (string[] bloatware, string[] whitelist, bool scanAll) LoadNativeAppPatterns()
{
var bloatware = new List<string>();
var whitelist = new List<string>();
bool scanAll = false;
string pathToUse = GetActivePatternFilePath(activePatternFile);
if (string.IsNullOrEmpty(pathToUse) || !File.Exists(pathToUse))
return (Array.Empty<string>(), Array.Empty<string>(), false);
foreach (var rawLine in File.ReadLines(pathToUse))
{
string line = rawLine.Split('#')[0].Trim();
if (string.IsNullOrWhiteSpace(line))
continue;
if (line == "*" || line == "*.*")
{
scanAll = true;
continue;
}
if (line.StartsWith("!"))
whitelist.Add(line.Substring(1).Trim().ToLowerInvariant());
else
bloatware.Add(line.ToLowerInvariant());
}
return (bloatware.ToArray(), whitelist.ToArray(), scanAll);
}
/// <summary>
/// Gets the path to the selected profile pattern file inside /app directory.
/// </summary>
private string GetActivePatternFilePath(string fileName, bool ensureExists = false)
{
string baseDir = AppDomain.CurrentDomain.BaseDirectory;
string appDir = Path.Combine(baseDir, "app");
string profilePath = Path.Combine(appDir, fileName);
if (ensureExists && !File.Exists(profilePath))
{
Directory.CreateDirectory(appDir); // make sure folder exists
File.WriteAllText(profilePath, "# Add your app detection patterns here\n");
}
return profilePath;
}
private async void btnRemoveSelected_Click(object sender, EventArgs e)
{
lblStatus.Text = "Starting cleanup...";
progressBar.Visible = true;
var toRemove = new List<string>();
// Get selected app full names from checked list
foreach (var item in checkedListBoxApps.CheckedItems)
{
string appName = item.ToString();
if (_appDirectory.TryGetValue(appName, out var fullName))
{
toRemove.Add(fullName);
}
}
if (toRemove.Count == 0)
{
MessageBox.Show("Please select at least one app.", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
progressBar.Minimum = 0;
progressBar.Maximum = toRemove.Count;
progressBar.Value = 0;
int count = 0;
foreach (var fullName in toRemove)
{
string name = _appDirectory.FirstOrDefault(x => x.Value == fullName).Key;
lblStatus.Text = $"Removing {name}...";
bool success = await UninstallAppAsync(fullName);
count++;
progressBar.Value = count;
if (!success)
{
lblStatus.Text = $"Failed to remove {name}";
Logger.Log($"Uninstalling {name} ({fullName}) - Success: {success}", LogLevel.Warning);
}
else
{
lblStatus.Text = $"Removed {name}";
Logger.Log($"Successfully uninstalled {name} ({fullName})", LogLevel.Info);
}
await Task.Delay(200); // pause to visualize progress
}
MessageBox.Show("Uninstallation completed.", "Done", MessageBoxButtons.OK, MessageBoxIcon.Information);
// Refresh app list after cleanup
await LoadAndDisplayApps();
progressBar.Value = 0;
progressBar.Visible = false;
lblStatus.Text = "Cleanup finished.";
}
// Uninstalls a UWP app using its full package name
private async Task<bool> UninstallAppAsync(string fullName)
{
try
{
var pm = new PackageManager();
var operation = pm.RemovePackageAsync(fullName);
var resetEvent = new ManualResetEvent(false);
operation.Completed = (o, s) => resetEvent.Set();
await Task.Run(() => resetEvent.WaitOne());
return operation.Status == AsyncStatus.Completed;
}
catch (Exception ex)
{
MessageBox.Show($"Error uninstalling:\n{ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
}
}
private void btnEdit_Click(object sender, EventArgs e)
{
try
{
// Use the currently active profile file
string filePath = GetActivePatternFilePath(activePatternFile, ensureExists: true);
System.Diagnostics.Process.Start("notepad.exe", filePath);
}
catch (Exception ex)
{
MessageBox.Show("Error opening profile file: " + ex.Message, "Error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void textSearch_TextChanged(object sender, EventArgs e)
{
currentSearchTerm = textSearch.Text.Trim().ToLower();
_ = LoadAndDisplayApps(); // Re-run with filter
}
private void textSearch_Click(object sender, EventArgs e)
{
textSearch.Clear();
}
public async void RefreshView()
{
currentSearchTerm = string.Empty;
checkedListBoxApps.Items.Clear();
await ApplyProfileChange(); // reload with current profile
}
private void checkSelectAll_CheckedChanged(object sender, EventArgs e)
{
bool checkAll = checkSelectAll.Checked;
for (int i = 0; i < checkedListBoxApps.Items.Count; i++)
{
checkedListBoxApps.SetItemChecked(i, checkAll);
}
}
}
}