├── .gitattributes
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── RssReader.png
├── RssReader.sln
├── RssReader
├── App.xaml
├── App.xaml.cs
├── AppShell.xaml
├── AppShell.xaml.cs
├── Assets
│ ├── LockScreenLogo.scale-100.png
│ ├── LockScreenLogo.scale-125.png
│ ├── LockScreenLogo.scale-150.png
│ ├── LockScreenLogo.scale-200.png
│ ├── LockScreenLogo.scale-400.png
│ ├── SplashScreen.scale-100.png
│ ├── SplashScreen.scale-125.png
│ ├── SplashScreen.scale-150.png
│ ├── SplashScreen.scale-200.png
│ ├── SplashScreen.scale-400.png
│ ├── Square150x150Logo.scale-100.png
│ ├── Square150x150Logo.scale-125.png
│ ├── Square150x150Logo.scale-150.png
│ ├── Square150x150Logo.scale-200.png
│ ├── Square150x150Logo.scale-400.png
│ ├── Square310x310Logo.scale-100.png
│ ├── Square310x310Logo.scale-125.png
│ ├── Square310x310Logo.scale-150.png
│ ├── Square310x310Logo.scale-200.png
│ ├── Square310x310Logo.scale-400.png
│ ├── Square44x44Logo.scale-100.png
│ ├── Square44x44Logo.scale-125.png
│ ├── Square44x44Logo.scale-150.png
│ ├── Square44x44Logo.scale-200.png
│ ├── Square44x44Logo.scale-400.png
│ ├── Square44x44Logo.targetsize-16_altform-unplated.png
│ ├── Square44x44Logo.targetsize-20_altform-unplated.png
│ ├── Square44x44Logo.targetsize-24_altform-unplated.png
│ ├── Square44x44Logo.targetsize-256_altform-unplated.png
│ ├── Square44x44Logo.targetsize-30_altform-unplated.png
│ ├── Square44x44Logo.targetsize-32_altform-unplated.png
│ ├── Square44x44Logo.targetsize-36_altform-unplated.png
│ ├── Square44x44Logo.targetsize-40_altform-unplated.png
│ ├── Square44x44Logo.targetsize-48_altform-unplated.png
│ ├── Square44x44Logo.targetsize-60_altform-unplated.png
│ ├── Square44x44Logo.targetsize-64_altform-unplated.png
│ ├── Square44x44Logo.targetsize-72_altform-unplated.png
│ ├── Square44x44Logo.targetsize-80_altform-unplated.png
│ ├── Square44x44Logo.targetsize-96_altform-unplated.png
│ ├── Square71x71Logo.scale-100.png
│ ├── Square71x71Logo.scale-125.png
│ ├── Square71x71Logo.scale-150.png
│ ├── Square71x71Logo.scale-200.png
│ ├── Square71x71Logo.scale-400.png
│ ├── StoreLogo.scale-100.png
│ ├── StoreLogo.scale-125.png
│ ├── StoreLogo.scale-150.png
│ ├── StoreLogo.scale-200.png
│ ├── StoreLogo.scale-400.png
│ ├── Wide310x150Logo.scale-100.png
│ ├── Wide310x150Logo.scale-125.png
│ ├── Wide310x150Logo.scale-150.png
│ ├── Wide310x150Logo.scale-200.png
│ ├── Wide310x150Logo.scale-400.png
│ ├── feeds.dat
│ ├── icon_feederror1.png
│ ├── icon_noeditfeeds.png
│ └── icon_nofavs.png
├── Common
│ ├── BindableBase.cs
│ ├── BooleanToVisibilityConverter.cs
│ ├── Canceller.cs
│ ├── Helpers.cs
│ └── Serializer.cs
├── Controls
│ ├── NavMenuListView.cs
│ ├── PageHeader.xaml
│ └── PageHeader.xaml.cs
├── NavMenuItem.cs
├── Package.appxmanifest
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Default.rd.xml
│ └── DesignTimeResources.xaml
├── RSSReader.csproj
├── Styles
│ └── Styles.xaml
├── ViewModels
│ ├── ArticleViewModel.cs
│ ├── FeedDataSource.cs
│ ├── FeedViewModel.cs
│ └── MainViewModel.cs
└── Views
│ ├── AddFeedView.xaml
│ ├── AddFeedView.xaml.cs
│ ├── DetailPage.xaml
│ ├── DetailPage.xaml.cs
│ ├── EditFeedsView.xaml
│ ├── EditFeedsView.xaml.cs
│ ├── FeedView.xaml
│ ├── FeedView.xaml.cs
│ ├── MasterDetailPage.xaml
│ └── MasterDetailPage.xaml.cs
└── SECURITY.md
/.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 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | build/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opensdf
80 | *.sdf
81 | *.cachefile
82 |
83 | # Visual Studio profiler
84 | *.psess
85 | *.vsp
86 | *.vspx
87 | *.sap
88 |
89 | # TFS 2012 Local Workspace
90 | $tf/
91 |
92 | # Guidance Automation Toolkit
93 | *.gpState
94 |
95 | # ReSharper is a .NET coding add-in
96 | _ReSharper*/
97 | *.[Rr]e[Ss]harper
98 | *.DotSettings.user
99 |
100 | # JustCode is a .NET coding add-in
101 | .JustCode
102 |
103 | # TeamCity is a build add-in
104 | _TeamCity*
105 |
106 | # DotCover is a Code Coverage Tool
107 | *.dotCover
108 |
109 | # NCrunch
110 | _NCrunch_*
111 | .*crunch*.local.xml
112 | nCrunchTemp_*
113 |
114 | # MightyMoose
115 | *.mm.*
116 | AutoTest.Net/
117 |
118 | # Web workbench (sass)
119 | .sass-cache/
120 |
121 | # Installshield output folder
122 | [Ee]xpress/
123 |
124 | # DocProject is a documentation generator add-in
125 | DocProject/buildhelp/
126 | DocProject/Help/*.HxT
127 | DocProject/Help/*.HxC
128 | DocProject/Help/*.hhc
129 | DocProject/Help/*.hhk
130 | DocProject/Help/*.hhp
131 | DocProject/Help/Html2
132 | DocProject/Help/html
133 |
134 | # Click-Once directory
135 | publish/
136 |
137 | # Publish Web Output
138 | *.[Pp]ublish.xml
139 | *.azurePubxml
140 | # TODO: Comment the next line if you want to checkin your web deploy settings
141 | # but database connection strings (with potential passwords) will be unencrypted
142 | *.pubxml
143 | *.publishproj
144 |
145 | # NuGet Packages
146 | *.nupkg
147 | # The packages folder can be ignored because of Package Restore
148 | **/packages/*
149 | # except build/, which is used as an MSBuild target.
150 | !**/packages/build/
151 | # Uncomment if necessary however generally it will be regenerated when needed
152 | #!**/packages/repositories.config
153 |
154 | # Windows Azure Build Output
155 | csx/
156 | *.build.csdef
157 |
158 | # Microsoft Store app package directory
159 | AppPackages/
160 |
161 | # Visual Studio cache files
162 | # files ending in .cache can be ignored
163 | *.[Cc]ache
164 | # but keep track of directories ending in .cache
165 | !*.[Cc]ache/
166 |
167 | # Others
168 | ClientBin/
169 | [Ss]tyle[Cc]op.*
170 | ~$*
171 | *~
172 | *.dbmdl
173 | *.dbproj.schemaview
174 | *.pfx
175 | *.publishsettings
176 | node_modules/
177 | orleans.codegen.cs
178 |
179 | # RIA/Silverlight projects
180 | Generated_Code/
181 |
182 | # Backup & report files from converting an old project file
183 | # to a newer Visual Studio version. Backup files are not needed,
184 | # because we have git ;-)
185 | _UpgradeReport_Files/
186 | Backup*/
187 | UpgradeLog*.XML
188 | UpgradeLog*.htm
189 |
190 | # SQL Server files
191 | *.mdf
192 | *.ldf
193 |
194 | # Business Intelligence projects
195 | *.rdl.data
196 | *.bim.layout
197 | *.bim_*.settings
198 |
199 | # Microsoft Fakes
200 | FakesAssemblies/
201 |
202 | # Node.js Tools for Visual Studio
203 | .ntvs_analysis.dat
204 |
205 | # Visual Studio 6 build log
206 | *.plg
207 |
208 | # Visual Studio 6 workspace options file
209 | *.opt
210 |
211 | # Visual Studio LightSwitch build output
212 | **/*.HTMLClient/GeneratedArtifacts
213 | **/*.DesktopClient/GeneratedArtifacts
214 | **/*.DesktopClient/ModelManifest.xml
215 | **/*.Server/GeneratedArtifacts
216 | **/*.Server/ModelManifest.xml
217 | _Pvt_Extensions
218 |
219 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Microsoft
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | page_type: sample
3 | languages:
4 | - csharp
5 | products:
6 | - windows
7 | - windows-uwp
8 | statusNotificationTargets:
9 | - codefirst@microsoft.com
10 | ---
11 |
12 |
15 |
16 | # RssReader sample
17 |
18 | A mini-app for retrieving RSS feeds and viewing articles, showing MVVM and design best practices.
19 | Users can specify the URL of a feed, view articles in a WebView control, and save favorite articles to local storage.
20 | This sample runs on the Universal Windows Platform (UWP) and WinUI 2.4.
21 |
22 | > Note - This sample is targeted and tested for Windows 10, version 2004 (10.0; Build 19041), and Visual Studio 2019. If you prefer, you can use project properties to retarget the project(s) to Windows 10, version 1903 (10.0; Build 18362).
23 |
24 | 
25 |
26 | ## Features
27 |
28 | **Note:** Features in this app are subject to change.
29 |
30 | RssReader demonstrates:
31 |
32 | * The navigation menu (hamburger menu) pattern and screen-width adaptivity using the
33 | [SplitView](https://msdn.microsoft.com/library/windows/apps/windows.ui.xaml.controls.splitview.aspx) control and the
34 | [AdaptiveTrigger](https://msdn.microsoft.com/library/windows/apps/windows.ui.xaml.adaptivetrigger.aspx) class.
35 | * The Syndication APIs ([Windows.Web.Syndication](https://msdn.microsoft.com/library/windows/apps/windows.web.syndication.aspx))
36 | to retrieve RSS feed data.
37 | * The [DataContractSerializer](https://msdn.microsoft.com/library/windows/apps/system.runtime.serialization.datacontractserializer.aspx) class to save and
38 | restore app data from local storage.
39 | * C# and XAML using the MVVM design pattern.
40 |
41 | ### June 2020 update
42 |
43 | This update includes:
44 |
45 | * Refactoring for WinUI 2.4 controls, specifically [ProgressBar](https://docs.microsoft.com/windows/uwp/design/controls-and-patterns/progress-controls).
46 |
47 | ### September 2016 update
48 |
49 | This update includes:
50 |
51 | * General cleanup, commenting, and refactoring for clarity.
52 | * Improved error handling and performance.
53 | * Fixes for several bugs related to layout, navigation, browser launch, and URL handling.
54 |
55 | ### March 2016 update
56 |
57 | This update includes:
58 |
59 | * A complete redesign of the UI to show effective use of color, type, images, and animated effects.
60 | * Major improvements to layout, navigation, and window-size adaptivity to support small and large screens.
61 | * Use of the [WebView](https://msdn.microsoft.com/library/windows/apps/windows.ui.xaml.controls.webview.aspx)
62 | control to show articles within the app.
63 | * The ability to rename feeds and to rearrange feeds and favorites.
64 |
65 | We implemented the navigation and layout patterns in this sample using code from the
66 | [XAML navigation menu](https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/XamlNavigation) and
67 | [XAML master/detail](https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/XamlMasterDetail) samples in the
68 | [Windows-universal-samples](https://github.com/Microsoft/Windows-universal-samples) repo. These samples represent the current
69 | minimum recommendations for these patterns, and the RssReader sample will continue to reflect this guidance in future updates.
70 |
71 | Please report any bugs or suggestions on the [Issues](https://github.com/Microsoft/Windows-appsample-rssreader/issues) list.
72 | All feedback is welcome!
73 |
74 | ## Code at a glance
75 |
76 | If you're just interested in code snippets for certain API and don't want to browse or run the full sample,
77 | check out the following files for examples of some highlighted features:
78 |
79 | * [FeedView.xaml](RssReader/Views/FeedView.xaml#L25), [AddFeedView.xaml](RssReader/Views/AddFeedView.xaml#L25),
80 | [EditFeedsView.xaml](RssReader/Views/EditFeedsView.xaml#L25), and [Styles.xaml](RssReader/Styles/Styles.xaml#L25)
81 | - Rich UI experiences and XAML resources for colors, templates, and animated effects.
82 | * [AppShell.xaml](RssReader/AppShell.xaml#L25) and [AppShell.xaml.cs](RssReader/AppShell.xaml.cs#L25)
83 | - Adapted from the [XAML navigation menu](https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/XamlNavigation) sample.
84 | - Use of the [SplitView](https://msdn.microsoft.com/library/windows/apps/windows.ui.xaml.controls.splitview.aspx) control
85 | to implement a navigation menu with a hamburger button.
86 | - Use of [AdaptiveTrigger](https://msdn.microsoft.com/library/windows/apps/windows.ui.xaml.adaptivetrigger.aspx) with
87 | [VisualState.Setters](https://msdn.microsoft.com/library/windows/apps/windows.ui.xaml.visualstate.setters.aspx) and
88 | [VisualStateManager](https://msdn.microsoft.com/library/windows/apps/windows.ui.xaml.visualstatemanager.aspx)
89 | to adjust the navigation menu depending on the current window width.
90 | - Code that adjusts header margins depending on the state of the navigation menu and hamburger button.
91 | - Keyboard support and Frame navigation.
92 | * [MasterDetailPage.xaml](RssReader/Views/MasterDetailPage.xaml#L25), [MasterDetailPage.xaml.cs](RssReader/Views/MasterDetailPage.xaml.cs#L25),
93 | [DetailPage.xaml](RssReader/Views/DetailPage.xaml#L25) and [DetailPage.xaml.cs](RssReader/Views/DetailPage.xaml.cs#L25)
94 | - Adapted from the [XAML master/detail](https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/XamlMasterDetail) sample.
95 | - Code that adjusts the display of the articles list, the
96 | [WebView](https://msdn.microsoft.com/library/windows/apps/windows.ui.xaml.controls.webview.aspx) showing article content, and
97 | the title-bar back button depending on the current window width.
98 | * [FeedDataSource.cs](RssReader/ViewModels/FeedDataSource.cs#L25) and [Serializer.cs](RssReader/Common/Serializer.cs#L25)
99 | - Loading default feed data from the app package using [StorageFile.GetFileFromApplicationUriAsync](https://msdn.microsoft.com/library/windows/apps/windows.storage.storagefile.getfilefromapplicationuriasync.aspx).
100 | - Loading feed and article data from an RSS server using [SyndicationClient.RetrieveFeedAsync](https://msdn.microsoft.com/library/windows/apps/windows.web.syndication.syndicationclient.retrievefeedasync.aspx).
101 | - Loading and saving feed and favorites data to/from local storage using [StorageFolder.TryGetItemAsync](https://msdn.microsoft.com/library/windows/apps/windows.storage.storagefolder.trygetitemasync.aspx),
102 | [StorageFolder.CreateFileAsync](https://msdn.microsoft.com/library/windows/apps/br227250.aspx),
103 | and [DataContractSerializer](https://msdn.microsoft.com/library/windows/apps/system.runtime.serialization.datacontractserializer.aspx).
104 |
105 | ## Universal Windows Platform development
106 |
107 | ### Prerequisites
108 |
109 | - Windows 10. Minimum: Windows 10, version 1809 (10.0; Build 17763), also known as the Windows 10 October 2018 Update.
110 | - [Windows 10 SDK](https://developer.microsoft.com/windows/downloads/windows-10-sdk). Minimum: Windows SDK version 10.0.17763.0 (Windows 10, version 1809).
111 | - [Visual Studio 2019](https://visualstudio.microsoft.com/downloads/) (or Visual Studio 2017). You can use the free Visual Studio Community Edition to build and run Windows Universal Platform (UWP) apps.
112 |
113 | To get the latest updates to Windows and the development tools, and to help shape their development, join
114 | the [Windows Insider Program](https://insider.windows.com).
115 |
116 | ## Running the sample
117 |
118 | The default project is RssReader and you can Start Debugging (F5) or Start Without Debugging (Ctrl+F5) to try it out.
119 | The app will run in the emulator or on physical devices.
120 |
121 | **Note:** This sample assumes you have an internet connection. Also, the platform target currently defaults to ARM,
122 | so be sure to change that to x64 or x86 if you want to test on a non-ARM device.
123 |
124 |
125 |
--------------------------------------------------------------------------------
/RssReader.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader.png
--------------------------------------------------------------------------------
/RssReader.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.24720.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RssReader", "RssReader\RssReader.csproj", "{81C6601D-A4FE-4B6E-97C8-BD9FAEFC03DA}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D74543E8-93DA-44EE-8523-8923CD8E9D32}"
9 | ProjectSection(SolutionItems) = preProject
10 | CONTRIBUTING.md = CONTRIBUTING.md
11 | LICENSE = LICENSE
12 | README.md = README.md
13 | RssReader.png = RssReader.png
14 | EndProjectSection
15 | EndProject
16 | Global
17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
18 | Debug|ARM = Debug|ARM
19 | Debug|x64 = Debug|x64
20 | Debug|x86 = Debug|x86
21 | Release|ARM = Release|ARM
22 | Release|x64 = Release|x64
23 | Release|x86 = Release|x86
24 | EndGlobalSection
25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
26 | {81C6601D-A4FE-4B6E-97C8-BD9FAEFC03DA}.Debug|ARM.ActiveCfg = Debug|ARM
27 | {81C6601D-A4FE-4B6E-97C8-BD9FAEFC03DA}.Debug|ARM.Build.0 = Debug|ARM
28 | {81C6601D-A4FE-4B6E-97C8-BD9FAEFC03DA}.Debug|ARM.Deploy.0 = Debug|ARM
29 | {81C6601D-A4FE-4B6E-97C8-BD9FAEFC03DA}.Debug|x64.ActiveCfg = Debug|x64
30 | {81C6601D-A4FE-4B6E-97C8-BD9FAEFC03DA}.Debug|x64.Build.0 = Debug|x64
31 | {81C6601D-A4FE-4B6E-97C8-BD9FAEFC03DA}.Debug|x64.Deploy.0 = Debug|x64
32 | {81C6601D-A4FE-4B6E-97C8-BD9FAEFC03DA}.Debug|x86.ActiveCfg = Debug|x86
33 | {81C6601D-A4FE-4B6E-97C8-BD9FAEFC03DA}.Debug|x86.Build.0 = Debug|x86
34 | {81C6601D-A4FE-4B6E-97C8-BD9FAEFC03DA}.Debug|x86.Deploy.0 = Debug|x86
35 | {81C6601D-A4FE-4B6E-97C8-BD9FAEFC03DA}.Release|ARM.ActiveCfg = Release|ARM
36 | {81C6601D-A4FE-4B6E-97C8-BD9FAEFC03DA}.Release|ARM.Build.0 = Release|ARM
37 | {81C6601D-A4FE-4B6E-97C8-BD9FAEFC03DA}.Release|ARM.Deploy.0 = Release|ARM
38 | {81C6601D-A4FE-4B6E-97C8-BD9FAEFC03DA}.Release|x64.ActiveCfg = Release|x64
39 | {81C6601D-A4FE-4B6E-97C8-BD9FAEFC03DA}.Release|x64.Build.0 = Release|x64
40 | {81C6601D-A4FE-4B6E-97C8-BD9FAEFC03DA}.Release|x64.Deploy.0 = Release|x64
41 | {81C6601D-A4FE-4B6E-97C8-BD9FAEFC03DA}.Release|x86.ActiveCfg = Release|x86
42 | {81C6601D-A4FE-4B6E-97C8-BD9FAEFC03DA}.Release|x86.Build.0 = Release|x86
43 | {81C6601D-A4FE-4B6E-97C8-BD9FAEFC03DA}.Release|x86.Deploy.0 = Release|x86
44 | EndGlobalSection
45 | GlobalSection(SolutionProperties) = preSolution
46 | HideSolutionNode = FALSE
47 | EndGlobalSection
48 | EndGlobal
49 |
--------------------------------------------------------------------------------
/RssReader/App.xaml:
--------------------------------------------------------------------------------
1 |
24 |
25 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
41 |
42 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/RssReader/App.xaml.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using RssReader.Views;
26 | using System;
27 | using Windows.ApplicationModel;
28 | using Windows.ApplicationModel.Activation;
29 | using Windows.Foundation;
30 | using Windows.UI;
31 | using Windows.UI.ViewManagement;
32 | using Windows.UI.Xaml;
33 | using Windows.UI.Xaml.Navigation;
34 |
35 | namespace RssReader
36 | {
37 | ///
38 | /// Provides application-specific behavior to supplement the default Application class.
39 | ///
40 | sealed partial class App : Application
41 | {
42 | ///
43 | /// Initializes the singleton application object. This is the first line of authored code
44 | /// executed, and as such is the logical equivalent of main() or WinMain().
45 | ///
46 | public App()
47 | {
48 | this.InitializeComponent();
49 | this.Suspending += OnSuspending;
50 | }
51 |
52 | ///
53 | /// Invoked when the application is launched normally by the end user. Other entry points
54 | /// will be used such as when the application is launched to open a specific file.
55 | ///
56 | /// Details about the launch request and process.
57 | protected override void OnLaunched(LaunchActivatedEventArgs e)
58 | {
59 | ApplicationViewTitleBar titleBar = ApplicationView.GetForCurrentView().TitleBar;
60 | if (titleBar != null)
61 | {
62 | Color titleBarColor = (Color)App.Current.Resources["SystemChromeMediumColor"];
63 | titleBar.BackgroundColor = titleBarColor;
64 | titleBar.ButtonBackgroundColor = titleBarColor;
65 | }
66 |
67 | AppShell shell = Window.Current.Content as AppShell;
68 |
69 | // Do not repeat app initialization when the Window already has content,
70 | // just ensure that the window is active
71 | if (shell == null)
72 | {
73 | // Create a AppShell to act as the navigation context and navigate to the first page
74 | shell = new AppShell();
75 |
76 | // Set the default language
77 | shell.Language = Windows.Globalization.ApplicationLanguages.Languages[0];
78 |
79 | shell.AppFrame.NavigationFailed += OnNavigationFailed;
80 |
81 | if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
82 | {
83 | //TODO: Load state from previously suspended application
84 | }
85 | }
86 |
87 | // Place our app shell in the current Window
88 | Window.Current.Content = shell;
89 |
90 | if (shell.AppFrame.Content == null)
91 | {
92 | // When the navigation stack isn't restored, navigate to the first page
93 | // suppressing the initial entrance animation.
94 | shell.AppFrame.Navigate(typeof(MasterDetailPage), e.Arguments, new Windows.UI.Xaml.Media.Animation.SuppressNavigationTransitionInfo());
95 | }
96 |
97 | // Ensure the current window is active
98 | Window.Current.Activate();
99 |
100 | ApplicationView.GetForCurrentView().SetPreferredMinSize(new Size(320, 200));
101 | }
102 |
103 | ///
104 | /// Invoked when Navigation to a certain page fails
105 | ///
106 | /// The Frame which failed navigation
107 | /// Details about the navigation failure
108 | void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
109 | {
110 | throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
111 | }
112 |
113 | ///
114 | /// Invoked when application execution is being suspended. Application state is saved
115 | /// without knowing whether the application will be terminated or resumed with the contents
116 | /// of memory still intact.
117 | ///
118 | /// The source of the suspend request.
119 | /// Details about the suspend request.
120 | private void OnSuspending(object sender, SuspendingEventArgs e)
121 | {
122 | var deferral = e.SuspendingOperation.GetDeferral();
123 | //TODO: Save application state and stop any background activity
124 | deferral.Complete();
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/RssReader/AppShell.xaml:
--------------------------------------------------------------------------------
1 |
24 |
25 |
40 |
41 |
46 |
47 |
48 |
49 |
50 |
51 |
53 |
54 |
55 |
56 |
57 |
58 |
62 |
68 |
73 |
74 |
75 |
76 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
91 |
92 |
93 |
100 |
101 |
102 |
109 |
110 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
133 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
144 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
155 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
176 |
185 |
192 |
201 |
202 |
203 |
204 |
206 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
235 |
236 |
237 |
--------------------------------------------------------------------------------
/RssReader/AppShell.xaml.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using RssReader.Controls;
26 | using RssReader.ViewModels;
27 | using RssReader.Views;
28 | using System.Collections.Generic;
29 | using Windows.Foundation;
30 | using Windows.UI.Core;
31 | using Windows.UI.Xaml;
32 | using Windows.UI.Xaml.Automation;
33 | using Windows.UI.Xaml.Controls;
34 | using Windows.UI.Xaml.Input;
35 | using Windows.UI.Xaml.Navigation;
36 |
37 | namespace RssReader
38 | {
39 | ///
40 | /// The "chrome" layer of the app that provides top-level navigation with
41 | /// proper keyboarding navigation.
42 | ///
43 | public sealed partial class AppShell : Page
44 | {
45 | public static AppShell Current = null;
46 |
47 | public MainViewModel ViewModel { get; } = new MainViewModel();
48 |
49 | public List NavList { get; } = new List(new[]
50 | {
51 | new NavMenuItem()
52 | {
53 | Symbol = Symbol.Add,
54 | Label = "Add feed",
55 | DestPage = typeof(MasterDetailPage),
56 | Arguments = typeof(AddFeedView)
57 | },
58 | new NavMenuItem()
59 | {
60 | Symbol = Symbol.Edit,
61 | Label = "Edit feeds",
62 | DestPage = typeof(MasterDetailPage),
63 | Arguments = typeof(EditFeedsView)
64 | }
65 | });
66 |
67 | ///
68 | /// Initializes a new instance of the AppShell, sets the static 'Current' reference,
69 | /// adds callbacks for Back requests and changes in the SplitView's DisplayMode, and
70 | /// provide the nav menu list with the data to display.
71 | ///
72 | public AppShell()
73 | {
74 | this.InitializeComponent();
75 | Current = this;
76 |
77 | this.Loaded += async (sender, args) =>
78 | {
79 | await ViewModel.InitializeFeedsAsync();
80 | FeedsList.SelectedIndex = FeedsList.Items.Count > 1 ? 1 : 0;
81 | var titleBar = Windows.ApplicationModel.Core.CoreApplication.GetCurrentView().TitleBar;
82 | };
83 |
84 | ViewModel.BadFeedRemoved += (s, e) => FeedsList.SelectedItem = ViewModel.CurrentFeed;
85 | }
86 |
87 | public Frame AppFrame => AppShellFrame;
88 |
89 | ///
90 | /// Default keyboard focus movement for any unhandled keyboarding
91 | ///
92 | ///
93 | ///
94 | private void AppShell_KeyDown(object sender, KeyRoutedEventArgs e)
95 | {
96 | FocusNavigationDirection direction = FocusNavigationDirection.None;
97 | switch (e.Key)
98 | {
99 | case Windows.System.VirtualKey.Left:
100 | case Windows.System.VirtualKey.GamepadDPadLeft:
101 | case Windows.System.VirtualKey.GamepadLeftThumbstickLeft:
102 | case Windows.System.VirtualKey.NavigationLeft:
103 | direction = FocusNavigationDirection.Left;
104 | break;
105 | case Windows.System.VirtualKey.Right:
106 | case Windows.System.VirtualKey.GamepadDPadRight:
107 | case Windows.System.VirtualKey.GamepadLeftThumbstickRight:
108 | case Windows.System.VirtualKey.NavigationRight:
109 | direction = FocusNavigationDirection.Right;
110 | break;
111 |
112 | case Windows.System.VirtualKey.Up:
113 | case Windows.System.VirtualKey.GamepadDPadUp:
114 | case Windows.System.VirtualKey.GamepadLeftThumbstickUp:
115 | case Windows.System.VirtualKey.NavigationUp:
116 | direction = FocusNavigationDirection.Up;
117 | break;
118 |
119 | case Windows.System.VirtualKey.Down:
120 | case Windows.System.VirtualKey.GamepadDPadDown:
121 | case Windows.System.VirtualKey.GamepadLeftThumbstickDown:
122 | case Windows.System.VirtualKey.NavigationDown:
123 | direction = FocusNavigationDirection.Down;
124 | break;
125 | }
126 |
127 | if (direction != FocusNavigationDirection.None)
128 | {
129 | var control = FocusManager.FindNextFocusableElement(direction) as Control;
130 | if (control != null)
131 | {
132 | control.Focus(FocusState.Programmatic);
133 | e.Handled = true;
134 | }
135 | }
136 | }
137 |
138 | public void OpenNavePane()
139 | {
140 | TogglePaneButton.IsChecked = true;
141 | }
142 |
143 | #region BackRequested Handlers
144 |
145 | private void SystemNavigationManager_BackRequested(object sender, BackRequestedEventArgs e)
146 | {
147 | bool handled = e.Handled;
148 | this.BackRequested(ref handled);
149 | e.Handled = handled;
150 | }
151 |
152 | private void BackRequested(ref bool handled)
153 | {
154 | // Get a hold of the current frame so that we can inspect the app back stack.
155 |
156 | if (this.AppFrame == null)
157 | return;
158 |
159 | // Check to see if this is the top-most page on the app back stack.
160 | if (this.AppFrame.CanGoBack && !handled)
161 | {
162 | // If not, set the event to handled and go back to the previous page in the app.
163 | handled = true;
164 | this.AppFrame.GoBack();
165 | }
166 | }
167 |
168 | #endregion
169 |
170 | #region Navigation
171 |
172 | ///
173 | /// Navigate to the Page for the selected .
174 | ///
175 | ///
176 | ///
177 | private void NavMenuList_ItemInvoked(object sender, ListViewItem listViewItem)
178 | {
179 | FeedsList.SelectedIndex = -1;
180 |
181 | var item = (NavMenuItem)((NavMenuListView)sender).ItemFromContainer(listViewItem);
182 |
183 | if (item != null)
184 | {
185 | AppFrame.Navigate(typeof(MasterDetailPage), item.Arguments);
186 | }
187 | }
188 |
189 | ///
190 | /// Navigate to the Page for the selected .
191 | ///
192 | ///
193 | ///
194 | private void FeedsList_ItemInvoked(object sender, ListViewItem listViewItem)
195 | {
196 | NavMenuList.SelectedIndex = -1;
197 |
198 | var item = (FeedViewModel)((NavMenuListView)sender).ItemFromContainer(listViewItem);
199 |
200 | if (item != null)
201 | {
202 | AppFrame.Navigate(typeof(MasterDetailPage), item.Link);
203 | }
204 | }
205 |
206 | ///
207 | /// Update the FeedViewModel.IsSelectedInNavList state when the selection changes in the UI.
208 | ///
209 | private void FeedsList_SelectionChanged(object sender, SelectionChangedEventArgs e)
210 | {
211 | if (e.AddedItems.Count == 1) (e.AddedItems[0] as FeedViewModel).IsSelectedInNavList = true;
212 | if (e.RemovedItems.Count == 1) (e.RemovedItems[0] as FeedViewModel).IsSelectedInNavList = false;
213 | }
214 |
215 | public void NavigateToCurrentFeed()
216 | {
217 | FeedsList.SelectedItem = ViewModel.CurrentFeed;
218 | NavMenuList.SelectedIndex = -1;
219 | AppShell.Current.AppFrame.Navigate(typeof(MasterDetailPage), ViewModel.CurrentFeed.Link);
220 | }
221 |
222 | public void NavigateToAddFeedView()
223 | {
224 | NavMenuList.SelectedIndex = 0;
225 | AppFrame.Navigate(typeof(MasterDetailPage), typeof(AddFeedView));
226 | }
227 |
228 | ///
229 | /// Ensures the nav menu reflects reality when navigation is triggered outside of
230 | /// the nav menu buttons.
231 | ///
232 | ///
233 | ///
234 | private void OnNavigatingToPage(object sender, NavigatingCancelEventArgs e)
235 | {
236 | }
237 |
238 | private void OnNavigatedToPage(object sender, NavigationEventArgs e)
239 | {
240 | // After a successful navigation set keyboard focus to the loaded page
241 | if (e.Content is Page && e.Content != null)
242 | {
243 | var control = (Page)e.Content;
244 | control.Loaded += Page_Loaded;
245 | }
246 | }
247 |
248 | private void Page_Loaded(object sender, RoutedEventArgs e)
249 | {
250 | ((Page)sender).Focus(FocusState.Programmatic);
251 | ((Page)sender).Loaded -= Page_Loaded;
252 | }
253 |
254 | #endregion
255 |
256 | public Rect TogglePaneButtonRect { get; private set; }
257 |
258 | ///
259 | /// An event to notify listeners when the hamburger button may occlude other content in the app.
260 | /// The custom "PageHeader" user control is using this.
261 | ///
262 | public event TypedEventHandler TogglePaneButtonRectChanged;
263 |
264 | ///
265 | /// Check for the conditions where the navigation pane does not occupy the space under the floating
266 | /// hamburger button and trigger the event.
267 | ///
268 | private void CheckTogglePaneButtonSizeChanged()
269 | {
270 | TogglePaneButtonRect =
271 | RootSplitView.DisplayMode == SplitViewDisplayMode.Inline ||
272 | RootSplitView.DisplayMode == SplitViewDisplayMode.Overlay
273 | ? TogglePaneButton.TransformToVisual(this).TransformBounds(
274 | new Rect(0, 0, TogglePaneButton.ActualWidth, TogglePaneButton.ActualHeight))
275 | : new Rect();
276 | TogglePaneButtonRectChanged?.Invoke(this, this.TogglePaneButtonRect);
277 | }
278 |
279 | private void Root_SizeChanged(object sender, SizeChangedEventArgs e) => CheckTogglePaneButtonSizeChanged();
280 |
281 | ///
282 | /// Enable accessibility on each nav menu item by setting the AutomationProperties.Name on each container
283 | /// using the associated Label of each item.
284 | ///
285 | private void UpdateAutomationName(ContainerContentChangingEventArgs args, string value)
286 | {
287 | if (!args.InRecycleQueue && args.Item != null && args.Item is T)
288 | {
289 | args.ItemContainer.SetValue(AutomationProperties.NameProperty, value);
290 | }
291 | else
292 | {
293 | args.ItemContainer.ClearValue(AutomationProperties.NameProperty);
294 | }
295 | }
296 |
297 | ///
298 | /// Enable accessibility on each nav menu item by setting the AutomationProperties.Name on each container
299 | /// using the associated Label of each item.
300 | ///
301 | private void NavMenuItemContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args) =>
302 | UpdateAutomationName(args, ((NavMenuItem)args.Item).Label);
303 |
304 | ///
305 | /// Enable accessibility on each nav menu item by setting the AutomationProperties.Name on each container
306 | /// using the associated Label of each item.
307 | ///
308 | private void FeedsListItemContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args) =>
309 | UpdateAutomationName(args, ((FeedViewModel)args.Item)?.Name);
310 | }
311 | }
312 |
--------------------------------------------------------------------------------
/RssReader/Assets/LockScreenLogo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/LockScreenLogo.scale-100.png
--------------------------------------------------------------------------------
/RssReader/Assets/LockScreenLogo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/LockScreenLogo.scale-125.png
--------------------------------------------------------------------------------
/RssReader/Assets/LockScreenLogo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/LockScreenLogo.scale-150.png
--------------------------------------------------------------------------------
/RssReader/Assets/LockScreenLogo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/LockScreenLogo.scale-200.png
--------------------------------------------------------------------------------
/RssReader/Assets/LockScreenLogo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/LockScreenLogo.scale-400.png
--------------------------------------------------------------------------------
/RssReader/Assets/SplashScreen.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/SplashScreen.scale-100.png
--------------------------------------------------------------------------------
/RssReader/Assets/SplashScreen.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/SplashScreen.scale-125.png
--------------------------------------------------------------------------------
/RssReader/Assets/SplashScreen.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/SplashScreen.scale-150.png
--------------------------------------------------------------------------------
/RssReader/Assets/SplashScreen.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/SplashScreen.scale-200.png
--------------------------------------------------------------------------------
/RssReader/Assets/SplashScreen.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/SplashScreen.scale-400.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square150x150Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square150x150Logo.scale-100.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square150x150Logo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square150x150Logo.scale-125.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square150x150Logo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square150x150Logo.scale-150.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square150x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square150x150Logo.scale-200.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square150x150Logo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square150x150Logo.scale-400.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square310x310Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square310x310Logo.scale-100.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square310x310Logo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square310x310Logo.scale-125.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square310x310Logo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square310x310Logo.scale-150.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square310x310Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square310x310Logo.scale-200.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square310x310Logo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square310x310Logo.scale-400.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square44x44Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square44x44Logo.scale-100.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square44x44Logo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square44x44Logo.scale-125.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square44x44Logo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square44x44Logo.scale-150.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square44x44Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square44x44Logo.scale-200.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square44x44Logo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square44x44Logo.scale-400.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square44x44Logo.targetsize-16_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square44x44Logo.targetsize-16_altform-unplated.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square44x44Logo.targetsize-20_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square44x44Logo.targetsize-20_altform-unplated.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square44x44Logo.targetsize-24_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square44x44Logo.targetsize-256_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square44x44Logo.targetsize-256_altform-unplated.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square44x44Logo.targetsize-30_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square44x44Logo.targetsize-30_altform-unplated.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square44x44Logo.targetsize-32_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square44x44Logo.targetsize-32_altform-unplated.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square44x44Logo.targetsize-36_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square44x44Logo.targetsize-36_altform-unplated.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square44x44Logo.targetsize-40_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square44x44Logo.targetsize-40_altform-unplated.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square44x44Logo.targetsize-48_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square44x44Logo.targetsize-48_altform-unplated.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square44x44Logo.targetsize-60_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square44x44Logo.targetsize-60_altform-unplated.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square44x44Logo.targetsize-64_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square44x44Logo.targetsize-64_altform-unplated.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square44x44Logo.targetsize-72_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square44x44Logo.targetsize-72_altform-unplated.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square44x44Logo.targetsize-80_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square44x44Logo.targetsize-80_altform-unplated.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square44x44Logo.targetsize-96_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square44x44Logo.targetsize-96_altform-unplated.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square71x71Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square71x71Logo.scale-100.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square71x71Logo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square71x71Logo.scale-125.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square71x71Logo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square71x71Logo.scale-150.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square71x71Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square71x71Logo.scale-200.png
--------------------------------------------------------------------------------
/RssReader/Assets/Square71x71Logo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Square71x71Logo.scale-400.png
--------------------------------------------------------------------------------
/RssReader/Assets/StoreLogo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/StoreLogo.scale-100.png
--------------------------------------------------------------------------------
/RssReader/Assets/StoreLogo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/StoreLogo.scale-125.png
--------------------------------------------------------------------------------
/RssReader/Assets/StoreLogo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/StoreLogo.scale-150.png
--------------------------------------------------------------------------------
/RssReader/Assets/StoreLogo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/StoreLogo.scale-200.png
--------------------------------------------------------------------------------
/RssReader/Assets/StoreLogo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/StoreLogo.scale-400.png
--------------------------------------------------------------------------------
/RssReader/Assets/Wide310x150Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Wide310x150Logo.scale-100.png
--------------------------------------------------------------------------------
/RssReader/Assets/Wide310x150Logo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Wide310x150Logo.scale-125.png
--------------------------------------------------------------------------------
/RssReader/Assets/Wide310x150Logo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Wide310x150Logo.scale-150.png
--------------------------------------------------------------------------------
/RssReader/Assets/Wide310x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Wide310x150Logo.scale-200.png
--------------------------------------------------------------------------------
/RssReader/Assets/Wide310x150Logo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/Wide310x150Logo.scale-400.png
--------------------------------------------------------------------------------
/RssReader/Assets/feeds.dat:
--------------------------------------------------------------------------------
1 |
2 |
3 | Building Apps for Windows
4 | http://blogs.windows.com/buildingapps/feed
5 |
6 |
7 | Windows Blog
8 | http://blogs.windows.com/feed
9 |
10 |
11 | The Visual Studio Blog
12 | https://blogs.msdn.microsoft.com/visualstudio/feed/
13 |
14 |
15 | .NET Blog
16 | https://blogs.msdn.microsoft.com/dotnet/feed
17 |
18 |
19 | The Fire Hose
20 | http://blogs.microsoft.com/firehose/feed
21 |
22 |
23 | Official Microsoft Blog
24 | http://blogs.microsoft.com/feed
25 |
26 |
27 | Microsoft on the Issues
28 | http://blogs.microsoft.com/on-the-issues/feed
29 |
30 |
31 | Next at Microsoft
32 | http://blogs.microsoft.com/next/feed
33 |
34 |
35 | Internet of Things
36 | http://blogs.microsoft.com/iot/feed
37 |
38 |
--------------------------------------------------------------------------------
/RssReader/Assets/icon_feederror1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/icon_feederror1.png
--------------------------------------------------------------------------------
/RssReader/Assets/icon_noeditfeeds.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/icon_noeditfeeds.png
--------------------------------------------------------------------------------
/RssReader/Assets/icon_nofavs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/Windows-appsample-rssreader/19ea7832a3826bad611a92c394e1de29e8286390/RssReader/Assets/icon_nofavs.png
--------------------------------------------------------------------------------
/RssReader/Common/BindableBase.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System;
26 | using System.ComponentModel;
27 | using System.Runtime.CompilerServices;
28 |
29 | namespace RssReader.Common
30 | {
31 | ///
32 | /// Provides a standard change-notification implementation.
33 | ///
34 | public abstract class BindableBase : INotifyPropertyChanged
35 | {
36 | ///
37 | /// Occurs when a property value changes.
38 | ///
39 | public event PropertyChangedEventHandler PropertyChanged;
40 |
41 | ///
42 | /// Raises the PropertyChanged event for the property with the specified
43 | /// name, or the calling property if no name is specified.
44 | ///
45 | public void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
46 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
47 |
48 | ///
49 | /// Checks whether the value of the specified field is different than the specified value, and
50 | /// if they are different, updates the field and raises the PropertyChanged event for
51 | /// the property with the specified name, or the calling property if no name is specified.
52 | ///
53 | protected bool SetProperty(ref T storage, T value,
54 | [CallerMemberName] String propertyName = null)
55 | {
56 | if (object.Equals(storage, value)) return false;
57 | storage = value;
58 | OnPropertyChanged(propertyName);
59 | return true;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/RssReader/Common/BooleanToVisibilityConverter.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System;
26 | using Windows.UI.Xaml;
27 | using Windows.UI.Xaml.Data;
28 |
29 | namespace RssReader.Common
30 | {
31 | ///
32 | /// Converts between Boolean and Visibility values.
33 | ///
34 | public class BooleanToVisibilityConverter : IValueConverter
35 | {
36 | ///
37 | /// Converts true values to Visibility.Visible and false values to
38 | /// Visibility.Collapsed, or the reverse if the parameter is "Reverse".
39 | ///
40 | public object Convert(object value, Type targetType, object parameter, string language) =>
41 | (bool)value ^ (parameter as string ?? string.Empty).Equals("Reverse") ?
42 | Visibility.Visible : Visibility.Collapsed;
43 |
44 | ///
45 | /// Converts Visibility.Visible values to true and Visibility.Collapsed
46 | /// values to false, or the reverse if the parameter is "Reverse".
47 | ///
48 | public object ConvertBack(object value, Type targetType, object parameter, string language) =>
49 | (Visibility)value == Visibility.Visible ^ (parameter as string ?? string.Empty).Equals("Reverse");
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/RssReader/Common/Canceller.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System;
26 | using System.Threading;
27 |
28 | namespace RssReader.Common
29 | {
30 | ///
31 | /// Encapsulates the thread-cancellation mechanism.
32 | ///
33 | public class Canceller : IDisposable
34 | {
35 | ///
36 | /// Gets or sets the current token source, which will
37 | /// be reset on the next call to the Cancel method.
38 | ///
39 | private CancellationTokenSource TokenSource { get; set; } = new CancellationTokenSource();
40 |
41 | ///
42 | /// Gets the token from the current token source, which
43 | /// you can cancel with the next call to the Cancel method.
44 | ///
45 | public CancellationToken Token => TokenSource.Token;
46 |
47 | ///
48 | /// Cancels the current Token and resets the TokenSource.
49 | ///
50 | public void Cancel()
51 | {
52 | TokenSource.Cancel();
53 | var t = Token;
54 | TokenSource.Dispose();
55 | TokenSource = new CancellationTokenSource();
56 | }
57 |
58 | /// Releases resources used by the class.
59 | ~Canceller() { Dispose(false); }
60 |
61 | /// Releases resources used by this class.
62 | public void Dispose()
63 | {
64 | Dispose(true);
65 | GC.SuppressFinalize(this);
66 | }
67 |
68 | /// Releases resources used by this class.
69 | /// true to release both managed and unmanaged resources;
70 | /// false to release only unmanaged resources.
71 | protected virtual void Dispose(bool disposing)
72 | {
73 | if (!_isDisposed) return;
74 | if (disposing) TokenSource.Dispose();
75 | _isDisposed = true;
76 | }
77 | private bool _isDisposed = false;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/RssReader/Common/Helpers.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System;
26 | using System.Linq.Expressions;
27 | using System.Reflection;
28 | using System.Text.RegularExpressions;
29 | using System.Threading.Tasks;
30 | using Windows.ApplicationModel.Core;
31 | using Windows.System;
32 | using Windows.UI.Xaml.Controls;
33 | using Windows.UI.Xaml.Input;
34 |
35 | namespace RssReader.Common
36 | {
37 | ///
38 | /// Encapsulates handy helper and extension methods.
39 | ///
40 | public static class Helpers
41 | {
42 | ///
43 | /// Performs the specified action after the specified delay in milliseconds.
44 | ///
45 | public static void DoAfterDelay(int millisecondsDelay, Action action)
46 | {
47 | var withoutAwait = CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
48 | Windows.UI.Core.CoreDispatcherPriority.Normal,
49 | async () => { await Task.Delay(millisecondsDelay); action(); });
50 | }
51 |
52 | ///
53 | /// Processes TextBox.KeyDown event data to determine whether ESC or ENTER have been pressed;
54 | /// for ESC, clears the text box; for ENTER, updates the target object by committing the
55 | /// TextBox.Text value to the property indicated by the specified property expression.
56 | ///
57 | public static void HandleEnterAndEscape(this KeyRoutedEventArgs e, object sender,
58 | object target, Expression> propertyExpression)
59 | {
60 | var textbox = sender as TextBox;
61 | if (e.Key == VirtualKey.Enter)
62 | {
63 | ((propertyExpression.Body as MemberExpression).Member as PropertyInfo)
64 | .SetValue(target, textbox.Text);
65 | }
66 | else if (e.Key == VirtualKey.Escape) textbox.Text = string.Empty;
67 | }
68 |
69 | ///
70 | /// Removes regular-expression pattern matches from the
71 | /// string and returns the result as a new string.
72 | ///
73 | public static String RegexRemove(this string input, string pattern) =>
74 | Regex.Replace(input, pattern, string.Empty);
75 |
76 | ///
77 | /// Gets a value that indicates whether the Uri has the same host and path as the specified Uri.
78 | ///
79 | public static bool IsEquivalentTo(this Uri uri, Uri uriToMatch) =>
80 | Uri.Compare(uri, uriToMatch, UriComponents.Host | UriComponents.Path,
81 | UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) == 0;
82 |
83 | ///
84 | /// Compares the URI to the attempted WebView navigation URI.
85 | /// If they are different, cancels the WebView navigation, opens the URI
86 | /// in the browser, and returns true; otherwise, returns false.
87 | ///
88 | public static async Task LaunchBrowserForNonMatchingUriAsync(
89 | this WebViewNavigationStartingEventArgs e, Uri uriToMatch)
90 | {
91 | if (e.Uri.IsEquivalentTo(uriToMatch)) return false;
92 | e.Cancel = true;
93 | await Launcher.LaunchUriAsync(e.Uri);
94 | return true;
95 | }
96 |
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/RssReader/Common/Serializer.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System.IO;
26 | using System.Runtime.Serialization;
27 |
28 | namespace RssReader.Common
29 | {
30 | ///
31 | /// Provides basic serialization and deserialization functions.
32 | ///
33 | public static class Serializer
34 | {
35 | ///
36 | /// Serializes the specified object as a byte array.
37 | ///
38 | public static byte[] Serialize(T obj)
39 | {
40 | MemoryStream stream = new MemoryStream();
41 | DataContractSerializer dcs = new DataContractSerializer(typeof(T));
42 | dcs.WriteObject(stream, obj);
43 | return stream.ToArray();
44 | }
45 |
46 | ///
47 | /// Deserializes the specified byte array as an instance of type T.
48 | ///
49 | public static T Deserialize(byte[] buffer)
50 | {
51 | MemoryStream stream = new MemoryStream(buffer);
52 | DataContractSerializer dcs = new DataContractSerializer(typeof(T));
53 | return (T)dcs.ReadObject(stream);
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/RssReader/Controls/NavMenuListView.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System;
26 | using Windows.System;
27 | using Windows.UI.Core;
28 | using Windows.UI.Xaml;
29 | using Windows.UI.Xaml.Controls;
30 | using Windows.UI.Xaml.Input;
31 | using Windows.UI.Xaml.Media;
32 | using Windows.UI.Xaml.Media.Animation;
33 |
34 | namespace RssReader.Controls
35 | {
36 | ///
37 | /// A specialized ListView to represent the items in the navigation menu.
38 | ///
39 | ///
40 | /// This class handles the following:
41 | /// 1. Sizes the panel that hosts the items so they fit in the hosting pane. Otherwise, the keyboard
42 | /// may appear cut off on one side b/c the Pane clips instead of affecting layout.
43 | /// 2. Provides a single selection experience where keyboard focus can move without changing selection.
44 | /// Both the 'Space' and 'Enter' keys will trigger selection. The up/down arrow keys can move
45 | /// keyboard focus without triggering selection. This is different than the default behavior when
46 | /// SelectionMode == Single. The default behavior for a ListView in single selection requires using
47 | /// the Ctrl + arrow key to move keyboard focus without triggering selection. Users won't expect
48 | /// this type of keyboarding model on the nav menu.
49 | ///
50 | public class NavMenuListView : ListView
51 | {
52 | private SplitView splitViewHost;
53 |
54 | public NavMenuListView()
55 | {
56 | this.SelectionMode = ListViewSelectionMode.Single;
57 | this.IsItemClickEnabled = true;
58 | this.ItemClick += ItemClickedHandler;
59 |
60 | // Locate the hosting SplitView control
61 | this.Loaded += (s, a) =>
62 | {
63 | var parent = VisualTreeHelper.GetParent(this);
64 | while (parent != null && !(parent is Page) && !(parent is SplitView))
65 | {
66 | parent = VisualTreeHelper.GetParent(parent);
67 | }
68 |
69 | if (parent != null && parent is SplitView)
70 | {
71 | this.splitViewHost = parent as SplitView;
72 |
73 | splitViewHost.RegisterPropertyChangedCallback(SplitView.IsPaneOpenProperty, (sender, args) =>
74 | {
75 | this.OnPaneToggled();
76 | });
77 |
78 | // Call once to ensure we're in the correct state
79 | this.OnPaneToggled();
80 | }
81 | };
82 | }
83 |
84 | protected override void OnApplyTemplate()
85 | {
86 | base.OnApplyTemplate();
87 |
88 | // Remove the entrance animation on the item containers.
89 | for (int i = 0; i < this.ItemContainerTransitions.Count; i++)
90 | {
91 | if (this.ItemContainerTransitions[i] is EntranceThemeTransition)
92 | {
93 | this.ItemContainerTransitions.RemoveAt(i);
94 | }
95 | }
96 | }
97 |
98 | ///
99 | /// Mark the as selected and ensures everything else is not.
100 | /// If the is null then everything is unselected.
101 | ///
102 | ///
103 | public void SetSelectedItem(ListViewItem item)
104 | {
105 | int index = -1;
106 | if (item != null)
107 | {
108 | index = this.IndexFromContainer(item);
109 | }
110 |
111 | for (int i = 0; i < this.Items.Count; i++)
112 | {
113 | var lvi = (ListViewItem)this.ContainerFromIndex(i);
114 | if (lvi != null)
115 | {
116 | if (i != index)
117 | {
118 | lvi.IsSelected = false;
119 | }
120 | else if (i == index)
121 | {
122 | lvi.IsSelected = true;
123 | }
124 | }
125 | }
126 | }
127 |
128 | ///
129 | /// Occurs when an item has been selected
130 | ///
131 | public event EventHandler ItemInvoked;
132 |
133 | ///
134 | /// Custom keyboarding logic to enable movement via the arrow keys without triggering selection
135 | /// until a 'Space' or 'Enter' key is pressed.
136 | ///
137 | ///
138 | protected override void OnKeyDown(KeyRoutedEventArgs e)
139 | {
140 | var focusedItem = FocusManager.GetFocusedElement();
141 |
142 | switch (e.Key)
143 | {
144 | case VirtualKey.Up:
145 | this.TryMoveFocus(FocusNavigationDirection.Up);
146 | e.Handled = true;
147 | break;
148 |
149 | case VirtualKey.Down:
150 | this.TryMoveFocus(FocusNavigationDirection.Down);
151 | e.Handled = true;
152 | break;
153 |
154 | case VirtualKey.Tab:
155 | var shiftKeyState = CoreWindow.GetForCurrentThread().GetKeyState(VirtualKey.Shift);
156 | var shiftKeyDown = (shiftKeyState & CoreVirtualKeyStates.Down) == CoreVirtualKeyStates.Down;
157 |
158 | // If we're on the header item then this will be null and we'll still get the default behavior.
159 | if (focusedItem is ListViewItem)
160 | {
161 | var currentItem = (ListViewItem)focusedItem;
162 | bool onlastitem = currentItem != null && this.IndexFromContainer(currentItem) == this.Items.Count - 1;
163 | bool onfirstitem = currentItem != null && this.IndexFromContainer(currentItem) == 0;
164 |
165 | if (!shiftKeyDown)
166 | {
167 | if (onlastitem)
168 | {
169 | this.TryMoveFocus(FocusNavigationDirection.Next);
170 | }
171 | else
172 | {
173 | this.TryMoveFocus(FocusNavigationDirection.Down);
174 | }
175 | }
176 | else // Shift + Tab
177 | {
178 | if (onfirstitem)
179 | {
180 | this.TryMoveFocus(FocusNavigationDirection.Previous);
181 | }
182 | else
183 | {
184 | this.TryMoveFocus(FocusNavigationDirection.Up);
185 | }
186 | }
187 | }
188 | else if (focusedItem is Control)
189 | {
190 | if (!shiftKeyDown)
191 | {
192 | this.TryMoveFocus(FocusNavigationDirection.Down);
193 | }
194 | else // Shift + Tab
195 | {
196 | this.TryMoveFocus(FocusNavigationDirection.Up);
197 | }
198 | }
199 |
200 | e.Handled = true;
201 | break;
202 |
203 | case VirtualKey.Space:
204 | case VirtualKey.Enter:
205 | // Fire our event using the item with current keyboard focus
206 | this.InvokeItem(focusedItem);
207 | e.Handled = true;
208 | break;
209 |
210 | default:
211 | base.OnKeyDown(e);
212 | break;
213 | }
214 | }
215 |
216 | ///
217 | /// This method is a work-around until the bug in FocusManager.TryMoveFocus is fixed.
218 | ///
219 | ///
220 | private void TryMoveFocus(FocusNavigationDirection direction)
221 | {
222 | if (direction == FocusNavigationDirection.Next || direction == FocusNavigationDirection.Previous)
223 | {
224 | FocusManager.TryMoveFocus(direction);
225 | }
226 | else
227 | {
228 | var control = FocusManager.FindNextFocusableElement(direction) as Control;
229 | if (control != null)
230 | {
231 | control.Focus(FocusState.Programmatic);
232 | }
233 | if (control is ListViewItem)
234 | {
235 | var item = control as ListViewItem;
236 | ScrollIntoView(item.Content);
237 | }
238 | }
239 | }
240 |
241 | private void ItemClickedHandler(object sender, ItemClickEventArgs e)
242 | {
243 | // Triggered when the item is selected using something other than a keyboard
244 | var item = this.ContainerFromItem(e.ClickedItem);
245 | this.InvokeItem(item);
246 | }
247 |
248 | private void InvokeItem(object focusedItem)
249 | {
250 | this.SetSelectedItem(focusedItem as ListViewItem);
251 | this.ItemInvoked?.Invoke(this, focusedItem as ListViewItem);
252 |
253 | if (this.splitViewHost == null || this.splitViewHost.IsPaneOpen)
254 | {
255 | if (this.splitViewHost != null &&
256 | (this.splitViewHost.DisplayMode == SplitViewDisplayMode.CompactOverlay ||
257 | this.splitViewHost.DisplayMode == SplitViewDisplayMode.Overlay))
258 | {
259 | this.splitViewHost.IsPaneOpen = false;
260 | }
261 | if (focusedItem is ListViewItem)
262 | {
263 | ((ListViewItem)focusedItem).Focus(FocusState.Programmatic);
264 | }
265 | }
266 | }
267 |
268 | ///
269 | /// Re-size the ListView's Panel when the SplitView is compact so the items
270 | /// will fit within the visible space and correctly display a keyboard focus rect.
271 | ///
272 | private void OnPaneToggled()
273 | {
274 | if (this.ItemsPanelRoot == null) return;
275 | if (this.splitViewHost.IsPaneOpen)
276 | {
277 | this.ItemsPanelRoot.ClearValue(FrameworkElement.WidthProperty);
278 | this.ItemsPanelRoot.ClearValue(FrameworkElement.HorizontalAlignmentProperty);
279 | }
280 | else if (this.splitViewHost.DisplayMode == SplitViewDisplayMode.CompactInline ||
281 | this.splitViewHost.DisplayMode == SplitViewDisplayMode.CompactOverlay)
282 | {
283 | this.ItemsPanelRoot.SetValue(FrameworkElement.WidthProperty, this.splitViewHost.CompactPaneLength);
284 | this.ItemsPanelRoot.SetValue(FrameworkElement.HorizontalAlignmentProperty, HorizontalAlignment.Left);
285 | }
286 | }
287 | }
288 | }
289 |
--------------------------------------------------------------------------------
/RssReader/Controls/PageHeader.xaml:
--------------------------------------------------------------------------------
1 |
24 |
25 |
41 |
42 |
43 |
44 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/RssReader/Controls/PageHeader.xaml.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using Windows.Foundation;
26 | using Windows.UI.Xaml;
27 | using Windows.UI.Xaml.Controls;
28 |
29 | namespace RssReader.Controls
30 | {
31 | public sealed partial class PageHeader : UserControl
32 | {
33 | private static readonly double DEFAULT_LEFT_MARGIN = 24;
34 |
35 | public PageHeader()
36 | {
37 | this.InitializeComponent();
38 |
39 | this.Loaded += (s, a) =>
40 | {
41 | AppShell.Current.TogglePaneButtonRectChanged += Current_TogglePaneButtonSizeChanged;
42 | double leftMargin = AppShell.Current.TogglePaneButtonRect.Right;
43 | leftMargin = leftMargin > 0 ? leftMargin : DEFAULT_LEFT_MARGIN;
44 | TitleBar.Margin = new Thickness(leftMargin, 0, 0, 0);
45 | };
46 | }
47 |
48 | private void Current_TogglePaneButtonSizeChanged(AppShell sender, Rect e)
49 | {
50 | // If there is no adjustment due to the toggle button, use the default left margin.
51 | TitleBar.Margin = new Thickness(e.Right == 0 ? DEFAULT_LEFT_MARGIN : e.Right, 0, 0, 0);
52 | }
53 |
54 | public UIElement HeaderContent
55 | {
56 | get { return (UIElement)GetValue(HeaderContentProperty); }
57 | set { SetValue(HeaderContentProperty, value); }
58 | }
59 |
60 | public static readonly DependencyProperty HeaderContentProperty =
61 | DependencyProperty.Register("HeaderContent", typeof(UIElement), typeof(PageHeader), new PropertyMetadata(DependencyProperty.UnsetValue));
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/RssReader/NavMenuItem.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using System;
26 | using Windows.UI.Xaml.Controls;
27 |
28 | namespace RssReader
29 | {
30 | ///
31 | /// Data to represent an item in the nav menu.
32 | ///
33 | public class NavMenuItem
34 | {
35 | public string Label { get; set; }
36 | public Symbol Symbol { get; set; }
37 | public char SymbolAsChar => (char)Symbol;
38 | public Type DestPage { get; set; }
39 | public object Arguments { get; set; }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/RssReader/Package.appxmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | RssReader
7 | Microsoft Corporation
8 | Assets\StoreLogo.png
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/RssReader/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("RssReader")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("RssReader")]
13 | [assembly: AssemblyCopyright("Copyright © 2015")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Version information for an assembly consists of the following four values:
18 | //
19 | // Major Version
20 | // Minor Version
21 | // Build Number
22 | // Revision
23 | //
24 | // You can specify all the values or you can default the Build and Revision Numbers
25 | // by using the '*' as shown below:
26 | // [assembly: AssemblyVersion("1.0.*")]
27 | [assembly: AssemblyVersion("1.0.0.0")]
28 | [assembly: AssemblyFileVersion("1.0.0.0")]
29 | [assembly: ComVisible(false)]
--------------------------------------------------------------------------------
/RssReader/Properties/Default.rd.xml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/RssReader/Properties/DesignTimeResources.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/RssReader/RSSReader.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | x86
7 | {81C6601D-A4FE-4B6E-97C8-BD9FAEFC03DA}
8 | AppContainerExe
9 | Properties
10 | RssReader
11 | RssReader
12 | en-US
13 | UAP
14 | 10.0.19041.0
15 | 10.0.17763.0
16 | 14
17 | true
18 | 512
19 | {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
20 | 3298e190
21 | Always
22 | x86|x64|arm
23 | win10-arm;win10-arm-aot;win10-x86;win10-x86-aot;win10-x64;win10-x64-aot
24 |
25 |
26 | true
27 | bin\x86\Debug\
28 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UAP
29 | ;2008
30 | full
31 | x86
32 | false
33 | prompt
34 | true
35 |
36 |
37 | bin\x86\Release\
38 | TRACE;NETFX_CORE;WINDOWS_UAP
39 | true
40 | ;2008
41 | pdbonly
42 | x86
43 | false
44 | prompt
45 | true
46 | true
47 |
48 |
49 | true
50 | bin\ARM\Debug\
51 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UAP
52 | ;2008
53 | full
54 | ARM
55 | false
56 | prompt
57 | true
58 |
59 |
60 | bin\ARM\Release\
61 | TRACE;NETFX_CORE;WINDOWS_UAP
62 | true
63 | ;2008
64 | pdbonly
65 | ARM
66 | false
67 | prompt
68 | true
69 | true
70 |
71 |
72 | true
73 | bin\x64\Debug\
74 | DEBUG;TRACE;NETFX_CORE;WINDOWS_UAP
75 | ;2008
76 | full
77 | x64
78 | false
79 | prompt
80 | true
81 |
82 |
83 | bin\x64\Release\
84 | TRACE;NETFX_CORE;WINDOWS_UAP
85 | true
86 | ;2008
87 | pdbonly
88 | x64
89 | false
90 | prompt
91 | true
92 | true
93 |
94 |
95 |
96 | App.xaml
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | PageHeader.xaml
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | AppShell.xaml
114 |
115 |
116 |
117 | AddFeedView.xaml
118 |
119 |
120 | DetailPage.xaml
121 |
122 |
123 | EditFeedsView.xaml
124 |
125 |
126 | FeedView.xaml
127 |
128 |
129 | MasterDetailPage.xaml
130 |
131 |
132 |
133 |
134 | Designer
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 | MSBuild:Compile
200 | Designer
201 |
202 |
203 | Designer
204 | MSBuild:Compile
205 |
206 |
207 | Designer
208 | MSBuild:Compile
209 | PreserveNewest
210 | true
211 |
212 |
213 | Designer
214 | MSBuild:Compile
215 | PreserveNewest
216 |
217 |
218 | Designer
219 | MSBuild:Compile
220 |
221 |
222 | Designer
223 | MSBuild:Compile
224 |
225 |
226 | Designer
227 | MSBuild:Compile
228 |
229 |
230 | Designer
231 | MSBuild:Compile
232 |
233 |
234 | Designer
235 | MSBuild:Compile
236 |
237 |
238 | Designer
239 | MSBuild:Compile
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 | 6.2.10
248 |
249 |
250 | 2.4.2
251 |
252 |
253 |
254 | 14.0
255 |
256 |
257 |
264 |
--------------------------------------------------------------------------------
/RssReader/ViewModels/ArticleViewModel.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using RssReader.Common;
26 | using System;
27 |
28 | namespace RssReader.ViewModels
29 | {
30 | ///
31 | /// Represents an article in an RSS feed and user interactions with the article.
32 | ///
33 | public class ArticleViewModel : BindableBase
34 | {
35 | ///
36 | /// Gets or sets the title of the article.
37 | ///
38 | public string Title { get; set; }
39 |
40 | ///
41 | /// Gets or sets a summary describing the article contents.
42 | ///
43 | public string Summary { get; set; }
44 |
45 | ///
46 | /// Gets or sets a string that indicates the article author(s).
47 | ///
48 | public string Author { get; set; }
49 |
50 | ///
51 | /// Gets or sets the URI of the article.
52 | ///
53 | public Uri Link { get; set; }
54 |
55 | ///
56 | /// Gets or sets the date that the article was published.
57 | ///
58 | public DateTimeOffset PublishedDate { get; set; }
59 |
60 | ///
61 | /// Gets a formatted version of the article's publication date.
62 | ///
63 | public string PublishedDateFormatted => PublishedDate.ToString("MMM dd, yyyy h:mm tt").ToUpper();
64 |
65 | ///
66 | /// Updates the FavoritesFeed when an article is starred or unstarred.
67 | ///
68 | public void SyncFavoritesFeed() => AppShell.Current.ViewModel.SyncFavoritesFeed(this);
69 |
70 | ///
71 | /// Determines whether the specified object is equal to the current object.
72 | ///
73 | public override bool Equals(object obj) =>
74 | obj is ArticleViewModel ? (obj as ArticleViewModel).GetHashCode() == GetHashCode() : false;
75 |
76 | ///
77 | /// Returns the hash code of the ArticleViewModel, which is based on
78 | /// a string representation the Link value, using only the host and path.
79 | ///
80 | public override int GetHashCode() =>
81 | Link.GetComponents(UriComponents.Host | UriComponents.Path, UriFormat.Unescaped).GetHashCode();
82 |
83 | ///
84 | /// Gets or sets a value that indicates whether the user has starred the article.
85 | ///
86 | public bool? IsStarred { get { return _isStarred; } set { SetProperty(ref _isStarred, value); } }
87 | private bool? _isStarred = false;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/RssReader/ViewModels/FeedDataSource.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using RssReader.Common;
26 | using System;
27 | using System.Collections.Generic;
28 | using System.Linq;
29 | using System.Runtime.InteropServices.WindowsRuntime;
30 | using System.Threading;
31 | using System.Threading.Tasks;
32 | using Windows.Storage;
33 | using Windows.UI.Xaml.Controls;
34 | using Windows.Web.Syndication;
35 |
36 | namespace RssReader.ViewModels
37 | {
38 | ///
39 | /// Encapsulates methods that retrieve and save RSS feed data.
40 | ///
41 | public static class FeedDataSource
42 | {
43 | ///
44 | /// Gets the favorites feed, either from local storage,
45 | /// or by initializing a new FeedViewModel instance.
46 | ///
47 | public static async Task GetFavoritesAsync()
48 | {
49 | var favoritesFile = await ApplicationData.Current.LocalFolder
50 | .TryGetItemAsync("favorites.dat") as StorageFile;
51 | if (favoritesFile != null)
52 | {
53 | var buffer = await FileIO.ReadBufferAsync(favoritesFile);
54 | return Serializer.Deserialize(buffer.ToArray());
55 | }
56 | else
57 | {
58 | return new FeedViewModel
59 | {
60 | Name = "Favorites",
61 | Description = "Articles that you've starred",
62 | Symbol = Symbol.OutlineStar,
63 | Link = new Uri("http://localhost"),
64 | IsFavoritesFeed = true
65 | };
66 | }
67 | }
68 |
69 | ///
70 | /// Gets the initial set of feeds, either from local storage or
71 | /// from the app package if there is nothing in local storage.
72 | ///
73 | public static async Task> GetFeedsAsync()
74 | {
75 | var feeds = new List();
76 | var feedFile =
77 | await ApplicationData.Current.LocalFolder.TryGetItemAsync("feeds.dat") as StorageFile ??
78 | await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/feeds.dat"));
79 | if (feedFile != null)
80 | {
81 | var bytes = (await FileIO.ReadBufferAsync(feedFile)).ToArray();
82 | var feedData = Serializer.Deserialize(bytes);
83 | foreach (var feed in feedData)
84 | {
85 | var feedVM = new FeedViewModel { Name = feed[0], Link = new Uri(feed[1]) };
86 | feeds.Add(feedVM);
87 | var withoutAwait = feedVM.RefreshAsync();
88 | }
89 | }
90 | return feeds;
91 | }
92 |
93 | ///
94 | /// Retrieves feed data from the server and updates the appropriate FeedViewModel properties.
95 | ///
96 | private static async Task TryGetFeedAsync(FeedViewModel feedViewModel, CancellationToken? cancellationToken = null)
97 | {
98 | try
99 | {
100 | var feed = await new SyndicationClient().RetrieveFeedAsync(feedViewModel.Link);
101 |
102 | if (cancellationToken.HasValue && cancellationToken.Value.IsCancellationRequested) return false;
103 |
104 | feedViewModel.LastSyncDateTime = DateTime.Now;
105 | feedViewModel.Name = String.IsNullOrEmpty(feedViewModel.Name) ? feed.Title.Text : feedViewModel.Name;
106 | feedViewModel.Description = feed.Subtitle?.Text ?? feed.Title.Text;
107 |
108 | feed.Items.Select(item => new ArticleViewModel
109 | {
110 | Title = item.Title.Text,
111 | Summary = item.Summary == null ? string.Empty :
112 | item.Summary.Text.RegexRemove("\\&.{0,4}\\;").RegexRemove("<.*?>"),
113 | Author = item.Authors.Select(a => a.NodeValue).FirstOrDefault(),
114 | Link = item.ItemUri ?? item.Links.Select(l => l.Uri).FirstOrDefault(),
115 | PublishedDate = item.PublishedDate
116 | })
117 | .ToList().ForEach(article =>
118 | {
119 | var favorites = AppShell.Current.ViewModel.FavoritesFeed;
120 | var existingCopy = favorites.Articles.FirstOrDefault(a => a.Equals(article));
121 | article = existingCopy ?? article;
122 | if (!feedViewModel.Articles.Contains(article)) feedViewModel.Articles.Add(article);
123 | });
124 | feedViewModel.IsInError = false;
125 | feedViewModel.ErrorMessage = null;
126 | return true;
127 | }
128 | catch (Exception)
129 | {
130 | if (!cancellationToken.HasValue || !cancellationToken.Value.IsCancellationRequested)
131 | {
132 | feedViewModel.IsInError = true;
133 | feedViewModel.ErrorMessage = feedViewModel.Articles.Count == 0 ? BAD_URL_MESSAGE : NO_REFRESH_MESSAGE;
134 | }
135 | return false;
136 | }
137 | }
138 |
139 | ///
140 | /// Attempts to update the feed with new data from the server.
141 | ///
142 | public static async Task RefreshAsync(this FeedViewModel feedViewModel, CancellationToken? cancellationToken = null)
143 | {
144 | if (feedViewModel.Link.Host == "localhost" ||
145 | (feedViewModel.Link.Scheme != "http" && feedViewModel.Link.Scheme != "https")) return;
146 |
147 | feedViewModel.IsLoading = true;
148 |
149 | int numberOfAttempts = 5;
150 | bool success = false;
151 | do { success = await TryGetFeedAsync(feedViewModel, cancellationToken); }
152 | while (!success && numberOfAttempts-- > 0 &&
153 | (!cancellationToken.HasValue || !cancellationToken.Value.IsCancellationRequested));
154 |
155 | feedViewModel.IsLoading = false;
156 | }
157 |
158 | ///
159 | /// Saves the favorites feed (the first feed of the feeds list) to local storage.
160 | ///
161 | public static async Task SaveFavoritesAsync(this FeedViewModel favorites)
162 | {
163 | var file = await ApplicationData.Current.LocalFolder
164 | .CreateFileAsync("favorites.dat", CreationCollisionOption.ReplaceExisting);
165 | byte[] array = Serializer.Serialize(favorites);
166 | await FileIO.WriteBytesAsync(file, array);
167 | }
168 |
169 | ///
170 | /// Saves the feed data (not including the Favorites feed) to local storage.
171 | ///
172 | public static async Task SaveAsync(this IEnumerable feeds)
173 | {
174 | var file = await ApplicationData.Current.LocalFolder
175 | .CreateFileAsync("feeds.dat", CreationCollisionOption.ReplaceExisting);
176 | byte[] array = Serializer.Serialize(feeds.Select(feed => new[] { feed.Name, feed.Link.ToString() }).ToArray());
177 | await FileIO.WriteBytesAsync(file, array);
178 | }
179 |
180 | private const string BAD_URL_MESSAGE = "Hmm... Are you sure this is an RSS URL?";
181 | private const string NO_REFRESH_MESSAGE = "Sorry. We can't get more articles right now.";
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/RssReader/ViewModels/FeedViewModel.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using RssReader.Common;
26 | using System;
27 | using System.Collections.ObjectModel;
28 | using System.Runtime.Serialization;
29 | using Windows.UI.Xaml.Controls;
30 |
31 | namespace RssReader.ViewModels
32 | {
33 | ///
34 | /// Represents an RSS feed and user interactions with the feed.
35 | ///
36 | public class FeedViewModel : BindableBase
37 | {
38 | ///
39 | /// Initializes a new instance of the FeedViewModel class.
40 | ///
41 | public FeedViewModel()
42 | {
43 | Articles = new ObservableCollection();
44 | Articles.CollectionChanged += (s, e) =>
45 | {
46 | OnPropertyChanged(nameof(IsEmpty));
47 | OnPropertyChanged(nameof(IsNotEmpty));
48 | OnPropertyChanged(nameof(IsInErrorAndEmpty));
49 | OnPropertyChanged(nameof(IsInErrorAndNotEmpty));
50 | OnPropertyChanged(nameof(IsLoadingAndNotEmpty));
51 | };
52 | }
53 |
54 | ///
55 | /// Gets or sets the URI of the feed.
56 | ///
57 | public Uri Link
58 | {
59 | get { return _link; }
60 | set { if (SetProperty(ref _link, value)) OnPropertyChanged(nameof(LinkAsString)); }
61 | }
62 | private Uri _link;
63 |
64 | ///
65 | /// Gets or sets a string representation of the URI of the feed.
66 | ///
67 | [IgnoreDataMember] public string LinkAsString
68 | {
69 | get { return Link?.OriginalString ?? String.Empty; }
70 | set
71 | {
72 | if (string.IsNullOrWhiteSpace(value)) return;
73 |
74 | if (!value.Trim().StartsWith("http://") && !value.Trim().StartsWith("https://"))
75 | {
76 | IsInError = true;
77 | ErrorMessage = NOT_HTTP_MESSAGE;
78 | }
79 | else
80 | {
81 | Uri uri = null;
82 | if (Uri.TryCreate(value.Trim(), UriKind.Absolute, out uri)) Link = uri;
83 | else
84 | {
85 | IsInError = true;
86 | ErrorMessage = INVALID_URL_MESSAGE;
87 | }
88 | }
89 | }
90 | }
91 |
92 | ///
93 | /// Gets the name of the feed.
94 | ///
95 | public string Name { get { return _name; } set { SetProperty(ref _name, value); } }
96 | private string _name;
97 |
98 | ///
99 | /// Gets a description of the feed.
100 | ///
101 | public string Description { get { return _description; } set { SetProperty(ref _description, value); } }
102 | private string _description;
103 |
104 | ///
105 | /// Gets the symbol that represents the feed in the navigation pane.
106 | ///
107 | public Symbol Symbol
108 | {
109 | get { return _symbol; }
110 | set { if (SetProperty(ref _symbol, value)) OnPropertyChanged(nameof(SymbolAsChar)); }
111 | }
112 | private Symbol _symbol = Symbol.PostUpdate;
113 |
114 | ///
115 | /// Gets a character representation of the symbol that represents the feed in the navigation pane.
116 | ///
117 | public char SymbolAsChar => (char)Symbol;
118 |
119 | ///
120 | /// Gets the collection of articles that have been loaded for this feed.
121 | ///
122 | public ObservableCollection Articles { get; }
123 |
124 | ///
125 | /// Gets the articles collection as an instance of type Object.
126 | ///
127 | public object ArticlesAsObject => Articles as object;
128 |
129 | ///
130 | /// Gets a value that indicates whether the articles collection is empty.
131 | ///
132 | public bool IsEmpty => Articles.Count == 0;
133 |
134 | ///
135 | /// Gets a value that indicates whether the feed has at least one article.
136 | ///
137 | public bool IsNotEmpty => !IsEmpty;
138 |
139 | ///
140 | /// Gets or sets a value that indicates whether the feed represents the collection of starred articles.
141 | ///
142 | public bool IsFavoritesFeed { get; set; }
143 |
144 | ///
145 | /// Gets a value that indicates whether the feed is a normal (non-favorites) feed that isn't in error.
146 | ///
147 | public bool IsNotFavoritesOrInError => !IsFavoritesFeed && !IsInError;
148 |
149 | ///
150 | /// Gets or sets the date and time of the last successful article retrieval.
151 | ///
152 | public DateTime LastSyncDateTime { get { return _lastSyncDateTime; } set { SetProperty(ref _lastSyncDateTime, value); } }
153 | private DateTime _lastSyncDateTime;
154 |
155 | ///
156 | /// Gets a message to display when new articles cannot be retrieved.
157 | ///
158 | public string FeedDownMessage
159 | {
160 | get
161 | {
162 | string lastSync = LastSyncDateTime.ToString(LastSyncDateTime.Date == DateTime.Today ? "t" : "g");
163 | return $"It looks like this feed is down. Last synced {lastSync}. Tap here to refresh.";
164 | }
165 | }
166 |
167 | ///
168 | /// Gets or sets a value that indicates whether the feed is the current selection in the navigation pane.
169 | ///
170 | [IgnoreDataMember] public bool IsSelectedInNavList
171 | {
172 | get { return _isSelectedInNavList; }
173 | set { SetProperty(ref _isSelectedInNavList, value); }
174 | }
175 | [IgnoreDataMember] private bool _isSelectedInNavList;
176 |
177 | ///
178 | /// Gets or sets a value that indicates whether the feed is currently loading article data.
179 | ///
180 | [IgnoreDataMember] public bool IsLoading
181 | {
182 | get { return _isLoading; }
183 | set
184 | {
185 | if (SetProperty(ref _isLoading, value))
186 | {
187 | OnPropertyChanged(nameof(IsInError));
188 | OnPropertyChanged(nameof(IsLoadingAndNotEmpty));
189 | OnPropertyChanged(nameof(IsNotFavoritesOrInError));
190 | OnPropertyChanged(nameof(IsInErrorAndEmpty));
191 | OnPropertyChanged(nameof(IsInErrorAndNotEmpty));
192 | }
193 | }
194 | }
195 | [IgnoreDataMember] private bool _isLoading;
196 |
197 | public bool IsLoadingAndNotEmpty => IsLoading && !IsEmpty;
198 |
199 | ///
200 | /// Gets or sets a value that indicates whether the feed is currently being renamed.
201 | ///
202 | [IgnoreDataMember] public bool IsInEdit { get { return _isInEdit; } set { SetProperty(ref _isInEdit, value); } }
203 | [IgnoreDataMember] private bool _isInEdit;
204 |
205 | ///
206 | /// Gets or sets a value that indicates whether the feed is currently in an error state
207 | /// and is no longer trying to retrieve new data.
208 | ///
209 | [IgnoreDataMember] public bool IsInError
210 | {
211 | get { return _isInError && !IsLoading; }
212 | set
213 | {
214 | if (SetProperty(ref _isInError, value))
215 | {
216 | OnPropertyChanged(nameof(IsNotFavoritesOrInError));
217 | OnPropertyChanged(nameof(IsInErrorAndEmpty));
218 | OnPropertyChanged(nameof(IsInErrorAndNotEmpty));
219 | }
220 | }
221 | }
222 | [IgnoreDataMember] private bool _isInError;
223 |
224 | ///
225 | /// Gets a value that indicates whether the feed is both in error and has no articles.
226 | ///
227 | public bool IsInErrorAndEmpty => IsInError && IsEmpty;
228 |
229 | ///
230 | /// Gets a value that indicates whether the feed is in error, but has already retrieved some articles.
231 | ///
232 | public bool IsInErrorAndNotEmpty => IsInError && !IsEmpty;
233 |
234 | ///
235 | /// Gets or sets the description of the current error, if the feed is in an error state.
236 | ///
237 | [IgnoreDataMember] public string ErrorMessage { get { return _errorMessage; } set { SetProperty(ref _errorMessage, value); } }
238 | [IgnoreDataMember] public string _errorMessage;
239 |
240 | ///
241 | /// Determines whether the specified object is equal to the current object.
242 | ///
243 | public override bool Equals(object obj) =>
244 | obj is FeedViewModel ? (obj as FeedViewModel).GetHashCode() == GetHashCode() : false;
245 |
246 | ///
247 | /// Returns the hash code of the FeedViewModel, which is based on
248 | /// a string representation the Link value, using only the host and path.
249 | ///
250 | public override int GetHashCode() =>
251 | Link?.GetComponents(UriComponents.Host | UriComponents.Path, UriFormat.Unescaped).GetHashCode() ?? 0;
252 |
253 | private const string NOT_HTTP_MESSAGE = "Sorry. The URL must begin with http:// or https://";
254 | private const string INVALID_URL_MESSAGE = "Sorry. That is not a valid URL.";
255 | }
256 | }
257 |
--------------------------------------------------------------------------------
/RssReader/Views/AddFeedView.xaml:
--------------------------------------------------------------------------------
1 |
24 |
25 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
55 |
56 |
57 |
58 |
63 |
64 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
90 |
91 |
92 |
97 |
98 |
99 |
102 |
103 |
104 |
105 |
106 |
107 |
112 |
113 |
117 |
118 |
119 |
128 |
129 |
131 |
132 |
133 |
134 |
135 |
136 |
144 |
145 |
153 |
154 |
155 |
156 |
159 |
160 |
161 |
162 |
163 |
164 |
170 |
171 |
177 |
178 |
180 |
183 |
184 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
--------------------------------------------------------------------------------
/RssReader/Views/AddFeedView.xaml.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using RssReader.Common;
26 | using RssReader.ViewModels;
27 | using System;
28 | using System.ComponentModel;
29 | using System.Runtime.CompilerServices;
30 | using System.Threading;
31 | using Windows.UI.Xaml;
32 | using Windows.UI.Xaml.Controls;
33 | using Windows.UI.Xaml.Input;
34 | using Windows.UI.Xaml.Navigation;
35 |
36 | namespace RssReader.Views
37 | {
38 | ///
39 | /// Represents the UI for adding a new feed to the feeds list.
40 | ///
41 | public sealed partial class AddFeedView : Page, INotifyPropertyChanged
42 | {
43 | #region INotifyPropertyChanged
44 |
45 | ///
46 | /// Occurs when a property value changes.
47 | ///
48 | public event PropertyChangedEventHandler PropertyChanged;
49 |
50 | ///
51 | /// Raises the PropertyChanged event for the property with the specified
52 | /// name, or the calling property if no name is specified.
53 | ///
54 | public void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
55 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
56 |
57 | ///
58 | /// Checks whether the value of the specified field is different than the specified value, and
59 | /// if they are different, updates the field and raises the PropertyChanged event for
60 | /// the property with the specified name, or the calling property if no name is specified.
61 | ///
62 | public bool SetProperty(ref T storage, T value,
63 | [CallerMemberName] String propertyName = null)
64 | {
65 | if (object.Equals(storage, value)) return false;
66 | storage = value;
67 | OnPropertyChanged(propertyName);
68 | return true;
69 | }
70 |
71 | #endregion
72 |
73 | ///
74 | /// Gets the MainViewModel used by the app.
75 | ///
76 | private MainViewModel ViewModel => AppShell.Current.ViewModel;
77 |
78 | ///
79 | /// Gets a Canceller that enables cancellation of a feed refresh attempt.
80 | ///
81 | private Canceller Canceller => new Canceller();
82 |
83 | ///
84 | /// Gets or sets a value that indicates whether the feed URI value is valid,
85 | /// meaning that the Name text box and Save buttons can be enabled.
86 | ///
87 | private bool AreFeedControlsEnabled
88 | {
89 | get { return _areFeedControlsEnabled; }
90 | set { SetProperty(ref _areFeedControlsEnabled, value); }
91 | }
92 | private bool _areFeedControlsEnabled;
93 |
94 | ///
95 | /// Gets a value that indicates whether the Save buttons are enabled.
96 | ///
97 | private bool AreSaveButtonsEnabled =>
98 | AreFeedControlsEnabled && !String.IsNullOrWhiteSpace(NameTextBox.Text);
99 |
100 | ///
101 | /// Initializes a new instance of the AddFeedView class.
102 | ///
103 | public AddFeedView()
104 | {
105 | this.InitializeComponent();
106 | Loaded += (s, e) => LinkTextBox.Focus(FocusState.Programmatic);
107 | NameTextBox.TextChanged += (s, e) => OnPropertyChanged(nameof(AreSaveButtonsEnabled));
108 | }
109 |
110 | ///
111 | /// Clears the add-feed UI so the user can enter new data.
112 | ///
113 | private void ResetFeed()
114 | {
115 | ViewModel.CurrentFeed = new FeedViewModel();
116 | ViewModel.CurrentFeed.PropertyChanged += CurrentFeed_PropertyChanged;
117 | AreFeedControlsEnabled = false;
118 | }
119 |
120 | ///
121 | /// Resets the feed to remove the error state.
122 | ///
123 | private void ClearErrorState()
124 | {
125 | ViewModel.CurrentFeed.IsInError = false;
126 | ViewModel.CurrentFeed.ErrorMessage = string.Empty;
127 | }
128 |
129 | ///
130 | /// Listens for changes to the FeedViewModel in order to enable UI controls
131 | /// when the feed has been refreshed and to reset the feed or cancel a refresh
132 | /// attempt when the user modifies the feed link.
133 | ///
134 | private void CurrentFeed_PropertyChanged(object sender, PropertyChangedEventArgs e)
135 | {
136 | switch (e.PropertyName)
137 | {
138 | case nameof(FeedViewModel.Name):
139 | AreFeedControlsEnabled = true;
140 | ViewModel.CurrentFeed.PropertyChanged -= CurrentFeed_PropertyChanged;
141 | LinkTextBox.TextChanged += LinkTextBox_TextChangedAfterFeedRefresh;
142 | break;
143 | case nameof(FeedViewModel.IsInError):
144 | LinkTextBox.TextChanged += LinkTextBox_TextChangedAfterFeedError;
145 | break;
146 | case nameof(FeedViewModel.Link):
147 | var withoutAwait = ViewModel.CurrentFeed.RefreshAsync(Canceller.Token);
148 | LinkTextBox.TextChanged += LinkTextBox_TextChangedAfterFeedLinkChanged;
149 | break;
150 | default:
151 | break;
152 | }
153 | }
154 |
155 | ///
156 | /// Resets the form when the user starts to change the feed URL, since at this point
157 | /// the current feed data is no longer valid. Also removes this handler from the
158 | /// TextChanged event until the next time the feed data is loaded (via the
159 | /// CurrentFeed_PropertyChanged handler attached in the ResetFeed method).
160 | ///
161 | private void LinkTextBox_TextChangedAfterFeedRefresh(object sender, TextChangedEventArgs e)
162 | {
163 | if (LinkTextBox.Text.Trim() == ViewModel.CurrentFeed.LinkAsString) return;
164 |
165 | LinkTextBox.TextChanged -= LinkTextBox_TextChangedAfterFeedRefresh;
166 |
167 | // Preserve the text and cursor position for the user's current edits when
168 | // feed is reset, which clears all feed property values including LinkAsString.
169 | var currentLinkText = LinkTextBox.Text;
170 | int selectionStart = LinkTextBox.SelectionStart;
171 |
172 | ResetFeed();
173 |
174 | // Restore the preserved text and cursor position to the new feed instance.
175 | LinkTextBox.SetValue(TextBox.TextProperty, currentLinkText);
176 | LinkTextBox.SelectionStart = selectionStart;
177 | }
178 |
179 | ///
180 | /// Resets the feed to remove the error state when the user starts to change the feed URL.
181 | ///
182 | private void LinkTextBox_TextChangedAfterFeedError(object sender, TextChangedEventArgs e)
183 | {
184 | if (LinkTextBox.Text.Trim() == ViewModel.CurrentFeed.LinkAsString) return;
185 | LinkTextBox.TextChanged -= LinkTextBox_TextChangedAfterFeedError;
186 | ClearErrorState();
187 | }
188 |
189 | ///
190 | /// Cancels any current feed refresh attempt and clears the error state when the user starts changing the feed URI.
191 | ///
192 | private void LinkTextBox_TextChangedAfterFeedLinkChanged(object sender, TextChangedEventArgs e)
193 | {
194 | if (LinkTextBox.Text.Trim() == ViewModel.CurrentFeed.LinkAsString) return;
195 | LinkTextBox.TextChanged -= LinkTextBox_TextChangedAfterFeedLinkChanged;
196 | Canceller.Cancel();
197 | ClearErrorState();
198 | }
199 |
200 | ///
201 | /// Sets focus to the text box and selects the current contents.
202 | ///
203 | private void InitializeLinkTextBox()
204 | {
205 | LinkTextBox.Focus(FocusState.Programmatic);
206 | LinkTextBox.SelectAll();
207 | }
208 |
209 | ///
210 | /// Resets the UI on navigation to this page.
211 | ///
212 | protected override void OnNavigatedTo(NavigationEventArgs e)
213 | {
214 | ResetFeed();
215 | InitializeLinkTextBox();
216 | }
217 |
218 | ///
219 | /// Adds the current feed to the feeds list and navigates to articles list for the feed.
220 | ///
221 | private void SaveAndLeaveButton_Click(object sender, RoutedEventArgs e)
222 | {
223 | if (ViewModel.TryAddCurrentFeed())
224 | {
225 | AppShell.Current.NavigateToCurrentFeed();
226 | }
227 | }
228 |
229 | ///
230 | /// Adds the current feed to the feeds list and resets the add-feed UI so the user can add another feed.
231 | ///
232 | private void SaveAndStayButton_Click(object sender, RoutedEventArgs e)
233 | {
234 | if (ViewModel.TryAddCurrentFeed())
235 | {
236 | ResetFeed();
237 | InitializeLinkTextBox();
238 | }
239 | }
240 |
241 | ///
242 | /// Handles ESC and ENTER keypresses for the feed URI text box.
243 | ///
244 | private void LinkTextBox_KeyDown(object sender, KeyRoutedEventArgs e) =>
245 | e.HandleEnterAndEscape(sender, ViewModel.CurrentFeed, () => ViewModel.CurrentFeed.LinkAsString);
246 |
247 | ///
248 | /// Handles ESC and ENTER keypresses for the feed name text box.
249 | ///
250 | private void NameTextBox_KeyDown(object sender, KeyRoutedEventArgs e) =>
251 | e.HandleEnterAndEscape(sender, ViewModel.CurrentFeed, () => ViewModel.CurrentFeed.Name);
252 |
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/RssReader/Views/DetailPage.xaml:
--------------------------------------------------------------------------------
1 |
24 |
25 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
49 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/RssReader/Views/DetailPage.xaml.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using RssReader.Common;
26 | using RssReader.ViewModels;
27 | using Windows.UI.Core;
28 | using Windows.UI.Xaml;
29 | using Windows.UI.Xaml.Controls;
30 | using Windows.UI.Xaml.Media.Animation;
31 | using Windows.UI.Xaml.Navigation;
32 |
33 | namespace RssReader.Views
34 | {
35 |
36 | public sealed partial class DetailPage : Page
37 | {
38 | private MainViewModel ViewModel => AppShell.Current.ViewModel;
39 |
40 | public DetailPage()
41 | {
42 | InitializeComponent();
43 |
44 | ArticleWebView.NavigationStarting += async (s, e) =>
45 | await e.LaunchBrowserForNonMatchingUriAsync(ViewModel.CurrentArticle.Link);
46 |
47 | ArticleWebView.LoadCompleted += (s, e) => ArticleWebView.Visibility = Visibility.Visible;
48 | }
49 |
50 | protected override void OnNavigatedTo(NavigationEventArgs e)
51 | {
52 | base.OnNavigatedTo(e);
53 |
54 | if (ViewModel.CurrentArticle != null)
55 | {
56 | if (!ViewModel.CurrentArticle.Link.IsEquivalentTo(ArticleWebView.Source))
57 | {
58 | ArticleWebView.Navigate(ViewModel.CurrentArticle.Link);
59 | }
60 | else
61 | {
62 | ArticleWebView.Visibility = Visibility.Visible;
63 | }
64 | }
65 |
66 | var backStack = Frame.BackStack;
67 | var backStackCount = backStack.Count;
68 |
69 | if (backStackCount > 0)
70 | {
71 | var masterPageEntry = backStack[backStackCount - 1];
72 | backStack.RemoveAt(backStackCount - 1);
73 |
74 | // Clear the navigation parameter so that navigation back to the
75 | // MasterDetail page won't reset the current article selection.
76 | var modifiedEntry = new PageStackEntry(
77 | masterPageEntry.SourcePageType,
78 | null,
79 | masterPageEntry.NavigationTransitionInfo
80 | );
81 | backStack.Add(modifiedEntry);
82 | }
83 |
84 | // Register for hardware and software back request from the system
85 | SystemNavigationManager systemNavigationManager = SystemNavigationManager.GetForCurrentView();
86 | systemNavigationManager.BackRequested += DetailPage_BackRequested;
87 | systemNavigationManager.AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
88 | ViewModel.IsInDetailsMode = true;
89 | }
90 |
91 | protected override void OnNavigatedFrom(NavigationEventArgs e)
92 | {
93 | base.OnNavigatedFrom(e);
94 |
95 | SystemNavigationManager systemNavigationManager = SystemNavigationManager.GetForCurrentView();
96 | systemNavigationManager.BackRequested -= DetailPage_BackRequested;
97 | systemNavigationManager.AppViewBackButtonVisibility = AppViewBackButtonVisibility.Collapsed;
98 | ViewModel.IsInDetailsMode = false;
99 | ArticleWebView.Visibility = Visibility.Collapsed;
100 | }
101 |
102 | private void OnBackRequested()
103 | {
104 | // Page above us will be our master view.
105 | // Make sure we are using the "drill out" animation in this transition.
106 |
107 | if (Frame.CanGoBack) Frame.GoBack(new DrillInNavigationTransitionInfo());
108 | }
109 |
110 | void NavigateBackForWideState(bool useTransition)
111 | {
112 | if (useTransition)
113 | {
114 | Frame.GoBack(new EntranceNavigationTransitionInfo());
115 | }
116 | else
117 | {
118 | Frame.GoBack(new SuppressNavigationTransitionInfo());
119 | }
120 | }
121 |
122 | private bool ShouldGoToWideState()
123 | {
124 | return Window.Current.Bounds.Width >= 720;
125 | }
126 |
127 | private void PageRoot_Loaded(object sender, RoutedEventArgs e)
128 | {
129 | if (ShouldGoToWideState())
130 | {
131 | // We shouldn't see this page since we are in "wide master-detail" mode.
132 | // Play a transition as we are navigating from a separate page.
133 | NavigateBackForWideState(useTransition: true);
134 | }
135 | Window.Current.SizeChanged += Window_SizeChanged;
136 | }
137 |
138 | private void PageRoot_Unloaded(object sender, RoutedEventArgs e)
139 | {
140 | Window.Current.SizeChanged -= Window_SizeChanged;
141 | }
142 |
143 | private void Window_SizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
144 | {
145 | if (ShouldGoToWideState())
146 | {
147 | // Make sure we are no longer listening to window change events.
148 | Window.Current.SizeChanged -= Window_SizeChanged;
149 |
150 | // We shouldn't see this page since we are in "wide master-detail" mode.
151 | NavigateBackForWideState(useTransition: false);
152 | }
153 | }
154 |
155 | private void DetailPage_BackRequested(object sender, BackRequestedEventArgs e)
156 | {
157 | // Mark event as handled so we don't get bounced out of the app.
158 | e.Handled = true;
159 |
160 | OnBackRequested();
161 | }
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/RssReader/Views/EditFeedsView.xaml:
--------------------------------------------------------------------------------
1 |
24 |
25 |
36 |
37 |
38 |
39 |
41 |
42 |
46 |
47 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
70 |
75 |
76 |
77 |
83 |
84 |
90 |
91 |
97 |
98 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
122 |
123 |
124 |
125 |
130 |
131 |
139 |
140 |
152 |
153 |
154 |
158 |
163 |
167 |
168 |
169 |
170 |
171 |
172 |
--------------------------------------------------------------------------------
/RssReader/Views/EditFeedsView.xaml.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using RssReader.ViewModels;
26 | using System;
27 | using System.ComponentModel;
28 | using System.Linq;
29 | using System.Runtime.CompilerServices;
30 | using Windows.System;
31 | using Windows.UI.Xaml;
32 | using Windows.UI.Xaml.Controls;
33 | using Windows.UI.Xaml.Input;
34 |
35 | namespace RssReader.Views
36 | {
37 | ///
38 | /// Represents the UI for editing the feeds list.
39 | ///
40 | public sealed partial class EditFeedsView : Page, INotifyPropertyChanged
41 | {
42 | #region INotifyPropertyChanged
43 |
44 | ///
45 | /// Occurs when a property value changes.
46 | ///
47 | public event PropertyChangedEventHandler PropertyChanged;
48 |
49 | ///
50 | /// Raises the PropertyChanged event for the property with the specified
51 | /// name, or the calling property if no name is specified.
52 | ///
53 | public void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
54 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
55 |
56 | ///
57 | /// Checks whether the value of the specified field is different than the specified value, and
58 | /// if they are different, updates the field and raises the PropertyChanged event for
59 | /// the property with the specified name, or the calling property if no name is specified.
60 | ///
61 | public bool SetProperty(ref T storage, T value,
62 | [CallerMemberName] String propertyName = null)
63 | {
64 | if (object.Equals(storage, value)) return false;
65 | storage = value;
66 | OnPropertyChanged(propertyName);
67 | return true;
68 | }
69 |
70 | #endregion
71 |
72 | ///
73 | /// Gets the MainViewModel used by the app.
74 | ///
75 | private MainViewModel ViewModel => AppShell.Current.ViewModel;
76 |
77 | ///
78 | /// Initializes a new instance of the EditFeedsView class.
79 | ///
80 | public EditFeedsView()
81 | {
82 | this.InitializeComponent();
83 | }
84 |
85 | ///
86 | /// Navigates to the add-feed view.
87 | ///
88 | private void AddFeed() => AppShell.Current.NavigateToAddFeedView();
89 |
90 | ///
91 | /// Deletes the feeds that are currently selected in the edit feeds list.
92 | ///
93 | private void DeleteSelectedFeeds() =>
94 | ViewModel.RemoveFeeds(EditFeedsList.SelectedItems.Cast());
95 |
96 | ///
97 | /// Puts the selected feed into edit mode so the user can rename it.
98 | ///
99 | private void EditFeed()
100 | {
101 | (EditFeedsList.SelectedItem as FeedViewModel).IsInEdit = true;
102 | var item = EditFeedsList.ContainerFromIndex(EditFeedsList.SelectedIndex) as ListViewItem;
103 | var textbox = (item.ContentTemplateRoot as Grid).FindName("EditTextBox") as TextBox;
104 | textbox.Focus(FocusState.Programmatic);
105 | textbox.SelectAll();
106 | }
107 |
108 | ///
109 | /// Leaves edit mode and saves the new name for the feed.
110 | ///
111 | private void EndEdit(object sender, RoutedEventArgs e)
112 | {
113 | (EditFeedsList.SelectedItem as FeedViewModel).IsInEdit = false;
114 | var withoutAwait = ViewModel.SaveFeedsAsync();
115 | }
116 |
117 | ///
118 | /// Gets a value that indicates whether a single feed has been selected,
119 | /// meaning that it can be put into edit mode.
120 | ///
121 | public bool CanEdit => EditFeedsList.SelectedItems.Count == 1;
122 |
123 | ///
124 | /// Raises PropertyChanged for the CanEdit property when the selection changes
125 | /// in the edit-feeds list; this enables or disables the Edit button, as appropriate.
126 | ///
127 | private void SelectionChanged() => OnPropertyChanged(nameof(CanEdit));
128 |
129 | ///
130 | /// Handles ESC and ENTER keypresses for the feed-rename text box.
131 | ///
132 | private void EditTextBox_KeyDown(object sender, KeyRoutedEventArgs e)
133 | {
134 | var textbox = sender as TextBox;
135 | if (e.Key == VirtualKey.Escape) textbox.Text = string.Empty;
136 | else if (e.Key == VirtualKey.Enter)
137 | {
138 | EndEdit(this, null);
139 | e.Handled = true;
140 | }
141 | }
142 | }
143 | }
144 |
145 |
--------------------------------------------------------------------------------
/RssReader/Views/FeedView.xaml.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using RssReader.ViewModels;
26 | using Windows.UI.Xaml;
27 | using Windows.UI.Xaml.Controls;
28 | using Windows.UI.Xaml.Controls.Primitives;
29 |
30 | namespace RssReader.Views
31 | {
32 | ///
33 | /// Represents the UI for viewing a list of articles from a feed.
34 | ///
35 | public sealed partial class FeedView : Page
36 | {
37 | ///
38 | /// Gets the MainViewModel used by the app.
39 | ///
40 | public MainViewModel ViewModel => AppShell.Current.ViewModel;
41 |
42 | ///
43 | /// Initializes a new instance of the FeedView class.
44 | ///
45 | public FeedView()
46 | {
47 | this.InitializeComponent();
48 | ViewModel.Initialized += (s, e) =>
49 | {
50 | // Realize the UI elements marked x:DeferLoadStrategy="Lazy".
51 | // Deferred loading ensures that these elements do not appear
52 | // in the UI before the feed data is available.
53 | FindName("NormalFeedView");
54 | FindName("FeedErrorMessage");
55 | FindName("FavoritesIsEmptyMessage");
56 | };
57 | }
58 |
59 | ///
60 | /// Sets the ViewModel.CurrentArticle property to the clicked item.
61 | ///
62 | ///
63 | /// The ArticlesListView.ItemsSource property must be bound to a property
64 | /// of type Object, so it is bound to ViewModel.CurrentArticleAsObject.
65 | /// Making this a two-way binding would require a CurrentArticleAsObject
66 | /// setter that updates CurrentArticle. However, CurrentArticle must
67 | /// raise the PropertyChanged event with every setter call (not just ones
68 | /// that change its value), which causes an infinite recursion. The easiest
69 | /// way to prevent this is to use a one-way binding, and update CurrentArticle
70 | /// in this ItemClick event handler.
71 | ///
72 | private void ArticlesListView_ItemInvoked(object sender, ListViewItem listViewItem) =>
73 | ViewModel.CurrentArticle = ArticlesListView.ItemFromContainer(listViewItem) as ArticleViewModel;
74 |
75 | ///
76 | /// Updates the favorites list when the user stars or unstars an article.
77 | ///
78 | private void ToggleButton_Toggled(object sender, RoutedEventArgs e) =>
79 | ViewModel.SyncFavoritesFeed(((ToggleButton)sender).DataContext as ArticleViewModel);
80 |
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/RssReader/Views/MasterDetailPage.xaml:
--------------------------------------------------------------------------------
1 |
24 |
25 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
57 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
67 |
69 |
70 |
71 |
72 |
73 |
79 |
80 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/RssReader/Views/MasterDetailPage.xaml.cs:
--------------------------------------------------------------------------------
1 | // ---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // The MIT License (MIT)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | // ---------------------------------------------------------------------------------
24 |
25 | using RssReader.Common;
26 | using RssReader.ViewModels;
27 | using System;
28 | using System.Linq;
29 | using Windows.UI.Xaml;
30 | using Windows.UI.Xaml.Controls;
31 | using Windows.UI.Xaml.Media.Animation;
32 | using Windows.UI.Xaml.Navigation;
33 |
34 | namespace RssReader.Views
35 | {
36 | public sealed partial class MasterDetailPage : Page
37 | {
38 | private MainViewModel ViewModel => AppShell.Current.ViewModel;
39 |
40 | private Canceller Canceller { get; } = new Canceller();
41 |
42 | private bool isCurrentFeedNew = false;
43 |
44 | public MasterDetailPage()
45 | {
46 | InitializeComponent();
47 |
48 | ViewModel.PropertyChanged += ViewModel_PropertyChanged;
49 |
50 | ArticleWebView.NavigationStarting += async (s, e) =>
51 | {
52 | if (e.Uri != null && !await e.LaunchBrowserForNonMatchingUriAsync(ViewModel.CurrentArticle.Link))
53 | {
54 | // In-app navigation, so hide the WebView control and display the progress
55 | // animation until the page load is completed.
56 | ArticleWebView.Visibility = Visibility.Collapsed;
57 | LoadingProgressBar.Visibility = Visibility.Visible;
58 | }
59 | };
60 |
61 | ArticleWebView.LoadCompleted += (s, e) =>
62 | {
63 | ArticleWebView.Visibility = Visibility.Visible;
64 | LoadingProgressBar.Visibility = Visibility.Collapsed;
65 | };
66 | }
67 |
68 | private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
69 | {
70 | // Set a flag so that, in narrow mode, details-only navigation doesn't occur if
71 | // the CurrentArticle is changed solely as a side-effect of changing the CurrentFeed.
72 | if (e.PropertyName == nameof(ViewModel.CurrentFeed))
73 | {
74 | isCurrentFeedNew = true;
75 | }
76 | else if (e.PropertyName == nameof(ViewModel.CurrentArticle))
77 | {
78 | if (ViewModel.CurrentArticle == null)
79 | {
80 | ArticleWebView.NavigateToString(string.Empty);
81 | }
82 | else
83 | {
84 | if (!ViewModel.CurrentArticle.Link.IsEquivalentTo(ArticleWebView.Source))
85 | {
86 | ArticleWebView.Navigate(ViewModel.CurrentArticle.Link);
87 | }
88 | if (AdaptiveStates.CurrentState == NarrowState)
89 | {
90 | bool switchToDetailsView = !isCurrentFeedNew;
91 | isCurrentFeedNew = false;
92 | if (switchToDetailsView)
93 | {
94 | // Use "drill in" transition for navigating from master list to detail view
95 | Frame.Navigate(typeof(DetailPage), null, new DrillInNavigationTransitionInfo());
96 | }
97 | }
98 | }
99 | }
100 | }
101 |
102 | protected override void OnNavigatedTo(NavigationEventArgs e)
103 | {
104 | base.OnNavigatedTo(e);
105 |
106 | if (e.Parameter is Uri || e.Parameter == null ||
107 | (e.Parameter is string && String.IsNullOrEmpty(e.Parameter as string)))
108 | {
109 | if (MasterFrame.CurrentSourcePageType != typeof(FeedView))
110 | {
111 | MasterFrame.Navigate(typeof(FeedView));
112 | }
113 |
114 | var feedUri = e.Parameter as Uri;
115 | if (feedUri != null)
116 | {
117 | ViewModel.CurrentFeed = ViewModel.FeedsWithFavorites.FirstOrDefault(f => f.Link == feedUri);
118 | Canceller.Cancel();
119 | var withoutAwait = ViewModel.CurrentFeed.RefreshAsync(Canceller.Token);
120 | }
121 | }
122 | else
123 | {
124 | var viewType = e.Parameter as Type;
125 |
126 | if (viewType != null && MasterFrame.CurrentSourcePageType != viewType)
127 | {
128 | MasterFrame.Navigate(viewType);
129 | }
130 |
131 | UpdateForVisualState(AdaptiveStates.CurrentState);
132 | }
133 | }
134 |
135 | private void AdaptiveStates_CurrentStateChanged(object sender, VisualStateChangedEventArgs e)
136 | {
137 | UpdateForVisualState(e.NewState, e.OldState);
138 | }
139 |
140 | private void UpdateForVisualState(VisualState newState, VisualState oldState = null)
141 | {
142 | var isNarrow = newState == NarrowState;
143 |
144 | if (isNarrow && oldState == DefaultState && MasterFrame.CurrentSourcePageType == typeof(FeedView))
145 | {
146 | // Resize down to the detail item. Don't play a transition.
147 | Frame.Navigate(typeof(DetailPage), null, new SuppressNavigationTransitionInfo());
148 | }
149 | }
150 |
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
40 |
41 |
42 |
--------------------------------------------------------------------------------