Files
nfc-actions/NfcActions/ViewModels/MainViewModel.cs
Amal Graafstra 5b6a7f0fa5 Initial commit: NFC Actions Windows tray application
Implement PC/SC-based NFC card reader monitoring application with WPF UI. Features include:
- System tray integration with single-click to open main window
- Dynamic reader detection and management with enable/disable per reader
- NDEF payload extraction supporting Type 2 and Type 4 tags
- Auto-detection of block sizes (4-byte vs 16-byte) for different reader types
- Configurable actions: copy to clipboard, launch URLs, keyboard input simulation
- URI record type detection - only launches browser for actual URI records
- Real-time activity logging with color-coded levels (Debug, Info, Warning, Error)
- File-based debug logging for troubleshooting
- Settings persistence between sessions
- Dangerous Things branding with custom icons and clickable logo

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-06 22:56:11 -08:00

217 lines
6.2 KiB
C#

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using NfcActions.Models;
using NfcActions.Services;
namespace NfcActions.ViewModels;
public class MainViewModel : INotifyPropertyChanged
{
private readonly CardReaderService _cardReaderService;
private readonly SettingsService _settingsService;
private readonly ActionService _actionService;
private readonly LogService _logService;
private AppSettings _settings;
private bool _copyToClipboard;
private bool _launchUrls;
private bool _typeAsKeyboard;
public ObservableCollection<ReaderItem> Readers { get; } = new();
public bool CopyToClipboard
{
get => _copyToClipboard;
set
{
if (_copyToClipboard != value)
{
_copyToClipboard = value;
OnPropertyChanged(nameof(CopyToClipboard));
_settings.CopyToClipboard = value;
_settingsService.Save(_settings);
}
}
}
public bool LaunchUrls
{
get => _launchUrls;
set
{
if (_launchUrls != value)
{
_launchUrls = value;
OnPropertyChanged(nameof(LaunchUrls));
_settings.LaunchUrls = value;
_settingsService.Save(_settings);
}
}
}
public bool TypeAsKeyboard
{
get => _typeAsKeyboard;
set
{
if (_typeAsKeyboard != value)
{
_typeAsKeyboard = value;
OnPropertyChanged(nameof(TypeAsKeyboard));
_settings.TypeAsKeyboard = value;
_settingsService.Save(_settings);
}
}
}
public ObservableCollection<LogEntry> LogEntries => _logService.LogEntries;
public MainViewModel(
CardReaderService cardReaderService,
SettingsService settingsService,
ActionService actionService,
LogService logService)
{
_cardReaderService = cardReaderService;
_settingsService = settingsService;
_actionService = actionService;
_logService = logService;
// Load settings
_settings = _settingsService.Load();
_copyToClipboard = _settings.CopyToClipboard;
_launchUrls = _settings.LaunchUrls;
_typeAsKeyboard = _settings.TypeAsKeyboard;
// Set up event handlers
_cardReaderService.ReaderAdded += OnReaderAdded;
_cardReaderService.ReaderRemoved += OnReaderRemoved;
_cardReaderService.CardInserted += OnCardInserted;
// Set disabled readers from settings
_cardReaderService.SetDisabledReaders(_settings.DisabledReaders);
// Load current readers
LoadReaders();
}
private void LoadReaders()
{
Readers.Clear();
var readers = _cardReaderService.GetAvailableReaders();
foreach (var readerName in readers)
{
var item = new ReaderItem
{
Name = readerName,
IsEnabled = _cardReaderService.IsReaderEnabled(readerName)
};
item.PropertyChanged += OnReaderItemPropertyChanged;
Readers.Add(item);
}
}
private void OnReaderAdded(object? sender, ReaderEventArgs e)
{
var existing = Readers.FirstOrDefault(r => r.Name == e.ReaderName);
if (existing == null)
{
var item = new ReaderItem
{
Name = e.ReaderName,
IsEnabled = _cardReaderService.IsReaderEnabled(e.ReaderName)
};
item.PropertyChanged += OnReaderItemPropertyChanged;
Readers.Add(item);
}
}
private void OnReaderRemoved(object? sender, ReaderEventArgs e)
{
var item = Readers.FirstOrDefault(r => r.Name == e.ReaderName);
if (item != null)
{
item.PropertyChanged -= OnReaderItemPropertyChanged;
Readers.Remove(item);
}
}
private void OnReaderItemPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (sender is ReaderItem item && e.PropertyName == nameof(ReaderItem.IsEnabled))
{
if (item.IsEnabled)
{
_cardReaderService.EnableReader(item.Name);
_settings.DisabledReaders.Remove(item.Name);
}
else
{
_cardReaderService.DisableReader(item.Name);
_settings.DisabledReaders.Add(item.Name);
}
_settingsService.Save(_settings);
}
}
private void OnCardInserted(object? sender, CardEventArgs e)
{
if (e.CardData == null)
{
_logService.Warning("Card inserted but no data available");
return;
}
_logService.Info($"Processing card data ({e.CardData.Length} bytes)");
var record = NdefParser.ExtractFirstRecord(e.CardData);
if (record == null || string.IsNullOrEmpty(record.Payload))
{
_logService.Warning("Failed to extract NDEF payload from card data");
return;
}
_logService.Info($"NDEF Payload extracted: {record.Payload}");
_logService.Info($"Record type: {(record.IsUri ? "URI" : "Text/Other")}");
// Perform actions based on settings
if (CopyToClipboard)
{
_logService.Info("Copying to clipboard...");
_actionService.CopyToClipboard(record.Payload);
_logService.Info("Copied to clipboard successfully");
}
if (LaunchUrls)
{
if (record.IsUri)
{
_logService.Info("Attempting to launch URL...");
_actionService.LaunchUrl(record.Payload);
}
else
{
_logService.Info("Skipping browser launch - record is not a URI");
}
}
if (TypeAsKeyboard)
{
_logService.Info("Typing as keyboard input...");
_actionService.TypeText(record.Payload);
}
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}