Merge with OUT-445
authorThomas
Mon, 06 Apr 2020 10:11:47 +0200
changeset 30973d0bf28073c0
parent 3095 f40b0d1bb27d
parent 3096 bc707403d91f
child 3098 44dbf2736457
child 3103 1b900c30cb8f
Merge with OUT-445

The Update now button itself is implemented. Other functionality regarding this feature is tracked in other tickets.
     1.1 --- a/Properties/Resources.Designer.cs	Thu Apr 02 10:57:40 2020 +0200
     1.2 +++ b/Properties/Resources.Designer.cs	Mon Apr 06 10:11:47 2020 +0200
     1.3 @@ -1378,6 +1378,15 @@
     1.4          }
     1.5          
     1.6          /// <summary>
     1.7 +        ///   Looks up a localized string similar to No updates found.
     1.8 +        /// </summary>
     1.9 +        public static string Options_NoUpdateFound {
    1.10 +            get {
    1.11 +                return ResourceManager.GetString("Options_NoUpdateFound", resourceCulture);
    1.12 +            }
    1.13 +        }
    1.14 +        
    1.15 +        /// <summary>
    1.16          ///   Looks up a localized string similar to OK.
    1.17          /// </summary>
    1.18          public static string Options_OKText {
    1.19 @@ -1522,6 +1531,33 @@
    1.20          }
    1.21          
    1.22          /// <summary>
    1.23 +        ///   Looks up a localized string similar to p≡p for Outlook is updating....
    1.24 +        /// </summary>
    1.25 +        public static string Options_UpdateNotificationMessage {
    1.26 +            get {
    1.27 +                return ResourceManager.GetString("Options_UpdateNotificationMessage", resourceCulture);
    1.28 +            }
    1.29 +        }
    1.30 +        
    1.31 +        /// <summary>
    1.32 +        ///   Looks up a localized string similar to Update.
    1.33 +        /// </summary>
    1.34 +        public static string Options_UpdateNotificationTitle {
    1.35 +            get {
    1.36 +                return ResourceManager.GetString("Options_UpdateNotificationTitle", resourceCulture);
    1.37 +            }
    1.38 +        }
    1.39 +        
    1.40 +        /// <summary>
    1.41 +        ///   Looks up a localized string similar to Update now.
    1.42 +        /// </summary>
    1.43 +        public static string Options_UpdateNow {
    1.44 +            get {
    1.45 +                return ResourceManager.GetString("Options_UpdateNow", resourceCulture);
    1.46 +            }
    1.47 +        }
    1.48 +        
    1.49 +        /// <summary>
    1.50          ///   Looks up a localized string similar to Cc.
    1.51          /// </summary>
    1.52          public static string PreviewMessage_CcText {
     2.1 --- a/Properties/Resources.resx	Thu Apr 02 10:57:40 2020 +0200
     2.2 +++ b/Properties/Resources.resx	Mon Apr 06 10:11:47 2020 +0200
     2.3 @@ -808,4 +808,16 @@
     2.4    <data name="IntroTutorial_PrivacyStatusExplanation2" xml:space="preserve">
     2.5      <value>In this case the Privacy Status of the message is Secure, because this is the lowest common denominator of the two communication partners.</value>
     2.6    </data>
     2.7 +  <data name="Options_NoUpdateFound" xml:space="preserve">
     2.8 +    <value>No updates found</value>
     2.9 +  </data>
    2.10 +  <data name="Options_UpdateNotificationTitle" xml:space="preserve">
    2.11 +    <value>Update</value>
    2.12 +  </data>
    2.13 +  <data name="Options_UpdateNotificationMessage" xml:space="preserve">
    2.14 +    <value>p≡p for Outlook is updating...</value>
    2.15 +  </data>
    2.16 +  <data name="Options_UpdateNow" xml:space="preserve">
    2.17 +    <value>Update now</value>
    2.18 +  </data>
    2.19  </root>
    2.20 \ No newline at end of file
     3.1 Binary file Resources/AnimationWindowsLoading.gif has changed
     4.1 --- a/UI/FormControlOptions.xaml	Thu Apr 02 10:57:40 2020 +0200
     4.2 +++ b/UI/FormControlOptions.xaml	Mon Apr 06 10:11:47 2020 +0200
     4.3 @@ -778,18 +778,26 @@
     4.4                          <RowDefinition Height="Auto" />
     4.5                          <RowDefinition Height="Auto" />
     4.6                          <RowDefinition Height="*" />
     4.7 +                        <RowDefinition Height="Auto" />
     4.8                      </Grid.RowDefinitions>
     4.9 +                    <Grid.ColumnDefinitions>
    4.10 +                        <ColumnDefinition Width="Auto" />
    4.11 +                        <ColumnDefinition Width="*" />
    4.12 +                    </Grid.ColumnDefinitions>
    4.13  
    4.14                      <!-- About title -->
    4.15                      <TextBlock Grid.Row="0"
    4.16 +                               Grid.ColumnSpan="2"
    4.17                                 Text="{x:Static p:Resources.Options_PageSectionAboutText}"
    4.18                                 Margin="6,6,0,0"
    4.19                                 FontWeight="Bold"
    4.20                                 FontSize="16" />
    4.21 -                    <Separator Grid.Row="1" />
    4.22 +                    <Separator Grid.Row="1" 
    4.23 +                               Grid.ColumnSpan="2"/>
    4.24  
    4.25                      <!-- Logo -->
    4.26                      <Image Grid.Row="2"
    4.27 +                           Grid.Column="0"
    4.28                             Source="pack://application:,,,/pEp;component/Resources/ImageLogoLarge.png"
    4.29                             Stretch="Uniform"
    4.30                             Width="150"
    4.31 @@ -803,8 +811,28 @@
    4.32                          </Image.Margin>
    4.33                      </Image>
    4.34  
    4.35 +                    <!--Update Now button and feedback text-->
    4.36 +                    <StackPanel Grid.Row="2"
    4.37 +                                Grid.Column="1"
    4.38 +                                VerticalAlignment="Top"
    4.39 +                                HorizontalAlignment="Right">
    4.40 +
    4.41 +                        <Button Margin="5"
    4.42 +                            Content="{x:Static p:Resources.Options_UpdateNow}"
    4.43 +                            Style="{StaticResource StyleButtonGray}"
    4.44 +                            Padding="{StaticResource ButtonPaddingStandard}"
    4.45 +                            Command="{Binding CommandCheckForUpdates}"
    4.46 +                            IsEnabled="{Binding Path=IsCheckingForUpdates, Converter={StaticResource InvertBoolToVisibility}}">
    4.47 +                        </Button>
    4.48 +
    4.49 +                        <TextBlock Margin="5"
    4.50 +                                   TextWrapping="Wrap"
    4.51 +                                   Text="{Binding UpdateFeedbackText}" />
    4.52 +                    </StackPanel>
    4.53 +
    4.54                      <!-- pEp info -->
    4.55 -                    <StackPanel Grid.Row="3">
    4.56 +                    <StackPanel Grid.Row="3"
    4.57 +                                Grid.ColumnSpan="2">
    4.58                          <StackPanel.Margin>
    4.59                              <Thickness Bottom="0"
    4.60                                         Left="{StaticResource PageLeftSpacing}"
    4.61 @@ -835,6 +863,7 @@
    4.62  
    4.63                      <!-- System info -->
    4.64                      <ItemsControl Grid.Row="4"
    4.65 +                                  Grid.ColumnSpan="2"
    4.66                                    Grid.IsSharedSizeScope="True"
    4.67                                    ItemsSource="{Binding Path=SystemInfo, Mode=OneWay}">
    4.68                          <ItemsControl.Margin>
    4.69 @@ -869,6 +898,7 @@
    4.70  
    4.71                      <!-- Auto updates -->
    4.72                      <Grid Grid.Row="5" 
    4.73 +                          Grid.ColumnSpan="2"
    4.74                            VerticalAlignment="Bottom">
    4.75                          <Grid.Margin>
    4.76                              <Thickness Bottom="{StaticResource PageBottomSpacing}"
     5.1 --- a/UI/FormControlOptions.xaml.cs	Thu Apr 02 10:57:40 2020 +0200
     5.2 +++ b/UI/FormControlOptions.xaml.cs	Mon Apr 06 10:11:47 2020 +0200
     5.3 @@ -12,6 +12,8 @@
     5.4  using System.Linq;
     5.5  using System.Text;
     5.6  using System.Text.RegularExpressions;
     5.7 +using System.Threading;
     5.8 +using System.Threading.Tasks;
     5.9  using System.Windows;
    5.10  using System.Windows.Controls;
    5.11  using System.Windows.Documents;
    5.12 @@ -313,12 +315,14 @@
    5.13              private ObservableCollection<KVPair<PEPIdentity, bool>> _BlacklistDisplayed;
    5.14              private string                                          _BlacklistEnteredFingerprint;
    5.15              private int                                             _BlacklistSelectedIndex;
    5.16 +            private RelayCommand                                    _CommandCheckForUpdates;
    5.17              private RelayCommand                                    _CommandExportKeys;
    5.18              private RelayCommand                                    _CommandImportKeys;
    5.19              private bool                                            _HideInternalMessages;
    5.20              private bool                                            _IsAdvancedEnabled;
    5.21              private bool                                            _IsAutoUpdateEnabled;
    5.22              private bool                                            _IsBlacklistAddEnabled;
    5.23 +            private bool                                            _IsCheckingForUpdates;
    5.24              private bool                                            _IsDeveloperModeEnabled;
    5.25              private bool                                            _IsEncryptAllAccountsEnabled;
    5.26              private bool                                            _IsGrouped;
    5.27 @@ -342,6 +346,7 @@
    5.28              private List<KVPair<string, string>>                    _SystemInfo;
    5.29              private CultureInfo                                     _TrustwordsCulture;
    5.30              private List<KeyValuePair<CultureInfo, string>>         _TrustwordsCultureList;
    5.31 +            private string                                          _UpdateFeedbackText;
    5.32  
    5.33              private bool calcDepPropIsEnabled;
    5.34  
    5.35 @@ -437,6 +442,22 @@
    5.36              }
    5.37  
    5.38              /// <summary>
    5.39 +            /// Command for when the Update Now button is clicked.
    5.40 +            /// </summary>
    5.41 +            public RelayCommand CommandCheckForUpdates
    5.42 +            {
    5.43 +                get
    5.44 +                {
    5.45 +                    if (this._CommandCheckForUpdates == null)
    5.46 +                    {
    5.47 +                        this._CommandCheckForUpdates = new RelayCommand(p => this.CheckForUpdates());
    5.48 +                    }
    5.49 +
    5.50 +                    return this._CommandCheckForUpdates;
    5.51 +                }
    5.52 +            }
    5.53 +
    5.54 +            /// <summary>
    5.55              /// Gets the command to execute when the Export Keys button is clicked.
    5.56              /// </summary>
    5.57              public RelayCommand CommandExportKeys
    5.58 @@ -521,6 +542,19 @@
    5.59              }
    5.60  
    5.61              /// <summary>
    5.62 +            /// Gets or sets whether we are currently checking for updates.
    5.63 +            /// </summary>
    5.64 +            public bool IsCheckingForUpdates
    5.65 +            {
    5.66 +                get => this._IsCheckingForUpdates;
    5.67 +                set
    5.68 +                {
    5.69 +                    this._IsCheckingForUpdates = value;
    5.70 +                    this.RaisePropertyChangedEvent(nameof(this.IsCheckingForUpdates));
    5.71 +                }
    5.72 +            }
    5.73 +
    5.74 +            /// <summary>
    5.75              /// Gets or sets whether developer mode is enabled.
    5.76              /// </summary>
    5.77              public bool IsDeveloperModeEnabled
    5.78 @@ -904,6 +938,19 @@
    5.79                  get { return (this._TrustwordsCultureList); }
    5.80              }
    5.81  
    5.82 +            /// <summary>
    5.83 +            /// Gets or sets the text to be shown after the user clicked the Update Now button.
    5.84 +            /// </summary>
    5.85 +            public string UpdateFeedbackText
    5.86 +            {
    5.87 +                get { return (this._UpdateFeedbackText); }
    5.88 +                set
    5.89 +                {
    5.90 +                    this._UpdateFeedbackText = value;
    5.91 +                    this.RaisePropertyChangedEvent(nameof(this.UpdateFeedbackText));
    5.92 +                }
    5.93 +            }
    5.94 +
    5.95              /**************************************************************
    5.96               * 
    5.97               * Methods
    5.98 @@ -964,6 +1011,93 @@
    5.99              }
   5.100  
   5.101              /// <summary>
   5.102 +            /// Checks for updates and launches the installer or gives the respective user feedback.
   5.103 +            /// </summary>
   5.104 +            private async void CheckForUpdates()
   5.105 +            {
   5.106 +                bool updateFound = false;
   5.107 +                this.IsCheckingForUpdates = true;
   5.108 +                Process installerProcess = await Task.Run(() =>
   5.109 +                {
   5.110 +                    string updateStr = string.Format(Properties.Settings.Default.UpdateURL, Properties.Settings.Default.Serial);
   5.111 +                    try
   5.112 +                    {
   5.113 +                        ThisAddIn.PEPEngine.UpdateNow(updateStr, out updateFound);
   5.114 +                    }
   5.115 +                    catch (Exception ex)
   5.116 +                    {
   5.117 +                        updateFound = false;
   5.118 +                        Log.Error("CheckForUpdates: Error checking for updates. " + ex.ToString());
   5.119 +                    }
   5.120 +
   5.121 +                    // If update is found, show progress
   5.122 +                    if (updateFound)
   5.123 +                    {
   5.124 +                        int counter = 0;
   5.125 +                        List<Process> installerProcesses = null;
   5.126 +                        do
   5.127 +                        {
   5.128 +                            // Try to get the installer process during 5 s
   5.129 +                            installerProcesses = Process.GetProcesses().ToList().Where(a => a.MainWindowTitle.Contains("pEp for Outlook Setup"))?.ToList();
   5.130 +                            if (((installerProcesses?.Count > 0) != true) &&
   5.131 +                                (++counter < 10))
   5.132 +                            {
   5.133 +                                Thread.Sleep(500);
   5.134 +                            }
   5.135 +                            else
   5.136 +                            {
   5.137 +                                break;
   5.138 +                            }
   5.139 +                        } while (true);
   5.140 +
   5.141 +                        // If the installer is found, show notification while process is running
   5.142 +                        if (installerProcesses?.Count == 1)
   5.143 +                        {
   5.144 +                            return installerProcesses.First();
   5.145 +                        }
   5.146 +                        else
   5.147 +                        {
   5.148 +                            Log.Error("CheckForUpdates: Error getting installer process.");
   5.149 +                        }
   5.150 +                    }
   5.151 +                    else
   5.152 +                    {
   5.153 +                        this.UpdateFeedbackText = Properties.Resources.Options_NoUpdateFound;
   5.154 +                    }
   5.155 +                    return null;
   5.156 +                });
   5.157 +
   5.158 +                if (installerProcess != null)
   5.159 +                {
   5.160 +                    // Show notification and close when process has finished
   5.161 +                    try
   5.162 +                    {
   5.163 +                        Notification notification = new Notification(Properties.Resources.Options_UpdateNotificationTitle, Properties.Resources.Options_UpdateNotificationMessage, true)
   5.164 +                        {
   5.165 +                            CloseAutomatically = false
   5.166 +                        };
   5.167 +                        notification.Show();
   5.168 +
   5.169 +                        installerProcess.EnableRaisingEvents = true;
   5.170 +                        installerProcess.Exited += (s, e) =>
   5.171 +                        {
   5.172 +                            notification.InvokeClosing();
   5.173 +                            this.IsCheckingForUpdates = false;
   5.174 +                        };
   5.175 +                    }
   5.176 +                    catch (Exception ex)
   5.177 +                    {
   5.178 +                        this.IsCheckingForUpdates = false;
   5.179 +                        Log.Error("CheckForUpdates: Error showing notification. " + ex.ToString());
   5.180 +                    }
   5.181 +                }
   5.182 +                else
   5.183 +                {
   5.184 +                    this.IsCheckingForUpdates = false;
   5.185 +                }
   5.186 +            }
   5.187 +
   5.188 +            /// <summary>
   5.189              /// Exports all keys to the "PEP_USER_FOLDER\keys" directory.
   5.190              /// </summary>
   5.191              private void ExportKeys()
   5.192 @@ -1139,6 +1273,7 @@
   5.193                  copy.SelectedPageIndex = this._SelectedPageIndex;
   5.194                  copy.SystemInfo = this._SystemInfo;
   5.195                  copy.TrustwordsCulture = ((this._TrustwordsCulture != null) ? new CultureInfo(this._TrustwordsCulture.LCID) : null);
   5.196 +                copy.UpdateFeedbackText = this._UpdateFeedbackText;
   5.197  
   5.198                  return copy;
   5.199              }
   5.200 @@ -1196,6 +1331,7 @@
   5.201                  this._SystemInfo = null;
   5.202                  this._TrustwordsCulture = new CultureInfo(PEPSettings.CULTURE_CODE_DEFAULT);
   5.203                  this._TrustwordsCultureList = Globals.ThisAddIn.LanguageList;
   5.204 +                this._UpdateFeedbackText = null;
   5.205  
   5.206                  // Connect events
   5.207                  this._AccountSettingsList.CollectionChanged += AccountSettingsList_CollectionChanged;
   5.208 @@ -1232,6 +1368,7 @@
   5.209                  this.RaisePropertyChangedEvent(nameof(this.SystemInfo));
   5.210                  this.RaisePropertyChangedEvent(nameof(this.TrustwordsCulture));
   5.211                  this.RaisePropertyChangedEvent(nameof(this.TrustwordsCultureList));
   5.212 +                this.RaisePropertyChangedEvent(nameof(this.UpdateFeedbackText));
   5.213  
   5.214                  // Set dependent properties -- raises own events
   5.215                  this.CalcDependentProperties();
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/UI/GIFImage.cs	Mon Apr 06 10:11:47 2020 +0200
     6.3 @@ -0,0 +1,181 @@
     6.4 +using System;
     6.5 +using System.Diagnostics;
     6.6 +using System.Windows;
     6.7 +using System.Windows.Controls;
     6.8 +using System.Windows.Media.Animation;
     6.9 +using System.Windows.Media.Imaging;
    6.10 +
    6.11 +namespace pEp.UI
    6.12 +{
    6.13 +    /// <summary>
    6.14 +    /// Class to display an animated GIF image.
    6.15 +    /// </summary>
    6.16 +    internal class GifImage : Image
    6.17 +    {
    6.18 +        private bool isInitialized = false;
    6.19 +
    6.20 +        // Dependency properties
    6.21 +        public static readonly DependencyProperty AutoStartProperty = DependencyProperty.Register("AutoStart", typeof(bool?), typeof(GifImage), new UIPropertyMetadata(false, AutoStartPropertyChanged));
    6.22 +        public static readonly DependencyProperty FrameIndexProperty = DependencyProperty.Register("FrameIndex", typeof(int), typeof(GifImage), new UIPropertyMetadata(0, FrameIndexChanged));
    6.23 +        public static readonly DependencyProperty GifSourceProperty = DependencyProperty.Register("GifSource", typeof(string), typeof(GifImage), new UIPropertyMetadata(string.Empty, GifSourcePropertyChanged));
    6.24 +
    6.25 +        #region Event handlers
    6.26 +        /// <summary>
    6.27 +        /// Event handler for when the Visibility property changes.
    6.28 +        /// </summary>
    6.29 +        private static void VisibilityPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    6.30 +        {
    6.31 +            if (sender is GifImage image)
    6.32 +            {
    6.33 +                if ((Visibility)e.NewValue == Visibility.Visible)
    6.34 +                {
    6.35 +                    image.StartAnimation();
    6.36 +                }
    6.37 +                else
    6.38 +                {
    6.39 +                    image.StopAnimation();
    6.40 +                }
    6.41 +            }
    6.42 +            else
    6.43 +            {
    6.44 +                Debug.Fail("GifImage.VisibilityPropertyChanged: Wrong dependency object.");
    6.45 +            }
    6.46 +        }
    6.47 +
    6.48 +        /// <summary>
    6.49 +        /// Event handler for when the AutoStart property changes.
    6.50 +        /// </summary>
    6.51 +        private static void AutoStartPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    6.52 +        {
    6.53 +            if (sender is GifImage image)
    6.54 +            {
    6.55 +                if (e.NewValue as bool? == true)
    6.56 +                {
    6.57 +                    image.StartAnimation();
    6.58 +                }
    6.59 +            }
    6.60 +            else
    6.61 +            {
    6.62 +                Debug.Fail("GifImage.AutoStartPropertyChanged: Wrong dependency object.");
    6.63 +            }
    6.64 +        }
    6.65 +
    6.66 +        /// <summary>
    6.67 +        /// Event handler for when the FrameIndex property changes.
    6.68 +        /// </summary>
    6.69 +        private static void FrameIndexChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    6.70 +        {
    6.71 +            if (sender is GifImage image)
    6.72 +            {
    6.73 +                image.Source = image.GifBitmapDecoder.Frames[(int)e.NewValue];
    6.74 +            }
    6.75 +            else
    6.76 +            {
    6.77 +                Debug.Fail("GifImage.FrameIndexChanged: Wrong dependency object.");
    6.78 +            }
    6.79 +        }
    6.80 +
    6.81 +        /// <summary>
    6.82 +        /// Event handler for when the GifSource property changes.
    6.83 +        /// </summary>
    6.84 +        private static void GifSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    6.85 +        {
    6.86 +            if (sender is GifImage image)
    6.87 +            {
    6.88 +                image.Initialize();
    6.89 +            }
    6.90 +            else
    6.91 +            {
    6.92 +                Debug.Fail("GifImage.GifSourcePropertyChanged: Wrong dependency object.");
    6.93 +            }
    6.94 +        }
    6.95 +        #endregion
    6.96 +
    6.97 +        #region Properties
    6.98 +        /// <summary>
    6.99 +        /// Gets or sets the animation of this GIF image.
   6.100 +        /// </summary>
   6.101 +        public Int32Animation Animation { get; private set; } = null;
   6.102 +
   6.103 +        /// <summary>
   6.104 +        /// Gets or sets whether the animation starts on its own.
   6.105 +        /// </summary>
   6.106 +        public bool AutoStart
   6.107 +        {
   6.108 +            get => (GetValue(AutoStartProperty) as bool? == true);
   6.109 +            set => SetValue(AutoStartProperty, value);
   6.110 +        }
   6.111 +
   6.112 +        /// <summary>
   6.113 +        /// Gets or sets the frame index.
   6.114 +        /// </summary>
   6.115 +        public int FrameIndex
   6.116 +        {
   6.117 +            get => (int)GetValue(FrameIndexProperty); 
   6.118 +            set => SetValue(FrameIndexProperty, value);
   6.119 +        }
   6.120 +
   6.121 +        /// <summary>
   6.122 +        /// Gets or sets the GIF bitmap decoder for this GIF image.
   6.123 +        /// </summary>
   6.124 +        public GifBitmapDecoder GifBitmapDecoder { get; private set; } = null;
   6.125 +
   6.126 +        /// <summary>
   6.127 +        /// Gets or sets the source of the GIF image.
   6.128 +        /// </summary>
   6.129 +        public string GifSource
   6.130 +        {
   6.131 +            get => GetValue(GifSourceProperty) as string;
   6.132 +            set => SetValue(GifSourceProperty, value);
   6.133 +        }
   6.134 +        #endregion
   6.135 +
   6.136 +        #region Methods
   6.137 +        /// <summary>
   6.138 +        /// Initializes this GifImage and starts the animation if needed.
   6.139 +        /// </summary>
   6.140 +        private void Initialize()
   6.141 +        {
   6.142 +            if ((string.IsNullOrEmpty(this.GifSource) == false) &&
   6.143 +                (this.isInitialized == false))
   6.144 +            {
   6.145 +                this.GifBitmapDecoder = new GifBitmapDecoder(new Uri(this.GifSource), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
   6.146 +                int frameCount = this.GifBitmapDecoder.Frames.Count;
   6.147 +                Duration animationDuration = new Duration(new TimeSpan(0, 0, 0, frameCount / 10, (int)((frameCount / 10.0 - frameCount / 10) * 1000)));
   6.148 +                this.Animation = new Int32Animation(0, frameCount - 1, animationDuration)
   6.149 +                {
   6.150 +                    RepeatBehavior = RepeatBehavior.Forever
   6.151 +                };
   6.152 +                this.Source = this.GifBitmapDecoder.Frames[0];
   6.153 +
   6.154 +                // Start the animation if needed
   6.155 +                if (this.AutoStart)
   6.156 +                {
   6.157 +                    this.StartAnimation();
   6.158 +                }
   6.159 +
   6.160 +                this.isInitialized = true;
   6.161 +            }
   6.162 +        }
   6.163 +
   6.164 +        /// <summary>
   6.165 +        /// Starts the animation.
   6.166 +        /// </summary>
   6.167 +        private void StartAnimation()
   6.168 +        {
   6.169 +            if (this.Animation != null)
   6.170 +            {
   6.171 +                this.BeginAnimation(GifImage.FrameIndexProperty, this.Animation);
   6.172 +            }
   6.173 +        }
   6.174 +
   6.175 +        /// <summary>
   6.176 +        /// Stops the animation.
   6.177 +        /// </summary>
   6.178 +        private void StopAnimation()
   6.179 +        {
   6.180 +            this.BeginAnimation(GifImage.FrameIndexProperty, null);
   6.181 +        }
   6.182 +        #endregion
   6.183 +    }
   6.184 +}
     7.1 --- a/UI/Notification.xaml	Thu Apr 02 10:57:40 2020 +0200
     7.2 +++ b/UI/Notification.xaml	Mon Apr 06 10:11:47 2020 +0200
     7.3 @@ -13,6 +13,16 @@
     7.4          Topmost="True"
     7.5          Height="100" 
     7.6          Width="350">
     7.7 +    <Window.Resources>
     7.8 +        <ResourceDictionary>
     7.9 +            <!-- Converters -->
    7.10 +            <BooleanToVisibilityConverter x:Key="BoolToVisibility" />
    7.11 +            <local:ValueConverterGroup x:Key="InvertBoolToVisibility">
    7.12 +                <local:InvertBoolConverter />
    7.13 +                <BooleanToVisibilityConverter />
    7.14 +            </local:ValueConverterGroup>
    7.15 +        </ResourceDictionary>
    7.16 +    </Window.Resources>
    7.17  
    7.18      <Grid RenderTransformOrigin="0,1">
    7.19  
    7.20 @@ -60,19 +70,29 @@
    7.21                         VerticalAlignment="Stretch"
    7.22                         HorizontalAlignment="Center"
    7.23                         Margin="0,0,15,0"
    7.24 -                       Source="pack://application:,,,/pEp;component/Resources/ImageSyncAnimationAddToGroup1.png">
    7.25 -                </Image>
    7.26 +                       Visibility="{Binding AnimatedIcon, Converter={StaticResource InvertBoolToVisibility}}"
    7.27 +                       Source="pack://application:,,,/pEp;component/Resources/ImageSyncAnimationAddToGroup1.png" />
    7.28 +                <local:GifImage Grid.Column="0"
    7.29 +                                Grid.RowSpan="2"
    7.30 +                                Height="80"
    7.31 +                                Stretch="Uniform"
    7.32 +                                VerticalAlignment="Stretch"
    7.33 +                                HorizontalAlignment="Center"
    7.34 +                                Margin="0,0,15,0"
    7.35 +                                Visibility="{Binding AnimatedIcon, Converter={StaticResource BoolToVisibility}}"
    7.36 +                                AutoStart="True"
    7.37 +                                GifSource="pack://application:,,,/pEp;component/Resources/AnimationWindowsLoading.gif" />
    7.38  
    7.39                  <!--The title text-->
    7.40 -                <TextBlock Grid.Column="1"
    7.41 -                           Grid.Row="0"                           
    7.42 +                    <TextBlock Grid.Column="1"
    7.43 +                           Grid.Row="0"
    7.44                             Foreground="White" 
    7.45                             FontWeight="Bold" 
    7.46                             Text="{Binding TitleText}" 
    7.47                             Margin="5,7,5,5"/>
    7.48  
    7.49 -                <!--The notification content-->
    7.50 -                <TextBlock Grid.Column="1"
    7.51 +                    <!--The notification content-->
    7.52 +                    <TextBlock Grid.Column="1"
    7.53                             Grid.Row="1"
    7.54                             TextWrapping="Wrap" 
    7.55                             Foreground="LightGray" 
    7.56 @@ -88,15 +108,14 @@
    7.57                      <Storyboard Completed="Storyboard_Completed">
    7.58                          <DoubleAnimation Storyboard.TargetName="NotificationWindow"
    7.59                                           Storyboard.TargetProperty="Left"
    7.60 -                                         From="{Binding AnimationStartX}" 
    7.61 -                                         To="{Binding AnimationEndX}"  
    7.62 -                                         Duration="0:0:0.5"                                        
    7.63 -                                         />
    7.64 +                                         From="{Binding AnimationLeftFromX}" 
    7.65 +                                         To="{Binding AnimationLeftToX}"  
    7.66 +                                         Duration="0:0:0.5"/>
    7.67                          <DoubleAnimation Name="OpacityAnimation"
    7.68                                           Storyboard.TargetName="NotificationWindow"
    7.69                                           Storyboard.TargetProperty="Opacity"
    7.70                                           From="1"
    7.71 -                                         To="0"
    7.72 +                                         To="{Binding AnimationOpacityTo}"
    7.73                                           BeginTime="0:0:2"
    7.74                                           Duration="0:0:4"/>
    7.75                      </Storyboard>
     8.1 --- a/UI/Notification.xaml.cs	Thu Apr 02 10:57:40 2020 +0200
     8.2 +++ b/UI/Notification.xaml.cs	Mon Apr 06 10:11:47 2020 +0200
     8.3 @@ -1,4 +1,5 @@
     8.4 -using System;
     8.5 +using pEp.UI.Views;
     8.6 +using System;
     8.7  using System.Windows;
     8.8  using System.Windows.Input;
     8.9  
    8.10 @@ -10,10 +11,22 @@
    8.11      public partial class Notification : Window
    8.12      {
    8.13          private const double offset = 10;
    8.14 -        public string TitleText { get; private set; }
    8.15 -        public string NotificationText { get; private set; }
    8.16 -        public double AnimationStartX { get; private set; }
    8.17 -        public double AnimationEndX { get; private set; }
    8.18 +        private bool _CloseAutomatically = true;
    8.19 +        public bool     AnimatedIcon        { get; private set; } = false;
    8.20 +        public double   AnimationLeftFromX  { get; private set; } = 0;
    8.21 +        public double   AnimationLeftToX    { get; private set; } = 0;
    8.22 +        public double   AnimationOpacityTo  { get; private set; } = 0;
    8.23 +        public bool     CloseAutomatically
    8.24 +        {
    8.25 +            get => this._CloseAutomatically;
    8.26 +            set 
    8.27 +            {
    8.28 +                this._CloseAutomatically = value;
    8.29 +                this.AnimationOpacityTo = this._CloseAutomatically ? 0 : 1;
    8.30 +            }
    8.31 +        }
    8.32 +        public string   NotificationText    { get; private set; } = null;
    8.33 +        public string   TitleText           { get; private set; } = null;
    8.34  
    8.35          /// <summary>
    8.36          /// The primary constructor.
    8.37 @@ -23,9 +36,9 @@
    8.38              InitializeComponent();
    8.39              this.DataContext = this;
    8.40  
    8.41 -            var desktopWorkingArea = SystemParameters.WorkArea;
    8.42 -            this.AnimationStartX = desktopWorkingArea.Right;
    8.43 -            this.AnimationEndX = desktopWorkingArea.Right - this.Width - Notification.offset;
    8.44 +            Rect desktopWorkingArea = SystemParameters.WorkArea;
    8.45 +            this.AnimationLeftFromX = desktopWorkingArea.Right;
    8.46 +            this.AnimationLeftToX = desktopWorkingArea.Right - this.Width - Notification.offset;
    8.47              this.Top = desktopWorkingArea.Bottom - this.Height - Notification.offset;
    8.48          }
    8.49  
    8.50 @@ -34,18 +47,33 @@
    8.51          /// </summary>
    8.52          /// <param name="title">The title of this notification.</param>
    8.53          /// <param name="text">The content of this notification.</param>
    8.54 -        public Notification(string title, string text) : this()
    8.55 +        public Notification(string title, string text, bool animatedIcon = false) : this()
    8.56          {
    8.57 +            this.AnimatedIcon = animatedIcon;
    8.58 +            this.NotificationText = text;
    8.59              this.TitleText = title;
    8.60 -            this.NotificationText = text;
    8.61 +        }
    8.62 +
    8.63 +        /// <summary>
    8.64 +        /// Marshals the closing call to the notification's thread and executes it.
    8.65 +        /// </summary>
    8.66 +        public void InvokeClosing()
    8.67 +        {
    8.68 +            this.Dispatcher.Invoke(() =>
    8.69 +            {
    8.70 +                this.Close();
    8.71 +            });
    8.72          }
    8.73  
    8.74          /// <summary>
    8.75          /// Event handler for when the notification's animation has finished.
    8.76          /// </summary>
    8.77          private void Storyboard_Completed(object sender, EventArgs e)
    8.78 -        {            
    8.79 -            this.Close();
    8.80 +        {
    8.81 +            if (this.CloseAutomatically)
    8.82 +            {
    8.83 +                this.Close();
    8.84 +            }
    8.85          }
    8.86  
    8.87          /// <summary>
    8.88 @@ -55,25 +83,25 @@
    8.89          {
    8.90              this.Close();
    8.91          }
    8.92 -
    8.93 +        
    8.94          /// <summary>
    8.95          /// Shows a notification with the given message.
    8.96          /// </summary>
    8.97          /// <param name="title">The title of the notification.</param>
    8.98          /// <param name="text">The content of the notification.</param>
    8.99 -        public static void Show(string title, string text)
   8.100 +        public static void Show(string title, string text, bool animatedIcon = false)
   8.101          {
   8.102              Log.Verbose("Notification.Show: Showing notification '{0}': {1}", title, text);
   8.103  
   8.104              // Create STA thread for WPF window
   8.105              try
   8.106              {
   8.107 -                Extensions.TaskExtensions.StartSTATask(() => new Notification(title, text)?.ShowDialog());
   8.108 +                Extensions.TaskExtensions.StartSTATask(() => new Notification(title, text, animatedIcon)?.ShowDialog());
   8.109              }
   8.110              catch (Exception ex)
   8.111              {
   8.112                  Log.Error("Notification.Show: Error showing notification. " + ex.ToString());
   8.113              }
   8.114 -        }        
   8.115 +        }
   8.116      }
   8.117  }
     9.1 --- a/pEpForOutlook.csproj	Thu Apr 02 10:57:40 2020 +0200
     9.2 +++ b/pEpForOutlook.csproj	Mon Apr 06 10:11:47 2020 +0200
     9.3 @@ -397,6 +397,7 @@
     9.4      <Compile Include="UI\CustomMessageBox.xaml.cs">
     9.5        <DependentUpon>CustomMessageBox.xaml</DependentUpon>
     9.6      </Compile>
     9.7 +    <Compile Include="UI\GifImage.cs" />
     9.8      <Compile Include="UI\InputMessageBox.xaml.cs">
     9.9        <DependentUpon>InputMessageBox.xaml</DependentUpon>
    9.10      </Compile>
    9.11 @@ -595,6 +596,7 @@
    9.12      <Resource Include="Resources\ImagePrivacyStatusGreenInvert.png" />
    9.13      <Resource Include="Resources\ImagePrivacyStatusNoColorInvert.png" />
    9.14      <Resource Include="Resources\ImagePrivacyStatusYellowInvert.png" />
    9.15 +    <Resource Include="Resources\AnimationWindowsLoading.gif" />
    9.16      <Content Include="Resources\ImageReaderSplash.png" />
    9.17      <Resource Include="Resources\ImageSyncAnimationAddToGroup1.png" />
    9.18      <Resource Include="Resources\ImageSyncAnimationAddToGroup2.png" />