Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using BrickController2.CreationManagement;
using BrickController2.PlatformServices.InputDevice;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace BrickController2.BusinessLogic
{
Expand All @@ -11,6 +11,8 @@ public interface IPlayLogic
CreationValidationResult ValidateCreation(Creation creation);
bool ValidateControllerAction(ControllerAction controllerAction);

IEnumerable<string> GetMissingDevices(Creation creation);

void StartPlay();
void StopPlay();

Expand Down
15 changes: 10 additions & 5 deletions BrickController2/BrickController2/BusinessLogic/PlayLogic.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using BrickController2.CreationManagement;
using BrickController2.CreationManagement;
using BrickController2.DeviceManagement;
using BrickController2.PlatformServices.InputDevice;
using System;
using System.Collections.Generic;
using System.Linq;

namespace BrickController2.BusinessLogic
{
Expand Down Expand Up @@ -35,7 +35,7 @@ public CreationValidationResult ValidateCreation(Creation creation)
var deviceIds = creation.GetDeviceIds();
var sequenceNames = creation.GetSequenceNames();

if (deviceIds == null || deviceIds.Count() == 0)
if (deviceIds.Count == 0)
{
return CreationValidationResult.MissingControllerAction;
}
Expand All @@ -59,6 +59,11 @@ public bool ValidateControllerAction(ControllerAction controllerAction)
return device != null && (controllerAction.ButtonType != ControllerButtonType.Sequence || sequence != null);
}

public IEnumerable<string> GetMissingDevices(Creation creation)
{
return creation.GetDeviceIds().Where(d => _deviceManager.GetDeviceById(d) == null);
}

public void StartPlay()
{
_sequencePlayer.StartPlayer();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,5 +122,17 @@ public override string ToString()
{
return $"{DeviceId} - {Channel}";
}

public bool RemapDevice(string sourceDeviceId, string newDeviceId)
{
// check action to remap the device ID
if (DeviceId == sourceDeviceId)
{
DeviceId = newDeviceId;
return true;
}

return false;
}
}
}
10 changes: 3 additions & 7 deletions BrickController2/BrickController2/CreationManagement/Creation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,17 @@ public override string ToString()
return Name;
}

public IEnumerable<string> GetDeviceIds()
public IReadOnlySet<string> GetDeviceIds()
{
var deviceIds = new List<string>();
var deviceIds = new HashSet<string>();

foreach (var profile in ControllerProfiles)
{
foreach (var controllerEvent in profile.ControllerEvents)
{
foreach (var controllerAction in controllerEvent.ControllerActions)
{
var deviceId = controllerAction.DeviceId;
if (!deviceIds.Contains(deviceId))
{
deviceIds.Add(deviceId);
}
deviceIds.Add(controllerAction.DeviceId);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,30 @@ public async Task RenameCreationAsync(Creation creation, string newName)
}
}

public async Task<int> RemapDevice(Creation creation, string sourceDeviceId, string newDeviceId)
{
using (await _asyncLock.LockAsync())
{
var counter = 0;
foreach (var profile in creation.ControllerProfiles)
{
foreach (var controllerEvent in profile.ControllerEvents)
{
foreach (var controllerAction in controllerEvent.ControllerActions)
{
// if remapping is applied, persist the change
if (controllerAction.RemapDevice(sourceDeviceId, newDeviceId))
{
await _creationRepository.UpdateControllerActionAsync(controllerAction);
counter++;
}
}
}
}
return counter;
}
}

public async Task<bool> IsControllerProfileNameAvailableAsync(Creation creation, string controllerProfileName)
{
using (await _asyncLock.LockAsync())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public interface ICreationManager
Task<Creation> AddCreationAsync(string creationName);
Task DeleteCreationAsync(Creation creation);
Task RenameCreationAsync(Creation creation, string newName);
Task<int> RemapDevice(Creation creation, string sourceDeviceId, string newDeviceId);

Task ImportControllerProfileAsync(Creation creation, string controllerProfileFilename);
Task ImportControllerProfileAsync(Creation creation, ControllerProfile controllerProfile);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
using BrickController2.CreationManagement;
using BrickController2.BusinessLogic;
using BrickController2.CreationManagement;
using BrickController2.CreationManagement.Sharing;
using BrickController2.DeviceManagement;
using BrickController2.PlatformServices.SharedFileStorage;
using BrickController2.UI.Services.Dialog;
using BrickController2.UI.Services.Navigation;
using BrickController2.UI.Services.Translation;
using BrickController2.UI.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;

namespace BrickController2.UI.Commands;

internal class CreationCommandFactory : ItemCommandFactoryBase<Creation>, ICommandFactory<Creation>
internal class CreationCommandFactory : ItemCommandFactoryBase<Creation>, ICommandFactory<Creation>, ICreationCommandFactory
{
private readonly ICreationManager _creationManager;
private readonly IDeviceManager _deviceManager;
private readonly IPlayLogic _playLogic;

public CreationCommandFactory
(
Expand All @@ -20,12 +28,28 @@ public CreationCommandFactory
ISharingManager<Creation> sharingManager,
ISharedFileStorageService sharedFileStorageService,
INavigationService navigationService,
ICreationManager creationManager
ICreationManager creationManager,
IDeviceManager deviceManager,
IPlayLogic playLogic
) : base(dialogService, translationService, sharingManager, sharedFileStorageService, navigationService)
{
_creationManager = creationManager;
_deviceManager = deviceManager;
_playLogic = playLogic;
}

public ICommand PlayCommand(PageViewModelBase viewModel, Creation creation, ControllerProfile? controllerProfile = default!)
=> new SafeCommand(() => PlayAsync(viewModel, creation, controllerProfile));

public ICommand PlayCreationCommand(PageViewModelBase viewModel)
=> new SafeCommand<Creation>((creation) => PlayAsync(viewModel, creation));

public ICommand PlayControllerProfileCommand(PageViewModelBase viewModel)
=> new SafeCommand<ControllerProfile>((profile) => PlayAsync(viewModel, profile.Creation, profile));

public ICommand RemapDeviceCommand(PageViewModelBase viewModel, Creation creation)
=> new SafeCommand(() => RemapDeviceAsync(viewModel, creation));

protected override string ItemsTitle => Translate("Creations");
protected override string ItemNameHint => Translate("CreationName");
protected override string NoItemToImportMessage => Translate("NoCreationsToImport");
Expand All @@ -37,4 +61,171 @@ protected override Task ExportItemAsync(Creation model, string fileName)

protected override Task ImportItemAsync(Creation model)
=> _creationManager.ImportCreationAsync(model);

private async Task PlayAsync(PageViewModelBase viewModel, Creation creation, ControllerProfile? controllerProfile = default!)
{
try
{
var validationResult = _playLogic.ValidateCreation(creation);

string warning = string.Empty;
switch (validationResult)
{
case CreationValidationResult.MissingControllerAction:
warning = Translate("NoControllerActions");
break;

case CreationValidationResult.MissingDevice:
warning = Translate("MissingDevices");
break;

case CreationValidationResult.MissingSequence:
warning = Translate("MissingSequence");
break;
}

if (validationResult == CreationValidationResult.Ok)
{
await NavigationService.NavigateToAsync<PlayerPageViewModel>(new NavigationParameters(
("creation", creation),
("profile", controllerProfile)));
}
else
{
await DialogService.ShowMessageBoxAsync(
Translate("Warning"),
warning,
Translate("Ok"),
viewModel.DisappearingToken);
}
}
catch (OperationCanceledException)
{
}
}

private async Task RemapDeviceAsync(PageViewModelBase viewModel, Creation creation)
{
try
{
await RemapDevicesAsync(viewModel, creation);
}
catch (OperationCanceledException)
{
}
}

private async Task<bool> RemapDevicesAsync(PageViewModelBase viewModel, Creation creation)
{
// get source device IDs
var sourceDeviceIds = creation.GetDeviceIds()
.Select(id =>
{
DeviceId.TryParse(id, out var deviceType, out var deviceAddress);
return (DeviceType: deviceType, Address: deviceAddress);
})
.Where(x => x.DeviceType != DeviceType.Unknown && x.Address != null)
.ToList();

// get source types
var sourceTypes = sourceDeviceIds.Select(x => x.DeviceType).ToHashSet();
var addresses = sourceDeviceIds.Select(x => x.Address!).ToHashSet();
// source types that have some existing device of such type present, but not used in creation
var suitableTypes = sourceTypes.Where(x => _deviceManager.Devices
.Any(d => d.DeviceType == x && !addresses.Contains(d.Address)))
.ToHashSet();

if (sourceTypes.Count == 0 || suitableTypes.Count == 0)
{
// report error - no suitable device to replace
await DialogService.ShowMessageBoxAsync(
Translate("Information"),
Translate("No suitable device found to remap a device."),
Translate("Ok"),
viewModel.DisappearingToken);
return false;
}

var sourceType = await ChooseDeviceTypeToRemapAsync(viewModel, suitableTypes);
if (sourceType is null)
{
// user cancelled
return false;
}

// have source device type, get addresses of such type
var sourceDeviceAddresses = sourceDeviceIds
.Where(x => x.DeviceType == sourceType.Value)
.Select(x => x.Address!)
.ToList();

var sourceDeviceAddress = await ChooseDeviceAddressToRemapAsync(viewModel, sourceDeviceAddresses);
if (sourceDeviceAddress is null)
{
// user cancelled
return false;
}

// choose target device by name but avoid the source one
var suitableDevices = _deviceManager.Devices
.Where(d => d.DeviceType == sourceType.Value && d.Address != sourceDeviceAddress)
.OrderBy(x => x.Name)
.ToList();

var targetDevice = await DialogService.ShowSelectionDialogAsync(
suitableDevices,
Translate("Target device"),
Translate("Cancel"),
viewModel.DisappearingToken);

if (targetDevice.IsOk)
{
// replace all source device IDs with the selected one
var sourceDeviceId = DeviceId.Get(sourceType.Value, sourceDeviceAddress);
var newDeviceId = targetDevice.SelectedItem!.Id;
var count = await _creationManager.RemapDevice(creation, sourceDeviceId, newDeviceId);

await DialogService.ShowMessageBoxAsync(
Translate("Information"),
Translate($"Remapped {count} controller actions from device '{sourceDeviceId}' to '{newDeviceId}'"),
Translate("Ok"),
viewModel.DisappearingToken);
}

return true;
}

private async Task<string?> ChooseDeviceAddressToRemapAsync(PageViewModelBase viewModel, List<string> missingDeviceAddresses)
{
if (missingDeviceAddresses.Count == 1)
{
return missingDeviceAddresses[0];
}

// choose address to remap
var sourceDevice = await DialogService.ShowSelectionDialogAsync(
missingDeviceAddresses.Order(),
Translate("Source device"),
Translate("Cancel"),
viewModel.DisappearingToken);

return sourceDevice.IsOk ? sourceDevice.SelectedItem : default;
}

private async Task<DeviceType?> ChooseDeviceTypeToRemapAsync(PageViewModelBase viewModel, HashSet<DeviceType> suitableTypes)
{
if (suitableTypes.Count == 1)
{
return suitableTypes.First();
}

// choose device type to remap
var deviceType = await DialogService.ShowSelectionDialogAsync(
suitableTypes.OrderBy(x => x.ToString()),
Translate("Source device type"),
Translate("Cancel"),
viewModel.DisappearingToken);

return deviceType.IsOk ? deviceType.SelectedItem : default;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using BrickController2.CreationManagement;
using BrickController2.UI.ViewModels;
using System.Windows.Input;

namespace BrickController2.UI.Commands;

public interface ICreationCommandFactory : ICommandFactory<Creation>
{
ICommand PlayCommand(PageViewModelBase viewModel, Creation creation, ControllerProfile? controllerProfile = default!);

ICommand PlayCreationCommand(PageViewModelBase viewModel);

ICommand PlayControllerProfileCommand(PageViewModelBase viewModel);

ICommand RemapDeviceCommand(PageViewModelBase viewModel, Creation creation);
}
2 changes: 1 addition & 1 deletion BrickController2/BrickController2/UI/DI/UiModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ protected override void Load(ContainerBuilder builder)
});

// command related registration
builder.RegisterType<CreationCommandFactory>().As<ICommandFactory<Creation>>().SingleInstance();
builder.RegisterType<CreationCommandFactory>().As<ICommandFactory<Creation>>().As<ICreationCommandFactory>().SingleInstance();
builder.RegisterType<SequenceCommandFactory>().As<ICommandFactory<Sequence>>().SingleInstance();

// Xamarin forms related
Expand Down
Loading
Loading