├── .gitattributes
├── .gitignore
├── LICENSE
├── Logo.psd
├── README.md
├── SteamFriendsManager.sln
└── SteamFriendsManager
├── App.config
├── App.xaml
├── App.xaml.cs
├── Converter
├── BooleanToVisibilityConverter.cs
├── InverseBooleanConverter.cs
├── SteamFriendAvatarConverter.cs
├── SteamPersonaStateConverter.cs
└── SteamPersonaStateDisplayNameConverter.cs
├── Logo.ico
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Messages.cs
├── Page
├── FriendListPage.xaml
├── FriendListPage.xaml.cs
├── LoginPage.xaml
├── LoginPage.xaml.cs
├── WelcomePage.xaml
└── WelcomePage.xaml.cs
├── Resources
├── Entypo-license.txt
├── Entypo.ttf
├── Icons.xaml
├── IconsNonShared.xaml
└── WindowsIcons-license.txt
├── Service
├── ApplicationSettingsService.cs
└── SteamClientService.cs
├── SteamFriendsManager.csproj
├── Utility
├── AnimatedWrapPanel.cs
├── DragSelectionHelper.cs
├── Image.cs
├── PasswordBoxHelper.cs
├── TransitioningContentControl.cs
├── TransitioningContentControl.xaml
├── UriExtension.cs
└── VisualStates.cs
└── ViewModel
├── FriendListPageViewModel.cs
├── LoginPageViewModel.cs
├── MainWindowViewModel.cs
├── ViewModelLocator.cs
└── WelcomePageViewModel.cs
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # JustCode is a .NET coding add-in
131 | .JustCode
132 |
133 | # TeamCity is a build add-in
134 | _TeamCity*
135 |
136 | # DotCover is a Code Coverage Tool
137 | *.dotCover
138 |
139 | # AxoCover is a Code Coverage Tool
140 | .axoCover/*
141 | !.axoCover/settings.json
142 |
143 | # Visual Studio code coverage results
144 | *.coverage
145 | *.coveragexml
146 |
147 | # NCrunch
148 | _NCrunch_*
149 | .*crunch*.local.xml
150 | nCrunchTemp_*
151 |
152 | # MightyMoose
153 | *.mm.*
154 | AutoTest.Net/
155 |
156 | # Web workbench (sass)
157 | .sass-cache/
158 |
159 | # Installshield output folder
160 | [Ee]xpress/
161 |
162 | # DocProject is a documentation generator add-in
163 | DocProject/buildhelp/
164 | DocProject/Help/*.HxT
165 | DocProject/Help/*.HxC
166 | DocProject/Help/*.hhc
167 | DocProject/Help/*.hhk
168 | DocProject/Help/*.hhp
169 | DocProject/Help/Html2
170 | DocProject/Help/html
171 |
172 | # Click-Once directory
173 | publish/
174 |
175 | # Publish Web Output
176 | *.[Pp]ublish.xml
177 | *.azurePubxml
178 | # Note: Comment the next line if you want to checkin your web deploy settings,
179 | # but database connection strings (with potential passwords) will be unencrypted
180 | *.pubxml
181 | *.publishproj
182 |
183 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
184 | # checkin your Azure Web App publish settings, but sensitive information contained
185 | # in these scripts will be unencrypted
186 | PublishScripts/
187 |
188 | # NuGet Packages
189 | *.nupkg
190 | # NuGet Symbol Packages
191 | *.snupkg
192 | # The packages folder can be ignored because of Package Restore
193 | **/[Pp]ackages/*
194 | # except build/, which is used as an MSBuild target.
195 | !**/[Pp]ackages/build/
196 | # Uncomment if necessary however generally it will be regenerated when needed
197 | #!**/[Pp]ackages/repositories.config
198 | # NuGet v3's project.json files produces more ignorable files
199 | *.nuget.props
200 | *.nuget.targets
201 |
202 | # Microsoft Azure Build Output
203 | csx/
204 | *.build.csdef
205 |
206 | # Microsoft Azure Emulator
207 | ecf/
208 | rcf/
209 |
210 | # Windows Store app package directories and files
211 | AppPackages/
212 | BundleArtifacts/
213 | Package.StoreAssociation.xml
214 | _pkginfo.txt
215 | *.appx
216 | *.appxbundle
217 | *.appxupload
218 |
219 | # Visual Studio cache files
220 | # files ending in .cache can be ignored
221 | *.[Cc]ache
222 | # but keep track of directories ending in .cache
223 | !?*.[Cc]ache/
224 |
225 | # Others
226 | ClientBin/
227 | ~$*
228 | *~
229 | *.dbmdl
230 | *.dbproj.schemaview
231 | *.jfm
232 | *.pfx
233 | *.publishsettings
234 | orleans.codegen.cs
235 |
236 | # Including strong name files can present a security risk
237 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
238 | #*.snk
239 |
240 | # Since there are multiple workflows, uncomment next line to ignore bower_components
241 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
242 | #bower_components/
243 |
244 | # RIA/Silverlight projects
245 | Generated_Code/
246 |
247 | # Backup & report files from converting an old project file
248 | # to a newer Visual Studio version. Backup files are not needed,
249 | # because we have git ;-)
250 | _UpgradeReport_Files/
251 | Backup*/
252 | UpgradeLog*.XML
253 | UpgradeLog*.htm
254 | ServiceFabricBackup/
255 | *.rptproj.bak
256 |
257 | # SQL Server files
258 | *.mdf
259 | *.ldf
260 | *.ndf
261 |
262 | # Business Intelligence projects
263 | *.rdl.data
264 | *.bim.layout
265 | *.bim_*.settings
266 | *.rptproj.rsuser
267 | *- [Bb]ackup.rdl
268 | *- [Bb]ackup ([0-9]).rdl
269 | *- [Bb]ackup ([0-9][0-9]).rdl
270 |
271 | # Microsoft Fakes
272 | FakesAssemblies/
273 |
274 | # GhostDoc plugin setting file
275 | *.GhostDoc.xml
276 |
277 | # Node.js Tools for Visual Studio
278 | .ntvs_analysis.dat
279 | node_modules/
280 |
281 | # Visual Studio 6 build log
282 | *.plg
283 |
284 | # Visual Studio 6 workspace options file
285 | *.opt
286 |
287 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
288 | *.vbw
289 |
290 | # Visual Studio LightSwitch build output
291 | **/*.HTMLClient/GeneratedArtifacts
292 | **/*.DesktopClient/GeneratedArtifacts
293 | **/*.DesktopClient/ModelManifest.xml
294 | **/*.Server/GeneratedArtifacts
295 | **/*.Server/ModelManifest.xml
296 | _Pvt_Extensions
297 |
298 | # Paket dependency manager
299 | .paket/paket.exe
300 | paket-files/
301 |
302 | # FAKE - F# Make
303 | .fake/
304 |
305 | # CodeRush personal settings
306 | .cr/personal
307 |
308 | # Python Tools for Visual Studio (PTVS)
309 | __pycache__/
310 | *.pyc
311 |
312 | # Cake - Uncomment if you are using it
313 | # tools/**
314 | # !tools/packages.config
315 |
316 | # Tabs Studio
317 | *.tss
318 |
319 | # Telerik's JustMock configuration file
320 | *.jmconfig
321 |
322 | # BizTalk build output
323 | *.btp.cs
324 | *.btm.cs
325 | *.odx.cs
326 | *.xsd.cs
327 |
328 | # OpenCover UI analysis results
329 | OpenCover/
330 |
331 | # Azure Stream Analytics local run output
332 | ASALocalRun/
333 |
334 | # MSBuild Binary and Structured Log
335 | *.binlog
336 |
337 | # NVidia Nsight GPU debugger configuration file
338 | *.nvuser
339 |
340 | # MFractors (Xamarin productivity tool) working folder
341 | .mfractor/
342 |
343 | # Local History for Visual Studio
344 | .localhistory/
345 |
346 | # BeatPulse healthcheck temp database
347 | healthchecksdb
348 |
349 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
350 | MigrationBackup/
351 |
352 | # Ionide (cross platform F# VS Code tools) working folder
353 | .ionide/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014, Saiqi Jia
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | * Neither the name of Steam Friends Manager nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/Logo.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stackia/SteamFriendsManager/4613b6ada87fb38001c928e46eb5bc7fb70a977c/Logo.psd
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Steam Friends Manager [![Build Status][1]][2]
2 |
3 | Manage your Steam friends without the Steam client!
4 |
5 | ## Download
6 |
7 | [Click Here to Download the Latest Release][4]
8 |
9 | ## Screenshots
10 |
11 | (currently only available in Chinese)
12 |
13 | ![Welcome Page][5]
14 |
15 | ![Login Page][6]
16 |
17 | ![Friend List Page][7]
18 |
19 | ![Search][8]
20 |
21 | ![Add Friend][9]
22 |
23 | ![Send Message][10]
24 |
25 | ![Switch Persona State][11]
26 |
27 | ## License
28 |
29 | New BSD License
30 |
31 | > Copyright (c) 2014, Saiqi Jia
32 | > All rights reserved.
33 | >
34 | > Redistribution and use in source and binary forms, with or without
35 | > modification, are permitted provided that the following conditions are met:
36 | >
37 | > * Redistributions of source code must retain the above copyright notice, this
38 | > list of conditions and the following disclaimer.
39 | >
40 | > * Redistributions in binary form must reproduce the above copyright notice,
41 | > this list of conditions and the following disclaimer in the documentation
42 | > and/or other materials provided with the distribution.
43 | >
44 | > * Neither the name of Steam Friends Manager nor the names of its
45 | > contributors may be used to endorse or promote products derived from
46 | > this software without specific prior written permission.
47 | >
48 | > THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
49 | > AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
50 | > IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
51 | > DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
52 | > FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
53 | > DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
54 | > SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
55 | > CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
56 | > OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
57 | > OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
58 |
59 |
60 | [1]: https://ci.appveyor.com/api/projects/status/jc602cxpc3ook962?svg=true
61 | [2]: https://ci.appveyor.com/project/stackia/steamfriendsmanager
62 | [3]: http://www.microsoft.com/en-us/download/details.aspx?id=42642
63 | [4]: https://github.com/stackia/SteamFriendsManager/releases
64 | [5]: http://i.imgur.com/OfhD89B.png
65 | [6]: http://i.imgur.com/EaD7h7k.png
66 | [7]: http://i.imgur.com/LCf1mR4.png
67 | [8]: http://i.imgur.com/wZgS4zu.png
68 | [9]: http://i.imgur.com/lBEkFWZ.png
69 | [10]: http://i.imgur.com/Fp9uuXo.png
70 | [11]: http://i.imgur.com/Ma0LyQv.png
71 |
--------------------------------------------------------------------------------
/SteamFriendsManager.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29326.143
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{95D9A2FD-C649-488B-B77A-594535970B05}"
7 | ProjectSection(SolutionItems) = preProject
8 | LICENSE = LICENSE
9 | Logo.psd = Logo.psd
10 | README.md = README.md
11 | EndProjectSection
12 | EndProject
13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SteamFriendsManager", "SteamFriendsManager\SteamFriendsManager.csproj", "{73B74B92-57AE-4404-B9B7-91018701ECE9}"
14 | EndProject
15 | Global
16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
17 | Debug|Any CPU = Debug|Any CPU
18 | Release|Any CPU = Release|Any CPU
19 | EndGlobalSection
20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
21 | {73B74B92-57AE-4404-B9B7-91018701ECE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22 | {73B74B92-57AE-4404-B9B7-91018701ECE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
23 | {73B74B92-57AE-4404-B9B7-91018701ECE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
24 | {73B74B92-57AE-4404-B9B7-91018701ECE9}.Release|Any CPU.Build.0 = Release|Any CPU
25 | EndGlobalSection
26 | GlobalSection(SolutionProperties) = preSolution
27 | HideSolutionNode = FALSE
28 | EndGlobalSection
29 | GlobalSection(ExtensibilityGlobals) = postSolution
30 | SolutionGuid = {A26A9AB4-4530-40ED-95D7-D64FF52B5BC6}
31 | EndGlobalSection
32 | EndGlobal
33 |
--------------------------------------------------------------------------------
/SteamFriendsManager/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/SteamFriendsManager/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
15 |
16 |
17 |
18 | Segoe UI, Lucida Sans Unicode, Verdana, Microsoft JhengHei UI Light
19 | Segoe UI Light, Lucida Sans Unicode, Verdana, Microsoft JhengHei UI Light
20 | Segoe UI, Lucida Sans Unicode, Verdana, Microsoft JhengHei UI
21 |
25 |
26 |
27 |
28 |
29 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/SteamFriendsManager/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using GalaSoft.MvvmLight.Threading;
2 |
3 | namespace SteamFriendsManager
4 | {
5 | public partial class App
6 | {
7 | public App()
8 | {
9 | DispatcherHelper.Initialize();
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/Converter/BooleanToVisibilityConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows;
4 | using System.Windows.Data;
5 |
6 | namespace SteamFriendsManager.Converter
7 | {
8 | [ValueConversion(typeof(bool), typeof(Visibility))]
9 | public class BooleanToVisibilityConverter : IValueConverter
10 | {
11 | public object Convert(object value, Type targetType, object parameter,
12 | CultureInfo culture)
13 | {
14 | return (bool?) value ?? false ? Visibility.Visible : Visibility.Collapsed;
15 | }
16 |
17 | public object ConvertBack(object value, Type targetType, object parameter,
18 | CultureInfo culture)
19 | {
20 | return ((Visibility?) value ?? Visibility.Collapsed) == Visibility.Visible;
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/Converter/InverseBooleanConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows.Data;
4 |
5 | namespace SteamFriendsManager.Converter
6 | {
7 | [ValueConversion(typeof(bool), typeof(bool))]
8 | public class InverseBooleanConverter : IValueConverter
9 | {
10 | public object Convert(object value, Type targetType, object parameter,
11 | CultureInfo culture)
12 | {
13 | return !(bool?) value ?? true;
14 | }
15 |
16 | public object ConvertBack(object value, Type targetType, object parameter,
17 | CultureInfo culture)
18 | {
19 | return !(bool?) value ?? true;
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/Converter/SteamFriendAvatarConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows.Data;
4 |
5 | namespace SteamFriendsManager.Converter
6 | {
7 | [ValueConversion(typeof(byte[]), typeof(string))]
8 | public class SteamFriendAvatarConverter : IValueConverter
9 | {
10 | private const string DefaultAvatar =
11 | "http://cdn.akamai.steamstatic.com/steamcommunity/public/images/avatars/fe/fef49e7fa7e1997310d705b2a6158ff8dc1cdfeb_medium.jpg";
12 |
13 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
14 | {
15 | if (value == null)
16 | return DefaultAvatar;
17 |
18 | var avatarHash = BitConverter.ToString((byte[]) value).Replace("-", "").ToLower();
19 | return string.IsNullOrEmpty(avatarHash) || string.IsNullOrEmpty(avatarHash.Replace("0", ""))
20 | ? DefaultAvatar
21 | : $"http://cdn.akamai.steamstatic.com/steamcommunity/public/images/avatars/{avatarHash.Substring(0, 2)}/{avatarHash}_medium.jpg";
22 | }
23 |
24 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
25 | {
26 | throw new NotSupportedException();
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/Converter/SteamPersonaStateConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows.Data;
4 | using SteamKit2;
5 |
6 | namespace SteamFriendsManager.Converter
7 | {
8 | [ValueConversion(typeof(string), typeof(EPersonaState))]
9 | public class SteamPersonaStateConverter : IValueConverter
10 | {
11 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
12 | {
13 | if (!(value is string stateString))
14 | throw new ArgumentException("Cannot convert null value.");
15 |
16 | if (Enum.TryParse(stateString, true, out EPersonaState state))
17 | return state;
18 | throw new ArgumentException("Cannot convert this value.");
19 | }
20 |
21 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
22 | {
23 | if (!(value is EPersonaState state))
24 | throw new ArgumentException("Cannot convert back null value.");
25 |
26 | return ((EPersonaState?) state).ToString();
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/Converter/SteamPersonaStateDisplayNameConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows.Data;
4 | using SteamKit2;
5 |
6 | namespace SteamFriendsManager.Converter
7 | {
8 | [ValueConversion(typeof(EPersonaState), typeof(string))]
9 | public class SteamPersonaStateDisplayNameConverter : IValueConverter
10 | {
11 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
12 | {
13 | if (!(value is EPersonaState state))
14 | throw new ArgumentException("Cannot convert null value.");
15 |
16 | return state switch
17 | {
18 | EPersonaState.Offline => "离线",
19 | EPersonaState.Online => "在线",
20 | EPersonaState.Busy => "忙碌",
21 | EPersonaState.Away => "离开",
22 | EPersonaState.Snooze => "打盹",
23 | EPersonaState.LookingToTrade => "想交易",
24 | EPersonaState.LookingToPlay => "想玩游戏",
25 | EPersonaState.Invisible => "隐身",
26 | _ => state.ToString()
27 | };
28 | }
29 |
30 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
31 | {
32 | throw new NotSupportedException();
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/Logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stackia/SteamFriendsManager/4613b6ada87fb38001c928e46eb5bc7fb70a977c/SteamFriendsManager/Logo.ico
--------------------------------------------------------------------------------
/SteamFriendsManager/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
32 |
33 |
35 |
37 |
38 |
39 |
41 |
42 |
43 |
44 |
45 |
47 |
48 |
50 |
51 |
52 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
64 |
65 |
66 |
68 |
69 |
70 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
82 |
83 | True
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
98 |
108 |
109 |
--------------------------------------------------------------------------------
/SteamFriendsManager/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using GalaSoft.MvvmLight.Messaging;
2 | using GalaSoft.MvvmLight.Threading;
3 | using MahApps.Metro.Controls.Dialogs;
4 |
5 | namespace SteamFriendsManager
6 | {
7 | public partial class MainWindow
8 | {
9 | public MainWindow()
10 | {
11 | InitializeComponent();
12 |
13 | Messenger.Default.Register(this,
14 | msg =>
15 | {
16 | DispatcherHelper.CheckBeginInvokeOnUI(
17 | async () => { await this.ShowMessageAsync(msg.Title, msg.Message); });
18 | });
19 |
20 | Messenger.Default.Register(this,
21 | msg =>
22 | {
23 | DispatcherHelper.CheckBeginInvokeOnUI(
24 | async () => { msg.Execute(await this.ShowMessageAsync(msg.Title, msg.Message, msg.Style)); });
25 | });
26 |
27 | Messenger.Default.Register(this,
28 | msg =>
29 | {
30 | DispatcherHelper.CheckBeginInvokeOnUI(
31 | async () =>
32 | {
33 | if (msg.DefaultText != null)
34 | msg.Execute(await this.ShowInputAsync(msg.Title, msg.Message, new MetroDialogSettings
35 | {
36 | DefaultText = msg.DefaultText
37 | }));
38 | else
39 | msg.Execute(await this.ShowInputAsync(msg.Title, msg.Message));
40 | });
41 | });
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/Messages.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using GalaSoft.MvvmLight.Messaging;
3 | using MahApps.Metro.Controls.Dialogs;
4 | using SteamFriendsManager.Page;
5 |
6 | namespace SteamFriendsManager
7 | {
8 | internal class SwitchPageMessage : MessageBase
9 | {
10 | public enum Page
11 | {
12 | Login,
13 | Welcome,
14 | FriendList
15 | }
16 |
17 | public SwitchPageMessage(Page targetPage, bool clearPageHistory = false)
18 | {
19 | TargetPage = targetPage;
20 | ClearPageHistory = clearPageHistory;
21 | }
22 |
23 | public Page TargetPage { get; }
24 | public bool ClearPageHistory { get; }
25 |
26 | public static Type GetPageType(Page pageEnum)
27 | {
28 | return pageEnum switch
29 | {
30 | Page.Login => typeof(LoginPage),
31 | Page.Welcome => typeof(WelcomePage),
32 | Page.FriendList => typeof(FriendListPage),
33 | _ => throw new ArgumentOutOfRangeException(nameof(pageEnum))
34 | };
35 | }
36 | }
37 |
38 | internal class ClearPageHistoryMessage : MessageBase
39 | {
40 | }
41 |
42 | internal class ClearPageHistoryOnNextTryLoginMessage : MessageBase
43 | {
44 | }
45 |
46 | internal class LogoutOnNextTryLoginMessage : MessageBase
47 | {
48 | }
49 |
50 | internal class ReconnectFailedMessage : MessageBase
51 | {
52 | }
53 |
54 | internal interface IMessageDialogMessage
55 | {
56 | string Title { get; set; }
57 | string Message { get; set; }
58 | }
59 |
60 | internal class ShowMessageDialogMessage : IMessageDialogMessage
61 | {
62 | public ShowMessageDialogMessage(string title, string message)
63 | {
64 | Title = title;
65 | Message = message;
66 | }
67 |
68 | public string Title { get; set; }
69 | public string Message { get; set; }
70 | }
71 |
72 | internal class ShowMessageDialogMessageWithCallback : NotificationMessageAction,
73 | IMessageDialogMessage
74 | {
75 | public ShowMessageDialogMessageWithCallback(string title, string message, Action callback)
76 | : this(title, message, MessageDialogStyle.Affirmative, callback)
77 | {
78 | }
79 |
80 | public ShowMessageDialogMessageWithCallback(string title, string message, MessageDialogStyle style,
81 | Action callback)
82 | : base(null, callback)
83 | {
84 | Title = title;
85 | Message = message;
86 | Style = style;
87 | }
88 |
89 | public MessageDialogStyle Style { get; }
90 | public string Title { get; set; }
91 | public string Message { get; set; }
92 | }
93 |
94 | internal class ShowInputDialogMessage : NotificationMessageAction, IMessageDialogMessage
95 | {
96 | public ShowInputDialogMessage(string title, string message, Action callback) : base(null, callback)
97 | {
98 | Title = title;
99 | Message = message;
100 | }
101 |
102 | public ShowInputDialogMessage(string title, string message, string defaultText, Action callback)
103 | : base(null, callback)
104 | {
105 | Title = title;
106 | Message = message;
107 | DefaultText = defaultText;
108 | }
109 |
110 | public string DefaultText { get; }
111 | public string Title { get; set; }
112 | public string Message { get; set; }
113 | }
114 |
115 | internal class PersonaNameChangedMessage : MessageBase
116 | {
117 | }
118 |
119 | internal class PersonaStateChangedMessage : MessageBase
120 | {
121 | }
122 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/Page/FriendListPage.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
69 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
99 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
158 |
159 |
160 |
162 |
163 |
164 |
166 |
167 |
168 |
170 |
171 |
172 |
174 |
175 |
176 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
186 |
187 |
188 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
228 |
277 |
289 |
308 |
320 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
375 |
376 |
377 |
378 |
383 |
384 |
385 |
395 |
396 |
397 |
398 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
413 |
425 |
426 |
427 |
428 |
429 |
430 |
433 |
436 |
438 |
439 |
440 |
441 |
442 |
443 |
446 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
458 |
459 |
460 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
474 |
475 |
479 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
--------------------------------------------------------------------------------
/SteamFriendsManager/Page/FriendListPage.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace SteamFriendsManager.Page
2 | {
3 | ///
4 | /// Interaction logic for FriendListPage.xaml
5 | ///
6 | public partial class FriendListPage
7 | {
8 | public FriendListPage()
9 | {
10 | InitializeComponent();
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/Page/LoginPage.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
22 |
24 |
26 |
28 |
29 |
30 |
32 |
33 |
34 |
36 |
37 |
38 |
40 |
41 |
42 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
52 |
53 |
54 |
56 |
57 |
58 |
60 |
61 |
62 |
64 |
65 |
66 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
77 |
78 |
79 |
81 |
82 |
83 |
85 |
86 |
87 |
89 |
90 |
91 |
93 |
94 |
95 |
97 |
98 |
99 |
101 |
102 |
103 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
116 |
117 | True
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
145 | 记住用户名、密码
146 |
147 |
151 |
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/SteamFriendsManager/Page/LoginPage.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace SteamFriendsManager.Page
2 | {
3 | public partial class LoginPage
4 | {
5 | public LoginPage()
6 | {
7 | InitializeComponent();
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/Page/WelcomePage.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 | 特色功能:
24 |
25 |
26 | 快速添加好友
27 |
28 |
29 | 批量删除好友
30 |
31 |
32 | 向好友群发消息
33 |
34 |
35 | 修改 Steam 在线状态(在线、离开、想玩游戏、想交易、打盹、离线)
36 |
37 |
38 | 修改自己的昵称
39 |
40 |
41 | 作者:Stackia
42 |
43 | 新版发布及常见问题:
44 |
45 |
46 |
47 |
48 |
50 |
51 |
52 |
54 |
55 |
56 |
57 | 由于需要登录你的 Steam 帐号,请确保在安全的环境中运行此工具。
58 |
59 | 本软件不会收集任何用户信息。
60 |
61 |
62 |
63 |
67 |
68 |
--------------------------------------------------------------------------------
/SteamFriendsManager/Page/WelcomePage.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace SteamFriendsManager.Page
2 | {
3 | public partial class WelcomePage
4 | {
5 | public WelcomePage()
6 | {
7 | InitializeComponent();
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/Resources/Entypo-license.txt:
--------------------------------------------------------------------------------
1 | Entypo (http://www.entypo.com/) is created by Daniel Bruce and released under the Creative Commons, Share Alike/Attribution license.
2 |
3 | http://creativecommons.org/licenses/by-sa/3.0/
--------------------------------------------------------------------------------
/SteamFriendsManager/Resources/Entypo.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stackia/SteamFriendsManager/4613b6ada87fb38001c928e46eb5bc7fb70a977c/SteamFriendsManager/Resources/Entypo.ttf
--------------------------------------------------------------------------------
/SteamFriendsManager/Resources/WindowsIcons-license.txt:
--------------------------------------------------------------------------------
1 | # License
2 |
3 | Please carefully understand the license and download the latest icons at ModernUIIcons.com.
4 |
5 | ## Understand Your Rights
6 | No Attribution and No Derived Works
7 | http://creativecommons.org/licenses/by-nd/3.0/ *
8 |
9 | - If your project is open source include this license file in the source.
10 | - Nothing is needed in the front facing project (UNLESS you
11 | are using any of the icons listed below in the attribution section).
12 | - Commercial use is not only allowed but encouraged. If it is an icon
13 | in the attribution list below, you still need to attribute those!
14 | - Do not distribute the entire package (I've allowed this dozens of
15 | times for open source projects, but email me first).
16 |
17 | ## Creator
18 | - Austin Andrews (@templarian)
19 |
20 | ## Contributor**
21 | - Jay Zawrotny (@JayZawrotny)
22 | - A Bunch
23 | - Oren Nachman
24 | - appbar.chevron.down
25 | - appbar.chevron.up
26 | - appbar.chevron.left
27 | - appbar.chevron.right
28 |
29 | ## Derived Works
30 | - Alex Peattie
31 | - Social: http://www.alexpeattie.com/projects/justvector_icons/
32 |
33 | ## Attribution***
34 | - Kris Vandermotten (@kvandermotten)
35 | - appbar.medical.pulse
36 | - Constantin Kichinsky (@kichinsky)
37 | - appbar.currency.rubles
38 | - appbar.currency.grivna
39 | - Massimo Savazzi (@msavazzi)
40 | - List of missing exported icons
41 | - Proletkult Graphik, from The Noun Project
42 | - appbar.draw.pen (inspired)
43 | - Olivier Guin, from The Noun Project
44 | - appbar.draw.marker
45 | - Gibran Bisio, from The Noun Project
46 | - appbar.draw.bucket
47 | Andrew Forrester, from The Noun Project
48 | - appbar.fingerprint
49 |
50 | * The license is for attribution, but this is not required.
51 | ** Developers and designers that emailed Templarian the source .design icons to be added into the package. PNGs also accepted, but may take longer to be added.
52 | *** Icons I've copied so closely you want to attribute them and are also under the CC license.
53 |
54 | Contact
55 | - http://templarian.com/
56 | - admin[@]templarian[.]com
57 |
58 | * Does not apply to copyrighted logos
59 | - Skype
60 | - Facebook
61 | - Twitter
62 | - etc...
63 |
--------------------------------------------------------------------------------
/SteamFriendsManager/Service/ApplicationSettingsService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Threading.Tasks;
5 | using GalaSoft.MvvmLight;
6 | using Newtonsoft.Json;
7 |
8 | namespace SteamFriendsManager.Service
9 | {
10 | public class ApplicationSettingsService
11 | {
12 | private const string SettingsFileName = "settings.json";
13 | private readonly object _saveLock = new object();
14 |
15 | public ApplicationSettingsService()
16 | {
17 | if (ViewModelBase.IsInDesignModeStatic) return;
18 | Load();
19 | }
20 |
21 | public ApplicationSettings Settings { get; private set; }
22 |
23 | public void Save()
24 | {
25 | FileStream settingsFileStream = null;
26 | lock (_saveLock)
27 | {
28 | try
29 | {
30 | settingsFileStream = File.Open(Path.Combine(Environment.CurrentDirectory, SettingsFileName),
31 | FileMode.Create, FileAccess.Write);
32 | using var streamWriter = new StreamWriter(settingsFileStream);
33 | settingsFileStream = null;
34 |
35 | var jsonSerializer = new JsonSerializer();
36 | jsonSerializer.Serialize(streamWriter, Settings);
37 | }
38 | finally
39 | {
40 | settingsFileStream?.Dispose();
41 | }
42 | }
43 | }
44 |
45 | public Task SaveAsync()
46 | {
47 | return Task.Run(Save);
48 | }
49 |
50 | public void Load()
51 | {
52 | FileStream settingsFileStream = null;
53 | try
54 | {
55 | settingsFileStream = File.Open(Path.Combine(Environment.CurrentDirectory, SettingsFileName),
56 | FileMode.OpenOrCreate, FileAccess.Read);
57 | StreamReader streamReader = null;
58 | try
59 | {
60 | streamReader = new StreamReader(settingsFileStream);
61 | settingsFileStream = null;
62 | using var jsonTextReader = new JsonTextReader(streamReader);
63 | streamReader = null;
64 |
65 | var jsonSerializer = new JsonSerializer();
66 | Settings = jsonSerializer.Deserialize(jsonTextReader) ??
67 | new ApplicationSettings();
68 | }
69 | finally
70 | {
71 | streamReader?.Dispose();
72 | }
73 | }
74 | finally
75 | {
76 | settingsFileStream?.Dispose();
77 | }
78 | }
79 |
80 | public Task LoadAsync()
81 | {
82 | return Task.Run(Load);
83 | }
84 |
85 | public class ApplicationSettings
86 | {
87 | public bool ShouldRememberAccount { get; set; }
88 | public string LastUsername { get; set; }
89 | public string LastPassword { get; set; }
90 | public Dictionary SentryHashStore { get; set; }
91 | public List PreferredCmServers { get; set; }
92 | }
93 | }
94 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/Service/SteamClientService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.ComponentModel;
5 | using System.Globalization;
6 | using System.Linq;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 | using System.Windows.Media;
10 | using GalaSoft.MvvmLight.Messaging;
11 | using GalaSoft.MvvmLight.Threading;
12 | using SteamKit2;
13 | using SteamKit2.Discovery;
14 |
15 | namespace SteamFriendsManager.Service
16 | {
17 | public class SteamClientService : IDisposable
18 | {
19 | private readonly ApplicationSettingsService _applicationSettingsService;
20 | private readonly SteamClient _steamClient = new SteamClient();
21 | private readonly SteamFriends _steamFriends;
22 |
23 | private readonly SteamUser _steamUser;
24 |
25 | //private TaskCompletionSource _logoutTaskCompletionSource;
26 | private TaskCompletionSource _addFriendTaskCompletionSource;
27 | private CancellationTokenSource _callbackHandlerCancellationTokenSource;
28 | private TaskCompletionSource _connectTaskCompletionSource;
29 | private TaskCompletionSource _disconnectTaskCompletionSource;
30 | private bool _disposed;
31 | private SteamUser.LogOnDetails _lastLoginDetails;
32 | private TaskCompletionSource _loginTaskCompletionSource;
33 | private int _retryCount;
34 | private CancellationTokenSource _retryCountResetCancellationTokenSource;
35 | private TaskCompletionSource _setPersonaNameTaskCompletionSource;
36 | private TaskCompletionSource _setPersonaStateTaskCompletionSource;
37 |
38 | public SteamClientService(ApplicationSettingsService applicationSettingsService)
39 | {
40 | _applicationSettingsService = applicationSettingsService;
41 |
42 | _steamUser = _steamClient.GetHandler();
43 | _steamFriends = _steamClient.GetHandler();
44 | }
45 |
46 | public TimeSpan DefaultTimeout { get; set; } = TimeSpan.FromMilliseconds(10000);
47 | public bool ReconnectOnDisconnected { get; set; }
48 |
49 | public bool IsConnected => _steamClient.IsConnected;
50 |
51 | public ObservableCollection Friends { get; } = new ObservableCollection();
52 |
53 | public string PersonaName => _steamFriends.GetPersonaName();
54 |
55 | public EPersonaState PersonaState => _steamFriends.GetPersonaState();
56 |
57 | public void Dispose()
58 | {
59 | Dispose(true);
60 | GC.SuppressFinalize(this);
61 | }
62 |
63 | private void Dispose(bool disposing)
64 | {
65 | if (_disposed)
66 | return;
67 |
68 | if (disposing)
69 | {
70 | _callbackHandlerCancellationTokenSource.Dispose();
71 | _retryCountResetCancellationTokenSource.Dispose();
72 | }
73 |
74 | _disposed = true;
75 | }
76 |
77 | public void Start()
78 | {
79 | _callbackHandlerCancellationTokenSource = new CancellationTokenSource();
80 | Task.Run(() =>
81 | {
82 | var manager = new CallbackManager(_steamClient);
83 | manager.Subscribe(cb =>
84 | {
85 | if (_connectTaskCompletionSource != null && !_connectTaskCompletionSource.Task.IsCompleted)
86 | _connectTaskCompletionSource.TrySetResult(cb);
87 |
88 | // If the connection doesn't disconnect in 3 seconds, reset the retry count.
89 | _retryCountResetCancellationTokenSource = new CancellationTokenSource();
90 | Task.Delay(3000, _retryCountResetCancellationTokenSource.Token).ContinueWith(task =>
91 | {
92 | if (!task.IsCanceled)
93 | _retryCount = 0;
94 | });
95 | });
96 |
97 | manager.Subscribe(cb =>
98 | {
99 | if (_disconnectTaskCompletionSource != null && !_disconnectTaskCompletionSource.Task.IsCompleted)
100 | _disconnectTaskCompletionSource.TrySetResult(cb);
101 | else if (ReconnectOnDisconnected)
102 | TryReconnect();
103 | });
104 |
105 | manager.Subscribe(cb =>
106 | {
107 | if (_loginTaskCompletionSource != null && !_loginTaskCompletionSource.Task.IsCompleted)
108 | _loginTaskCompletionSource.TrySetResult(cb);
109 | });
110 |
111 | //manager.Subscribe(cb =>
112 | //{
113 | // if (_logoutTaskCompletionSource != null && !_logoutTaskCompletionSource.Task.IsCompleted)
114 | // _logoutTaskCompletionSource.TrySetResult(cb);
115 | //});
116 |
117 | manager.Subscribe(async cb =>
118 | {
119 | if (_applicationSettingsService.Settings.SentryHashStore == null)
120 | _applicationSettingsService.Settings.SentryHashStore = new Dictionary();
121 |
122 | var sentryHash = CryptoHelper.SHAHash(cb.Data);
123 | _applicationSettingsService.Settings.SentryHashStore[_lastLoginDetails.Username] = sentryHash;
124 | await _applicationSettingsService.SaveAsync();
125 |
126 | _steamUser.SendMachineAuthResponse(new SteamUser.MachineAuthDetails
127 | {
128 | BytesWritten = cb.BytesToWrite,
129 | FileName = cb.FileName,
130 | FileSize = cb.Data.Length,
131 | JobID = cb.JobID,
132 | LastError = 0,
133 | Offset = cb.Offset,
134 | OneTimePassword = cb.OneTimePassword,
135 | Result = EResult.OK,
136 | SentryFileHash = sentryHash
137 | });
138 | });
139 |
140 | manager.Subscribe(cb =>
141 | {
142 | if (cb.FriendID == _steamUser.SteamID)
143 | {
144 | if (_setPersonaStateTaskCompletionSource != null &&
145 | !_setPersonaStateTaskCompletionSource.Task.IsCompleted)
146 | _setPersonaStateTaskCompletionSource.TrySetResult(cb);
147 |
148 | return;
149 | }
150 |
151 | var query = from f in Friends where f.SteamId.Equals(cb.FriendID) select f;
152 | var friends = query as Friend[] ?? query.ToArray();
153 |
154 | if (!friends.Any())
155 | return;
156 |
157 | var friend = friends.First();
158 | friend.OnStateChanged();
159 | });
160 |
161 | manager.Subscribe(async cb =>
162 | {
163 | if (_applicationSettingsService.Settings.PreferredCmServers == null)
164 | _applicationSettingsService.Settings.PreferredCmServers = new List();
165 | _applicationSettingsService.Settings.PreferredCmServers.Clear();
166 | _applicationSettingsService.Settings.PreferredCmServers.AddRange(
167 | (from sv in cb.Servers select sv.EndPoint.ToString()).Take(8));
168 | await _applicationSettingsService.SaveAsync();
169 | });
170 |
171 | manager.Subscribe(cb =>
172 | {
173 | if (_setPersonaNameTaskCompletionSource != null &&
174 | !_setPersonaNameTaskCompletionSource.Task.IsCompleted)
175 | _setPersonaNameTaskCompletionSource.TrySetResult(cb);
176 | Messenger.Default.Send(new PersonaNameChangedMessage());
177 | });
178 |
179 | manager.Subscribe(cb =>
180 | {
181 | DispatcherHelper.CheckBeginInvokeOnUI(() =>
182 | {
183 | if (!cb.Incremental)
184 | Friends.Clear();
185 |
186 | foreach (var friend in from friendRaw in cb.FriendList
187 | where friendRaw.SteamID.IsIndividualAccount
188 | select new Friend(friendRaw.SteamID, _steamFriends))
189 | if (Friends.Contains(friend))
190 | {
191 | if (friend.Relationship == EFriendRelationship.None)
192 | Friends.Remove(friend);
193 | }
194 | else
195 | {
196 | Friends.Add(friend);
197 | }
198 | });
199 | });
200 |
201 | manager.Subscribe(cb =>
202 | {
203 | if (_addFriendTaskCompletionSource != null && !_addFriendTaskCompletionSource.Task.IsCompleted)
204 | _addFriendTaskCompletionSource.TrySetResult(cb);
205 | });
206 |
207 | while (!_callbackHandlerCancellationTokenSource.IsCancellationRequested)
208 | manager.RunWaitCallbacks(TimeSpan.FromMilliseconds(50));
209 | }, _callbackHandlerCancellationTokenSource.Token);
210 | }
211 |
212 | private void TryReconnect()
213 | {
214 | if (_retryCount < 3)
215 | {
216 | // Unexpectedly disconnect, try reconnect.
217 | if (_retryCountResetCancellationTokenSource != null &&
218 | !_retryCountResetCancellationTokenSource.IsCancellationRequested)
219 | _retryCountResetCancellationTokenSource.Cancel();
220 | Task.Run(async () =>
221 | {
222 | try
223 | {
224 | await ConnectAsync();
225 | if (_lastLoginDetails != null)
226 | await LoginAsync(_lastLoginDetails);
227 | }
228 | catch (TimeoutException)
229 | {
230 | if (ReconnectOnDisconnected)
231 | TryReconnect();
232 | }
233 | });
234 | _retryCount++;
235 | }
236 | else
237 | {
238 | ReconnectOnDisconnected = false;
239 | _retryCount = 0;
240 | Messenger.Default.Send(new ReconnectFailedMessage());
241 | }
242 | }
243 |
244 | public async Task StopAsync()
245 | {
246 | if (_steamClient.IsConnected)
247 | while (true)
248 | try
249 | {
250 | await LogoutAsync(); // Not sure if it is of any use to logout here
251 | await DisconnectAsync();
252 | break;
253 | }
254 | catch (TimeoutException)
255 | {
256 | }
257 |
258 | _callbackHandlerCancellationTokenSource.Cancel();
259 | }
260 |
261 | public Task ConnectAsync()
262 | {
263 | _connectTaskCompletionSource = new TaskCompletionSource();
264 | Task.Run(() =>
265 | {
266 | if (_applicationSettingsService.Settings.PreferredCmServers == null)
267 | {
268 | _steamClient.Connect();
269 | }
270 | else
271 | {
272 | var cmServer =
273 | _applicationSettingsService.Settings.PreferredCmServers[
274 | new Random().Next(0, _applicationSettingsService.Settings.PreferredCmServers.Count)];
275 |
276 | var ep = cmServer.Split(':');
277 | var host = ep[0];
278 | var port = int.Parse(ep[^1], NumberStyles.None, NumberFormatInfo.CurrentInfo);
279 | _steamClient.Connect(ServerRecord.CreateServer(host, port, ProtocolTypes.All));
280 | }
281 | });
282 | ThrowIfTimeout(_connectTaskCompletionSource);
283 | return _connectTaskCompletionSource.Task;
284 | }
285 |
286 | public Task LoginAsync(SteamUser.LogOnDetails logOnDetails)
287 | {
288 | _loginTaskCompletionSource = new TaskCompletionSource();
289 | Task.Run(() =>
290 | {
291 | _lastLoginDetails = logOnDetails;
292 | if (_applicationSettingsService.Settings.SentryHashStore != null &&
293 | _applicationSettingsService.Settings.SentryHashStore.ContainsKey(logOnDetails.Username))
294 | logOnDetails.SentryFileHash =
295 | _applicationSettingsService.Settings.SentryHashStore[logOnDetails.Username];
296 | _steamUser.LogOn(logOnDetails);
297 | });
298 | ThrowIfTimeout(_loginTaskCompletionSource);
299 | return _loginTaskCompletionSource.Task;
300 | }
301 |
302 | public Task SetPersonaNameAsync(string personaName)
303 | {
304 | _setPersonaNameTaskCompletionSource = new TaskCompletionSource();
305 | var oldName = PersonaName;
306 | Task.Run(() =>
307 | {
308 | _steamFriends.SetPersonaName(personaName);
309 | Messenger.Default.Send(new PersonaNameChangedMessage());
310 | });
311 | ThrowIfTimeout(_setPersonaNameTaskCompletionSource, () =>
312 | {
313 | Task.Run(() =>
314 | {
315 | _steamFriends.SetPersonaName(oldName);
316 | Messenger.Default.Send(new PersonaNameChangedMessage());
317 | });
318 | });
319 | return _setPersonaNameTaskCompletionSource.Task;
320 | }
321 |
322 | public Task SetPersonaStateAsync(EPersonaState state)
323 | {
324 | _setPersonaStateTaskCompletionSource = new TaskCompletionSource();
325 | var oldState = PersonaState;
326 | Task.Run(() =>
327 | {
328 | _steamFriends.SetPersonaState(state);
329 | Messenger.Default.Send(new PersonaStateChangedMessage());
330 | });
331 | ThrowIfTimeout(_setPersonaStateTaskCompletionSource, () =>
332 | {
333 | Task.Run(() =>
334 | {
335 | _steamFriends.SetPersonaState(oldState);
336 | Messenger.Default.Send(new PersonaStateChangedMessage());
337 | });
338 | });
339 | return _setPersonaStateTaskCompletionSource.Task;
340 | }
341 |
342 | public Task SendChatMessageAsync(SteamID target, EChatEntryType type, string message)
343 | {
344 | var taskCompletionSource = new TaskCompletionSource();
345 | Task.Run(() =>
346 | {
347 | _steamFriends.SendChatMessage(target, type, message);
348 | if (!taskCompletionSource.Task.IsCompleted)
349 | taskCompletionSource.TrySetResult(true);
350 | });
351 | ThrowIfTimeout(taskCompletionSource);
352 | return taskCompletionSource.Task;
353 | }
354 |
355 | public Task RemoveFriendAsync(SteamID target)
356 | {
357 | var taskCompletionSource = new TaskCompletionSource();
358 | Task.Run(() =>
359 | {
360 | _steamFriends.RemoveFriend(target);
361 | if (!taskCompletionSource.Task.IsCompleted)
362 | taskCompletionSource.TrySetResult(true);
363 | });
364 | ThrowIfTimeout(taskCompletionSource);
365 | return taskCompletionSource.Task;
366 | }
367 |
368 | public Task AddFriendAsync(SteamID target)
369 | {
370 | _addFriendTaskCompletionSource = new TaskCompletionSource();
371 | Task.Run(() => { _steamFriends.AddFriend(target); });
372 | ThrowIfTimeout(_addFriendTaskCompletionSource);
373 | return _addFriendTaskCompletionSource.Task;
374 | }
375 |
376 | public Task AddFriendAsync(string targetAccountNameOrEmail)
377 | {
378 | _addFriendTaskCompletionSource = new TaskCompletionSource();
379 | Task.Run(() => { _steamFriends.AddFriend(targetAccountNameOrEmail); });
380 | ThrowIfTimeout(_addFriendTaskCompletionSource);
381 | return _addFriendTaskCompletionSource.Task;
382 | }
383 |
384 | public void Logout()
385 | {
386 | //LogoutAsync().Wait();
387 | _steamUser.LogOff();
388 | }
389 |
390 | // The LoggedOffCallback doesn't work. We cannot get any response on this operation.
391 | public Task LogoutAsync()
392 | {
393 | //_logoutTaskCompletionSource = new TaskCompletionSource();
394 | //Task.Run(() => _steamUser.LogOff());
395 | //ThrowIfTimeout(_logoutTaskCompletionSource);
396 | //return _logoutTaskCompletionSource.Task;
397 | return Task.Run(Logout);
398 | }
399 |
400 | public Task DisconnectAsync()
401 | {
402 | _disconnectTaskCompletionSource = new TaskCompletionSource();
403 | Task.Run(() => _steamClient.Disconnect());
404 | ThrowIfTimeout(_disconnectTaskCompletionSource);
405 | return _disconnectTaskCompletionSource.Task;
406 | }
407 |
408 | private void ThrowIfTimeout(object taskCompletionSource, Action timeoutAction = null)
409 | {
410 | var cts = new CancellationTokenSource(DefaultTimeout);
411 | cts.Token.Register(() =>
412 | {
413 | var trySetExceptionMethod = taskCompletionSource.GetType()
414 | .GetMethod("TrySetException", new[] {typeof(Exception)});
415 | if (!(taskCompletionSource.GetType().GetProperty("Task")
416 | ?.GetValue(taskCompletionSource) is Task task) || trySetExceptionMethod == null ||
417 | task.IsCompleted) return;
418 | timeoutAction?.Invoke();
419 | trySetExceptionMethod.Invoke(taskCompletionSource,
420 | new object[] {new TimeoutException(taskCompletionSource.GetType().Name)});
421 | });
422 | }
423 |
424 | public class Friend : INotifyPropertyChanged
425 | {
426 | private readonly SteamFriends _steamFriends;
427 | private bool _visible;
428 |
429 | public Friend(SteamID steamId, SteamFriends steamFriends)
430 | {
431 | SteamId = steamId;
432 | _steamFriends = steamFriends;
433 | Visible = true;
434 | }
435 |
436 | public SteamID SteamId { get; }
437 |
438 | public byte[] Avatar => _steamFriends.GetFriendAvatar(SteamId);
439 |
440 | public GameID GamePlayed => _steamFriends.GetFriendGamePlayed(SteamId);
441 |
442 | public string GamePlayedName => _steamFriends.GetFriendGamePlayedName(SteamId);
443 |
444 | public string PersonaName => _steamFriends.GetFriendPersonaName(SteamId);
445 |
446 | public EPersonaState PersonaState => _steamFriends.GetFriendPersonaState(SteamId);
447 |
448 | public Brush PersonaStateColor
449 | {
450 | get
451 | {
452 | return PersonaState switch
453 | {
454 | EPersonaState.Online => new SolidColorBrush(Color.FromRgb(115, 209, 245)),
455 | EPersonaState.LookingToTrade => new SolidColorBrush(Color.FromRgb(115, 209, 245)),
456 | EPersonaState.LookingToPlay => new SolidColorBrush(Color.FromRgb(115, 209, 245)),
457 | EPersonaState.Away => new SolidColorBrush(Color.FromRgb(66, 103, 119)),
458 | EPersonaState.Snooze => new SolidColorBrush(Color.FromRgb(66, 103, 119)),
459 | EPersonaState.Busy => new SolidColorBrush(Color.FromRgb(66, 103, 119)),
460 | _ => new SolidColorBrush(Color.FromRgb(255, 255, 255))
461 | };
462 | }
463 | }
464 |
465 | public EFriendRelationship Relationship => _steamFriends.GetFriendRelationship(SteamId);
466 |
467 | public bool Visible
468 | {
469 | get => _visible;
470 | set
471 | {
472 | if (_visible == value)
473 | return;
474 |
475 | _visible = value;
476 | OnPropertyChanged(nameof(Visible));
477 | }
478 | }
479 |
480 | public event PropertyChangedEventHandler PropertyChanged;
481 |
482 | private void OnPropertyChanged(string propertyName = null)
483 | {
484 | var handler = PropertyChanged;
485 | handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
486 | }
487 |
488 | public void OnStateChanged()
489 | {
490 | OnPropertyChanged(nameof(Avatar));
491 | OnPropertyChanged(nameof(GamePlayed));
492 | OnPropertyChanged(nameof(GamePlayedName));
493 | OnPropertyChanged(nameof(PersonaName));
494 | OnPropertyChanged(nameof(PersonaState));
495 | OnPropertyChanged(nameof(Relationship));
496 | OnPropertyChanged(nameof(Visible));
497 | OnPropertyChanged(nameof(PersonaStateColor));
498 | }
499 |
500 | public override bool Equals(object obj)
501 | {
502 | if (!(obj is Friend target))
503 | return false;
504 |
505 | return SteamId == target.SteamId;
506 | }
507 |
508 | public bool Equals(Friend friend)
509 | {
510 | if ((object) friend == null)
511 | return false;
512 |
513 | return SteamId == friend.SteamId;
514 | }
515 |
516 | public override int GetHashCode()
517 | {
518 | return SteamId.GetHashCode();
519 | }
520 |
521 | public static bool operator ==(Friend a, Friend b)
522 | {
523 | if (ReferenceEquals(a, b))
524 | return true;
525 |
526 | if (a is null || b is null)
527 | return false;
528 |
529 | return a.SteamId == b.SteamId;
530 | }
531 |
532 | public static bool operator !=(Friend a, Friend b)
533 | {
534 | return !(a == b);
535 | }
536 | }
537 | }
538 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/SteamFriendsManager.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | netcoreapp3.0
6 | true
7 | Logo.ico
8 | Manage your Steam friends without the Steam client.
9 | Steam Friends Manager
10 | Copyright © Stackia 2014
11 | SteamCN
12 | Stackia
13 | 2.0.0
14 | SteamFriendsManager
15 | https://steamcn.com/t117808-1-1
16 | https://github.com/stackia/SteamFriendsManager
17 | zh-CN
18 | win-x86
19 | true
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | Designer
49 | MSBuild:Compile
50 |
51 |
52 |
53 |
54 |
55 | Code
56 | App.xaml
57 |
58 |
59 | Code
60 | MainWindow.xaml
61 |
62 |
63 | FriendListPage.xaml
64 |
65 |
66 | LoginPage.xaml
67 |
68 |
69 | WelcomePage.xaml
70 |
71 |
72 |
73 |
74 |
75 | Designer
76 | MSBuild:Compile
77 |
78 |
79 | Designer
80 |
81 |
82 | Designer
83 |
84 |
85 | Designer
86 |
87 |
88 | Designer
89 |
90 |
91 | Designer
92 |
93 |
94 | Designer
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/SteamFriendsManager/Utility/AnimatedWrapPanel.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Controls;
3 | using System.Windows.Media;
4 |
5 | namespace SteamFriendsManager.Utility
6 | {
7 | public class AnimatedWrapPanel : Panel
8 | {
9 | //public static readonly DependencyProperty AnimationDurationProperty =
10 | // DependencyProperty.Register("AnimationDuration",
11 | // typeof (int), typeof (AnimatedWrapPanel), new PropertyMetadata(700));
12 |
13 | public static readonly DependencyProperty ItemGapProperty = DependencyProperty.Register("ItemGap",
14 | typeof(int), typeof(AnimatedWrapPanel), new PropertyMetadata(10));
15 |
16 | //private TimeSpan _animationLength = TimeSpan.FromMilliseconds(200);
17 | //private bool _firstArrangement = true;
18 |
19 | //public int AnimationDuration
20 | //{
21 | // get { return (int) GetValue(AnimationDurationProperty); }
22 | // set
23 | // {
24 | // _animationLength = TimeSpan.FromMilliseconds(value);
25 | // SetValue(AnimationDurationProperty, value);
26 | // }
27 | //}
28 |
29 | public int ItemGap
30 | {
31 | get => (int) GetValue(ItemGapProperty);
32 | set => SetValue(ItemGapProperty, value);
33 | }
34 |
35 | protected override Size MeasureOverride(Size availableSize)
36 | {
37 | var infiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
38 | double curX = 0, curY = 0, curLineHeight = 0;
39 | foreach (UIElement child in Children)
40 | {
41 | child.Measure(infiniteSize);
42 |
43 | double gapX = child.DesiredSize.Width > 0 ? ItemGap : 0;
44 | double gapY = child.DesiredSize.Height > 0 ? ItemGap : 0;
45 | if (curX + child.DesiredSize.Width > availableSize.Width)
46 | {
47 | //Wrap to next line
48 | curY += curLineHeight + gapY;
49 | curX = 0;
50 | curLineHeight = 0;
51 | }
52 |
53 | curX += child.DesiredSize.Width + gapX;
54 | if (child.DesiredSize.Height > curLineHeight)
55 | curLineHeight = child.DesiredSize.Height;
56 | }
57 |
58 | curX -= ItemGap;
59 | curY += curLineHeight;
60 |
61 | var resultSize = new Size
62 | {
63 | Width = double.IsPositiveInfinity(availableSize.Width)
64 | ? curX
65 | : availableSize.Width,
66 | Height = double.IsPositiveInfinity(availableSize.Height)
67 | ? curY
68 | : availableSize.Height
69 | };
70 |
71 | return resultSize;
72 | }
73 |
74 | protected override Size ArrangeOverride(Size finalSize)
75 | {
76 | if (Children.Count == 0)
77 | return finalSize;
78 |
79 | double curX = 0, curY = 0, curLineHeight = 0;
80 |
81 | foreach (UIElement child in Children)
82 | {
83 | double gapX = child.DesiredSize.Width > 0 ? ItemGap : 0;
84 | double gapY = child.DesiredSize.Height > 0 ? ItemGap : 0;
85 | var trans = child.RenderTransform as TranslateTransform;
86 | if (trans == null)
87 | {
88 | child.RenderTransformOrigin = new Point(0, 0);
89 | trans = new TranslateTransform();
90 | child.RenderTransform = trans;
91 | }
92 |
93 | if (curX + child.DesiredSize.Width > finalSize.Width)
94 | {
95 | //Wrap to next line
96 | curY += curLineHeight + gapY;
97 | curX = 0;
98 | curLineHeight = 0;
99 | }
100 |
101 | child.Arrange(new Rect(curX, curY, child.DesiredSize.Width,
102 | child.DesiredSize.Height));
103 |
104 | /* Temporarily disable animation due to bad performance. */
105 |
106 | //child.Arrange(new Rect(0, 0, child.DesiredSize.Width,
107 | // child.DesiredSize.Height));
108 |
109 | //if (!_firstArrangement && Math.Abs(trans.X - curX) > 0.01)
110 | //{
111 | // trans.BeginAnimation(TranslateTransform.XProperty,
112 | // new DoubleAnimation(curX, _animationLength)
113 | // {
114 | // EasingFunction = new CubicEase
115 | // {
116 | // EasingMode = EasingMode.EaseInOut
117 | // }
118 | // }, HandoffBehavior.SnapshotAndReplace);
119 | //}
120 | //else if (_firstArrangement)
121 | // trans.X = curX;
122 |
123 | //if (!_firstArrangement && Math.Abs(trans.Y - curY) > 0.01)
124 | //{
125 | // trans.BeginAnimation(TranslateTransform.YProperty,
126 | // new DoubleAnimation(curY, _animationLength)
127 | // {
128 | // EasingFunction = new CubicEase
129 | // {
130 | // EasingMode = EasingMode.EaseInOut
131 | // }
132 | // }, HandoffBehavior.SnapshotAndReplace);
133 | //}
134 | //else if (_firstArrangement)
135 | // trans.Y = curY;
136 |
137 | curX += child.DesiredSize.Width + gapX;
138 | if (child.DesiredSize.Height > curLineHeight)
139 | curLineHeight = child.DesiredSize.Height;
140 | }
141 |
142 | //if (_firstArrangement)
143 | // _firstArrangement = false;
144 |
145 | return finalSize;
146 | }
147 | }
148 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/Utility/DragSelectionHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Controls;
3 | using System.Windows.Controls.Primitives;
4 | using System.Windows.Input;
5 | using System.Windows.Media;
6 |
7 | namespace SteamFriendsManager.Utility
8 | {
9 | public class DragSelectionHelper : DependencyObject
10 | {
11 | #region IsDragSelectionEnabledProperty
12 |
13 | public static bool GetIsDragSelectionEnabled(DependencyObject obj)
14 | {
15 | return (bool) obj.GetValue(IsDragSelectionEnabledProperty);
16 | }
17 |
18 | public static void SetIsDragSelectionEnabled(DependencyObject obj, bool value)
19 | {
20 | obj.SetValue(IsDragSelectionEnabledProperty, value);
21 | }
22 |
23 | public static readonly DependencyProperty IsDragSelectionEnabledProperty =
24 | DependencyProperty.RegisterAttached("IsDragSelectingEnabled", typeof(bool), typeof(DragSelectionHelper),
25 | new UIPropertyMetadata(false, IsDragSelectingEnabledPropertyChanged));
26 |
27 | private static void IsDragSelectingEnabledPropertyChanged(DependencyObject o,
28 | DependencyPropertyChangedEventArgs e)
29 | {
30 | var listBox = o as ListBox;
31 |
32 | if (listBox == null)
33 | return;
34 |
35 | // if DragSelection is enabled
36 | if (GetIsDragSelectionEnabled(listBox))
37 | {
38 | // set the listbox's selection mode to multiple ( didn't work with extended )
39 | listBox.SelectionMode = SelectionMode.Multiple;
40 |
41 | // and subscribe to the required events to handle the drag selection and the attached properties
42 | listBox.PreviewMouseRightButtonDown += listBox_PreviewMouseRightButtonDown;
43 |
44 | listBox.PreviewMouseLeftButtonDown += listBox_PreviewMouseLeftButtonDown;
45 | listBox.PreviewMouseLeftButtonUp += listBox_PreviewMouseLeftButtonUp;
46 |
47 | listBox.PreviewKeyDown += listBox_PreviewKeyDown;
48 | listBox.PreviewKeyUp += listBox_PreviewKeyUp;
49 | }
50 | else // is selection is disabled
51 | {
52 | // set selection mode to the default
53 | listBox.SelectionMode = SelectionMode.Extended;
54 |
55 | // unsuscribe from the events
56 | listBox.PreviewMouseRightButtonDown -= listBox_PreviewMouseRightButtonDown;
57 |
58 | listBox.PreviewMouseLeftButtonDown -= listBox_PreviewMouseLeftButtonDown;
59 | listBox.PreviewMouseLeftButtonUp -= listBox_PreviewMouseLeftButtonUp;
60 |
61 | listBox.PreviewKeyDown -= listBox_PreviewKeyDown;
62 | listBox.PreviewKeyUp += listBox_PreviewKeyUp;
63 | }
64 | }
65 |
66 | private static void listBox_PreviewKeyDown(object sender, KeyEventArgs e)
67 | {
68 | var listBox = sender as ListBox;
69 | if (listBox == null)
70 | return;
71 |
72 | if (e.Key == Key.LeftShift || e.Key == Key.RightShift)
73 | SetIsDragSelectionEnabled(listBox, false);
74 | }
75 |
76 | private static void listBox_PreviewKeyUp(object sender, KeyEventArgs e)
77 | {
78 | var listBox = sender as ListBox;
79 | if (listBox == null)
80 | return;
81 |
82 | if (e.Key == Key.LeftShift || e.Key == Key.RightShift)
83 | SetIsDragSelectionEnabled(listBox, true);
84 | }
85 |
86 | private static void listBox_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
87 | {
88 | // to prevent the listbox from selecting / deselecting wells on right click
89 | e.Handled = true;
90 | }
91 |
92 | private static void listBox_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
93 | {
94 | SetIsDragClickStarted(sender as DependencyObject, true);
95 | }
96 |
97 | private static void listBox_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
98 | {
99 | SetIsDragClickStarted(sender as DependencyObject, false);
100 | }
101 |
102 | public static DependencyObject GetParent(DependencyObject obj)
103 | {
104 | if (obj == null)
105 | return null;
106 |
107 | var ce = obj as ContentElement;
108 | if (ce == null) return VisualTreeHelper.GetParent(obj);
109 |
110 | var parent = ContentOperations.GetParent(ce);
111 | if (parent != null)
112 | return parent;
113 |
114 | var fce = ce as FrameworkContentElement;
115 | return fce != null ? fce.Parent : null;
116 | }
117 |
118 | #endregion IsDragSelectionEnabledProperty
119 |
120 | #region IsDragSelectingProperty
121 |
122 | public static bool GetIsDragSelecting(DependencyObject obj)
123 | {
124 | return (bool) obj.GetValue(IsDragSelectingProperty);
125 | }
126 |
127 | public static void SetIsDragSelecting(DependencyObject obj, bool value)
128 | {
129 | obj.SetValue(IsDragSelectingProperty, value);
130 | }
131 |
132 | public static readonly DependencyProperty IsDragSelectingProperty =
133 | DependencyProperty.RegisterAttached("IsDragSelecting", typeof(bool), typeof(DragSelectionHelper),
134 | new UIPropertyMetadata(false, IsDragSelectingPropertyChanged));
135 |
136 | private static void IsDragSelectingPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
137 | {
138 | var listBoxItem = o as ListBoxItem;
139 |
140 | if (listBoxItem == null)
141 | return;
142 |
143 | if (!GetIsDragClickStarted(listBoxItem)) return;
144 |
145 | if (GetIsDragSelecting(listBoxItem))
146 | listBoxItem.IsSelected = true;
147 | }
148 |
149 | #endregion IsDragSelectingProperty
150 |
151 | #region IsDragClickStartedProperty
152 |
153 | public static bool GetIsDragClickStarted(DependencyObject obj)
154 | {
155 | return (bool) obj.GetValue(IsDragClickStartedProperty);
156 | }
157 |
158 | public static void SetIsDragClickStarted(DependencyObject obj, bool value)
159 | {
160 | obj.SetValue(IsDragClickStartedProperty, value);
161 | }
162 |
163 | public static readonly DependencyProperty IsDragClickStartedProperty =
164 | DependencyProperty.RegisterAttached("IsDragClickStarted", typeof(bool), typeof(DragSelectionHelper),
165 | new FrameworkPropertyMetadata(false, IsDragClickStartedPropertyChanged) {Inherits = true});
166 |
167 | private static void IsDragClickStartedPropertyChanged(DependencyObject obj,
168 | DependencyPropertyChangedEventArgs e)
169 | {
170 | var listBox = obj as ListBox;
171 |
172 | if (listBox == null)
173 | return;
174 |
175 | if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
176 | return;
177 |
178 | var hitTestResult = VisualTreeHelper.HitTest(listBox, Mouse.GetPosition(listBox));
179 | if (hitTestResult == null)
180 | return;
181 |
182 | var element = hitTestResult.VisualHit;
183 | while (element != null)
184 | {
185 | var scrollBar = element as ScrollBar;
186 | if (scrollBar != null)
187 | return;
188 | element = VisualTreeHelper.GetParent(element);
189 | }
190 |
191 | if (GetIsDragClickStarted(listBox))
192 | listBox.SelectedItems.Clear();
193 | }
194 |
195 | #endregion IsDragClickInitiatedProperty
196 | }
197 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/Utility/Image.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Cache;
3 | using System.Windows;
4 | using System.Windows.Media.Imaging;
5 |
6 | namespace SteamFriendsManager.Utility
7 | {
8 | public class Image : System.Windows.Controls.Image
9 | {
10 | public static readonly DependencyProperty ImageUrlProperty = DependencyProperty.Register("ImageUrl",
11 | typeof(string), typeof(Image), new PropertyMetadata("", ImageUrlPropertyChanged));
12 |
13 | static Image()
14 | {
15 | DefaultStyleKeyProperty.OverrideMetadata(typeof(Image),
16 | new FrameworkPropertyMetadata(typeof(Image)));
17 | }
18 |
19 | public string ImageUrl
20 | {
21 | get => (string) GetValue(ImageUrlProperty);
22 | set => SetValue(ImageUrlProperty, value);
23 | }
24 |
25 | private static void ImageUrlPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
26 | {
27 | var url = e.NewValue as string;
28 |
29 | if (string.IsNullOrEmpty(url))
30 | return;
31 |
32 | var cachedImage = (Image) obj;
33 | var bitmapImage = new BitmapImage();
34 | bitmapImage.BeginInit();
35 | bitmapImage.UriSource = new Uri(url);
36 | bitmapImage.UriCachePolicy = new RequestCachePolicy(RequestCacheLevel.Default);
37 | bitmapImage.EndInit();
38 | cachedImage.Source = bitmapImage;
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/Utility/PasswordBoxHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Controls;
3 |
4 | namespace SteamFriendsManager.Utility
5 | {
6 | internal static class PasswordBoxHelper
7 | {
8 | public static readonly DependencyProperty PasswordProperty =
9 | DependencyProperty.RegisterAttached("Password",
10 | typeof(string), typeof(PasswordBoxHelper),
11 | new FrameworkPropertyMetadata(string.Empty, OnPasswordPropertyChanged));
12 |
13 | public static readonly DependencyProperty AttachProperty =
14 | DependencyProperty.RegisterAttached("Attach",
15 | typeof(bool), typeof(PasswordBoxHelper), new PropertyMetadata(false, Attach));
16 |
17 | private static readonly DependencyProperty IsUpdatingProperty =
18 | DependencyProperty.RegisterAttached("IsUpdating", typeof(bool),
19 | typeof(PasswordBoxHelper));
20 |
21 | public static void SetAttach(DependencyObject dp, bool value)
22 | {
23 | dp.SetValue(AttachProperty, value);
24 | }
25 |
26 | public static bool GetAttach(DependencyObject dp)
27 | {
28 | return (bool) dp.GetValue(AttachProperty);
29 | }
30 |
31 | public static string GetPassword(DependencyObject dp)
32 | {
33 | return (string) dp.GetValue(PasswordProperty);
34 | }
35 |
36 | public static void SetPassword(DependencyObject dp, string value)
37 | {
38 | dp.SetValue(PasswordProperty, value);
39 | }
40 |
41 | private static bool GetIsUpdating(DependencyObject dp)
42 | {
43 | return (bool) dp.GetValue(IsUpdatingProperty);
44 | }
45 |
46 | private static void SetIsUpdating(DependencyObject dp, bool value)
47 | {
48 | dp.SetValue(IsUpdatingProperty, value);
49 | }
50 |
51 | private static void OnPasswordPropertyChanged(DependencyObject sender,
52 | DependencyPropertyChangedEventArgs e)
53 | {
54 | var passwordBox = sender as PasswordBox;
55 | if (passwordBox != null)
56 | {
57 | passwordBox.PasswordChanged -= PasswordChanged;
58 |
59 | if (!GetIsUpdating(passwordBox))
60 | passwordBox.Password = (string) e.NewValue;
61 | passwordBox.PasswordChanged += PasswordChanged;
62 | }
63 | }
64 |
65 | private static void Attach(DependencyObject sender,
66 | DependencyPropertyChangedEventArgs e)
67 | {
68 | var passwordBox = sender as PasswordBox;
69 |
70 | if (passwordBox == null)
71 | return;
72 |
73 | if ((bool) e.OldValue)
74 | passwordBox.PasswordChanged -= PasswordChanged;
75 |
76 | if ((bool) e.NewValue)
77 | passwordBox.PasswordChanged += PasswordChanged;
78 | }
79 |
80 | private static void PasswordChanged(object sender, RoutedEventArgs e)
81 | {
82 | var passwordBox = sender as PasswordBox;
83 | if (passwordBox != null)
84 | {
85 | SetIsUpdating(passwordBox, true);
86 | SetPassword(passwordBox, passwordBox.Password);
87 | SetIsUpdating(passwordBox, false);
88 | }
89 | }
90 | }
91 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/Utility/TransitioningContentControl.cs:
--------------------------------------------------------------------------------
1 | // (c) Copyright Microsoft Corporation.
2 | // This source is subject to the Microsoft Public License (Ms-PL).
3 | // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
4 | // All other rights reserved.
5 |
6 | using System;
7 | using System.Collections.ObjectModel;
8 | using System.Diagnostics.CodeAnalysis;
9 | using System.Globalization;
10 | using System.Linq;
11 | using System.Windows;
12 | using System.Windows.Controls;
13 | using System.Windows.Media.Animation;
14 |
15 | namespace SteamFriendsManager.Utility
16 | {
17 | ///
18 | /// enumeration for the different transition types
19 | ///
20 | public enum TransitionType
21 | {
22 | ///
23 | /// Use the VisualState DefaultTransition
24 | ///
25 | Default,
26 |
27 | ///
28 | /// Use the VisualState Normal
29 | ///
30 | Normal,
31 |
32 | ///
33 | /// Use the VisualState UpTransition
34 | ///
35 | Up,
36 |
37 | ///
38 | /// Use the VisualState DownTransition
39 | ///
40 | Down,
41 |
42 | ///
43 | /// Use the VisualState RightTransition
44 | ///
45 | Right,
46 |
47 | ///
48 | /// Use the VisualState RightReplaceTransition
49 | ///
50 | RightReplace,
51 |
52 | ///
53 | /// Use the VisualState LeftTransition
54 | ///
55 | Left,
56 |
57 | ///
58 | /// Use the VisualState LeftReplaceTransition
59 | ///
60 | LeftReplace,
61 |
62 | ///
63 | /// Use a custom VisualState, the name must be set using CustomVisualStatesName property
64 | ///
65 | Custom
66 | }
67 |
68 | ///
69 | /// A ContentControl that animates content as it loads and unloads.
70 | ///
71 | public class TransitioningContentControl : ContentControl
72 | {
73 | private const string PresentationGroup = "PresentationStates";
74 | private const string NormalState = "Normal";
75 | private const string PreviousContentPresentationSitePartName = "PreviousContentPresentationSite";
76 | private const string CurrentContentPresentationSitePartName = "CurrentContentPresentationSite";
77 | public const TransitionType DefaultTransitionState = TransitionType.Default;
78 |
79 | public static readonly DependencyProperty IsTransitioningProperty =
80 | DependencyProperty.Register("IsTransitioning", typeof(bool), typeof(TransitioningContentControl),
81 | new PropertyMetadata(OnIsTransitioningPropertyChanged));
82 |
83 | public static readonly DependencyProperty TransitionProperty = DependencyProperty.Register("Transition",
84 | typeof(TransitionType), typeof(TransitioningContentControl),
85 | new FrameworkPropertyMetadata(TransitionType.Default,
86 | FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.Inherits,
87 | OnTransitionPropertyChanged));
88 |
89 | public static readonly DependencyProperty RestartTransitionOnContentChangeProperty =
90 | DependencyProperty.Register("RestartTransitionOnContentChange", typeof(bool),
91 | typeof(TransitioningContentControl),
92 | new PropertyMetadata(false, OnRestartTransitionOnContentChangePropertyChanged));
93 |
94 | public static readonly DependencyProperty CustomVisualStatesProperty =
95 | DependencyProperty.Register("CustomVisualStates", typeof(ObservableCollection),
96 | typeof(TransitioningContentControl), new PropertyMetadata(null));
97 |
98 | public static readonly DependencyProperty CustomVisualStatesNameProperty =
99 | DependencyProperty.Register("CustomVisualStatesName", typeof(string), typeof(TransitioningContentControl),
100 | new PropertyMetadata("CustomTransition"));
101 |
102 | private bool _allowIsTransitioningWrite;
103 | private Storyboard _currentTransition;
104 |
105 | public TransitioningContentControl()
106 | {
107 | CustomVisualStates = new ObservableCollection();
108 | DefaultStyleKey = typeof(TransitioningContentControl);
109 | PreviewMouseDown += (sender, args) => { args.Handled = IsTransitioning; };
110 | PreviewKeyDown += (sender, args) => { args.Handled = IsTransitioning; };
111 | }
112 |
113 | private ContentPresenter CurrentContentPresentationSite { get; set; }
114 | private ContentPresenter PreviousContentPresentationSite { get; set; }
115 |
116 | public ObservableCollection CustomVisualStates
117 | {
118 | get => (ObservableCollection) GetValue(CustomVisualStatesProperty);
119 | set => SetValue(CustomVisualStatesProperty, value);
120 | }
121 |
122 | ///
123 | /// Gets or sets the name of the custom transition visual state.
124 | ///
125 | public string CustomVisualStatesName
126 | {
127 | get => (string) GetValue(CustomVisualStatesNameProperty);
128 | set => SetValue(CustomVisualStatesNameProperty, value);
129 | }
130 |
131 | ///
132 | /// Gets/sets if the content is transitioning.
133 | ///
134 | public bool IsTransitioning
135 | {
136 | get => (bool) GetValue(IsTransitioningProperty);
137 | private set
138 | {
139 | _allowIsTransitioningWrite = true;
140 | SetValue(IsTransitioningProperty, value);
141 | _allowIsTransitioningWrite = false;
142 | }
143 | }
144 |
145 | public TransitionType Transition
146 | {
147 | get => (TransitionType) GetValue(TransitionProperty);
148 | set => SetValue(TransitionProperty, value);
149 | }
150 |
151 | public bool RestartTransitionOnContentChange
152 | {
153 | get => (bool) GetValue(RestartTransitionOnContentChangeProperty);
154 | set => SetValue(RestartTransitionOnContentChangeProperty, value);
155 | }
156 |
157 | private Storyboard CurrentTransition
158 | {
159 | get => _currentTransition;
160 | set
161 | {
162 | // decouple event
163 | if (_currentTransition != null)
164 | _currentTransition.Completed -= OnTransitionCompleted;
165 |
166 | _currentTransition = value;
167 |
168 | if (_currentTransition != null)
169 | _currentTransition.Completed += OnTransitionCompleted;
170 | }
171 | }
172 |
173 | public event RoutedEventHandler TransitionCompleted;
174 |
175 | private static void OnIsTransitioningPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
176 | {
177 | var source = (TransitioningContentControl) d;
178 |
179 | if (!source._allowIsTransitioningWrite)
180 | {
181 | source.IsTransitioning = (bool) e.OldValue;
182 | throw new InvalidOperationException();
183 | }
184 | }
185 |
186 | private static void OnTransitionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
187 | {
188 | var source = (TransitioningContentControl) d;
189 | var oldTransition = (TransitionType) e.OldValue;
190 | var newTransition = (TransitionType) e.NewValue;
191 |
192 | if (source.IsTransitioning)
193 | source.AbortTransition();
194 |
195 | // find new transition
196 | var newStoryboard = source.GetStoryboard(newTransition);
197 |
198 | // unable to find the transition.
199 | if (newStoryboard == null)
200 | {
201 | // could be during initialization of xaml that presentationgroups was not yet defined
202 | if (VisualStates.TryGetVisualStateGroup(source, PresentationGroup) == null)
203 | {
204 | // will delay check
205 | source.CurrentTransition = null;
206 | }
207 | else
208 | {
209 | // revert to old value
210 | source.SetValue(TransitionProperty, oldTransition);
211 |
212 | throw new ArgumentException(
213 | string.Format(CultureInfo.CurrentCulture, "Temporary removed exception message"));
214 | }
215 | }
216 | else
217 | {
218 | source.CurrentTransition = newStoryboard;
219 | }
220 | }
221 |
222 | private static void OnRestartTransitionOnContentChangePropertyChanged(DependencyObject d,
223 | DependencyPropertyChangedEventArgs e)
224 | {
225 | ((TransitioningContentControl) d).OnRestartTransitionOnContentChangeChanged((bool) e.OldValue,
226 | (bool) e.NewValue);
227 | }
228 |
229 | protected virtual void OnRestartTransitionOnContentChangeChanged(bool oldValue, bool newValue)
230 | {
231 | }
232 |
233 | public override void OnApplyTemplate()
234 | {
235 | if (IsTransitioning)
236 | AbortTransition();
237 |
238 | if (CustomVisualStates != null && CustomVisualStates.Any())
239 | {
240 | var presentationGroup = VisualStates.TryGetVisualStateGroup(this, PresentationGroup);
241 | if (presentationGroup != null)
242 | foreach (var state in CustomVisualStates)
243 | presentationGroup.States.Add(state);
244 | }
245 |
246 | base.OnApplyTemplate();
247 |
248 | PreviousContentPresentationSite =
249 | GetTemplateChild(PreviousContentPresentationSitePartName) as ContentPresenter;
250 | CurrentContentPresentationSite =
251 | GetTemplateChild(CurrentContentPresentationSitePartName) as ContentPresenter;
252 |
253 | if (CurrentContentPresentationSite != null)
254 | {
255 | if (ContentTemplateSelector != null)
256 | CurrentContentPresentationSite.ContentTemplate = ContentTemplateSelector.SelectTemplate(Content,
257 | this);
258 | else
259 | CurrentContentPresentationSite.ContentTemplate = ContentTemplate;
260 |
261 | CurrentContentPresentationSite.Content = Content;
262 | }
263 |
264 | // hookup currenttransition
265 | var transition = GetStoryboard(Transition);
266 | CurrentTransition = transition;
267 | if (transition == null)
268 | {
269 | var invalidTransition = Transition;
270 | // revert to default
271 | Transition = DefaultTransitionState;
272 |
273 | throw new Exception($"'{invalidTransition}' Transition could not be found!");
274 | }
275 |
276 | VisualStateManager.GoToState(this, NormalState, false);
277 | }
278 |
279 | protected override void OnContentChanged(object oldContent, object newContent)
280 | {
281 | base.OnContentChanged(oldContent, newContent);
282 |
283 | StartTransition(oldContent, newContent);
284 | }
285 |
286 | [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "newContent",
287 | Justification = "Should be used in the future.")]
288 | private void StartTransition(object oldContent, object newContent)
289 | {
290 | // both presenters must be available, otherwise a transition is useless.
291 | if (CurrentContentPresentationSite != null && PreviousContentPresentationSite != null)
292 | {
293 | if (RestartTransitionOnContentChange)
294 | CurrentTransition.Completed -= OnTransitionCompleted;
295 |
296 | if (ContentTemplateSelector != null)
297 | {
298 | PreviousContentPresentationSite.ContentTemplate = ContentTemplateSelector.SelectTemplate(
299 | oldContent, this);
300 | CurrentContentPresentationSite.ContentTemplate = ContentTemplateSelector.SelectTemplate(newContent,
301 | this);
302 | }
303 |
304 | CurrentContentPresentationSite.Content = newContent;
305 | PreviousContentPresentationSite.Content = oldContent;
306 |
307 | // and start a new transition
308 | if (!IsTransitioning || RestartTransitionOnContentChange)
309 | {
310 | if (RestartTransitionOnContentChange)
311 | CurrentTransition.Completed += OnTransitionCompleted;
312 | IsTransitioning = true;
313 | VisualStateManager.GoToState(this, NormalState, false);
314 | VisualStateManager.GoToState(this, GetTransitionName(Transition), true);
315 | }
316 | }
317 | }
318 |
319 | ///
320 | /// Reload the current transition if the content is the same.
321 | ///
322 | public void ReloadTransition()
323 | {
324 | // both presenters must be available, otherwise a transition is useless.
325 | if (CurrentContentPresentationSite != null && PreviousContentPresentationSite != null)
326 | {
327 | if (RestartTransitionOnContentChange)
328 | CurrentTransition.Completed -= OnTransitionCompleted;
329 | if (!IsTransitioning || RestartTransitionOnContentChange)
330 | {
331 | if (RestartTransitionOnContentChange)
332 | CurrentTransition.Completed += OnTransitionCompleted;
333 | IsTransitioning = true;
334 | VisualStateManager.GoToState(this, NormalState, false);
335 | VisualStateManager.GoToState(this, GetTransitionName(Transition), true);
336 | }
337 | }
338 | }
339 |
340 | private void OnTransitionCompleted(object sender, EventArgs e)
341 | {
342 | AbortTransition();
343 |
344 | var handler = TransitionCompleted;
345 | handler?.Invoke(this, new RoutedEventArgs());
346 | }
347 |
348 | public void AbortTransition()
349 | {
350 | // go to normal state and release our hold on the old content.
351 | VisualStateManager.GoToState(this, NormalState, false);
352 | IsTransitioning = false;
353 | if (PreviousContentPresentationSite != null)
354 | PreviousContentPresentationSite.Content = null;
355 | }
356 |
357 | private Storyboard GetStoryboard(TransitionType newTransition)
358 | {
359 | var presentationGroup = VisualStates.TryGetVisualStateGroup(this, PresentationGroup);
360 | Storyboard newStoryboard = null;
361 | if (presentationGroup != null)
362 | {
363 | var transitionName = GetTransitionName(newTransition);
364 | newStoryboard = presentationGroup.States
365 | .OfType()
366 | .Where(state => state.Name == transitionName)
367 | .Select(state => state.Storyboard)
368 | .FirstOrDefault();
369 | }
370 |
371 | return newStoryboard;
372 | }
373 |
374 | private string GetTransitionName(TransitionType transition)
375 | {
376 | return transition switch
377 | {
378 | TransitionType.Normal => "Normal",
379 | TransitionType.Up => "UpTransition",
380 | TransitionType.Down => "DownTransition",
381 | TransitionType.Right => "RightTransition",
382 | TransitionType.RightReplace => "RightReplaceTransition",
383 | TransitionType.Left => "LeftTransition",
384 | TransitionType.LeftReplace => "LeftReplaceTransition",
385 | TransitionType.Custom => CustomVisualStatesName,
386 | _ => "DefaultTransition"
387 | };
388 | }
389 | }
390 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/Utility/TransitioningContentControl.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
414 |
415 |
--------------------------------------------------------------------------------
/SteamFriendsManager/Utility/UriExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text.RegularExpressions;
3 | using Microsoft.Win32;
4 |
5 | namespace SteamFriendsManager.Utility
6 | {
7 | public static class UriExtension
8 | {
9 | public static bool CheckSchemeExistance(this Uri uri)
10 | {
11 | var schemeKey = Registry.ClassesRoot.OpenSubKey(uri.Scheme);
12 | return schemeKey != null && schemeKey.GetValue("URL Protocol") != null;
13 | }
14 |
15 | public static string GetSchemeExecutable(this Uri uri)
16 | {
17 | var commandKey = Registry.ClassesRoot.OpenSubKey($@"{uri.Scheme}\Shell\Open\Command");
18 |
19 | var command = commandKey?.GetValue(null) as string;
20 | if (command == null)
21 | return null;
22 |
23 | return Regex.Match(command, @"(?<="").*?(?="")").Value;
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/Utility/VisualStates.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Linq;
3 | using System.Windows;
4 | using System.Windows.Media;
5 |
6 | namespace SteamFriendsManager.Utility
7 | {
8 | internal static class VisualStates
9 | {
10 | private static FrameworkElement GetImplementationRoot(DependencyObject dependencyObject)
11 | {
12 | Debug.Assert(dependencyObject != null, "DependencyObject should not be null.");
13 | return VisualTreeHelper.GetChildrenCount(dependencyObject) == 1
14 | ? VisualTreeHelper.GetChild(dependencyObject, 0) as FrameworkElement
15 | : null;
16 | }
17 |
18 | public static VisualStateGroup TryGetVisualStateGroup(DependencyObject dependencyObject, string groupName)
19 | {
20 | var root = GetImplementationRoot(dependencyObject);
21 | if (root == null)
22 | return null;
23 |
24 | var vsg = VisualStateManager.GetVisualStateGroups(root);
25 |
26 | return
27 | vsg?.OfType()
28 | .FirstOrDefault(group => string.CompareOrdinal(groupName, group.Name) == 0);
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/ViewModel/FriendListPageViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text.RegularExpressions;
6 | using System.Threading.Tasks;
7 | using GalaSoft.MvvmLight;
8 | using GalaSoft.MvvmLight.Command;
9 | using MahApps.Metro.Controls.Dialogs;
10 | using SteamFriendsManager.Service;
11 | using SteamKit2;
12 |
13 | namespace SteamFriendsManager.ViewModel
14 | {
15 | public class FriendListPageViewModel : ViewModelBase
16 | {
17 | private readonly SteamClientService _steamClientService;
18 | private RelayCommand _addFriend;
19 | private RelayCommand _changePersonaName;
20 | private RelayCommand _removeFriend;
21 | private string _searchText;
22 | private RelayCommand _sendChatMessage;
23 | private RelayCommand _switchAccount;
24 | private RelayCommand _switchPersonaState;
25 |
26 | public FriendListPageViewModel(SteamClientService steamClientService)
27 | {
28 | _steamClientService = steamClientService;
29 |
30 | Task.Delay(2000).ContinueWith(task => { _steamClientService.SetPersonaStateAsync(EPersonaState.Online); });
31 |
32 | MessengerInstance.Register(this,
33 | msg => { RaisePropertyChanged(() => PersonaName); });
34 |
35 | MessengerInstance.Register(this,
36 | msg => RaisePropertyChanged(() => PersonaState));
37 |
38 | MessengerInstance.Register(this, msg =>
39 | {
40 | MessengerInstance.Send(new ShowMessageDialogMessageWithCallback("连接中断",
41 | "你与 Steam 的服务器连接已中断。重试三次均无法连通,请检查网络后重新登录。",
42 | result =>
43 | {
44 | MessengerInstance.Send(new SwitchPageMessage(SwitchPageMessage.Page.Login));
45 | MessengerInstance.Send(new ClearPageHistoryMessage());
46 | }));
47 | });
48 | }
49 |
50 | public IEnumerable Friends => _steamClientService.Friends;
51 |
52 | public string PersonaName => _steamClientService.PersonaName;
53 |
54 | public string PersonaState
55 | {
56 | get
57 | {
58 | var state = _steamClientService.PersonaState;
59 | return state switch
60 | {
61 | EPersonaState.Offline => "离线",
62 | EPersonaState.Online => "在线",
63 | EPersonaState.Busy => "忙碌",
64 | EPersonaState.Away => "离开",
65 | EPersonaState.Snooze => "打盹",
66 | EPersonaState.LookingToTrade => "想交易",
67 | EPersonaState.LookingToPlay => "想玩游戏",
68 | EPersonaState.Invisible => "隐身",
69 | _ => state.ToString()
70 | };
71 | }
72 | }
73 |
74 | public string SearchText
75 | {
76 | get => _searchText;
77 | set
78 | {
79 | if (_searchText == value)
80 | return;
81 |
82 | _searchText = value;
83 | RaisePropertyChanged(() => SearchText);
84 |
85 | if (!string.IsNullOrEmpty(_searchText))
86 | foreach (var friend in _steamClientService.Friends)
87 | friend.Visible = friend.PersonaName.ToLower().Contains(_searchText.ToLower());
88 | else
89 | foreach (var friend in _steamClientService.Friends)
90 | friend.Visible = true;
91 | }
92 | }
93 |
94 | public RelayCommand SwitchAccount =>
95 | _switchAccount ??= new RelayCommand(() =>
96 | {
97 | MessengerInstance.Send(new ClearPageHistoryOnNextTryLoginMessage());
98 | MessengerInstance.Send(new LogoutOnNextTryLoginMessage());
99 | MessengerInstance.Send(new SwitchPageMessage(SwitchPageMessage.Page.Login));
100 | });
101 |
102 | public RelayCommand ChangePersonaName =>
103 | _changePersonaName ??= new RelayCommand(() =>
104 | {
105 | MessengerInstance.Send(new ShowInputDialogMessage("修改昵称", "请输入新昵称:", PersonaName, async s =>
106 | {
107 | if (s == null || s == PersonaName)
108 | return;
109 |
110 | if (string.IsNullOrWhiteSpace(s))
111 | {
112 | MessengerInstance.Send(new ShowMessageDialogMessage("修改失败", "你不能使用空的昵称。"));
113 | return;
114 | }
115 |
116 | try
117 | {
118 | await _steamClientService.SetPersonaNameAsync(s);
119 | }
120 | catch (TimeoutException)
121 | {
122 | MessengerInstance.Send(new ShowMessageDialogMessage("昵称修改失败", "连接超时,请重试。"));
123 | }
124 | }));
125 | });
126 |
127 | public RelayCommand SwitchPersonaState =>
128 | _switchPersonaState ??= new RelayCommand(async state =>
129 | {
130 | try
131 | {
132 | await _steamClientService.SetPersonaStateAsync(state);
133 | }
134 | catch (TimeoutException)
135 | {
136 | MessengerInstance.Send(new ShowMessageDialogMessage("切换状态失败", "连接超时,请重试。"));
137 | }
138 | });
139 |
140 | public RelayCommand SendChatMessage =>
141 | _sendChatMessage ??= new RelayCommand(friends =>
142 | {
143 | if (friends == null || friends.Count == 0)
144 | return;
145 |
146 | if (friends.Count == 1)
147 | {
148 | var friend = friends[0] as SteamClientService.Friend;
149 | if (friend == null)
150 | return;
151 |
152 | MessengerInstance.Send(new ShowInputDialogMessage("发送消息", "请输入内容:",
153 | s =>
154 | {
155 | _steamClientService.SendChatMessageAsync(friend.SteamId, EChatEntryType.ChatMsg, s);
156 | }));
157 | }
158 | else
159 | {
160 | MessengerInstance.Send(new ShowInputDialogMessage("群发消息", "请输入内容:", async s =>
161 | {
162 | if (string.IsNullOrEmpty(s))
163 | return;
164 |
165 | if (await Task.Factory.StartNew(() =>
166 | {
167 | var sendTasks = from friend in friends.OfType()
168 | select _steamClientService.SendChatMessageAsync(friend.SteamId,
169 | EChatEntryType.ChatMsg, $"{s} ♥");
170 | try
171 | {
172 | Task.WaitAll(sendTasks.ToArray());
173 | return true;
174 | }
175 | catch (AggregateException)
176 | {
177 | return false;
178 | }
179 | }))
180 | MessengerInstance.Send(new ShowMessageDialogMessage("群发成功", "所有消息都已成功送达!"));
181 | else
182 | MessengerInstance.Send(new ShowMessageDialogMessage("群发失败", "部分消息发送超时。"));
183 | }));
184 | }
185 | });
186 |
187 | public RelayCommand AddFriend =>
188 | _addFriend ??= new RelayCommand(() =>
189 | {
190 | MessengerInstance.Send(new ShowInputDialogMessage("添加好友", @"请输入你要添加好友ID,当前支持以下几种格式:
191 | * 对方用户名
192 | * STEAM_0:0:19874118
193 | * 76561198000013964
194 | * http://steamcommunity.com/profiles/76561198000013964",
195 | async s =>
196 | {
197 | if (s == null)
198 | return;
199 |
200 | if (string.IsNullOrWhiteSpace(s))
201 | MessengerInstance.Send(new ShowMessageDialogMessage("添加失败", "输入无效"));
202 |
203 | try
204 | {
205 | SteamFriends.FriendAddedCallback result;
206 | do
207 | {
208 | if (Regex.IsMatch(s, @"^7656\d{13}$"))
209 | {
210 | ulong.TryParse(s, out var steamId64);
211 | var steamId = new SteamID();
212 | steamId.SetFromUInt64(steamId64);
213 | result = await _steamClientService.AddFriendAsync(steamId64);
214 | break;
215 | }
216 |
217 | var match = Regex.Match(s,
218 | @"^http(?:s)?://steamcommunity.com/profiles/(7656\d{13})$");
219 | if (match.Success)
220 | {
221 | ulong.TryParse(match.Groups[1].Value, out var steamId64);
222 | var steamId = new SteamID();
223 | steamId.SetFromUInt64(steamId64);
224 | result = await _steamClientService.AddFriendAsync(steamId);
225 | break;
226 | }
227 |
228 | if (Regex.IsMatch(s, @"^(?i)STEAM(?-i)_\d:\d:\d{8}$"))
229 | {
230 | result = await _steamClientService.AddFriendAsync(new SteamID(s));
231 | break;
232 | }
233 |
234 | result = await _steamClientService.AddFriendAsync(s);
235 | } while (false);
236 |
237 | if (result == null)
238 | return;
239 |
240 | switch (result.Result)
241 | {
242 | case EResult.OK:
243 | MessengerInstance.Send(new ShowMessageDialogMessage("添加成功",
244 | $"你成功向 {result.PersonaName} 发出了好友请求。"));
245 | break;
246 |
247 | case EResult.DuplicateName:
248 | MessengerInstance.Send(new ShowMessageDialogMessage("添加失败", "对方已经是你的好友了。"));
249 | break;
250 |
251 | case EResult.AccountNotFound:
252 | MessengerInstance.Send(new ShowMessageDialogMessage("添加失败", "指定用户不存在。"));
253 | break;
254 |
255 | default:
256 | MessengerInstance.Send(new ShowMessageDialogMessage("添加失败",
257 | result.Result.ToString()));
258 | break;
259 | }
260 | }
261 | catch (TimeoutException)
262 | {
263 | MessengerInstance.Send(new ShowMessageDialogMessage("添加失败", "连接超时。"));
264 | }
265 | }));
266 | });
267 |
268 | public RelayCommand RemoveFriend =>
269 | _removeFriend ??= new RelayCommand(friends =>
270 | {
271 | if (friends == null || friends.Count == 0)
272 | return;
273 |
274 | MessengerInstance.Send(new ShowMessageDialogMessageWithCallback("删除好友", "你确认要删除选定好友吗?",
275 | MessageDialogStyle.AffirmativeAndNegative, async result =>
276 | {
277 | if (result == MessageDialogResult.Negative)
278 | return;
279 |
280 | if (await Task.Factory.StartNew(() =>
281 | {
282 | var removeTasks = from friend in friends.OfType()
283 | select _steamClientService.RemoveFriendAsync(friend.SteamId);
284 | try
285 | {
286 | Task.WaitAll(removeTasks.ToArray());
287 | return true;
288 | }
289 | catch (AggregateException)
290 | {
291 | return false;
292 | }
293 | }))
294 | MessengerInstance.Send(new ShowMessageDialogMessage("删除成功", "选定好友删除成功。"));
295 | else
296 | MessengerInstance.Send(new ShowMessageDialogMessage("删除失败", "部分好友删除请求发送超时。"));
297 | }));
298 | });
299 | }
300 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/ViewModel/LoginPageViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using GalaSoft.MvvmLight;
5 | using GalaSoft.MvvmLight.Command;
6 | using SteamFriendsManager.Service;
7 | using SteamKit2;
8 |
9 | namespace SteamFriendsManager.ViewModel
10 | {
11 | public class LoginPageViewModel : ViewModelBase
12 | {
13 | private readonly ApplicationSettingsService _applicationSettingsService;
14 | private readonly SteamClientService _steamClientService;
15 | private bool _clearPageHistoryOnNextTryLogin;
16 | private bool _isLoading;
17 | private RelayCommand _login;
18 | private bool _logoutOnNextTryLogin;
19 | private string _password;
20 | private bool _shouldRememberAccount;
21 | private string _username;
22 |
23 | public LoginPageViewModel(SteamClientService steamClientService,
24 | ApplicationSettingsService applicationSettingsService)
25 | {
26 | _steamClientService = steamClientService;
27 | _applicationSettingsService = applicationSettingsService;
28 |
29 | _shouldRememberAccount = _applicationSettingsService.Settings.ShouldRememberAccount;
30 | if (_shouldRememberAccount)
31 | {
32 | _username = _applicationSettingsService.Settings.LastUsername;
33 | _password = _applicationSettingsService.Settings.LastPassword;
34 | }
35 |
36 | MessengerInstance.Register(this,
37 | msg => { _clearPageHistoryOnNextTryLogin = true; });
38 |
39 | MessengerInstance.Register(this, msg => { _logoutOnNextTryLogin = true; });
40 | }
41 |
42 | public bool ShouldRememberAccount
43 | {
44 | get => _shouldRememberAccount;
45 | set
46 | {
47 | if (_shouldRememberAccount == value)
48 | return;
49 |
50 | _shouldRememberAccount = value;
51 | RaisePropertyChanged(() => ShouldRememberAccount);
52 | }
53 | }
54 |
55 | public string Username
56 | {
57 | get => _username;
58 | set
59 | {
60 | if (_username == value)
61 | return;
62 |
63 | _username = value;
64 | RaisePropertyChanged(() => Username);
65 | }
66 | }
67 |
68 | public string Password
69 | {
70 | get => _password;
71 | set
72 | {
73 | if (_password == value)
74 | return;
75 |
76 | _password = value;
77 | RaisePropertyChanged(() => Password);
78 | }
79 | }
80 |
81 | public bool IsLoading
82 | {
83 | get => _isLoading;
84 | private set
85 | {
86 | if (_isLoading == value)
87 | return;
88 |
89 | _isLoading = value;
90 | RaisePropertyChanged(() => IsLoading);
91 | }
92 | }
93 |
94 | public RelayCommand Login =>
95 | _login ??= new RelayCommand(async () =>
96 | {
97 | await Task.Run(async () =>
98 | {
99 | var stopTrying = false;
100 | var success = false;
101 | string authCode = null;
102 | string twoFactorCode = null;
103 | IsLoading = true;
104 |
105 | if (string.IsNullOrWhiteSpace(Username))
106 | {
107 | MessengerInstance.Send(new ShowMessageDialogMessageWithCallback("提示", "请输入用户名。",
108 | result => IsLoading = false));
109 | return;
110 | }
111 |
112 | if (string.IsNullOrWhiteSpace(Password))
113 | {
114 | MessengerInstance.Send(new ShowMessageDialogMessageWithCallback("提示", "请输入密码。",
115 | result => IsLoading = false));
116 | return;
117 | }
118 |
119 | while (!stopTrying)
120 | try
121 | {
122 | if (_clearPageHistoryOnNextTryLogin)
123 | {
124 | MessengerInstance.Send(new ClearPageHistoryMessage());
125 | _clearPageHistoryOnNextTryLogin = false;
126 | }
127 |
128 | if (_logoutOnNextTryLogin)
129 | {
130 | _logoutOnNextTryLogin = false;
131 | await _steamClientService.LogoutAsync();
132 | }
133 |
134 | _steamClientService.ReconnectOnDisconnected = false;
135 |
136 | if (_steamClientService.IsConnected)
137 | {
138 | await _steamClientService.DisconnectAsync();
139 | await Task.Delay(1000);
140 | // It may fail to connect if we immediately reconnect after disconnected
141 | }
142 |
143 | await _steamClientService.ConnectAsync();
144 | var result = await _steamClientService.LoginAsync(new SteamUser.LogOnDetails
145 | {
146 | Username = Username,
147 | Password = Password,
148 | AuthCode = authCode,
149 | TwoFactorCode = twoFactorCode
150 | });
151 |
152 | var authCodeInputHint = "你的 Steam 帐号开启了两步验证,验证码已发往你的邮箱。请输入验证码:";
153 | var twoFactorCodeInputHint = "你的 Steam 帐号开启了两步验证。请输入验证码:";
154 | switch (result.Result)
155 | {
156 | case EResult.AlreadyLoggedInElsewhere:
157 | MessengerInstance.Send(
158 | new ShowMessageDialogMessage("登录失败", "你已经在其他地方登录了这个帐号。"));
159 | stopTrying = true;
160 | break;
161 |
162 | case EResult.InvalidPassword:
163 | MessengerInstance.Send(new ShowMessageDialogMessage("登录失败", "密码错误。"));
164 | stopTrying = true;
165 | break;
166 |
167 | case EResult.TwoFactorCodeMismatch:
168 | twoFactorCodeInputHint = "Steam 令牌验证码错误,请重新输入:";
169 | goto case EResult.AccountLoginDeniedNeedTwoFactor;
170 |
171 | case EResult.AccountLoginDeniedNeedTwoFactor:
172 | var twoFactorCodeInputLock = new object();
173 | lock (twoFactorCodeInputLock)
174 | {
175 | MessengerInstance.Send(new ShowInputDialogMessage("Steam 令牌",
176 | twoFactorCodeInputHint,
177 | s =>
178 | {
179 | if (s == null)
180 | stopTrying = true;
181 |
182 | twoFactorCode = s;
183 | lock (twoFactorCodeInputLock)
184 | {
185 | Monitor.Pulse(twoFactorCodeInputLock);
186 | }
187 | }));
188 | Monitor.Wait(twoFactorCodeInputLock);
189 | }
190 |
191 | break;
192 |
193 | case EResult.InvalidLoginAuthCode:
194 | authCodeInputHint = "Steam 令牌验证码错误,请重新输入:";
195 | goto case EResult.AccountLogonDenied;
196 |
197 | case EResult.AccountLogonDenied:
198 | var authCodeInputLock = new object();
199 | lock (authCodeInputLock)
200 | {
201 | MessengerInstance.Send(new ShowInputDialogMessage("Steam 令牌",
202 | authCodeInputHint,
203 | s =>
204 | {
205 | if (s == null)
206 | stopTrying = true;
207 |
208 | authCode = s;
209 | lock (authCodeInputLock)
210 | {
211 | Monitor.Pulse(authCodeInputLock);
212 | }
213 | }));
214 | Monitor.Wait(authCodeInputLock);
215 | }
216 |
217 | break;
218 |
219 | case EResult.OK:
220 | success = true;
221 | stopTrying = true;
222 | break;
223 |
224 | default:
225 | MessengerInstance.Send(new ShowMessageDialogMessage("登录失败",
226 | "未知错误:" + result.Result));
227 | stopTrying = true;
228 | break;
229 | }
230 |
231 | if (success)
232 | {
233 | _steamClientService.ReconnectOnDisconnected = true;
234 | _applicationSettingsService.Settings.ShouldRememberAccount =
235 | ShouldRememberAccount;
236 | if (ShouldRememberAccount)
237 | {
238 | _applicationSettingsService.Settings.LastUsername = Username;
239 | _applicationSettingsService.Settings.LastPassword = Password;
240 | }
241 | else
242 | {
243 | _applicationSettingsService.Settings.LastUsername = null;
244 | _applicationSettingsService.Settings.LastPassword = null;
245 | }
246 |
247 | await _applicationSettingsService.SaveAsync();
248 | MessengerInstance.Send(new SwitchPageMessage(SwitchPageMessage.Page.FriendList,
249 | true));
250 | }
251 | else
252 | {
253 | _applicationSettingsService.Settings.ShouldRememberAccount = false;
254 | if (ShouldRememberAccount)
255 | {
256 | _applicationSettingsService.Settings.LastUsername = null;
257 | _applicationSettingsService.Settings.LastPassword = null;
258 | }
259 |
260 | await _applicationSettingsService.SaveAsync();
261 | }
262 | }
263 | catch (TimeoutException)
264 | {
265 | MessengerInstance.Send(new ShowMessageDialogMessage("登录失败", "连接超时,请重试。"));
266 | stopTrying = true;
267 | }
268 |
269 | IsLoading = false;
270 | });
271 | });
272 | }
273 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/ViewModel/MainWindowViewModel.cs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stackia/SteamFriendsManager/4613b6ada87fb38001c928e46eb5bc7fb70a977c/SteamFriendsManager/ViewModel/MainWindowViewModel.cs
--------------------------------------------------------------------------------
/SteamFriendsManager/ViewModel/ViewModelLocator.cs:
--------------------------------------------------------------------------------
1 | /*
2 | In App.xaml:
3 |
4 |
6 |
7 |
8 | In the View:
9 | DataContext="{Binding Source={StaticResource Locator}, Path=ViewModelName}"
10 |
11 | You can also use Blend to do all this with the tool's support.
12 | See http://www.galasoft.ch/mvvm
13 | */
14 |
15 | using CommonServiceLocator;
16 | using GalaSoft.MvvmLight.Ioc;
17 | using SteamFriendsManager.Service;
18 |
19 | namespace SteamFriendsManager.ViewModel
20 | {
21 | public class ViewModelLocator
22 | {
23 | public ViewModelLocator()
24 | {
25 | ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
26 |
27 | SimpleIoc.Default.Register();
28 | SimpleIoc.Default.Register();
29 | SimpleIoc.Default.Register();
30 | SimpleIoc.Default.Register();
31 |
32 | SimpleIoc.Default.Register(true);
33 | SimpleIoc.Default.Register(() =>
34 | {
35 | var steamClientService =
36 | new SteamClientService(ServiceLocator.Current.GetInstance());
37 | steamClientService.Start();
38 | return steamClientService;
39 | });
40 | }
41 |
42 | public MainWindowViewModel MainWindow => ServiceLocator.Current.GetInstance();
43 |
44 | public WelcomePageViewModel WelcomePage => ServiceLocator.Current.GetInstance();
45 |
46 | public LoginPageViewModel LoginPage => ServiceLocator.Current.GetInstance();
47 |
48 | public FriendListPageViewModel FriendListPage => ServiceLocator.Current.GetInstance();
49 | }
50 | }
--------------------------------------------------------------------------------
/SteamFriendsManager/ViewModel/WelcomePageViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using GalaSoft.MvvmLight;
3 | using GalaSoft.MvvmLight.Command;
4 |
5 | namespace SteamFriendsManager.ViewModel
6 | {
7 | public class WelcomePageViewModel : ViewModelBase
8 | {
9 | private RelayCommand _switchToLoginPage;
10 |
11 | public RelayCommand SwitchToLoginPage =>
12 | _switchToLoginPage ??= new RelayCommand(
13 | () => { MessengerInstance.Send(new SwitchPageMessage(SwitchPageMessage.Page.Login)); });
14 |
15 | public string Version
16 | {
17 | get
18 | {
19 | var version = Assembly.GetExecutingAssembly().GetName().Version;
20 | return $"{version.Major}.{version.Minor}.{version.Build}";
21 | }
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------