فهرست منبع

Merge remote-tracking branch 'origin/kenric' into frank

# Conflicts:
#	PRS.Avalonia/PRS.Avalonia.Desktop/PRS.Avalonia.Desktop.csproj
#	PRS.Avalonia/PRS.Avalonia.sln
frankvandenbos 5 ماه پیش
والد
کامیت
57564bc0b2

+ 4 - 0
PRS.Avalonia/Directory.Packages.props

@@ -12,7 +12,11 @@
     <PackageVersion Include="Avalonia.Desktop" Version="11.2.3" />
     <PackageVersion Include="Avalonia.Browser" Version="11.2.1" />
     <PackageVersion Include="Avalonia.Android" Version="11.2.3" />
+    <PackageVersion Include="Avalonia.Themes.Fluent" Version="11.2.3" />
+    <PackageVersion Include="Avalonia.Diagnostics" Version="11.2.3" />
+    <PackageVersion Include="AvaloniaDialogs" Version="3.6.1" />
     <PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
+    <PackageVersion Include="DialogHost.Avalonia" Version="0.9.2" />
     <PackageVersion Include="Material.Avalonia" Version="3.9.2" />
     <PackageVersion Include="Material.Avalonia.DataGrid" Version="3.9.2" />
     <PackageVersion Include="Material.Avalonia.Dialogs" Version="3.9.2" />

+ 3 - 0
PRS.Avalonia/PRS.Avalonia/App.axaml

@@ -1,6 +1,7 @@
 <Application xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:local="using:PRS.Avalonia"
+			 xmlns:dialogHostAvalonia="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
              x:Class="PRS.Avalonia.App"
              RequestedThemeVariant="Default">
     <!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
@@ -20,6 +21,8 @@
         <StyleInclude Source="avares://InABox.Avalonia/Theme/Classes/Separator.axaml" />
         <StyleInclude Source="avares://InABox.Avalonia/Theme/Classes/ListBox.axaml" />
 
+		<dialogHostAvalonia:DialogHostStyles/>
+
     </Application.Styles>
 
     <Application.Resources>

+ 37 - 0
PRS.Avalonia/PRS.Avalonia/Dialogs/MessageDialog.cs

@@ -0,0 +1,37 @@
+using InABox.Avalonia;
+using PRS.Avalonia.Dialogs;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRS.Avalonia;
+
+public enum MessageDialogButtonPosition
+{
+    Left,
+    Right
+}
+
+public enum MessageDialogResult
+{
+    None,
+    OK,
+    Cancel,
+    Yes,
+    No,
+    Other
+}
+
+public static class MessageDialog
+{
+    public static async Task ShowMessage(string message/*, string title, ImageSource? image = null*/)
+    {
+        await MessageDialogViewModel.ShowMessage(message);
+        // var result = await Navigation.Popup<MessageDialogViewModel, MessageDialogResult>(model =>
+        // {
+        //     model.Message = message;
+        // });
+    }
+}

+ 53 - 0
PRS.Avalonia/PRS.Avalonia/Dialogs/MessageDialogView.axaml

@@ -0,0 +1,53 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:dialogs="clr-namespace:PRS.Avalonia.Dialogs"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="PRS.Avalonia.Dialogs.MessageDialogView"
+			 x:DataType="dialogs:MessageDialogViewModel"
+			 MinWidth="150">
+	<UserControl.Resources>
+		<DataTemplate x:Key="ButtonTemplate" DataType="dialogs:MessageDialogButton">
+			<Button Content="{Binding Content}"
+					Command="{Binding $parent[ItemsControl].((dialogs:MessageDialogViewModel)DataContext).ButtonClickCommand}"
+					CommandParameter="{Binding}"/>
+		</DataTemplate>
+	</UserControl.Resources>
+	<Grid>
+		<Grid.RowDefinitions>
+			<RowDefinition Height="*" MinHeight="100"/>
+			<RowDefinition Height="Auto"/>
+		</Grid.RowDefinitions>
+		<Grid.ColumnDefinitions>
+			<ColumnDefinition Width="*"/>
+		</Grid.ColumnDefinitions>
+		<TextBlock Text="{Binding Message}" Grid.Row="0"
+				   VerticalAlignment="Center" TextWrapping="Wrap" Margin="5"/>
+		<Grid Grid.Row="1">
+			<Grid.ColumnDefinitions>
+				<ColumnDefinition Width="Auto"/>
+				<ColumnDefinition Width="*"/>
+				<ColumnDefinition Width="Auto"/>
+			</Grid.ColumnDefinitions>
+			<ItemsControl ItemsSource="{Binding LeftButtons}"
+						  ItemTemplate="{StaticResource ButtonTemplate}"
+						  Grid.Column="0">
+				<ItemsControl.ItemsPanel>
+					<ItemsPanelTemplate>
+						<StackPanel Orientation="Horizontal"/>
+					</ItemsPanelTemplate>
+				</ItemsControl.ItemsPanel>
+			</ItemsControl>
+			<ItemsControl ItemsSource="{Binding RightButtons}"
+						  ItemTemplate="{StaticResource ButtonTemplate}"
+						  Grid.Column="2">
+				<ItemsControl.ItemsPanel>
+					<ItemsPanelTemplate>
+						<StackPanel Orientation="Horizontal"/>
+					</ItemsPanelTemplate>
+				</ItemsControl.ItemsPanel>
+			</ItemsControl>
+		</Grid>
+	</Grid>
+</UserControl>

+ 13 - 0
PRS.Avalonia/PRS.Avalonia/Dialogs/MessageDialogView.axaml.cs

@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace PRS.Avalonia.Dialogs;
+
+public partial class MessageDialogView : UserControl
+{
+    public MessageDialogView()
+    {
+        InitializeComponent();
+    }
+}

+ 172 - 0
PRS.Avalonia/PRS.Avalonia/Dialogs/MessageDialogViewModel.cs

@@ -0,0 +1,172 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using InABox.Avalonia;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRS.Avalonia.Dialogs;
+
+public partial class MessageDialogViewModel : PopupViewModel<MessageDialogResult>
+{
+    [ObservableProperty]
+    private string _message;
+
+    public ObservableCollection<MessageDialogButton> Buttons { get; private set; } = new();
+
+    public IEnumerable<MessageDialogButton> LeftButtons => Buttons.Where(x => x.Position == MessageDialogButtonPosition.Left);
+    public IEnumerable<MessageDialogButton> RightButtons => Buttons.Where(x => x.Position == MessageDialogButtonPosition.Right);
+
+    public MessageDialogViewModel()
+    {
+        Message = "";
+    }
+
+    [RelayCommand]
+    private void ButtonClick(MessageDialogButton button)
+    {
+        button.Action(this, button);
+    }
+
+    public MessageDialogViewModel AddButton(MessageDialogButton button)
+    {
+        Buttons.Add(button);
+        return this;
+    }
+
+    public MessageDialogViewModel AddOKButton(string content = "OK")
+    {
+        Buttons.Add(new MessageDialogButton(content, OKButton_Click, MessageDialogButtonPosition.Right));
+        return this;
+    }
+
+    public MessageDialogViewModel AddCancelButton(string content = "Cancel")
+    {
+        Buttons.Add(new MessageDialogButton(content, CancelButton_Click, MessageDialogButtonPosition.Right));
+        return this;
+    }
+
+    public MessageDialogViewModel AddYesButton(string content = "Yes")
+    {
+        Buttons.Add(new MessageDialogButton(content, YesButton_Click, MessageDialogButtonPosition.Right));
+        return this;
+    }
+
+    public MessageDialogViewModel AddNoButton(string content = "No")
+    {
+        Buttons.Add(new MessageDialogButton(content, NoButton_Click, MessageDialogButtonPosition.Right));
+        return this;
+    }
+
+    private void YesButton_Click(MessageDialogViewModel window, MessageDialogButton button)
+    {
+        window.Close(MessageDialogResult.Yes);
+    }
+
+    private void NoButton_Click(MessageDialogViewModel window, MessageDialogButton button)
+    {
+        window.Close(MessageDialogResult.No);
+    }
+
+    private void CancelButton_Click(MessageDialogViewModel window, MessageDialogButton button)
+    {
+        window.Close(MessageDialogResult.Cancel);
+    }
+
+    private void OKButton_Click(MessageDialogViewModel window, MessageDialogButton button)
+    {
+        window.Close(MessageDialogResult.OK);
+    }
+
+    #region Static Constructors
+
+    public static MessageDialogViewModel New()
+    {
+        return new MessageDialogViewModel();
+    }
+
+    public static MessageDialogViewModel NewMessage(string message)
+    {
+        return new MessageDialogViewModel()
+            .Message(message)
+            .AddOKButton();
+    }
+    public static async Task ShowMessage(string message)
+    {
+        await NewMessage(message).Display();
+    }
+
+    public static MessageDialogViewModel NewOKCancel(string message)
+    {
+        return new MessageDialogViewModel()
+            .Message(message)
+            .AddOKButton()
+            .AddCancelButton();
+    }
+    public static async Task<bool> ShowOKCancel(string message)
+    {
+        return await NewOKCancel(message).Display() == MessageDialogResult.OK;
+    }
+
+    public static MessageDialogViewModel NewYesNo(string message)
+    {
+        return new MessageDialogViewModel()
+            .Message(message)
+            .AddYesButton()
+            .AddNoButton();
+    }
+    public static async Task<bool> ShowYesNo(string message)
+    {
+        return await NewYesNo(message)
+            .Display() == MessageDialogResult.Yes;
+    }
+    public static MessageDialogViewModel NewYesNoCancel(string message)
+    {
+        return new MessageDialogViewModel()
+            .Message(message)
+            .AddYesButton()
+            .AddNoButton()
+            .AddCancelButton();
+    }
+    public static async Task<MessageDialogResult> ShowYesNoCancel(string message)
+    {
+        return await NewYesNoCancel(message).Display();
+    }
+
+    #endregion
+}
+
+public partial class MessageDialogButton : ObservableObject
+{
+    public delegate void MessageDialogButtonDelegate(MessageDialogViewModel window, MessageDialogButton button);
+
+    public MessageDialogButtonPosition Position { get; set; }
+
+    [ObservableProperty]
+    private string _content;
+
+    public MessageDialogButtonDelegate Action { get; set; }
+
+    public MessageDialogButton(string content, MessageDialogButtonDelegate action, MessageDialogButtonPosition position)
+    {
+        Content = content;
+        Action = action;
+        Position = position;
+    }
+}
+public static class MessageDialogBuilder
+{
+    public static MessageDialogViewModel Message(this MessageDialogViewModel window, string message)
+    {
+        window.Message = message;
+        return window;
+    }
+
+    public static async Task<MessageDialogResult> Display(this MessageDialogViewModel window)
+    {
+        return await Navigation.Popup<MessageDialogViewModel, MessageDialogResult>(window, false);
+    }
+}

+ 75 - 71
PRS.Avalonia/PRS.Avalonia/MainView.axaml

@@ -3,91 +3,95 @@
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
              xmlns:avalonia="clr-namespace:PRS.Avalonia"
+			 xmlns:dialogHostAvalonia="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
              xmlns:components="clr-namespace:InABox.Avalonia.Components;assembly=InABox.Avalonia"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              x:Class="PRS.Avalonia.Modules.MainView"
              x:DataType="avalonia:MainViewModel">
 
-    <Grid Background="{StaticResource PrsSurfaceBackground}">
-        <Grid.RowDefinitions>
-            <RowDefinition Height="45" />
-            <RowDefinition Height="*" />
-        </Grid.RowDefinitions>
+	<dialogHostAvalonia:DialogHost CloseOnClickAway="True">
+		<Grid Background="{StaticResource PrsSurfaceBackground}">
+			<Grid.RowDefinitions>
+				<RowDefinition Height="45" />
+				<RowDefinition Height="*" />
+			</Grid.RowDefinitions>
 
-        <Grid
-            Grid.Row="0"
-            Grid.Column="0"
-            Background="{StaticResource PrsMenuBackground}">
-            <Grid.RowDefinitions>
-                <RowDefinition Height="*" />
-            </Grid.RowDefinitions>
+			<Grid
+				Grid.Row="0"
+				Grid.Column="0"
+				Background="{StaticResource PrsMenuBackground}">
+				<Grid.RowDefinitions>
+					<RowDefinition Height="*" />
+				</Grid.RowDefinitions>
 
-            <Grid.ColumnDefinitions>
-                <ColumnDefinition Width="Auto" />
-                <ColumnDefinition Width="Auto" />
-                <ColumnDefinition Width="*" />
-                <ColumnDefinition Width="Auto" />
-                <ColumnDefinition Width="Auto" />
-            </Grid.ColumnDefinitions>
+				<Grid.ColumnDefinitions>
+					<ColumnDefinition Width="Auto" />
+					<ColumnDefinition Width="Auto" />
+					<ColumnDefinition Width="*" />
+					<ColumnDefinition Width="Auto" />
+					<ColumnDefinition Width="Auto" />
+				</Grid.ColumnDefinitions>
 
-            <Button
-                Grid.Row="0"
-                Grid.Column="0"
-                Margin="5,0,0,0"
-                Classes="Transparent"
-                HorizontalAlignment="Center"
-                VerticalAlignment="Center"
-                IsVisible="{Binding BackButtonVisible}"
-                Command="{Binding BackButtonPressedCommand}">
-                <Image>
-                    <Image.Source>
-                        <SvgImage Source="../Images/arrow_white_left.svg" />
-                    </Image.Source>
-                </Image>
-            </Button>
+				<Button
+					Grid.Row="0"
+					Grid.Column="0"
+					Margin="5,0,0,0"
+					Classes="Transparent"
+					HorizontalAlignment="Center"
+					VerticalAlignment="Center"
+					IsVisible="{Binding BackButtonVisible}"
+					Command="{Binding BackButtonPressedCommand}">
+					<Image>
+						<Image.Source>
+							<SvgImage Source="../Images/arrow_white_left.svg" />
+						</Image.Source>
+					</Image>
+				</Button>
 
-            <components:AvaloniaMenuPanel
-                Grid.Row="0"
-                Grid.Column="1"
-                Items="{Binding SecondaryMenu}" />
+				<components:AvaloniaMenuPanel
+					Grid.Row="0"
+					Grid.Column="1"
+					Items="{Binding SecondaryMenu}" />
 
-            <Label
-                Grid.Row="0"
-                Grid.Column="2"
-                Content="{Binding Title}"
-                VerticalContentAlignment="Center"
-                Margin="5,0,0,0"
-                FontSize="{StaticResource PrsFontSizeLarge}"
-                FontWeight="{StaticResource PrsFontWeightBold}"
-                Foreground="{StaticResource PrsMainMenuForeground}" />
+				<Label
+					Grid.Row="0"
+					Grid.Column="2"
+					Content="{Binding Title}"
+					VerticalContentAlignment="Center"
+					Margin="5,0,0,0"
+					FontSize="{StaticResource PrsFontSizeLarge}"
+					FontWeight="{StaticResource PrsFontWeightBold}"
+					Foreground="{StaticResource PrsMainMenuForeground}" />
 
-            <ItemsControl
-                x:Name="NotificationsPanel"
-                Grid.Row="0"
-                Grid.Column="3"
-                Classes="MenuPanel" />
+				<ItemsControl
+					x:Name="NotificationsPanel"
+					Grid.Row="0"
+					Grid.Column="3"
+					Classes="MenuPanel" />
 
-            <components:AvaloniaMenuPanel
-                Grid.Row="0"
-                Grid.Column="4"
-                Margin="0,0,5,0"
-                Items="{Binding PrimaryMenu}" />
+				<components:AvaloniaMenuPanel
+					Grid.Row="0"
+					Grid.Column="4"
+					Margin="0,0,5,0"
+					Items="{Binding PrimaryMenu}" />
 
-        </Grid>
+			</Grid>
 
-        <TransitioningContentControl
-            Grid.Row="1"
-            Grid.Column="0"
-            Margin="{StaticResource PrsControlSpacing}"
-            Content="{Binding Content}"
-            IsTransitionReversed="{Binding ReverseTransition}"
-            TransitionCompleted="TransitioningContentControl_OnTransitionCompleted">
-            <TransitioningContentControl.PageTransition>
-                <!-- <CrossFade Duration="0:00:00.50"/> -->
-                <PageSlide Orientation="Horizontal" Duration="0:00:00.500" />
-            </TransitioningContentControl.PageTransition>
-        </TransitioningContentControl>
+			<TransitioningContentControl
+				Grid.Row="1"
+				Grid.Column="0"
+				Margin="{StaticResource PrsControlSpacing}"
+				Content="{Binding Content}"
+				IsTransitionReversed="{Binding ReverseTransition}"
+				TransitionCompleted="TransitioningContentControl_OnTransitionCompleted">
+				<TransitioningContentControl.PageTransition>
+					<!-- <CrossFade Duration="0:00:00.50"/> -->
+					<PageSlide Orientation="Horizontal" Duration="0:00:00.500" />
+				</TransitioningContentControl.PageTransition>
+			</TransitioningContentControl>
+
+		</Grid>
+	</dialogHostAvalonia:DialogHost>
 
-    </Grid>
 
 </UserControl>

+ 14 - 25
PRS.Avalonia/PRS.Avalonia/Modules/EquipmentModule/EquipmentList/EquipmentListView.axaml

@@ -33,19 +33,19 @@
                     CommandParameter="{Binding .}"
                     HorizontalContentAlignment="Stretch"
                     Margin="0,0,0,0"
+                    Padding="0,0,0,0"
                     >
                     <Grid 
                         x:DataType="avalonia:EquipmentShell">
 
                         <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto" />
+                            <ColumnDefinition Width="80" />
                             <ColumnDefinition Width="*" />
-                            <ColumnDefinition Width="Auto" />
                         </Grid.ColumnDefinitions>
 
                         <Grid.RowDefinitions>
-                            <RowDefinition Height="*"/>
-                            <RowDefinition Height="*"/>
+                            <RowDefinition Height="30"/>
+                            <RowDefinition Height="50"/>
                         </Grid.RowDefinitions>
 
                         <Image Classes="Large"
@@ -58,30 +58,19 @@
                         <Label
                             Content="{Binding Code}"
                             Grid.Row="0"
-                            Grid.Column="0"
-                            FontSize="{StaticResource PrsFontSizeSmall}"/>
-                        
-                        <Label
-                            Content="{Binding Description}"
-                            Grid.Row="0"
                             Grid.Column="1"
-                            Grid.ColumnSpan="2"
-                            FontSize="{StaticResource PrsFontSizeNormal}"/>
-
-                        <Label
-                            Content="{Binding Address}"
-                            Grid.Row="1"
-                            Grid.Column="0"
-                            Grid.ColumnSpan="2"
-                            FontSize="{StaticResource PrsFontSizeSmall}"/>
+                            FontSize="{StaticResource PrsFontSizeSmall}"
+                            HorizontalContentAlignment="Center"
+                            VerticalContentAlignment="Bottom"/>
                         
-                        <Label
-                            Content="{Binding TwoFactorCode}"
+                        <TextBlock
+                            Text="{Binding Description}"
                             Grid.Row="1"
-                            Grid.Column="2"
-                            FontSize="{StaticResource PrsFontSizeSmall}"
-                            FontStyle="Italic"/>
-
+                            Grid.Column="1"
+                            FontSize="{StaticResource PrsFontSizeNormal}"
+                            TextAlignment="Center"
+                            VerticalAlignment="Top"
+                            TextWrapping="Wrap"/>
                         
                     </Grid>
                 </Button>

+ 92 - 90
PRS.Avalonia/PRS.Avalonia/PRS.Avalonia.csproj

@@ -7,12 +7,12 @@
     </PropertyGroup>
 
     <ItemGroup>
-        <AvaloniaResource Include="Assets\**"/>
-        <None Remove="Images\arrow_white_down.svg"/>
-        <None Remove="Images\arrow_white_left.svg"/>
-        <None Remove="Images\arrow_white_right.svg"/>
-        <None Remove="Images\arrow_white_up.svg"/>
-        <None Remove="Images\badge.svg"/>
+        <AvaloniaResource Include="Assets\**" />
+        <None Remove="Images\arrow_white_down.svg" />
+        <None Remove="Images\arrow_white_left.svg" />
+        <None Remove="Images\arrow_white_right.svg" />
+        <None Remove="Images\arrow_white_up.svg" />
+        <None Remove="Images\badge.svg" />
         <AvaloniaResource Include="Images\arrow_white_down.svg">
           <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
@@ -28,270 +28,270 @@
         <AvaloniaResource Include="Images\badge.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\barcode.svg"/>
+        <None Remove="Images\barcode.svg" />
         <AvaloniaResource Include="Images\barcode.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\bookmark_gray.svg"/>
+        <None Remove="Images\bookmark_gray.svg" />
         <AvaloniaResource Include="Images\bookmark_gray.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\bookmark_green.svg"/>
+        <None Remove="Images\bookmark_green.svg" />
         <AvaloniaResource Include="Images\bookmark_green.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\books.svg"/>
+        <None Remove="Images\books.svg" />
         <AvaloniaResource Include="Images\books.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\cache.svg"/>
+        <None Remove="Images\cache.svg" />
         <AvaloniaResource Include="Images\cache.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\camcorder.svg"/>
+        <None Remove="Images\camcorder.svg" />
         <AvaloniaResource Include="Images\camcorder.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\camera.svg"/>
+        <None Remove="Images\camera.svg" />
         <AvaloniaResource Include="Images\camera.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\certificate.svg"/>
+        <None Remove="Images\certificate.svg" />
         <AvaloniaResource Include="Images\certificate.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\circle_gray.svg"/>
+        <None Remove="Images\circle_gray.svg" />
         <AvaloniaResource Include="Images\circle_gray.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\circle_green.svg"/>
+        <None Remove="Images\circle_green.svg" />
         <AvaloniaResource Include="Images\circle_green.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\circle_red.svg"/>
+        <None Remove="Images\circle_red.svg" />
         <AvaloniaResource Include="Images\circle_red.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\clock.svg"/>
+        <None Remove="Images\clock.svg" />
         <AvaloniaResource Include="Images\clock.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\cloud.svg"/>
+        <None Remove="Images\cloud.svg" />
         <AvaloniaResource Include="Images\cloud.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\construction.svg"/>
+        <None Remove="Images\construction.svg" />
         <AvaloniaResource Include="Images\construction.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\cross.svg"/>
+        <None Remove="Images\cross.svg" />
         <AvaloniaResource Include="Images\cross.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\delivery.svg"/>
+        <None Remove="Images\delivery.svg" />
         <AvaloniaResource Include="Images\delivery.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\digitalform.svg"/>
+        <None Remove="Images\digitalform.svg" />
         <AvaloniaResource Include="Images\digitalform.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\disconnected.svg"/>
+        <None Remove="Images\disconnected.svg" />
         <AvaloniaResource Include="Images\disconnected.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\dots.svg"/>
+        <None Remove="Images\dots.svg" />
         <AvaloniaResource Include="Images\dots.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\drawing.svg"/>
+        <None Remove="Images\drawing.svg" />
         <AvaloniaResource Include="Images\drawing.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\drill.svg"/>
+        <None Remove="Images\drill.svg" />
         <AvaloniaResource Include="Images\drill.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\factory.svg"/>
+        <None Remove="Images\factory.svg" />
         <AvaloniaResource Include="Images\factory.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\flowchart.svg"/>
+        <None Remove="Images\flowchart.svg" />
         <AvaloniaResource Include="Images\flowchart.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\folder.svg"/>
+        <None Remove="Images\folder.svg" />
         <AvaloniaResource Include="Images\folder.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\hide.svg"/>
+        <None Remove="Images\hide.svg" />
         <AvaloniaResource Include="Images\hide.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\holiday.svg"/>
+        <None Remove="Images\holiday.svg" />
         <AvaloniaResource Include="Images\holiday.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\key.svg"/>
+        <None Remove="Images\key.svg" />
         <AvaloniaResource Include="Images\key.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\lines.svg"/>
+        <None Remove="Images\lines.svg" />
         <AvaloniaResource Include="Images\key_disabled.svg">
           <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
         <AvaloniaResource Include="Images\lines.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\map.svg"/>
+        <None Remove="Images\map.svg" />
         <AvaloniaResource Include="Images\map.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\meeting.svg"/>
+        <None Remove="Images\meeting.svg" />
         <AvaloniaResource Include="Images\meeting.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\menu.svg"/>
-        <None Remove="Images\minus.svg"/>
+        <None Remove="Images\menu.svg" />
+        <None Remove="Images\minus.svg" />
         <AvaloniaResource Include="Images\menu.svg">
           <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
         <AvaloniaResource Include="Images\minus.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\more.svg"/>
+        <None Remove="Images\more.svg" />
         <AvaloniaResource Include="Images\more.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\notification.svg"/>
+        <None Remove="Images\notification.svg" />
         <AvaloniaResource Include="Images\notification.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\paperclip.svg"/>
+        <None Remove="Images\paperclip.svg" />
         <AvaloniaResource Include="Images\paperclip.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\person.svg"/>
+        <None Remove="Images\person.svg" />
         <AvaloniaResource Include="Images\person.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\phone.svg"/>
+        <None Remove="Images\phone.svg" />
         <AvaloniaResource Include="Images\phone.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\photolibrary.svg"/>
+        <None Remove="Images\photolibrary.svg" />
         <AvaloniaResource Include="Images\photolibrary.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\plus.svg"/>
+        <None Remove="Images\plus.svg" />
         <AvaloniaResource Include="Images\plus.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\print.svg"/>
+        <None Remove="Images\print.svg" />
         <AvaloniaResource Include="Images\print.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\question.svg"/>
+        <None Remove="Images\question.svg" />
         <AvaloniaResource Include="Images\question.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\refresh.svg"/>
+        <None Remove="Images\refresh.svg" />
         <AvaloniaResource Include="Images\refresh.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\rotate.svg"/>
+        <None Remove="Images\rotate.svg" />
         <AvaloniaResource Include="Images\rotate.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\save.svg"/>
+        <None Remove="Images\save.svg" />
         <AvaloniaResource Include="Images\save.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\schedule.svg"/>
+        <None Remove="Images\schedule.svg" />
         <AvaloniaResource Include="Images\schedule.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\search.svg"/>
+        <None Remove="Images\search.svg" />
         <AvaloniaResource Include="Images\search.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\settings.svg"/>
+        <None Remove="Images\settings.svg" />
         <AvaloniaResource Include="Images\settings.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\share.svg"/>
+        <None Remove="Images\share.svg" />
         <AvaloniaResource Include="Images\share.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\shoppingcart.svg"/>
+        <None Remove="Images\shoppingcart.svg" />
         <AvaloniaResource Include="Images\shoppingcart.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\show.svg"/>
+        <None Remove="Images\show.svg" />
         <AvaloniaResource Include="Images\show.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\stock.svg"/>
+        <None Remove="Images\stock.svg" />
         <AvaloniaResource Include="Images\stock.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\stock_issue.svg"/>
+        <None Remove="Images\stock_issue.svg" />
         <AvaloniaResource Include="Images\stock_issue.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\stock_receive.svg"/>
+        <None Remove="Images\stock_receive.svg" />
         <AvaloniaResource Include="Images\stock_receive.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\stock_relocate.svg"/>
+        <None Remove="Images\stock_relocate.svg" />
         <AvaloniaResource Include="Images\stock_relocate.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\stock_search.svg"/>
+        <None Remove="Images\stock_search.svg" />
         <AvaloniaResource Include="Images\stock_search.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\stock_stocktake.svg"/>
+        <None Remove="Images\stock_stocktake.svg" />
         <AvaloniaResource Include="Images\stock_stocktake.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\stock_transfer.svg"/>
+        <None Remove="Images\stock_transfer.svg" />
         <AvaloniaResource Include="Images\stock_transfer.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\task.svg"/>
+        <None Remove="Images\task.svg" />
         <AvaloniaResource Include="Images\task.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\tick.svg"/>
+        <None Remove="Images\tick.svg" />
         <AvaloniaResource Include="Images\tick.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\trash.svg"/>
+        <None Remove="Images\trash.svg" />
         <AvaloniaResource Include="Images\trash.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\trolley.svg"/>
+        <None Remove="Images\trolley.svg" />
         <AvaloniaResource Include="Images\trolley.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\upload.svg"/>
+        <None Remove="Images\upload.svg" />
         <AvaloniaResource Include="Images\upload.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\version.svg"/>
+        <None Remove="Images\version.svg" />
         <AvaloniaResource Include="Images\version.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\videolibrary.svg"/>
+        <None Remove="Images\videolibrary.svg" />
         <AvaloniaResource Include="Images\videolibrary.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\warehouse.svg"/>
+        <None Remove="Images\warehouse.svg" />
         <AvaloniaResource Include="Images\warehouse.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
-        <None Remove="Images\warning.svg"/>
+        <None Remove="Images\warning.svg" />
         <AvaloniaResource Include="Images\warning.svg">
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </AvaloniaResource>
@@ -299,23 +299,25 @@
 
     <ItemGroup>
         <PackageReference Include="Avalonia.Fonts.Inter" />
-        <PackageReference Include="Avalonia.Themes.Fluent"/>
+        <PackageReference Include="Avalonia.Themes.Fluent" />
         <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
-        <PackageReference Include="CommunityToolkit.Mvvm"/>
-        <PackageReference Include="Material.Avalonia"/>
-        <PackageReference Include="Material.Avalonia.DataGrid"/>
-        <PackageReference Include="Material.Avalonia.Dialogs"/>
+        <PackageReference Include="CommunityToolkit.Mvvm" />
+        <PackageReference Include="DialogHost.Avalonia" />
+        <PackageReference Include="Material.Avalonia" />
+        <PackageReference Include="Material.Avalonia.DataGrid" />
+        <PackageReference Include="Material.Avalonia.Dialogs" />
+        <PackageReference Include="SkiaSharp" />
         <PackageReference Include="SkiaSharp.Views" />
     </ItemGroup>
 
     <ItemGroup>
-        <ProjectReference Include="..\..\..\inabox\InABox.Avalonia.Platform\InABox.Avalonia.Platform.csproj"/>
-        <ProjectReference Include="..\..\..\inabox\InABox.Avalonia\InABox.Avalonia.csproj"/>
-        <ProjectReference Include="..\..\..\inabox\InABox.Client.RPC\InABox.Client.RPC.csproj"/>
-        <ProjectReference Include="..\..\..\inabox\InABox.Core\InABox.Core.csproj"/>
-        <ProjectReference Include="..\..\..\inabox\inabox.logging.shared\InABox.Logging.Shared.csproj"/>
-        <ProjectReference Include="..\..\..\inabox\InABox.RPC.Shared\InABox.RPC.Shared.csproj"/>
-        <ProjectReference Include="..\..\prs.classes\PRSClasses.csproj"/>
+        <ProjectReference Include="..\..\..\inabox\InABox.Avalonia.Platform\InABox.Avalonia.Platform.csproj" />
+        <ProjectReference Include="..\..\..\inabox\InABox.Avalonia\InABox.Avalonia.csproj" />
+        <ProjectReference Include="..\..\..\inabox\InABox.Client.RPC\InABox.Client.RPC.csproj" />
+        <ProjectReference Include="..\..\..\inabox\InABox.Core\InABox.Core.csproj" />
+        <ProjectReference Include="..\..\..\inabox\inabox.logging.shared\InABox.Logging.Shared.csproj" />
+        <ProjectReference Include="..\..\..\inabox\InABox.RPC.Shared\InABox.RPC.Shared.csproj" />
+        <ProjectReference Include="..\..\prs.classes\PRSClasses.csproj" />
     </ItemGroup>
 
     <ItemGroup>
@@ -394,15 +396,15 @@
     </ItemGroup>
 
     <ItemGroup>
-        <AdditionalFiles Include="HomePage\HomePageView.axaml"/>
+        <AdditionalFiles Include="HomePage\HomePageView.axaml" />
     </ItemGroup>
 
     <ItemGroup>
-        <UpToDateCheckInput Remove="Assets\Theme\Classes\Border.axaml"/>
-        <UpToDateCheckInput Remove="Assets\Theme\Classes\Button.axaml"/>
-        <UpToDateCheckInput Remove="Assets\Theme\Classes\Image.axaml"/>
-        <UpToDateCheckInput Remove="Assets\Theme\Classes\Label.axaml"/>
-        <UpToDateCheckInput Remove="Assets\Theme\Classes\TextBox.axaml"/>
+        <UpToDateCheckInput Remove="Assets\Theme\Classes\Border.axaml" />
+        <UpToDateCheckInput Remove="Assets\Theme\Classes\Button.axaml" />
+        <UpToDateCheckInput Remove="Assets\Theme\Classes\Image.axaml" />
+        <UpToDateCheckInput Remove="Assets\Theme\Classes\Label.axaml" />
+        <UpToDateCheckInput Remove="Assets\Theme\Classes\TextBox.axaml" />
         <UpToDateCheckInput Remove="Assets\Classes\Border.axaml" />
         <UpToDateCheckInput Remove="Assets\Classes\Button.axaml" />
         <UpToDateCheckInput Remove="Assets\Classes\Image.axaml" />

+ 29 - 0
PRS.Avalonia/PRS.Avalonia/ViewModelBase.cs

@@ -5,8 +5,10 @@ using System.Reflection;
 using System.Threading;
 using System.Threading.Tasks;
 using Avalonia.Svg.Skia;
+using Comal.Classes;
 using CommunityToolkit.Mvvm.ComponentModel;
 using CommunityToolkit.Mvvm.Input;
+using DialogHostAvalonia;
 using InABox.Avalonia;
 using InABox.Avalonia.Components;
 using InABox.Configuration;
@@ -213,4 +215,31 @@ public abstract partial class ViewModelBase : ObservableObject, IViewModelBase
     }
     
     
+}
+
+public abstract class PopupViewModel : ViewModelBase, IPopupViewModel
+{
+    public bool IsClosed { get; private set; }
+
+    public void Close()
+    {
+        IsClosed = true;
+        DialogHost.GetDialogSession(null)?.Close();
+    }
+}
+
+public abstract class PopupViewModel<TResult> : PopupViewModel, IPopupViewModel<TResult>
+{
+    private TResult? _result;
+
+    public TResult? GetResult()
+    {
+        return _result ?? default;
+    }
+
+    public void Close(TResult result)
+    {
+        _result = result;
+        Close();
+    }
 }

+ 5 - 18
PRS.DigitalKey/PRS.DigitalKey/DigitalKeys/DigitalKeysView.axaml

@@ -10,24 +10,11 @@
              x:DataType="digitalKey:DigitalKeysViewModel">
 <UserControl.Resources>
         
-        <converters:GuidToColorConverter 
+        <converters:DateTimeToColorConverter 
             x:Key="EquipmentColorConverter" 
-            Default="{StaticResource PrsTileBackground}"
-            Empty="{StaticResource PrsAlertBackground}" />
-
-        <converters:GuidToColorConverter 
-            x:Key="DigitalKeyColorConverter" 
-            Default="{StaticResource PrsButtonBackground}"
-            Empty="LightGray" />
+            Current="{StaticResource PrsButtonBackground}"
+            Expired="{StaticResource PrsTileBackground}" />
         
-        <converters:GuidToBooleanConverter
-            x:Key="IsEquipmentConverter"
-            Inverted="False"/>
-        
-        <converters:GuidToBooleanConverter
-            x:Key="IsDigitalKeyConverter"
-            Inverted="True"/>
-            
         <DataTemplate
             x:Key="DigitalKeyDisplayTemplate"
             x:DataType="digitalKey:DigitalKeyDisplay">
@@ -38,7 +25,7 @@
                 Padding="0,0,0,0"
                 CornerRadius="50"
                 x:DataType="digitalKey:DigitalKeyDisplay"
-                Background="{Binding Id, Converter={StaticResource EquipmentColorConverter}}"
+                Background="{Binding ., Converter={StaticResource EquipmentColorConverter}}"
                 Command="{Binding $parent[ItemsControl].((digitalKey:DigitalKeysViewModel)DataContext).StartCommand}"
                 CommandParameter="{Binding .}"
                 HorizontalContentAlignment="Stretch"
@@ -62,7 +49,6 @@
                         ProgressBackground="LightYellow"
                         ProgressForeground="Red"
                         IsActive="{Binding Active, Mode=TwoWay}"
-                        IsVisible="{Binding Id, Converter={StaticResource IsEquipmentConverter}}"
                         Image="{SvgImage /Images/key.svg}"
                         Stopped="{Binding $parent[ItemsControl].((digitalKey:DigitalKeysViewModel)DataContext).StoppedCommand}"
                     />
@@ -74,6 +60,7 @@
                         HorizontalContentAlignment="Center"
                         VerticalContentAlignment="Center"
                         FontWeight="{StaticResource PrsFontWeightBold}"/>
+                    
                 </Grid>
             </Button>
         

+ 9 - 2
PRS.DigitalKey/PRS.DigitalKey/DigitalKeys/DigitalKeysViewModel.cs

@@ -14,6 +14,7 @@ public partial class DigitalKeyDisplay : ObservableObject
     [ObservableProperty] private byte[] _image;
     [ObservableProperty] private bool _active;
     [ObservableProperty] private bool _controlServiceID;
+    [ObservableProperty] private DateTime _unlockedUntil;
 
 }
 
@@ -23,6 +24,7 @@ public partial class DigitalKeysViewModel : ViewModelBase
     [ObservableProperty] 
     private CoreObservableCollection<DigitalKeyDisplay> _keys = new();
 
+    private Dictionary<Guid, DateTime> _unlockedKeys = new();
 
     public DigitalKeysViewModel()
     {
@@ -60,13 +62,16 @@ public partial class DigitalKeysViewModel : ViewModelBase
             {
                 if (device.AvailableServices.Any())
                 {
+                    var id = device.AvailableServices.First();
+                    _unlockedKeys.TryGetValue(id, out DateTime _unlocktime);
                     
                     keys.Add(
                         new DigitalKeyDisplay()
                         {
                             Device = device,
-                            Id = device.AvailableServices.First(),
-                            Code = device.Name
+                            Id = id,
+                            Code = device.Name,
+                            UnlockedUntil = _unlocktime
                         }
                     );
                 }
@@ -84,6 +89,8 @@ public partial class DigitalKeysViewModel : ViewModelBase
     {
         if (shell.Id == Guid.Empty)
             return;
+        _unlockedKeys[shell.Id] = DateTime.Now.AddMinutes(5);
+        
         bActive = true;
         _ = Task.Run(async () =>
         {