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

thorstenalpers / CleanMyPosts / 15181658063

22 May 2025 08:20AM UTC coverage: 23.225% (+9.2%) from 14.069%
15181658063

push

github

thorstenalpers
Merge VersionInfo into Helper

86 of 356 branches covered (24.16%)

Branch coverage included in aggregate %.

3 of 3 new or added lines in 1 file covered. (100.0%)

102 existing lines in 7 files now uncovered.

277 of 1207 relevant lines covered (22.95%)

0.63 hits per line

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

57.69
/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
        {
UNCOV
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
            {
UNCOV
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
            }
UNCOV
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;
×
UNCOV
151

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

171
    [RelayCommand]
172
    private async Task ShowPosts()
173
    {
174
        EnableUserInteractions(false);
1✔
175
        await _xWebViewScriptService.ShowPostsAsync();
1✔
176
        EnableUserInteractions(true);
1✔
177
    }
1✔
178

179
    [RelayCommand]
180
    private async Task DeletePosts()
181
    {
182
        EnableUserInteractions(false, false);
1✔
183

184
        if (_userSettingsService.GetConfirmDeletion())
1!
185
        {
UNCOV
186
            _webViewHostService.Hide(true);
×
UNCOV
187
            var result = await _dialogCoordinator.ShowMessageAsync(
×
188
            this,
×
189
            "Confirm Deletion",
×
190
            "Are you sure you want to delete all posts?",
×
191
            MessageDialogStyle.AffirmativeAndNegative);
×
UNCOV
192
            _webViewHostService.Hide(false);
×
193

UNCOV
194
            if (result == MessageDialogResult.Affirmative)
×
195
            {
196
                var deletetCnt = await _xWebViewScriptService.DeletePostsAsync();
×
197
                await ShowNotificationAsync($"{deletetCnt} post(s) deleted successfully.", TimeSpan.FromSeconds(3));
×
198
            }
199
        }
200
        else
201
        {
202
            var deletetCnt = await _xWebViewScriptService.DeletePostsAsync();
1✔
203
            await ShowNotificationAsync($"{deletetCnt} post(s) deleted successfully.", TimeSpan.FromSeconds(3));
1✔
204
        }
205

206
        EnableUserInteractions(true);
1✔
207
    }
1✔
208

209
    private async Task ShowNotificationAsync(string msg, TimeSpan delay)
210
    {
211
        NotificationMessage = msg;
3✔
212
        IsNotificationOpen = true;
3✔
213
        await Task.Delay(delay);
3✔
214
        IsNotificationOpen = false;
3✔
215
    }
3✔
216

217
    [RelayCommand]
218
    private async Task ShowLikes()
219
    {
220
        EnableUserInteractions(false);
1✔
221
        await _xWebViewScriptService.ShowLikesAsync();
1✔
222
        EnableUserInteractions(true);
1✔
223
    }
1✔
224

225
    [RelayCommand]
226
    private async Task DeleteLikes()
227
    {
228
        EnableUserInteractions(false, false);
1✔
229

230
        if (_userSettingsService.GetConfirmDeletion())
1!
231
        {
232
            _webViewHostService.Hide(true);
×
UNCOV
233
            var result = await _dialogCoordinator.ShowMessageAsync(
×
234
            this,
×
235
            "Confirm Deletion",
×
UNCOV
236
            "Are you sure you want to delete all likes?",
×
237
            MessageDialogStyle.AffirmativeAndNegative);
×
UNCOV
238
            _webViewHostService.Hide(false);
×
239

240
            if (result == MessageDialogResult.Affirmative)
×
241
            {
UNCOV
242
                var deletetCnt = await _xWebViewScriptService.DeleteLikesAsync();
×
UNCOV
243
                await ShowNotificationAsync($"{deletetCnt} like(s) deleted successfully.", TimeSpan.FromSeconds(3));
×
244
            }
245
        }
246
        else
247
        {
248
            var deletetCnt = await _xWebViewScriptService.DeleteLikesAsync();
1✔
249
            await ShowNotificationAsync($"{deletetCnt} like(s) deleted successfully.", TimeSpan.FromSeconds(3));
1✔
250
        }
251
        EnableUserInteractions(true);
1✔
252
    }
1✔
253

254
    [RelayCommand]
255
    private async Task ShowFollowing()
256
    {
257
        EnableUserInteractions(false);
1✔
258
        await _xWebViewScriptService.ShowFollowingAsync();
1✔
259
        EnableUserInteractions(true);
1✔
260
    }
1✔
261

262
    [RelayCommand]
263
    private async Task DeleteFollowing()
264
    {
265
        EnableUserInteractions(false, false);
1✔
266

267
        if (_userSettingsService.GetConfirmDeletion())
1!
268
        {
269
            _webViewHostService.Hide(true);
×
270
            var result = await _dialogCoordinator.ShowMessageAsync(
×
271
                this,
×
UNCOV
272
                "Confirm Deletion",
×
UNCOV
273
                "Are you sure you want to delete all following?",
×
UNCOV
274
                MessageDialogStyle.AffirmativeAndNegative);
×
275
            _webViewHostService.Hide(false);
×
276

277
            if (result == MessageDialogResult.Affirmative)
×
278
            {
UNCOV
279
                var deletetCnt = await _xWebViewScriptService.DeleteFollowingAsync();
×
280
                await ShowNotificationAsync($"{deletetCnt} following(s) deleted successfully.", TimeSpan.FromSeconds(3));
×
281
            }
282
        }
283
        else
284
        {
285
            var deletetCnt = await _xWebViewScriptService.DeleteFollowingAsync();
1✔
286
            await ShowNotificationAsync($"{deletetCnt} following(s) deleted successfully.", TimeSpan.FromSeconds(3));
1✔
287
        }
288

289
        EnableUserInteractions(true);
1✔
290
    }
1✔
291

292
    private void EnableUserInteractions(bool enable, bool showOverlay = true)
293
    {
294
        if (enable)
12✔
295
        {
296
            IsWebViewEnabled = true;
6✔
297
            AreButtonsEnabled = true;
6✔
298
            if (_overlayWindow != null)
6✔
299
            {
300
                var mainWindow = Application.Current?.MainWindow;
3!
301
                if (mainWindow != null)
3!
302
                {
UNCOV
303
                    mainWindow.LocationChanged -= MainWindowOnLocationOrSizeChanged;
×
UNCOV
304
                    mainWindow.SizeChanged -= MainWindowOnLocationOrSizeChanged;
×
305
                }
306

307
                _overlayWindow.Close();
3✔
308
                _overlayWindow = null;
3✔
309
            }
310
        }
311
        else
312
        {
313
            IsWebViewEnabled = false;
6✔
314
            AreButtonsEnabled = false;
6✔
315

316
            if (showOverlay && _overlayWindow == null)
6✔
317
            {
318
                _overlayWindow = new OverlayWindow
3!
319
                {
3✔
320
                    WindowStartupLocation = WindowStartupLocation.Manual,
3✔
321
                    Owner = Application.Current?.MainWindow
3✔
322
                };
3✔
323
                UpdateOverlayPosition();
3✔
324
                var mainWindow = Application.Current?.MainWindow;
3!
325
                if (mainWindow != null)
3!
326
                {
UNCOV
327
                    mainWindow.LocationChanged += MainWindowOnLocationOrSizeChanged;
×
UNCOV
328
                    mainWindow.SizeChanged += MainWindowOnLocationOrSizeChanged;
×
329
                }
330
                _overlayWindow.Show();
3✔
331
            }
332
        }
333
    }
9✔
334

335
    private void MainWindowOnLocationOrSizeChanged(object sender, EventArgs e)
336
    {
UNCOV
337
        UpdateOverlayPosition();
×
UNCOV
338
    }
×
339

340
    private void UpdateOverlayPosition()
341
    {
342
        if (_overlayWindow == null)
3!
343
        {
UNCOV
344
            return;
×
345
        }
346

347
        var mainWindow = Application.Current?.MainWindow;
3!
348
        if (mainWindow == null)
3!
349
        {
350
            return;
3✔
351
        }
352

UNCOV
353
        var topLeft = mainWindow.PointToScreen(new Point(0, 0));
×
354

UNCOV
355
        var presentationSource = PresentationSource.FromVisual(mainWindow);
×
UNCOV
356
        if (presentationSource?.CompositionTarget != null)
×
357
        {
UNCOV
358
            var transform = presentationSource.CompositionTarget.TransformFromDevice;
×
UNCOV
359
            var topLeftInWpfUnits = transform.Transform(topLeft);
×
360

UNCOV
361
            _overlayWindow.Left = topLeftInWpfUnits.X;
×
UNCOV
362
            _overlayWindow.Top = topLeftInWpfUnits.Y;
×
UNCOV
363
            _overlayWindow.Width = mainWindow.ActualWidth;
×
UNCOV
364
            _overlayWindow.Height = mainWindow.ActualHeight;
×
365
        }
366
        else
367
        {
UNCOV
368
            _overlayWindow.Left = topLeft.X;
×
UNCOV
369
            _overlayWindow.Top = topLeft.Y;
×
UNCOV
370
            _overlayWindow.Width = mainWindow.ActualWidth;
×
UNCOV
371
            _overlayWindow.Height = mainWindow.ActualHeight;
×
372
        }
UNCOV
373
    }
×
374
}
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