1
1
using DynamicData ;
2
2
using Microsoft . Extensions . DependencyInjection ;
3
+ using Microsoft . Extensions . Logging ;
3
4
using Panzerfaust . Models ;
4
5
using ReactiveUI ;
5
6
using System ;
@@ -18,27 +19,122 @@ namespace Panzerfaust.ViewModels
18
19
{
19
20
internal class MainWindowViewModel : ViewModelBase
20
21
{
21
- public ObservableCollection < ProjectViewModel > Projects { get ; set ; } = new ( ) ;
22
+
23
+ // Field which keep updated with the user inputs in the search bar
24
+ private string searchText = string . Empty ;
25
+ public string SearchText
26
+ {
27
+ get => searchText ;
28
+ set => this . RaiseAndSetIfChanged ( ref searchText , value ) ;
29
+ }
30
+
31
+ // Use of DynamicData's SourceList for batch updates later on,
32
+ // reduces UI refreshes
33
+ private readonly SourceList < ProjectViewModel > projects = new ( ) ;
34
+ public ReadOnlyObservableCollection < ProjectViewModel > FilteredProjects { get ; set ; }
35
+
22
36
public ReactiveCommand < Unit , Unit > CreateProjectCommand { get ; }
23
37
public Interaction < ProjectWindowViewModel , ProjectViewModel ? > NewProjectDialog { get ; } = new ( ) ;
24
38
public Interaction < MessageBoxWindowViewModel , bool > DeleteProjectInteraction { get ; } = new ( ) ;
25
39
40
+
26
41
public MainWindowViewModel ( )
27
42
{
43
+ var filterPredicate = this . WhenAnyValue ( x => x . SearchText )
44
+ . Select ( searchText => CreateFilterPredicate ( searchText . Trim ( ) ) )
45
+ . DistinctUntilChanged ( ) ;
46
+
47
+ // The connect() method links the project list to the filtered view while
48
+ // bind() propagates the changes to FilteredProjects on the UI thread.
49
+ // Subscribe() is required to activate the "pipeline".
50
+ projects . Connect ( )
51
+ . Filter ( filterPredicate )
52
+ . Bind ( out var filteredProjects )
53
+ . Subscribe ( ) ;
54
+
55
+ FilteredProjects = filteredProjects ;
56
+
28
57
RxApp . MainThreadScheduler . Schedule ( LoadProjectsAsync ) ;
29
58
30
59
CreateProjectCommand = ReactiveCommand . CreateFromTask ( OnCreateProjectCommand ) ;
31
60
32
61
MessageBus . Current . Listen < ( string , ProjectViewModel ) > ( ) . Subscribe ( OnReceiveMessage ) ;
33
62
}
63
+ private static int [ ] BuildKMPTable ( string pattern )
64
+ {
65
+ int j = 0 ; // Position in the pattern
66
+
67
+ int m = pattern . Length ;
68
+ int [ ] lps = new int [ m ] ; // longest prefix which is also a suffix
69
+
70
+ lps [ 0 ] = 0 ;
71
+ for ( int i = 1 ; i < m ; i ++ )
72
+ {
73
+ while ( j > 0 && char . ToLower ( pattern [ i ] ) != char . ToLower ( pattern [ j ] ) )
74
+ {
75
+ j = lps [ j - 1 ] ;
76
+ }
77
+ if ( char . ToLower ( pattern [ i ] ) == char . ToLower ( pattern [ j ] ) )
78
+ {
79
+ j ++ ;
80
+ }
81
+ lps [ i ] = j ;
82
+ }
83
+ return lps ;
84
+ }
85
+
86
+ private static bool KMPSearch ( string searchText , string pattern )
87
+ {
88
+
89
+ int j ;
90
+ int [ ] lps ;
91
+
92
+ // Edge cases
93
+ if ( string . IsNullOrEmpty ( pattern ) )
94
+ return true ;
95
+
96
+ if ( string . IsNullOrEmpty ( searchText ) )
97
+ return false ;
98
+
99
+ if ( pattern . Length > searchText . Length )
100
+ return false ;
101
+
102
+ j = 0 ;
103
+ lps = BuildKMPTable ( pattern ) ;
104
+
105
+ for ( int i = 0 ; i < searchText . Length ; i ++ )
106
+ {
107
+ while ( j > 0 && char . ToLower ( searchText [ i ] ) != char . ToLower ( pattern [ j ] ) )
108
+ {
109
+ j = lps [ j - 1 ] ;
110
+ }
111
+ if ( char . ToLower ( searchText [ i ] ) == char . ToLower ( pattern [ j ] ) )
112
+ {
113
+ j ++ ;
114
+ }
115
+ if ( j == pattern . Length )
116
+ {
117
+ return true ;
118
+ }
119
+ }
120
+
121
+ return false ;
122
+ }
123
+
124
+ private Func < ProjectViewModel , bool > CreateFilterPredicate ( string searchTerm )
125
+ {
126
+ return string . IsNullOrWhiteSpace ( searchTerm )
127
+ ? _ => true
128
+ : p => KMPSearch ( p . Name , searchTerm ) ;
129
+ }
34
130
35
131
private void OnReceiveMessage ( ( string , ProjectViewModel ) message )
36
132
{
37
133
var ( action , data ) = message ;
38
134
39
135
if ( action == Message . DeleteAction )
40
136
{
41
- Projects . Remove ( data ) ;
137
+ projects . Remove ( data ) ;
42
138
}
43
139
}
44
140
@@ -49,7 +145,7 @@ private async Task OnCreateProjectCommand()
49
145
if ( result != null )
50
146
{
51
147
result . SetRemovalInteraction ( DeleteProjectInteraction ) ;
52
- Projects . Add ( result ) ;
148
+ projects . Add ( result ) ;
53
149
}
54
150
}
55
151
@@ -58,8 +154,14 @@ private async void LoadProjectsAsync()
58
154
var projectService = App . Current ? . ServiceProvider ? . GetService < Service . IProjectService > ( ) ;
59
155
if ( projectService == null ) return ;
60
156
61
- var projects = await projectService . LoadProjectsAsync ( ) ;
62
- Projects . AddRange ( projects . Select ( project => new ProjectViewModel ( project , DeleteProjectInteraction ) ) ) ;
157
+ var loadedProjects = await projectService . LoadProjectsAsync ( ) ;
158
+
159
+ projects . Edit ( innerList =>
160
+ {
161
+ innerList . AddRange (
162
+ loadedProjects . Select ( project => new ProjectViewModel ( project , DeleteProjectInteraction ) )
163
+ ) ;
164
+ } ) ;
63
165
}
64
166
}
65
167
}
0 commit comments