Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/OpenIPC.Viewer.App/Styles/Buttons.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,19 @@
secondary, ghost, danger variants converge on the Console spec.
-->

<!-- Flat icon button hosted in TextBox.InnerRightContent (paste-from-
clipboard affordance). Focusable=False keeps the soft keyboard up:
a focus steal on tap would dismiss the IME before the paste lands. -->
<Style Selector="Button.paste-inline">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="6,4" />
<Setter Property="Margin" Value="0,0,2,0" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Focusable" Value="False" />
</Style>
<Style Selector="Button.paste-inline:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="Transparent" />
</Style>

</Styles>
2 changes: 2 additions & 0 deletions src/OpenIPC.Viewer.App/Themes/Theme.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@
<StreamGeometry x:Key="IconCheck">M20,6 L9,17 L4,12</StreamGeometry>
<StreamGeometry x:Key="IconChevronRight">M9,18 L15,12 L9,6</StreamGeometry>
<StreamGeometry x:Key="IconChevronLeft">M15,18 L9,12 L15,6</StreamGeometry>
<!-- IconPaste — clipboard-paste. -->
<StreamGeometry x:Key="IconPaste">M11,14 h10 M16,4 h2 a2,2 0 0 1 2,2 v1.344 M17,18 l4,-4 l-4,-4 M8,4 H6 a2,2 0 0 0 -2,2 v14 a2,2 0 0 0 2,2 h12 a2,2 0 0 0 1.793,-1.113 M9,2 h6 a1,1 0 0 1 1,1 v2 a1,1 0 0 1 -1,1 h-6 a1,1 0 0 1 -1,-1 V3 a1,1 0 0 1 1,-1 Z</StreamGeometry>

<!-- Section icons for Settings (Lucide paths). -->
<!-- IconAppearance — palette. -->
Expand Down
21 changes: 19 additions & 2 deletions src/OpenIPC.Viewer.App/Views/Dialogs/CameraEditorContent.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,28 @@
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0" Spacing="6">
<TextBlock Text="{Binding [CameraEditor.Label.Username], Source={x:Static svc:Localizer.Instance}}" Foreground="{StaticResource TextSecondaryBrush}" FontSize="{StaticResource FontSizeSm}" />
<TextBox Text="{Binding Username, Mode=TwoWay}" PlaceholderText="{Binding [CameraEditor.Placeholder.Username], Source={x:Static svc:Localizer.Instance}}" />
<!-- Inline paste buttons (mobile-only, toggled in code-behind):
Avalonia's long-press → context-flyout path on Android is
unreliable (flyout hidden behind the soft keyboard, flaky
Holding gesture on empty fields), so credentials copied from
a password manager had no way into these fields. -->
<TextBox Name="UsernameBox" Text="{Binding Username, Mode=TwoWay}" PlaceholderText="{Binding [CameraEditor.Placeholder.Username], Source={x:Static svc:Localizer.Instance}}">
<TextBox.InnerRightContent>
<Button Name="PasteUsernameButton" Classes="paste-inline" Click="OnPasteUsername">
<Path Classes="lucide" Data="{StaticResource IconPaste}" Stroke="{StaticResource TextSecondaryBrush}" Width="16" Height="16" />
</Button>
</TextBox.InnerRightContent>
</TextBox>
</StackPanel>
<StackPanel Grid.Column="2" Spacing="6">
<TextBlock Text="{Binding [CameraEditor.Label.Password], Source={x:Static svc:Localizer.Instance}}" Foreground="{StaticResource TextSecondaryBrush}" FontSize="{StaticResource FontSizeSm}" />
<TextBox Text="{Binding Password, Mode=TwoWay}" PasswordChar="•" />
<TextBox Name="PasswordBox" Text="{Binding Password, Mode=TwoWay}" PasswordChar="•">
<TextBox.InnerRightContent>
<Button Name="PastePasswordButton" Classes="paste-inline" Click="OnPastePassword">
<Path Classes="lucide" Data="{StaticResource IconPaste}" Stroke="{StaticResource TextSecondaryBrush}" Width="16" Height="16" />
</Button>
</TextBox.InnerRightContent>
</TextBox>
</StackPanel>
</Grid>

Expand Down
19 changes: 19 additions & 0 deletions src/OpenIPC.Viewer.App/Views/Dialogs/CameraEditorContent.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Interactivity;
using OpenIPC.Viewer.App.Services;
using OpenIPC.Viewer.App.ViewModels.Dialogs;

namespace OpenIPC.Viewer.App.Views.Dialogs;
Expand All @@ -22,6 +23,12 @@ public CameraEditorContent()

this.FindControl<Button>("CancelButton")!.Click += (_, _) => _tcs.TrySetResult(null);
this.FindControl<Button>("SaveButton")!.Click += OnSave;

// Desktop has Ctrl+V and a right-click menu; the inline paste buttons
// exist for mobile, where the long-press flyout is unreliable.
var isMobile = OverlayDialogPresenter.IsMobile;
this.FindControl<Button>("PasteUsernameButton")!.IsVisible = isMobile;
this.FindControl<Button>("PastePasswordButton")!.IsVisible = isMobile;
AttachedToVisualTree += async (_, _) =>
{
if (DataContext is CameraEditorViewModel vm)
Expand All @@ -35,4 +42,16 @@ private void OnSave(object? sender, RoutedEventArgs e)
if (!vm.TryBuildRequest(out var newRequest, out var updateRequest)) return;
_tcs.TrySetResult(new CameraEditorResult(newRequest, updateRequest));
}

private void OnPasteUsername(object? sender, RoutedEventArgs e) =>
PasteInto(this.FindControl<TextBox>("UsernameBox")!);

private void OnPastePassword(object? sender, RoutedEventArgs e) =>
PasteInto(this.FindControl<TextBox>("PasswordBox")!);

private static void PasteInto(TextBox box)
{
box.Focus();
box.Paste();
}
}
Loading