• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

thorstenalpers / CleanMyPosts / 15202427745

23 May 2025 04:37AM UTC coverage: 22.458% (+0.08%) from 22.379%
15202427745

push

github

thorstenalpers
Register ExceptionHandler only once in xaml

91 of 364 branches covered (25.0%)

Branch coverage included in aggregate %.

280 of 1288 relevant lines covered (21.74%)

0.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

58.65
/src/UI/ViewModels/XViewModel.cs
1
using System.Windows;
2
using CleanMyPosts.UI.Contracts.Services;
3
using CleanMyPosts.UI.Models;
4
using CleanMyPosts.UI.Views;
5
using CommunityToolkit.Mvvm.ComponentModel;
6
using CommunityToolkit.Mvvm.Input;
7
using MahApps.Metro.Controls.Dialogs;
8
using Microsoft.Extensions.Logging;
9
using Microsoft.Extensions.Options;
10
using Microsoft.Web.WebView2.Wpf;
11

12
namespace CleanMyPosts.UI.ViewModels;
13

14
public partial class XViewModel : ObservableObject
15
{
16
    private readonly IWebViewHostService _webViewHostService;
17
    private readonly ILogger<XViewModel> _logger;
18
    private readonly IXScriptService _xWebViewScriptService;
19
    private readonly IDialogCoordinator _dialogCoordinator;
20
    private readonly IUserSettingsService _userSettingsService;
21
    private OverlayWindow _overlayWindow;
22

23
    private readonly string _xBaseUrl;
24

25
    [ObservableProperty]
26
    private bool _areButtonsEnabled;
27

28
    [ObservableProperty]
29
    private bool _isNotificationOpen;
30

31
    [ObservableProperty]
32
    private string _notificationMessage;
33

34
    [ObservableProperty]
35
    private bool _isWebViewEnabled = true;
9✔
36

37
    private bool _isInitialized = false;
38
    private string _userName;
39

40
    public XViewModel(ILogger<XViewModel> logger,
9✔
41
                         IUserSettingsService userSettingsService,
9✔
42
                         IWebViewHostService webViewHostService,
9✔
43
                         IDialogCoordinator dialogCoordinator,
9✔
44
                         IOptions<AppConfig> options,
9✔
45
                         IXScriptService xWebViewScriptService)
9✔
46
    {
47
        _webViewHostService = webViewHostService ?? throw new ArgumentNullException(nameof(webViewHostService));
9!
48
        _userSettingsService = userSettingsService ?? throw new ArgumentNullException(nameof(userSettingsService));
9!
49
        _dialogCoordinator = dialogCoordinator ?? throw new ArgumentNullException(nameof(dialogCoordinator));
9!
50
        _xWebViewScriptService = xWebViewScriptService ?? throw new ArgumentNullException(nameof(xWebViewScriptService));
9!
51
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
9!
52

53
        _xBaseUrl = options.Value.XBaseUrl;
9✔
54
        _webViewHostService.NavigationCompleted += OnNavigationCompleted;
9✔
55
        _webViewHostService.WebMessageReceived += OnWebMessageReceived;
9✔
56
    }
9✔
57

58
    public async Task InitializeAsync(WebView2 webView)
59
    {
60
        if (_isInitialized)
2!
61
        {
62
            return;
×
63
        }
64

65
        await _webViewHostService.InitializeAsync(webView);
2✔
66

67
        _webViewHostService.Source = new Uri(_xBaseUrl);
2✔
68

69
        // logging JS errors and warnings to ILogger
70
        var jsScript = @"
2✔
71
                window.onerror = function(message, source, lineno, colno, error) {
2✔
72
                    chrome.webview.postMessage(JSON.stringify({
2✔
73
                        level: 'error',
2✔
74
                        message: `JS Error: ${message} at ${source}:${lineno}:${colno}`
2✔
75
                    }));
2✔
76
                };
2✔
77
            ";
2✔
78

79
        await _webViewHostService.ExecuteScriptAsync(jsScript);
2✔
80

81
        _isInitialized = true;
2✔
82
    }
2✔
83

84
    private async void OnNavigationCompleted(object sender, NavigationCompletedEventArgs e)
85
    {
86
        if (e.IsSuccess)
1!
87
        {
88
            if (!string.IsNullOrEmpty(_userName))
1!
89
            {
90
                return;
×
91
            }
92

93
            const int maxRetries = 5;
94
            const int delayMs = 500;
95

96
            var attempts = 0;
1✔
97
            while (attempts < maxRetries)
2!
98
            {
99
                _userName = await _xWebViewScriptService.GetUserNameAsync();
2✔
100
                if (!string.IsNullOrEmpty(_userName))
2✔
101
                {
102
                    _logger.LogInformation("User logged in.");
1✔
103
                    AreButtonsEnabled = true;
1✔
104
                    return;
1✔
105
                }
106
                attempts++;
1✔
107
                await Task.Delay(delayMs);
1✔
108
            }
109
            _logger.LogInformation("User not logged in.");
×
110
        }
111
    }
1✔
112

113
    private void OnWebMessageReceived(object sender, WebMessageReceivedEventArgs e)
114
    {
115
        try
116
        {
117
            var jsonDoc = System.Text.Json.JsonDocument.Parse(e.Message);
3✔
118
            var root = jsonDoc.RootElement;
3✔
119
            var level = root.GetProperty("level").GetString();
3✔
120
            var message = root.GetProperty("message").GetString();
3✔
121

122
            if (level == "error")
3✔
123
            {
124
                _logger.LogError("JS Error: {Message}", message);
1✔
125
            }
126
            else if (level == "warning")
2✔
127
            {
128
                _logger.LogWarning("JS Warning: {Message}", message);
1✔
129
            }
130
            else
131
            {
132
                _logger.LogInformation("JS: {Message}", message);
1✔
133
            }
134
        }
3✔
135
        catch (Exception ex)
×
136
        {
137
            _logger.LogWarning(ex, "Malformed JS message: {Raw}", e.Message);
×
138
        }
×
139
    }
3✔
140

141
    private EventHandler<NavigationCompletedEventArgs> IsUserLoggedInEventHandler()
142
    {
143
        return async (s, e) =>
×
144
        {
×
145
            if (e.IsSuccess)
×
146
            {
×
147
                var maxRetries = 5;
×
148
                var delayInMilliseconds = 500;
×
149
                var attempts = 0;
×
150
                string userName = null;
×
151

×
152
                while (attempts < maxRetries)
×
153
                {
×
154
                    userName = await _xWebViewScriptService.GetUserNameAsync();
×
155
                    if (!string.IsNullOrEmpty(userName))
×
156
                    {
×
157
                        AreButtonsEnabled = true;
×
158
                        _webViewHostService.NavigationCompleted -= IsUserLoggedInEventHandler();
×
159
                        return;
×
160
                    }
×
161
                    else
×
162
                    {
×
163
                        attempts++;
×
164
                        await Task.Delay(delayInMilliseconds);
×
165
                    }
×
166
                }
×
167
            }
×
168
        };
×
169
    }
170

171
    [RelayCommand]
172
    private async Task ShowPosts()
173
    {
174
        try
175
        {
176
            EnableUserInteractions(false);
1✔
177
            await _xWebViewScriptService.ShowPostsAsync();
1✔
178
        }
1✔
179
        finally
180
        {
181
            EnableUserInteractions(true);
1✔
182
        }
183
    }
1✔
184

185
    [RelayCommand]
186
    private async Task DeletePosts()
187
    {
188
        try
189
        {
190
            EnableUserInteractions(false, false);
1✔
191

192
            if (_userSettingsService.GetConfirmDeletion())
1!
193
            {
194
                _webViewHostService.Hide(true);
×
195
                var result = await _dialogCoordinator.ShowMessageAsync(
×
196
                this,
×
197
                "Confirm Deletion",
×
198
                "Are you sure you want to delete all posts?",
×
199
                MessageDialogStyle.AffirmativeAndNegative);
×
200
                _webViewHostService.Hide(false);
×
201

202
                if (result == MessageDialogResult.Affirmative)
×
203
                {
204
                    var deletetCnt = await _xWebViewScriptService.DeletePostsAsync();
×
205
                    await ShowNotificationAsync($"{deletetCnt} post(s) deleted successfully.", TimeSpan.FromSeconds(3));
×
206
                }
207
            }
208
            else
209
            {
210
                var deletetCnt = await _xWebViewScriptService.DeletePostsAsync();
1✔
211
                await ShowNotificationAsync($"{deletetCnt} post(s) deleted successfully.", TimeSpan.FromSeconds(3));
1✔
212
            }
213
        }
1✔
214
        finally
215
        {
216
            EnableUserInteractions(true);
1✔
217
        }
218
    }
1✔
219

220
    private async Task ShowNotificationAsync(string msg, TimeSpan delay)
221
    {
222
        NotificationMessage = msg;
3✔
223
        IsNotificationOpen = true;
3✔
224
        await Task.Delay(delay);
3✔
225
        IsNotificationOpen = false;
3✔
226
    }
3✔
227

228
    [RelayCommand]
229
    private async Task ShowLikes()
230
    {
231
        try
232
        {
233
            EnableUserInteractions(false);
1✔
234
            await _xWebViewScriptService.ShowLikesAsync();
1✔
235
        }
1✔
236
        finally
237
        {
238
            EnableUserInteractions(true);
1✔
239
        }
240
    }
1✔
241

242
    [RelayCommand]
243
    private async Task DeleteLikes()
244
    {
245
        try
246
        {
247
            EnableUserInteractions(false, false);
1✔
248
            if (_userSettingsService.GetConfirmDeletion())
1!
249
            {
250
                _webViewHostService.Hide(true);
×
251
                var result = await _dialogCoordinator.ShowMessageAsync(
×
252
                this,
×
253
                "Confirm Deletion",
×
254
                "Are you sure you want to delete all likes?",
×
255
                MessageDialogStyle.AffirmativeAndNegative);
×
256
                _webViewHostService.Hide(false);
×
257

258
                if (result == MessageDialogResult.Affirmative)
×
259
                {
260
                    var deletetCnt = await _xWebViewScriptService.DeleteLikesAsync();
×
261
                    await ShowNotificationAsync($"{deletetCnt} like(s) deleted successfully.", TimeSpan.FromSeconds(3));
×
262
                }
263
            }
264
            else
265
            {
266
                var deletetCnt = await _xWebViewScriptService.DeleteLikesAsync();
1✔
267
                await ShowNotificationAsync($"{deletetCnt} like(s) deleted successfully.", TimeSpan.FromSeconds(3));
1✔
268
            }
269
        }
1✔
270
        finally
271
        {
272
            EnableUserInteractions(true);
1✔
273
        }
274
    }
1✔
275

276
    [RelayCommand]
277
    private async Task ShowFollowing()
278
    {
279
        try
280
        {
281
            EnableUserInteractions(false);
1✔
282
            await _xWebViewScriptService.ShowFollowingAsync();
1✔
283
        }
1✔
284
        finally
285
        {
286
            EnableUserInteractions(true);
1✔
287
        }
288
    }
1✔
289

290
    [RelayCommand]
291
    private async Task DeleteFollowing()
292
    {
293
        try
294
        {
295
            EnableUserInteractions(false, false);
1✔
296

297
            if (_userSettingsService.GetConfirmDeletion())
1!
298
            {
299
                _webViewHostService.Hide(true);
×
300
                var result = await _dialogCoordinator.ShowMessageAsync(
×
301
                    this,
×
302
                    "Confirm Deletion",
×
303
                    "Are you sure you want to delete all following?",
×
304
                    MessageDialogStyle.AffirmativeAndNegative);
×
305
                _webViewHostService.Hide(false);
×
306

307
                if (result == MessageDialogResult.Affirmative)
×
308
                {
309
                    var deletetCnt = await _xWebViewScriptService.DeleteFollowingAsync();
×
310
                    await ShowNotificationAsync($"{deletetCnt} following(s) deleted successfully.", TimeSpan.FromSeconds(3));
×
311
                }
312
            }
313
            else
314
            {
315
                var deletetCnt = await _xWebViewScriptService.DeleteFollowingAsync();
1✔
316
                await ShowNotificationAsync($"{deletetCnt} following(s) deleted successfully.", TimeSpan.FromSeconds(3));
1✔
317
            }
318
        }
1✔
319
        finally
320
        {
321
            EnableUserInteractions(true);
1✔
322
        }
323
    }
1✔
324

325
    private void EnableUserInteractions(bool enable, bool showOverlay = true)
326
    {
327
        if (enable)
12✔
328
        {
329
            IsWebViewEnabled = true;
6✔
330
            AreButtonsEnabled = true;
6✔
331
            if (_overlayWindow != null)
6✔
332
            {
333
                var mainWindow = Application.Current?.MainWindow;
3!
334
                if (mainWindow != null)
3!
335
                {
336
                    mainWindow.LocationChanged -= MainWindowOnLocationOrSizeChanged;
×
337
                    mainWindow.SizeChanged -= MainWindowOnLocationOrSizeChanged;
×
338
                }
339

340
                _overlayWindow.Close();
3✔
341
                _overlayWindow = null;
3✔
342
            }
343
        }
344
        else
345
        {
346
            IsWebViewEnabled = false;
6✔
347
            AreButtonsEnabled = false;
6✔
348

349
            if (showOverlay && _overlayWindow == null)
6✔
350
            {
351
                _overlayWindow = new OverlayWindow
3!
352
                {
3✔
353
                    WindowStartupLocation = WindowStartupLocation.Manual,
3✔
354
                    Owner = Application.Current?.MainWindow
3✔
355
                };
3✔
356
                UpdateOverlayPosition();
3✔
357
                var mainWindow = Application.Current?.MainWindow;
3!
358
                if (mainWindow != null)
3!
359
                {
360
                    mainWindow.LocationChanged += MainWindowOnLocationOrSizeChanged;
×
361
                    mainWindow.SizeChanged += MainWindowOnLocationOrSizeChanged;
×
362
                }
363
                _overlayWindow.Show();
3✔
364
            }
365
        }
366
    }
9✔
367

368
    private void MainWindowOnLocationOrSizeChanged(object sender, EventArgs e)
369
    {
370
        UpdateOverlayPosition();
×
371
    }
×
372

373
    private void UpdateOverlayPosition()
374
    {
375
        if (_overlayWindow == null)
3!
376
        {
377
            return;
×
378
        }
379

380
        var mainWindow = Application.Current?.MainWindow;
3!
381
        if (mainWindow == null)
3!
382
        {
383
            return;
3✔
384
        }
385

386
        var topLeft = mainWindow.PointToScreen(new Point(0, 0));
×
387

388
        var presentationSource = PresentationSource.FromVisual(mainWindow);
×
389
        if (presentationSource?.CompositionTarget != null)
×
390
        {
391
            var transform = presentationSource.CompositionTarget.TransformFromDevice;
×
392
            var topLeftInWpfUnits = transform.Transform(topLeft);
×
393

394
            _overlayWindow.Left = topLeftInWpfUnits.X;
×
395
            _overlayWindow.Top = topLeftInWpfUnits.Y;
×
396
            _overlayWindow.Width = mainWindow.ActualWidth;
×
397
            _overlayWindow.Height = mainWindow.ActualHeight;
×
398
        }
399
        else
400
        {
401
            _overlayWindow.Left = topLeft.X;
×
402
            _overlayWindow.Top = topLeft.Y;
×
403
            _overlayWindow.Width = mainWindow.ActualWidth;
×
404
            _overlayWindow.Height = mainWindow.ActualHeight;
×
405
        }
406
    }
×
407
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc