├── .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 | ![RssReader app displaying some sample feeds](RssReader.png) 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 |