Social bar UX: YT auto-add from connected account, two-box dialog (YT handle + URL/handle for other services), service auto-detection from URL domain, fediverse @user@domain support, footer repositioned (Social controls left under scenes/sources, meter centered under preview), validation with add-anyway fallback

This commit is contained in:
2026-08-12 07:40:42 -07:00
parent 5437c776fe
commit 8868f98392
3 changed files with 182 additions and 50 deletions
+76 -16
View File
@@ -889,6 +889,7 @@ public class MainViewModel : ViewModelBase
IsConnected = true;
SyncConnectedAccount();
AutoAddYouTubeSocial();
}
catch (Exception ex)
{
@@ -903,6 +904,34 @@ public class MainViewModel : ViewModelBase
AccountDisplayName = channel?.DisplayName ?? string.Empty;
}
/// <summary>
/// When the creator is connected to YouTube and hasn't defined any socials yet,
/// auto-add YouTube as the first entry — the connected channel is the default
/// social, no extra input needed.
/// </summary>
private void AutoAddYouTubeSocial()
{
if (_socials != null && _socials.Entries.Count > 0) return;
var channel = _youtubeAuth.CurrentChannel;
if (channel == null || string.IsNullOrWhiteSpace(channel.DisplayName)) return;
_socials ??= new SocialsConfig();
var handle = channel.DisplayName.Trim().TrimStart('@');
_socials.Entries.Add(new SocialEntry
{
Service = SocialService.YouTube,
Handle = handle,
ProfileUrl = !string.IsNullOrWhiteSpace(channel.ChannelId)
? $"https://www.youtube.com/channel/{channel.ChannelId}"
: SocialServiceIcons.CanonicalUrlFor(SocialService.YouTube, handle),
});
OnPropertyChanged(nameof(HasSocials));
OnPropertyChanged(nameof(CanToggleSceneSocialBar));
OnPropertyChanged(nameof(CanAddMoreSocials));
ScheduleSave();
}
private static string DefaultLayoutPath => Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"ytLlive",
@@ -1826,10 +1855,6 @@ public class MainViewModel : ViewModelBase
private void OpenSocialDialog()
{
// The social dialog is a simple input flow: pick a service, enter a
// handle/URL, the app validates by looking up the page, and on success
// adds the entry. For now this is a MessageBox-based flow — a proper
// dialog window follows once the bar rendering is validated.
if (!CanAddMoreSocials)
{
MessageBox.Show(
@@ -1840,34 +1865,69 @@ public class MainViewModel : ViewModelBase
return;
}
var services = string.Join(", ", Enum.GetNames<SocialService>());
var input = ShowInputDialog($"Service ({services}):", "Add Social — Step 1 of 2", "YouTube");
if (string.IsNullOrWhiteSpace(input)) return;
if (!Enum.TryParse<SocialService>(input, true, out var service))
// Step 1: YouTube handle — pre-filled from the connected account if available.
var ytHandle = IsConnected && _youtubeAuth.CurrentChannel is { } ch
? ch.DisplayName
: ShowInputDialog("Your YouTube handle (e.g. @yourchannel):", "Add Social — YouTube", "@yourchannel");
if (string.IsNullOrWhiteSpace(ytHandle)) return;
ytHandle = ytHandle.Trim().TrimStart('@');
// Validate the YT handle (or skip validation if it came from the connected account).
if (!IsConnected || _socials?.Entries.Any(e => e.Service == SocialService.YouTube) != true)
{
MessageBox.Show($"'{input}' isn't a recognized service. Try one of: {services}.",
"ytLlive", MessageBoxButton.OK, MessageBoxImage.Warning);
var ytResult = _socialValidator.LookupAsync(SocialService.YouTube, ytHandle).GetAwaiter().GetResult();
if (!ytResult.Success)
{
var proceed = MessageBox.Show(
$"{ytResult.Error}\n\nAdd anyway?", "ytLlive",
MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (proceed != MessageBoxResult.Yes) return;
}
_socials ??= new SocialsConfig();
_socials.Entries.Add(new SocialEntry
{
Service = SocialService.YouTube,
Handle = ytHandle,
ProfileUrl = SocialServiceIcons.CanonicalUrlFor(SocialService.YouTube, ytHandle),
});
}
// Step 2: Another social — URL or handle string, auto-detect the service.
if (!CanAddMoreSocials)
{
NotifySocialsChanged();
return;
}
var handle = ShowInputDialog($"Your {service} handle or profile URL:", "Add Social — Step 2 of 2", "@yourhandle");
if (string.IsNullOrWhiteSpace(handle)) return;
var input = ShowInputDialog(
"Enter a URL or handle for another social:\n" +
"e.g. https://x.com/yourname or @you@instance.tube",
"Add Social — Step 2 of 2", "");
if (string.IsNullOrWhiteSpace(input)) { NotifySocialsChanged(); return; }
var (service, handle, url) = SocialServiceIcons.DetectService(input);
var result = _socialValidator.LookupAsync(service, handle).GetAwaiter().GetResult();
if (!result.Success)
{
MessageBox.Show(result.Error, "ytLlive", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
var proceed = MessageBox.Show(
$"{result.Error}\n\nAdd anyway?", "ytLlive",
MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (proceed != MessageBoxResult.Yes) { NotifySocialsChanged(); return; }
}
_socials ??= new SocialsConfig();
_socials.Entries.Add(new SocialEntry
{
Service = service,
Handle = result.Handle,
ProfileUrl = result.ProfileUrl,
Handle = handle,
ProfileUrl = result.Success ? result.ProfileUrl : url,
});
NotifySocialsChanged();
}
private void NotifySocialsChanged()
{
OnPropertyChanged(nameof(HasSocials));
OnPropertyChanged(nameof(CanToggleSceneSocialBar));
OnPropertyChanged(nameof(CanAddMoreSocials));