Skip to content

Commit 8909da1

Browse files
committed
Add optional support links and clear monthly and one-time choices
1 parent 200239a commit 8909da1

8 files changed

Lines changed: 204 additions & 5 deletions

File tree

‎README.md‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,15 @@ The original PowerShell builders remain in this repository.
5252

5353
## Support the project
5454

55-
[GitHub Sponsors](https://github.com/sponsors/ntdevlabs) ·
56-
[Patreon](https://www.patreon.com/ntdev) ·
57-
[PayPal](https://paypal.me/ntdev2) ·
58-
[Ko-fi](https://ko-fi.com/ntdev)
55+
Tiny11 Builder is free. If it saved you time, you can help fund development,
56+
testing across Windows releases and fixes for reported problems.
57+
58+
**[Monthly support](https://github.com/sponsors/ntdevlabs)** ·
59+
**[One-time donation](https://ko-fi.com/ntdev)**
60+
61+
Prefer another service? [Patreon](https://www.patreon.com/ntdev) ·
62+
[PayPal](https://paypal.me/ntdev2)
63+
64+
No donation is needed to use any feature. Reporting reproducible bugs,
65+
testing builds in a VM and sharing the [demo](https://youtu.be/LfEWIz5mQ8A)
66+
help too.

‎docs/support.md‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Support Tiny11 Builder
2+
3+
Tiny11 Builder is free, including all build modes and removal options.
4+
Optional support helps fund development time, testing across Windows
5+
releases and fixes for reported problems.
6+
7+
**[Monthly support on GitHub Sponsors](https://github.com/sponsors/ntdevlabs)** ·
8+
**[One-time donation on Ko-fi](https://ko-fi.com/ntdev)**
9+
10+
Other options: [Patreon](https://www.patreon.com/ntdev) ·
11+
[PayPal](https://paypal.me/ntdev2).
12+
13+
You can also help by reporting reproducible bugs, testing output in a VM
14+
and sharing the [video demo](https://youtu.be/LfEWIz5mQ8A). Include your
15+
app version, image build, selected options and relevant logs in bug reports;
16+
remove personal paths and private information before sharing logs.
17+
18+
## Text for the video description or a pinned comment
19+
20+
> Tiny11 Builder is free. If it saved you time, you can support its development:
21+
>
22+
> Monthly: https://github.com/sponsors/ntdevlabs
23+
>
24+
> One-time: https://ko-fi.com/ntdev
25+
>
26+
> Download: https://github.com/ntdevlabs/tiny11builder/releases/latest
27+
>
28+
> Bug reports and sharing the project help too. All app features remain free.
29+
30+
## Project updates
31+
32+
Each release's notes link to the corresponding source and describe fixes,
33+
known limitations and any remaining work. Keep progress updates specific:
34+
what was fixed, what was tested and what still needs work. Set a funding goal
35+
only when it reflects a real, explained cost; no target or spending figure is
36+
claimed here.

‎docs/usage.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
Run the commands below from the repository root. For downloads, the video and
44
screenshots, see the [project page](../README.md).
55

6+
Optional [project support](support.md) is available from **Support development**
7+
in the sidebar and on the successful-build page. The choices open only when
8+
clicked, never interrupt a build, and never unlock features. Payments happen
9+
on the selected provider's website, not inside the app.
10+
611
**WinUI 3 desktop application with a C# image builder.** Its step-by-step layout
712
uses the [S9 WoA installer](https://github.com/ntdevlabs/exynos9810-woa) as a reference.
813
It does not wrap or execute the PowerShell builders. The original scripts remain

‎src/Tiny11Builder.App/MainWindow.xaml‎

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@
4141
<ComboBox x:Name="ThemeChoice" Header="Appearance" SelectionChanged="OnThemeChanged" HorizontalAlignment="Stretch" SelectedIndex="0">
4242
<ComboBoxItem Content="System"/><ComboBoxItem Content="Light"/><ComboBoxItem Content="Dark"/>
4343
</ComboBox>
44-
<HyperlinkButton Content="Upstream project ↗" NavigateUri="https://github.com/ntdevlabs/tiny11builder" Padding="0"/>
44+
<StackPanel Spacing="4">
45+
<HyperlinkButton x:Name="SupportButton" Content="Support development" Click="OnSupport" Padding="0"
46+
AutomationProperties.HelpText="Optional monthly support or one-time donation. All app features remain free."/>
47+
<HyperlinkButton Content="Upstream project ↗" NavigateUri="https://github.com/ntdevlabs/tiny11builder" Padding="0"/>
48+
</StackPanel>
4549
</StackPanel>
4650
</Grid>
4751
<Grid Grid.Column="1" Background="{ThemeResource LayerFillColorDefaultBrush}" CornerRadius="16,0,0,0"
@@ -478,6 +482,11 @@
478482
<Button Content="Open logs and report" Click="OnOpenLogs"/>
479483
</StackPanel>
480484
<TextBlock Text="Test the ISO in a VM before installing Windows." Style="{StaticResource Hint}"/>
485+
<StackPanel Spacing="4">
486+
<TextBlock Text="If Tiny11 Builder saved you time, you can support its development." Style="{StaticResource Hint}"/>
487+
<HyperlinkButton Content="Support development" Click="OnSupport" Padding="0"
488+
AutomationProperties.HelpText="Optional support. Opens support choices, not a payment."/>
489+
</StackPanel>
481490
</StackPanel>
482491
</Border>
483492
<StackPanel Spacing="16">
@@ -560,5 +569,30 @@
560569
Visibility="Collapsed" AutomationProperties.Name="Working"/>
561570
</Grid>
562571
</Grid>
572+
<ContentDialog x:Name="SupportDialog" Title="Support Tiny11 Builder" CloseButtonText="Close"
573+
DefaultButton="Close">
574+
<StackPanel Spacing="20" MaxWidth="440">
575+
<TextBlock x:Name="SupportPurpose" TextWrapping="Wrap"/>
576+
<StackPanel Spacing="8">
577+
<TextBlock Text="Monthly support" FontSize="18" FontWeight="SemiBold"/>
578+
<TextBlock Text="Help keep the project maintained over time." Style="{StaticResource Secondary}"/>
579+
<HyperlinkButton x:Name="SponsorsLink" Content="Support monthly on GitHub Sponsors" Click="OnSupportDestination" Tag="Sponsors" Padding="0"/>
580+
</StackPanel>
581+
<StackPanel Spacing="8">
582+
<TextBlock Text="One-time donation" FontSize="18" FontWeight="SemiBold"/>
583+
<TextBlock Text="A one-off thank-you, with no commitment." Style="{StaticResource Secondary}"/>
584+
<HyperlinkButton x:Name="KofiLink" Content="Donate once on Ko-fi" Click="OnSupportDestination" Tag="Kofi" Padding="0"/>
585+
</StackPanel>
586+
<StackPanel Spacing="8">
587+
<TextBlock Text="Prefer another service?" Style="{StaticResource Hint}"/>
588+
<StackPanel Orientation="Horizontal" Spacing="20">
589+
<HyperlinkButton Content="Patreon" Click="OnSupportDestination" Tag="Patreon" Padding="0"/>
590+
<HyperlinkButton Content="PayPal" Click="OnSupportDestination" Tag="PayPal" Padding="0"/>
591+
</StackPanel>
592+
</StackPanel>
593+
<TextBlock Text="Links open in your browser. Nothing is charged or subscribed to in the app." Style="{StaticResource Hint}"/>
594+
<InfoBar x:Name="SupportError" IsOpen="False" IsClosable="True" Severity="Error"/>
595+
</StackPanel>
596+
</ContentDialog>
563597
</Grid>
564598
</Window>

‎src/Tiny11Builder.App/MainWindow.xaml.cs‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ public MainWindow()
7878
$"Choose {CancelPrompts.ButtonLabel} and wait for the image operation to finish before closing.", InfoBarSeverity.Warning);
7979
};
8080
WorkspaceBox.Text = @"C:\tiny11";
81+
SupportPurpose.Text = ProjectSupport.Purpose;
8182
OutputBox.Text = Path.Combine(WorkspaceBox.Text, $"tiny11-{DateTime.Now:yyyyMMdd-HHmmss}.iso");
8283
BuildStages.ItemsSource = board.Stages;
8384
progressTimer.Tick += (_, _) => { FlushBuildProgress(); DrainDownload(); };
@@ -211,6 +212,7 @@ private void UpdateNavigation()
211212
Microsoft.UI.Xaml.Automation.AutomationProperties.SetName(NextButton, ActionLabel.Text);
212213
StepRail.IsEnabled = !busy && !completed;
213214
RecoverButton.IsEnabled = !busy;
215+
SupportButton.IsEnabled = !busy;
214216
ResumeButton.IsEnabled = !busy;
215217
ThemeChoice.IsEnabled = !busy && !HighContrast;
216218
// Advisory only: the checkpoint file's presence decides whether to offer Resume; the engine verifies it.
@@ -584,6 +586,50 @@ private async Task<bool> Confirm(string title, string content, string action)
584586
}
585587
}
586588

589+
private async void OnSupport(object sender, RoutedEventArgs args)
590+
{
591+
if (busy || modalOpen || Root.XamlRoot is null) return;
592+
modalOpen = true;
593+
var previousFocus = CurrentFocus();
594+
try
595+
{
596+
SupportDialog.XamlRoot = Root.XamlRoot;
597+
SupportDialog.RequestedTheme = DialogTheme();
598+
SupportError.IsOpen = false;
599+
await SupportDialog.ShowAsync();
600+
}
601+
catch (Exception error)
602+
{
603+
ShowNotice("Support options could not be opened.", ErrorText.Reason(error),
604+
InfoBarSeverity.Error, NoticeAction.CopyDetails, error.ToString());
605+
}
606+
finally
607+
{
608+
modalOpen = false;
609+
RestoreFocus(previousFocus);
610+
}
611+
}
612+
613+
private async void OnSupportDestination(object sender, RoutedEventArgs args)
614+
{
615+
try
616+
{
617+
if (sender is not FrameworkElement { Tag: string tag } ||
618+
!Enum.TryParse<SupportDestination>(tag, out var destination))
619+
throw new InvalidDataException("This support link has no valid destination.");
620+
var uri = ProjectSupport.Link(destination);
621+
if (!await Windows.System.Launcher.LaunchUriAsync(uri))
622+
throw new IOException($"Your browser could not open {uri}. No payment was made.");
623+
SupportError.IsOpen = false;
624+
}
625+
catch (Exception error)
626+
{
627+
SupportError.Title = "Could not open the support page";
628+
SupportError.Message = ErrorText.Reason(error);
629+
SupportError.IsOpen = true;
630+
}
631+
}
632+
587633
private async Task<string?> Pick(Func<Task<string?>> pick)
588634
{
589635
if (modalOpen) return null;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace Tiny11Builder.App;
2+
3+
internal enum SupportDestination { Sponsors, Kofi, Patreon, PayPal }
4+
5+
internal static class ProjectSupport
6+
{
7+
public const string Purpose = "Help fund development time, testing across Windows releases and fixes for reported problems. All app features remain free.";
8+
9+
public static Uri Link(SupportDestination destination) => new(destination switch
10+
{
11+
SupportDestination.Sponsors => "https://github.com/sponsors/ntdevlabs",
12+
SupportDestination.Kofi => "https://ko-fi.com/ntdev",
13+
SupportDestination.Patreon => "https://www.patreon.com/ntdev",
14+
SupportDestination.PayPal => "https://paypal.me/ntdev2",
15+
_ => throw new ArgumentOutOfRangeException(nameof(destination))
16+
});
17+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System.Xml.Linq;
2+
using Tiny11Builder.App;
3+
using Xunit;
4+
5+
namespace Tiny11Builder.Core.Tests;
6+
7+
public sealed class ProjectSupportTests
8+
{
9+
[Theory]
10+
[InlineData("Sponsors", "https://github.com/sponsors/ntdevlabs")]
11+
[InlineData("Kofi", "https://ko-fi.com/ntdev")]
12+
[InlineData("Patreon", "https://www.patreon.com/ntdev")]
13+
[InlineData("PayPal", "https://paypal.me/ntdev2")]
14+
public void Support_uses_only_the_existing_project_destinations(string destination, string expected)
15+
{
16+
var uri = ProjectSupport.Link(Enum.Parse<SupportDestination>(destination));
17+
Assert.Equal(expected, uri.AbsoluteUri);
18+
Assert.Equal("https", uri.Scheme);
19+
Assert.True(uri.IsDefaultPort);
20+
Assert.Empty(uri.Query);
21+
Assert.Empty(uri.UserInfo);
22+
}
23+
24+
[Fact]
25+
public void Unsupported_links_are_not_guessed_and_support_never_unlocks_features()
26+
{
27+
Assert.Throws<ArgumentOutOfRangeException>(() => ProjectSupport.Link((SupportDestination)99));
28+
Assert.Contains("All app features remain free", ProjectSupport.Purpose);
29+
}
30+
31+
[Fact]
32+
public void Support_is_only_a_user_opened_panel_and_the_success_link_stays_inside_the_result()
33+
{
34+
var document = XDocument.Load(Path.Combine(AppContext.BaseDirectory, "Deployment", "MainWindow.xaml"));
35+
XNamespace ns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation";
36+
XNamespace x = "http://schemas.microsoft.com/winfx/2006/xaml";
37+
var dialog = Assert.Single(document.Descendants(ns + "ContentDialog"));
38+
Assert.Equal("SupportDialog", dialog.Attribute(x + "Name")?.Value);
39+
Assert.Equal("Close", dialog.Attribute("DefaultButton")?.Value);
40+
Assert.Null(dialog.Attribute("IsOpen"));
41+
var triggers = document.Descendants(ns + "HyperlinkButton")
42+
.Where(element => element.Attribute("Click")?.Value == "OnSupport").ToArray();
43+
Assert.Equal(2, triggers.Length);
44+
var result = Assert.Single(document.Descendants(), element => element.Attribute(x + "Name")?.Value == "ResultPanel");
45+
Assert.Contains(triggers, element => element.Ancestors().Contains(result));
46+
var links = dialog.Descendants(ns + "HyperlinkButton").ToArray();
47+
Assert.Equal(4, links.Length);
48+
Assert.All(links, element => Assert.True(Enum.TryParse<SupportDestination>(element.Attribute("Tag")?.Value, out _)));
49+
Assert.All(links, element => Assert.Equal("OnSupportDestination", element.Attribute("Click")?.Value));
50+
}
51+
}

‎tests/Tiny11Builder.Core.Tests/Tiny11Builder.Core.Tests.csproj‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
<!-- WinUI-free app logic, compiled as-is so the tests run the shipped code. -->
1515
<Compile Include="..\..\src\Tiny11Builder.App\Choices.cs" Link="AppLogic\Choices.cs"/>
1616
<Compile Include="..\..\src\Tiny11Builder.App\OperationFlow.cs" Link="AppLogic\OperationFlow.cs"/>
17+
<Compile Include="..\..\src\Tiny11Builder.App\ProjectSupport.cs" Link="AppLogic\ProjectSupport.cs"/>
18+
<None Include="..\..\src\Tiny11Builder.App\MainWindow.xaml" Link="Deployment\MainWindow.xaml" CopyToOutputDirectory="PreserveNewest"/>
1719
<None Include="..\..\src\Tiny11Builder.App\app.manifest" Link="Deployment\app.manifest" CopyToOutputDirectory="PreserveNewest"/>
1820
</ItemGroup>
1921
</Project>

0 commit comments

Comments
 (0)