LSFE/LSFE.MobApp/Views/CoveragePlan/SignaturePadView.cs
2026-07-30 10:31:04 +08:00

171 lines
5.4 KiB
C#

using Microsoft.Maui.Graphics;
using Microsoft.Maui.Graphics.Skia;
using System.Collections.Generic;
namespace LSFE.MobApp.Views.CoveragePlan
{
public class SignaturePadView : GraphicsView
{
private readonly List<List<PointF>> _strokes = new();
private List<PointF> _currentStroke;
private readonly SignatureDrawable _drawable;
public SignaturePadView()
{
_drawable = new SignatureDrawable(_strokes, () => _currentStroke);
Drawable = _drawable;
BackgroundColor = Colors.White;
// Set minimum size to ensure it's touchable
MinimumHeightRequest = 200;
MinimumWidthRequest = 200;
StartInteraction += OnStartInteraction;
DragInteraction += OnDragInteraction;
EndInteraction += OnEndInteraction;
}
private void OnStartInteraction(object sender, TouchEventArgs e)
{
if (e.Touches.Length > 0)
{
_currentStroke = new List<PointF> { e.Touches[0] };
Invalidate();
}
}
private void OnDragInteraction(object sender, TouchEventArgs e)
{
if (_currentStroke != null && e.Touches.Length > 0)
{
foreach (var touch in e.Touches)
{
_currentStroke.Add(touch);
}
Invalidate();
}
}
private void OnEndInteraction(object sender, TouchEventArgs e)
{
if (_currentStroke != null && _currentStroke.Count > 0)
{
_strokes.Add(new List<PointF>(_currentStroke));
_currentStroke = null;
Invalidate();
}
}
public void Clear()
{
_strokes.Clear();
_currentStroke = null;
Invalidate();
}
public bool IsEmpty()
{
return _strokes.Count == 0 && (_currentStroke == null || _currentStroke.Count == 0);
}
public async Task<byte[]> GetSignatureImageAsync()
{
// if nothing drawn, return null
if (IsEmpty())
return null;
// Determine size (fall back to default if not laid out yet)
var width = (int)Width;
var height = (int)Height;
if (width <= 0 || height <= 0)
{
width = 800;
height = 400;
}
// Choose display scale (1.0f is fine; you can use DeviceDisplay.MainDisplayInfo.Density if needed)
float displayScale = 1.0f;
try
{
// Create Skia bitmap export context
using var bmp = new SkiaBitmapExportContext(width, height, displayScale, dpi: 72, disposeBitmap: true, transparent: false);
var canvas = bmp.Canvas;
// Draw white background
canvas.FillColor = Colors.White;
canvas.FillRectangle(0, 0, width, height);
// Draw stored strokes
_drawable.Draw(canvas, new RectF(0, 0, width, height));
// Write PNG to memory stream
using var ms = new MemoryStream();
bmp.WriteToStream(ms);
return await Task.FromResult(ms.ToArray());
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error creating signature image: {ex.Message}");
return null;
}
}
private class SignatureDrawable : IDrawable
{
private readonly List<List<PointF>> _strokes;
private readonly Func<List<PointF>> _getCurrentStroke;
public SignatureDrawable(List<List<PointF>> strokes, Func<List<PointF>> getCurrentStroke)
{
_strokes = strokes;
_getCurrentStroke = getCurrentStroke;
}
public void Draw(ICanvas canvas, RectF dirtyRect)
{
canvas.StrokeColor = Colors.Black;
canvas.StrokeSize = 3;
canvas.StrokeLineCap = LineCap.Round;
canvas.StrokeLineJoin = LineJoin.Round;
// Draw completed strokes
foreach (var stroke in _strokes)
{
DrawStroke(canvas, stroke);
}
// Draw current stroke being drawn in real-time
var currentStroke = _getCurrentStroke();
if (currentStroke != null && currentStroke.Count > 0)
{
DrawStroke(canvas, currentStroke);
}
}
private void DrawStroke(ICanvas canvas, List<PointF> stroke)
{
if (stroke.Count > 1)
{
var path = new PathF();
path.MoveTo(stroke[0]);
for (int i = 1; i < stroke.Count; i++)
{
path.LineTo(stroke[i]);
}
canvas.DrawPath(path);
}
else if (stroke.Count == 1)
{
// Draw single point as small dot
var p = stroke[0];
canvas.FillRectangle(p.X - 1.5f, p.Y - 1.5f, 3, 3);
}
}
}
}
}