From 27f613f4e494a50c31edf80cc5f489ad1720b476 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sat, 9 Mar 2024 17:20:24 +0800 Subject: [PATCH] feat: implement verificationcodeitem and corresponding delete operation. --- .../Pages/VerificationCodeDemo.axaml | 7 +- .../Pages/VerificationCodeDemo.axaml.cs | 7 ++ .../VerificationCodeDemoViewModel.cs | 22 +++++- .../Controls/VerificationCode.axaml | 72 ++++++++++++++----- .../VerificationCode/VerificationCode.cs | 64 ++++++++++++----- .../VerificationCodeCollection.cs | 5 +- .../VerificationCodeCompleteEventArgs.cs | 9 +++ .../VerificationCode/VerificationCodeItem.cs | 26 +++++++ 8 files changed, 168 insertions(+), 44 deletions(-) create mode 100644 src/Ursa/Controls/VerificationCode/VerificationCodeCompleteEventArgs.cs create mode 100644 src/Ursa/Controls/VerificationCode/VerificationCodeItem.cs diff --git a/demo/Ursa.Demo/Pages/VerificationCodeDemo.axaml b/demo/Ursa.Demo/Pages/VerificationCodeDemo.axaml index ecc21d6..437e85b 100644 --- a/demo/Ursa.Demo/Pages/VerificationCodeDemo.axaml +++ b/demo/Ursa.Demo/Pages/VerificationCodeDemo.axaml @@ -3,11 +3,14 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:u="https://irihi.tech/ursa" + xmlns:vm="using:Ursa.Demo.ViewModels" + x:DataType="vm:VerificationCodeDemoViewModel" + x:CompileBindings="True" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Ursa.Demo.Pages.VerificationCodeDemo"> - + - + diff --git a/demo/Ursa.Demo/Pages/VerificationCodeDemo.axaml.cs b/demo/Ursa.Demo/Pages/VerificationCodeDemo.axaml.cs index 6ac89e7..61aecec 100644 --- a/demo/Ursa.Demo/Pages/VerificationCodeDemo.axaml.cs +++ b/demo/Ursa.Demo/Pages/VerificationCodeDemo.axaml.cs @@ -1,6 +1,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Ursa.Controls; namespace Ursa.Demo.Pages; @@ -10,4 +11,10 @@ public partial class VerificationCodeDemo : UserControl { InitializeComponent(); } + + private async void VerificationCode_OnComplete(object? sender, VerificationCodeCompleteEventArgs e) + { + var text = string.Join(string.Empty, e.Code); + await MessageBox.ShowOverlayAsync(text); + } } \ No newline at end of file diff --git a/demo/Ursa.Demo/ViewModels/VerificationCodeDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/VerificationCodeDemoViewModel.cs index 6f106e3..94e4fff 100644 --- a/demo/Ursa.Demo/ViewModels/VerificationCodeDemoViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/VerificationCodeDemoViewModel.cs @@ -1,8 +1,26 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Windows.Input; +using Avalonia.Collections; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Ursa.Controls; namespace Ursa.Demo.ViewModels; public class VerificationCodeDemoViewModel: ObservableObject { - + public ICommand CompleteCommand { get; set; } + + public VerificationCodeDemoViewModel() + { + CompleteCommand = new AsyncRelayCommand>(OnComplete); + } + + private async Task OnComplete(IList? obj) + { + if (obj is null) return; + var code = string.Join("", obj); + await MessageBox.ShowOverlayAsync(code); + } } \ No newline at end of file diff --git a/src/Ursa.Themes.Semi/Controls/VerificationCode.axaml b/src/Ursa.Themes.Semi/Controls/VerificationCode.axaml index 74e6993..ca051f7 100644 --- a/src/Ursa.Themes.Semi/Controls/VerificationCode.axaml +++ b/src/Ursa.Themes.Semi/Controls/VerificationCode.axaml @@ -1,34 +1,68 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - + + - - - - - - - - diff --git a/src/Ursa/Controls/VerificationCode/VerificationCode.cs b/src/Ursa/Controls/VerificationCode/VerificationCode.cs index efbc150..cc1e9e1 100644 --- a/src/Ursa/Controls/VerificationCode/VerificationCode.cs +++ b/src/Ursa/Controls/VerificationCode/VerificationCode.cs @@ -28,13 +28,13 @@ public class VerificationCode: TemplatedControl set => SetValue(CompleteCommandProperty, value); } - public static readonly StyledProperty CountOfDigitProperty = AvaloniaProperty.Register( - nameof(CountOfDigit)); + public static readonly StyledProperty CountProperty = AvaloniaProperty.Register( + nameof(Count)); - public int CountOfDigit + public int Count { - get => GetValue(CountOfDigitProperty); - set => SetValue(CountOfDigitProperty, value); + get => GetValue(CountProperty); + set => SetValue(CountProperty, value); } public static readonly StyledProperty PasswordCharProperty = @@ -47,19 +47,29 @@ public class VerificationCode: TemplatedControl set => SetValue(PasswordCharProperty, value); } - public static readonly DirectProperty> DigitsProperty = AvaloniaProperty.RegisterDirect>( + public static readonly DirectProperty> DigitsProperty = AvaloniaProperty.RegisterDirect>( nameof(Digits), o => o.Digits, (o, v) => o.Digits = v); - private AvaloniaList _digits = []; - internal AvaloniaList Digits + private IList _digits = []; + internal IList Digits { get => _digits; set => SetAndRaise(DigitsProperty, ref _digits, value); } + + public static readonly RoutedEvent CompleteEvent = + RoutedEvent.Register( + nameof(Complete), RoutingStrategies.Bubble); + + public event EventHandler Complete + { + add => AddHandler(CompleteEvent, value); + remove => RemoveHandler(CompleteEvent, value); + } static VerificationCode() { - CountOfDigitProperty.Changed.AddClassHandler((code, args) => code.OnCountOfDigitChanged(args)); + CountProperty.Changed.AddClassHandler((code, args) => code.OnCountOfDigitChanged(args)); FocusableProperty.OverrideDefaultValue(true); } @@ -73,9 +83,8 @@ public class VerificationCode: TemplatedControl var newValue = args.NewValue.Value; if (newValue > 0) { - Digits = new AvaloniaList(Enumerable.Repeat(string.Empty, newValue)); + Digits = new List(Enumerable.Repeat(string.Empty, newValue)); } - } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) @@ -89,29 +98,48 @@ public class VerificationCode: TemplatedControl { if (e.Source is Control t) { - var text = t.FindLogicalAncestorOfType(); + var text = t.FindLogicalAncestorOfType(); if (text != null) { + text.Focus(); _currentIndex = _itemsControl?.IndexFromContainer(text) ?? 0; } } + e.Handled = true; } protected override void OnTextInput(TextInputEventArgs e) { base.OnTextInput(e); - if (e.Text?.Length == 1 && _currentIndex < CountOfDigit) + if (e.Text?.Length == 1 && _currentIndex < Count) { - Digits[_currentIndex] = e.Text; - var presenter = _itemsControl?.ContainerFromIndex(_currentIndex) as TextBox; + + var presenter = _itemsControl?.ContainerFromIndex(_currentIndex) as VerificationCodeItem; if (presenter is null) return; - _currentIndex++; - var newPresenter = _itemsControl?.ContainerFromIndex(_currentIndex)?.Focus(); presenter.Text = e.Text; - if (_currentIndex == CountOfDigit) + Digits[_currentIndex] = e.Text; + _currentIndex++; + _itemsControl?.ContainerFromIndex(_currentIndex)?.Focus(); + if (_currentIndex == Count) { CompleteCommand?.Execute(Digits); + RaiseEvent(new VerificationCodeCompleteEventArgs(Digits, CompleteEvent)); } } } + + protected override void OnKeyDown(KeyEventArgs e) + { + base.OnKeyDown(e); + if (e.Key == Key.Back && _currentIndex >= 0) + { + var presenter = _itemsControl?.ContainerFromIndex(_currentIndex) as VerificationCodeItem; + if (presenter is null) return; + Digits[_currentIndex] = string.Empty; + presenter.Text = string.Empty; + if (_currentIndex == 0) return; + _currentIndex--; + _itemsControl?.ContainerFromIndex(_currentIndex)?.Focus(); + } + } } \ No newline at end of file diff --git a/src/Ursa/Controls/VerificationCode/VerificationCodeCollection.cs b/src/Ursa/Controls/VerificationCode/VerificationCodeCollection.cs index 333af44..e403c87 100644 --- a/src/Ursa/Controls/VerificationCode/VerificationCodeCollection.cs +++ b/src/Ursa/Controls/VerificationCode/VerificationCodeCollection.cs @@ -11,14 +11,13 @@ public class VerificationCodeCollection: ItemsControl { protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey) { - return NeedsContainer(item, out recycleKey); + return NeedsContainer(item, out recycleKey); } protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey) { - return new TextBox() + return new VerificationCodeItem() { - TextAlignment = TextAlignment.Center, [InputMethod.IsInputMethodEnabledProperty] = false, }; } diff --git a/src/Ursa/Controls/VerificationCode/VerificationCodeCompleteEventArgs.cs b/src/Ursa/Controls/VerificationCode/VerificationCodeCompleteEventArgs.cs new file mode 100644 index 0000000..e1422cf --- /dev/null +++ b/src/Ursa/Controls/VerificationCode/VerificationCodeCompleteEventArgs.cs @@ -0,0 +1,9 @@ +using Avalonia.Collections; +using Avalonia.Interactivity; + +namespace Ursa.Controls; + +public class VerificationCodeCompleteEventArgs(IList code, RoutedEvent? @event) : RoutedEventArgs(@event) +{ + public IList Code { get; } = code; +} \ No newline at end of file diff --git a/src/Ursa/Controls/VerificationCode/VerificationCodeItem.cs b/src/Ursa/Controls/VerificationCode/VerificationCodeItem.cs new file mode 100644 index 0000000..081bc7f --- /dev/null +++ b/src/Ursa/Controls/VerificationCode/VerificationCodeItem.cs @@ -0,0 +1,26 @@ +using Avalonia; +using Avalonia.Controls.Primitives; +using Avalonia.Data; + +namespace Ursa.Controls; + +public class VerificationCodeItem: TemplatedControl +{ + public static readonly StyledProperty TextProperty = AvaloniaProperty.Register( + nameof(Text), defaultBindingMode: BindingMode.TwoWay); + + public string Text + { + get => GetValue(TextProperty); + set => SetValue(TextProperty, value); + } + + public static readonly StyledProperty PasswordCharProperty = AvaloniaProperty.Register( + nameof(PasswordChar), defaultBindingMode: BindingMode.TwoWay); + + public char PasswordChar + { + get => GetValue(PasswordCharProperty); + set => SetValue(PasswordCharProperty, value); + } +} \ No newline at end of file