├── .gitignore ├── LICENSE ├── README.md ├── WordPressPushNotification ├── AzureFunction.csx └── functions.php ├── WordPressUWP.sln └── WordPressUWP ├── .editorconfig ├── Activation ├── ActivationHandler.cs └── DefaultLaunchActivationHandler.cs ├── App.xaml ├── App.xaml.cs ├── Assets ├── LockScreenLogo.scale-200.png ├── Placeholder │ └── placeholder.jpg ├── SplashScreen.scale-200.png ├── Square150x150Logo.scale-200.png ├── Square44x44Logo.scale-200.png ├── Square44x44Logo.targetsize-24_altform-unplated.png ├── StoreLogo.png ├── Web │ ├── Dark.css │ ├── Light.css │ ├── Style.css │ ├── hammer.min.js │ └── script.js └── Wide310x150Logo.scale-200.png ├── Config.cs ├── Helpers ├── AuthorNameConverter.cs ├── DebugConverter.cs ├── DisplayTermsConverter.cs ├── EnumToBooleanConverter.cs ├── FeaturedImageConverter.cs ├── HtmlDecodeConverter.cs ├── HtmlTools.cs ├── InverseBoolConverter.cs ├── Json.cs ├── ProtocolToURLConverter.cs ├── ReplyToConverter.cs ├── ResourceExtensions.cs ├── SettingsStorageExtensions.cs ├── Singleton.cs ├── SwipedEventArgs.cs ├── TheadedCommentMarginConverter.cs └── VisibleWhenZeroConverter.cs ├── Interfaces ├── IPushNotificationService.cs └── IWordPressService.cs ├── Models ├── HtmlSettings.cs └── SettingsLocality.cs ├── Package.appxmanifest ├── Properties ├── AssemblyInfo.cs └── Default.rd.xml ├── Services ├── ActivationService.cs ├── FirstRunDisplayService.cs ├── HubNotificationsService.cs ├── LiveTileService.Samples.cs ├── LiveTileService.cs ├── NavigationServiceEx.cs ├── OnBackgroundEnteringEventArgs.cs ├── PostsService.cs ├── PushNotificationService.cs ├── SettingsService.cs ├── SuspendAndResumeService.cs ├── SuspensionState.cs ├── ThemeSelectorService.cs ├── WhatsNewDisplayService.cs └── WordPressService.cs ├── Strings ├── de │ └── Resources.resw └── en-us │ └── Resources.resw ├── Styles ├── TextBlock.xaml ├── _Colors.xaml ├── _FontSizes.xaml └── _Thickness.xaml ├── ViewModels ├── NewsDetailViewModel.cs ├── NewsViewModel.cs ├── SettingsViewModel.cs ├── ShellNavigationItem.cs ├── ShellViewModel.cs └── ViewModelLocator.cs ├── Views ├── FirstRunDialog.xaml ├── FirstRunDialog.xaml.cs ├── NewsDetailControl.xaml ├── NewsDetailControl.xaml.cs ├── NewsDetailPage.xaml ├── NewsDetailPage.xaml.cs ├── NewsPage.xaml ├── NewsPage.xaml.cs ├── SettingsPage.xaml ├── SettingsPage.xaml.cs ├── ShellPage.xaml ├── ShellPage.xaml.cs ├── WhatsNewDialog.xaml └── WhatsNewDialog.xaml.cs ├── WordPressUWP.csproj ├── WordPressUWP.csproj.user └── WordPressUWP_TemporaryKey.pfx /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # This .gitignore file was automatically created by Microsoft(R) Visual Studio. 3 | ################################################################################ 4 | 5 | /WordPressUWP/obj 6 | /WordPressUWP/bin 7 | *.suo 8 | /WordPressUWPApp/.vs 9 | /WordPressUWPTestApp/obj/x86/Debug 10 | /WordPressUWPTestApp/bin/x86/Debug 11 | /WordPressUWPApp/bin/x86/Debug 12 | /WordPressUWPApp/obj 13 | WordPressUWPApp/Utility/ApiCredentials.cs 14 | WordPressUWPTestApp/Utility/ApiCredentials.cs 15 | /WordPressUWPApp/bin/ARM/Debug 16 | /WordPressUWPTestApp/obj/ARM/Debug 17 | WordPressUWPApp/project.lock.json 18 | WordPressUWPApp/WordPressUWPApp.nuget.props 19 | WordPressUWPApp/bin/ 20 | .vs/ 21 | WordPressUWP/Helpers/ApiCredentials.cs 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Thomas Pentenrieder 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WordPressUWP 2 | 3 | [![Join the chat at https://gitter.im/ThomasPe/WordPressUWP](https://badges.gitter.im/ThomasPe/WordPressUWP.svg)](https://gitter.im/ThomasPe/WordPressUWP?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build status](https://build.appcenter.ms/v0.1/apps/5fd5b82a-1ce3-43b5-91e9-85f8b51cd33a/branches/master/badge)](https://appcenter.ms) 4 | 5 | This is a Universal Windows Platform app framework designed to turn WordPress Blogs / Sites into nice little apps. It's built on 6 | * Windows Template Studio 7 | * [WordPressPCL (WordPress REST API Wrapper)](https://github.com/ThomasPe/WordPressPCL) 8 | 9 | ## Features 10 | working and planned features for WordPressUWP: 11 | - [x] Show posts 12 | - [x] Show comments 13 | - [x] Settings page 14 | - [x] Sign In 15 | - [x] Add comment 16 | - [x] Push Notifications 17 | - [ ] Live Tiles 18 | - [x] Continuum Support 19 | 20 | 21 | # Quickstart 22 | 23 | ## WordPress Plugins 24 | Since WordPress 4.7 the REST API has been integrated into the core so there's no need for any plugins to get basic functionality. If you want to access protected endpoints, this library supports authentication through JSON Web Tokens (JWT) (plugin required). 25 | 26 | * [WordPress 4.7 or newer](https://wordpress.org/) 27 | * [JWT Authentication for WP REST API](https://wordpress.org/plugins/jwt-authentication-for-wp-rest-api/) 28 | 29 | ## Getting Started 30 | 31 | Just clone or download the repo and open it in Visual Studio. Go to the `Config.cs` class inside the root folder and enter your site uri. 32 | 33 | ```c# 34 | public static class Config 35 | { 36 | public const string BaseUri = "http://yoursite.com/"; 37 | public static string WordPressUri = $"{BaseUri}wp-json/"; 38 | 39 | // Push Notification Settings 40 | public const string HubName = "NotificationHubName"; 41 | public const string AccessSiganture = "Endpoint="; 42 | 43 | // Comments 44 | public static int CommentDepth = 3; 45 | } 46 | 47 | ``` 48 | 49 | ## Hall of Fame 50 | 51 | This is a list of apps based on the WordPressUWP framework. Feel free to add yours! 52 | - [WindowsArea](https://www.microsoft.com/de-de/store/p/windowsarea/9n9zxm79mqr7) 53 | -------------------------------------------------------------------------------- /WordPressPushNotification/AzureFunction.csx: -------------------------------------------------------------------------------- 1 | #r "Microsoft.Azure.NotificationHubs" 2 | #r "Newtonsoft.Json" 3 | 4 | using System.Net; 5 | using Microsoft.Azure.NotificationHubs; 6 | using Newtonsoft.Json; 7 | 8 | public static async Task Run(HttpRequestMessage req, TraceWriter log, IAsyncCollector notification) 9 | { 10 | log.Info("C# HTTP trigger function processed a request."); 11 | 12 | dynamic data = await req.Content.ReadAsAsync(); 13 | 14 | // Set name to query string or body data 15 | string title = data?.title; 16 | string id = data?.id; 17 | string img = data?.thumbnail; 18 | string author = data?.author; 19 | 20 | if(!string.IsNullOrEmpty(title)){ 21 | string wnsNotificationPayload = "" + 22 | "" + 23 | "" + 24 | title + 25 | "" + 26 | "" + 27 | author + 28 | "" + 29 | "" + 30 | ""; 31 | await notification.AddAsync(new WindowsNotification(wnsNotificationPayload)); 32 | } 33 | 34 | return title == null 35 | ? req.CreateResponse(HttpStatusCode.BadRequest, "Failure") 36 | : req.CreateResponse(HttpStatusCode.OK, "Push sent"); 37 | } 38 | -------------------------------------------------------------------------------- /WordPressPushNotification/functions.php: -------------------------------------------------------------------------------- 1 | function call_the_endpoint($new_status, $old_status, $post){ 2 | $pushurl = 'https://mypushfunction.azurewebsites.net/api/PostPushNotification' 3 | 4 | if('publish' === $new_status && 'publish' !== $old_status && $post->post_type === 'post') { 5 | 6 | $data = array(); 7 | // Store the title into the array 8 | $data['title'] = get_the_title(); 9 | // If there is a post thumbnail, get the link 10 | if (has_post_thumbnail()) { 11 | $data['thumbnail'] = get_the_post_thumbnail_url( get_the_ID(),'large' ); 12 | } 13 | $data['id'] = get_the_id(); 14 | $data['author'] = get_the_author(); 15 | $data['datetime'] = get_post_time('U', true); 16 | 17 | // Encode the data to be sent 18 | $json_data = json_encode($data); 19 | // Initiate the cURL 20 | $url = curl_init($pushurl); 21 | curl_setopt($url, CURLOPT_CUSTOMREQUEST, "POST"); 22 | curl_setopt($url, CURLOPT_POSTFIELDS, $json_data); 23 | curl_setopt($url, CURLOPT_RETURNTRANSFER, true); 24 | curl_setopt($url, CURLOPT_HTTPHEADER, array( 25 | 'Content-Type: application/json', 26 | 'Content-Length: ' . strlen($json_data)) 27 | ); 28 | 29 | // The results of our request, to use later if we want. 30 | $result = curl_exec($url); 31 | } 32 | } 33 | add_action('transition_post_status', 'call_the_endpoint',10,3); -------------------------------------------------------------------------------- /WordPressUWP.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.16 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WordPressUWP", "WordPressUWP\WordPressUWP.csproj", "{84EF0AFE-443E-40DC-B9E5-F570F2FDAD2F}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|ARM = Debug|ARM 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|ARM = Release|ARM 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {84EF0AFE-443E-40DC-B9E5-F570F2FDAD2F}.Debug|ARM.ActiveCfg = Debug|ARM 19 | {84EF0AFE-443E-40DC-B9E5-F570F2FDAD2F}.Debug|ARM.Build.0 = Debug|ARM 20 | {84EF0AFE-443E-40DC-B9E5-F570F2FDAD2F}.Debug|ARM.Deploy.0 = Debug|ARM 21 | {84EF0AFE-443E-40DC-B9E5-F570F2FDAD2F}.Debug|x64.ActiveCfg = Debug|x64 22 | {84EF0AFE-443E-40DC-B9E5-F570F2FDAD2F}.Debug|x64.Build.0 = Debug|x64 23 | {84EF0AFE-443E-40DC-B9E5-F570F2FDAD2F}.Debug|x64.Deploy.0 = Debug|x64 24 | {84EF0AFE-443E-40DC-B9E5-F570F2FDAD2F}.Debug|x86.ActiveCfg = Debug|x86 25 | {84EF0AFE-443E-40DC-B9E5-F570F2FDAD2F}.Debug|x86.Build.0 = Debug|x86 26 | {84EF0AFE-443E-40DC-B9E5-F570F2FDAD2F}.Debug|x86.Deploy.0 = Debug|x86 27 | {84EF0AFE-443E-40DC-B9E5-F570F2FDAD2F}.Release|ARM.ActiveCfg = Release|ARM 28 | {84EF0AFE-443E-40DC-B9E5-F570F2FDAD2F}.Release|ARM.Build.0 = Release|ARM 29 | {84EF0AFE-443E-40DC-B9E5-F570F2FDAD2F}.Release|ARM.Deploy.0 = Release|ARM 30 | {84EF0AFE-443E-40DC-B9E5-F570F2FDAD2F}.Release|x64.ActiveCfg = Release|x64 31 | {84EF0AFE-443E-40DC-B9E5-F570F2FDAD2F}.Release|x64.Build.0 = Release|x64 32 | {84EF0AFE-443E-40DC-B9E5-F570F2FDAD2F}.Release|x64.Deploy.0 = Release|x64 33 | {84EF0AFE-443E-40DC-B9E5-F570F2FDAD2F}.Release|x86.ActiveCfg = Release|x86 34 | {84EF0AFE-443E-40DC-B9E5-F570F2FDAD2F}.Release|x86.Build.0 = Release|x86 35 | {84EF0AFE-443E-40DC-B9E5-F570F2FDAD2F}.Release|x86.Deploy.0 = Release|x86 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {E9F4414B-260F-472B-8B53-44E86876099F} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /WordPressUWP/.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | end_of_line = crlf 6 | 7 | [*.{cs,xaml}] 8 | indent_style = space 9 | indent_size = 4 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | -------------------------------------------------------------------------------- /WordPressUWP/Activation/ActivationHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace WordPressUWP.Activation 5 | { 6 | // For more information on application activation see https://github.com/Microsoft/WindowsTemplateStudio/blob/master/docs/activation.md 7 | internal abstract class ActivationHandler 8 | { 9 | public abstract bool CanHandle(object args); 10 | 11 | public abstract Task HandleAsync(object args); 12 | } 13 | 14 | internal abstract class ActivationHandler : ActivationHandler 15 | where T : class 16 | { 17 | protected abstract Task HandleInternalAsync(T args); 18 | 19 | public override async Task HandleAsync(object args) 20 | { 21 | await HandleInternalAsync(args as T); 22 | } 23 | 24 | public override bool CanHandle(object args) 25 | { 26 | return args is T && CanHandleInternal(args as T); 27 | } 28 | 29 | protected virtual bool CanHandleInternal(T args) 30 | { 31 | return true; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /WordPressUWP/Activation/DefaultLaunchActivationHandler.cs: -------------------------------------------------------------------------------- 1 | using CommonServiceLocator; 2 | using System; 3 | using System.Threading.Tasks; 4 | 5 | using Windows.ApplicationModel.Activation; 6 | 7 | using WordPressUWP.Services; 8 | 9 | namespace WordPressUWP.Activation 10 | { 11 | internal class DefaultLaunchActivationHandler : ActivationHandler 12 | { 13 | private readonly string _navElement; 14 | 15 | private NavigationServiceEx NavigationService 16 | { 17 | get 18 | { 19 | return ServiceLocator.Current.GetInstance(); 20 | } 21 | } 22 | 23 | public DefaultLaunchActivationHandler(Type navElement) 24 | { 25 | _navElement = navElement.FullName; 26 | } 27 | 28 | protected override async Task HandleInternalAsync(LaunchActivatedEventArgs args) 29 | { 30 | // When the navigation stack isn't restored navigate to the first page, 31 | // configuring the new page by passing required information as a navigation 32 | // parameter 33 | NavigationService.Navigate(_navElement, args.Arguments); 34 | 35 | await Task.CompletedTask; 36 | } 37 | 38 | protected override bool CanHandleInternal(LaunchActivatedEventArgs args) 39 | { 40 | // None of the ActivationHandlers has handled the app activation 41 | return NavigationService.Frame.Content == null; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /WordPressUWP/App.xaml: -------------------------------------------------------------------------------- 1 |  7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /WordPressUWP/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using Windows.ApplicationModel; 4 | using Windows.ApplicationModel.Activation; 5 | using Windows.UI.Xaml; 6 | 7 | using WordPressUWP.Services; 8 | 9 | namespace WordPressUWP 10 | { 11 | /// 12 | /// Provides application-specific behavior to supplement the default Application class. 13 | /// 14 | public sealed partial class App : Application 15 | { 16 | private Lazy _activationService; 17 | 18 | private ActivationService ActivationService 19 | { 20 | get { return _activationService.Value; } 21 | } 22 | 23 | /// 24 | /// Initializes a new instance of the class. 25 | /// This is the first line of authored code executed, and as such 26 | /// is the logical equivalent of main() or WinMain(). 27 | /// 28 | public App() 29 | { 30 | InitializeComponent(); 31 | 32 | EnteredBackground += App_EnteredBackground; 33 | 34 | // Deferred execution until used. Check https://msdn.microsoft.com/library/dd642331(v=vs.110).aspx for further info on Lazy class. 35 | _activationService = new Lazy(CreateActivationService); 36 | } 37 | 38 | /// 39 | /// Invoked when the application is launched normally by the end user. Other entry points 40 | /// will be used such as when the application is launched to open a specific file. 41 | /// 42 | /// Details about the launch request and process. 43 | protected override async void OnLaunched(LaunchActivatedEventArgs e) 44 | { 45 | if (!e.PrelaunchActivated) 46 | { 47 | await ActivationService.ActivateAsync(e); 48 | } 49 | } 50 | 51 | /// 52 | /// Invoked when the application is activated by some means other than normal launching. 53 | /// 54 | /// Event data for the event. 55 | protected override async void OnActivated(IActivatedEventArgs args) 56 | { 57 | await ActivationService.ActivateAsync(args); 58 | } 59 | 60 | private async void App_EnteredBackground(object sender, EnteredBackgroundEventArgs e) 61 | { 62 | var deferral = e.GetDeferral(); 63 | await Helpers.Singleton.Instance.SaveStateAsync(); 64 | deferral.Complete(); 65 | } 66 | 67 | private ActivationService CreateActivationService() 68 | { 69 | return new ActivationService(this, typeof(ViewModels.NewsViewModel), new Views.ShellPage()); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /WordPressUWP/Assets/LockScreenLogo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wp-net/WordPressUWP/8919b15c25d60060971f0ba43a48fe4c6ffc89a8/WordPressUWP/Assets/LockScreenLogo.scale-200.png -------------------------------------------------------------------------------- /WordPressUWP/Assets/Placeholder/placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wp-net/WordPressUWP/8919b15c25d60060971f0ba43a48fe4c6ffc89a8/WordPressUWP/Assets/Placeholder/placeholder.jpg -------------------------------------------------------------------------------- /WordPressUWP/Assets/SplashScreen.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wp-net/WordPressUWP/8919b15c25d60060971f0ba43a48fe4c6ffc89a8/WordPressUWP/Assets/SplashScreen.scale-200.png -------------------------------------------------------------------------------- /WordPressUWP/Assets/Square150x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wp-net/WordPressUWP/8919b15c25d60060971f0ba43a48fe4c6ffc89a8/WordPressUWP/Assets/Square150x150Logo.scale-200.png -------------------------------------------------------------------------------- /WordPressUWP/Assets/Square44x44Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wp-net/WordPressUWP/8919b15c25d60060971f0ba43a48fe4c6ffc89a8/WordPressUWP/Assets/Square44x44Logo.scale-200.png -------------------------------------------------------------------------------- /WordPressUWP/Assets/Square44x44Logo.targetsize-24_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wp-net/WordPressUWP/8919b15c25d60060971f0ba43a48fe4c6ffc89a8/WordPressUWP/Assets/Square44x44Logo.targetsize-24_altform-unplated.png -------------------------------------------------------------------------------- /WordPressUWP/Assets/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wp-net/WordPressUWP/8919b15c25d60060971f0ba43a48fe4c6ffc89a8/WordPressUWP/Assets/StoreLogo.png -------------------------------------------------------------------------------- /WordPressUWP/Assets/Web/Dark.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #000000; 3 | color: #ffffff; 4 | } 5 | 6 | a, a:link, a:visited, a:hover { 7 | color: #ffffff; 8 | } 9 | 10 | #postmeta { 11 | color: #ccc; 12 | } -------------------------------------------------------------------------------- /WordPressUWP/Assets/Web/Light.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #ffffff; 3 | color: #000000; 4 | } 5 | 6 | #postmeta { 7 | color: #666; 8 | } 9 | -------------------------------------------------------------------------------- /WordPressUWP/Assets/Web/Style.css: -------------------------------------------------------------------------------- 1 | body { 2 | max-width:700px; 3 | margin: 0 auto; 4 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 5 | padding: 0 15px 10px 15px; 6 | box-sizing: border-box; 7 | } 8 | 9 | div[id^="attachment"] { 10 | max-width: calc(100% + 30px) !important; 11 | } 12 | 13 | 14 | img { 15 | max-width: calc(100% + 30px) !important; 16 | height: auto; 17 | margin-left: calc(-15px); 18 | margin-right: calc(-15px); 19 | } 20 | 21 | img#featured { 22 | width: calc(100% + 30px) !important; 23 | margin-left: calc(-15px); 24 | margin-right: calc(-15px); 25 | } 26 | 27 | h1 { 28 | font-weight: lighter; 29 | } 30 | 31 | #postmeta { 32 | font-style: italic; 33 | } 34 | 35 | iframe { 36 | width: calc(100% + 30px); 37 | margin-left: calc(-15px); 38 | margin-right: calc(-15px); 39 | } 40 | 41 | @media (max-width: 639px) { 42 | img { 43 | width: calc(100% + 25px); 44 | margin-left: calc(-15px); 45 | margin-right: calc(-15px); 46 | } 47 | } 48 | 49 | /*custom plugin styles */ 50 | 51 | .wpappbox { 52 | width: 100%; 53 | display: block; 54 | float: left; 55 | padding: 15px; 56 | } 57 | 58 | .wpappbox img { 59 | float:left; 60 | display: inline-block; 61 | margin-right: 12px; 62 | } 63 | 64 | .wpappbox .qrcode { 65 | margin-top: -10px; 66 | float: left; 67 | } 68 | 69 | .applinks { 70 | display: block; 71 | width: 100%; 72 | } -------------------------------------------------------------------------------- /WordPressUWP/Assets/Web/script.js: -------------------------------------------------------------------------------- 1 | var myElement = document.body; 2 | 3 | var mc = new Hammer(myElement); 4 | // listen to events... 5 | mc.on("swipeleft swiperight", function (ev) { 6 | window.external.notify(ev.type); 7 | }); -------------------------------------------------------------------------------- /WordPressUWP/Assets/Wide310x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wp-net/WordPressUWP/8919b15c25d60060971f0ba43a48fe4c6ffc89a8/WordPressUWP/Assets/Wide310x150Logo.scale-200.png -------------------------------------------------------------------------------- /WordPressUWP/Config.cs: -------------------------------------------------------------------------------- 1 | namespace WordPressUWP 2 | { 3 | public static class Config 4 | { 5 | public const string BaseUri = "https://windowsarea.de/"; 6 | public static string WordPressUri = $"{BaseUri}wp-json/"; 7 | 8 | // Push Notification Settings 9 | public const bool NotificationsEnabled = false; 10 | public const string HubName = "NotificationHubName"; 11 | public const string AccessSiganture = "Endpoint="; 12 | 13 | // Comments 14 | public static int CommentDepth = 3; 15 | 16 | // Authentication 17 | public static bool EnableLogin = true; 18 | 19 | // Style 20 | public static int DefaultFontSize = 16; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /WordPressUWP/Helpers/AuthorNameConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Windows.UI.Xaml.Data; 4 | using WordPressPCL.Models; 5 | 6 | namespace WordPressUWP.Helpers 7 | { 8 | public class AuthorNameConverter : IValueConverter 9 | { 10 | public object Convert(object value, Type targetType, object parameter, string language) 11 | { 12 | if(value is List authors) 13 | { 14 | return authors[0].Name; 15 | } 16 | return null; 17 | } 18 | 19 | public object ConvertBack(object value, Type targetType, object parameter, string language) 20 | { 21 | throw new NotImplementedException(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /WordPressUWP/Helpers/DebugConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Windows.UI.Xaml.Data; 7 | 8 | namespace WordPressUWP.Helpers 9 | { 10 | public class DebugConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, string language) 13 | { 14 | return value; 15 | } 16 | 17 | public object ConvertBack(object value, Type targetType, object parameter, string language) 18 | { 19 | throw new NotImplementedException(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /WordPressUWP/Helpers/DisplayTermsConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using Windows.UI.Xaml.Data; 6 | using WordPressPCL.Models; 7 | 8 | namespace WordPressUWP.Helpers 9 | { 10 | public class DisplayTermsConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, string language) 13 | { 14 | var terms = value as IEnumerable>; 15 | var taxonomy = parameter as string; 16 | if (String.IsNullOrEmpty(taxonomy)) 17 | taxonomy = "category"; 18 | 19 | ObservableCollection outputList = new ObservableCollection(); 20 | foreach (var list in terms) 21 | { 22 | var search = list.Where(x => x.Taxonomy.ToString() == taxonomy); 23 | foreach (var s in search) 24 | { 25 | outputList.Add(s); 26 | } 27 | } 28 | 29 | string output = ""; 30 | foreach (var term in outputList) 31 | { 32 | output += $"{term.Name}, "; 33 | } 34 | return output.Remove(output.Length - 2); 35 | } 36 | 37 | public object ConvertBack(object value, Type targetType, object parameter, string language) 38 | { 39 | throw new NotImplementedException(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /WordPressUWP/Helpers/EnumToBooleanConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using Windows.UI.Xaml.Data; 4 | 5 | namespace WordPressUWP.Helpers 6 | { 7 | public class EnumToBooleanConverter : IValueConverter 8 | { 9 | public Type EnumType { get; set; } 10 | 11 | public object Convert(object value, Type targetType, object parameter, string language) 12 | { 13 | if (parameter is string enumString) 14 | { 15 | if (!Enum.IsDefined(EnumType, value)) 16 | { 17 | throw new ArgumentException("value must be an Enum!"); 18 | } 19 | 20 | var enumValue = Enum.Parse(EnumType, enumString); 21 | 22 | return enumValue.Equals(value); 23 | } 24 | 25 | throw new ArgumentException("parameter must be an Enum name!"); 26 | } 27 | 28 | public object ConvertBack(object value, Type targetType, object parameter, string language) 29 | { 30 | if (parameter is string enumString) 31 | { 32 | return Enum.Parse(EnumType, enumString); 33 | } 34 | 35 | throw new ArgumentException("parameter must be an Enum name!"); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /WordPressUWP/Helpers/FeaturedImageConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Windows.UI.Xaml.Data; 5 | using WordPressPCL.Models; 6 | 7 | namespace WordPressUWP.Helpers 8 | { 9 | public class FeaturedImageConverter : IValueConverter 10 | { 11 | public object Convert(object value, Type targetType, object parameter, string language) 12 | { 13 | if (value is Embedded) 14 | { 15 | var p = (Embedded)value; 16 | if(p.WpFeaturedmedia != null) 17 | { 18 | var l = new List(p.WpFeaturedmedia); 19 | if(l.Count > 0) 20 | { 21 | return l.First().SourceUrl; 22 | } 23 | } 24 | 25 | } 26 | return String.Empty; 27 | } 28 | 29 | public object ConvertBack(object value, Type targetType, object parameter, string language) 30 | { 31 | throw new NotImplementedException(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /WordPressUWP/Helpers/HtmlDecodeConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using Windows.UI.Xaml.Data; 4 | 5 | namespace WordPressUWP.Helpers 6 | { 7 | public class HtmlDecodeConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, string language) 10 | { 11 | if(value is string) 12 | { 13 | return HtmlTools.Strip(WebUtility.HtmlDecode(value.ToString())); 14 | } else 15 | { 16 | return String.Empty; 17 | } 18 | } 19 | 20 | public object ConvertBack(object value, Type targetType, object parameter, string language) 21 | { 22 | if (value is string) 23 | { 24 | return WebUtility.HtmlEncode(value.ToString()); 25 | } 26 | else 27 | { 28 | return String.Empty; 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /WordPressUWP/Helpers/HtmlTools.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | using System.Text.RegularExpressions; 4 | using Windows.UI.Xaml; 5 | using WordPressPCL.Models; 6 | using WordPressUWP.Models; 7 | using WordPressUWP.Services; 8 | 9 | namespace WordPressUWP.Helpers 10 | { 11 | 12 | public static class HtmlTools 13 | 14 | { 15 | public static string Strip(string text) 16 | { 17 | return Regex.Replace(text, @"<(.|\n)*?>", string.Empty); 18 | } 19 | 20 | public static string WrapContent(Post post, HtmlSettings settings) 21 | { 22 | var sb = new StringBuilder(); 23 | var isDark = ThemeSelectorService.IsDarkMode(); 24 | var content = post.Content.Rendered; 25 | 26 | 27 | // remove first img from post if there's one 28 | if(post.Embedded.WpFeaturedmedia != null) 29 | { 30 | content = Regex.Replace(content, "^", ""); 32 | } 33 | // add missing protocols to img links 34 | content = Regex.Replace(content, "src=\"//", "src=\"https://"); 35 | 36 | sb.Append(""); 37 | sb.Append(""); 38 | // add stylesheets 39 | sb.Append(""); 40 | if (isDark) 41 | { 42 | sb.Append(""); 43 | } 44 | else 45 | { 46 | sb.Append(""); 47 | } 48 | 49 | // sb.Append(""); 50 | sb.Append(""); 51 | sb.Append(""); 52 | 53 | sb.Append(FeaturedImage(post)); 54 | sb.Append($"

{post.Title.Rendered}

"); 55 | 56 | var authors = new List(post.Embedded.Author); 57 | sb.Append($"

{authors[0].Name} | {post.Date}

"); 58 | sb.Append(content); 59 | sb.Append(""); 60 | 61 | // add javascript 62 | sb.Append(""); 63 | sb.Append(""); 64 | 65 | return sb.ToString(); 66 | } 67 | 68 | public static string FeaturedImage(Post post) 69 | { 70 | if (post.Embedded.WpFeaturedmedia == null) 71 | return string.Empty; 72 | 73 | var images = new List(post.Embedded.WpFeaturedmedia); 74 | 75 | var img = images[0]; 76 | var imgSrc = img.SourceUrl; 77 | if (imgSrc == null) 78 | return string.Empty; 79 | 80 | var sb = new StringBuilder(); 81 | sb.Append(""); 95 | } 96 | return sb.ToString(); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /WordPressUWP/Helpers/InverseBoolConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Windows.UI.Xaml.Data; 3 | 4 | namespace WordPressUWP.Helpers 5 | { 6 | public class InverseBoolConverter : IValueConverter 7 | { 8 | public object Convert(object value, Type targetType, object parameter, string language) 9 | { 10 | if (value is bool boolValue) 11 | return !boolValue; 12 | 13 | return true; 14 | } 15 | 16 | public object ConvertBack(object value, Type targetType, object parameter, string language) 17 | { 18 | throw new NotImplementedException(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /WordPressUWP/Helpers/Json.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | using Newtonsoft.Json; 5 | 6 | namespace WordPressUWP.Helpers 7 | { 8 | public static class Json 9 | { 10 | public static async Task ToObjectAsync(string value) 11 | { 12 | return await Task.Run(() => 13 | { 14 | return JsonConvert.DeserializeObject(value); 15 | }); 16 | } 17 | 18 | public static async Task StringifyAsync(object value) 19 | { 20 | return await Task.Run(() => 21 | { 22 | return JsonConvert.SerializeObject(value); 23 | }); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /WordPressUWP/Helpers/ProtocolToURLConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Windows.UI.Xaml.Data; 3 | 4 | namespace WordPressUWP.Helpers 5 | { 6 | public class ProtocolToURLConverter : IValueConverter 7 | { 8 | public object Convert(object value, Type targetType, object parameter, string language) 9 | { 10 | var st = value.ToString(); 11 | if (st.StartsWith("//www.")) 12 | { 13 | st = "https:" + st; 14 | } 15 | return st; 16 | } 17 | 18 | public object ConvertBack(object value, Type targetType, object parameter, string language) 19 | { 20 | throw new NotImplementedException(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /WordPressUWP/Helpers/ReplyToConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Windows.UI.Xaml.Data; 3 | using WordPressPCL.Models; 4 | 5 | namespace WordPressUWP.Helpers 6 | { 7 | public class ReplyToConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, string language) 10 | { 11 | if(value is Comment comment) 12 | { 13 | if (comment == null) 14 | return null; 15 | var response = "CommentReply".GetLocalized(); 16 | return String.Format(response, comment.AuthorName); 17 | } 18 | return null; 19 | 20 | } 21 | 22 | public object ConvertBack(object value, Type targetType, object parameter, string language) 23 | { 24 | throw new NotImplementedException(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /WordPressUWP/Helpers/ResourceExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | using Windows.ApplicationModel.Resources; 5 | 6 | namespace WordPressUWP.Helpers 7 | { 8 | internal static class ResourceExtensions 9 | { 10 | private static ResourceLoader _resLoader = new ResourceLoader(); 11 | 12 | public static string GetLocalized(this string resourceKey) 13 | { 14 | return _resLoader.GetString(resourceKey); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /WordPressUWP/Helpers/SettingsStorageExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Windows.ApplicationModel; 5 | using Windows.Security.Credentials; 6 | using Windows.Storage; 7 | using Windows.Storage.Streams; 8 | 9 | namespace WordPressUWP.Helpers 10 | { 11 | // Use these extension methods to store and retrieve local and roaming app data 12 | // For more info regarding storing and retrieving app data see documentation at 13 | // https://docs.microsoft.com/windows/uwp/app-settings/store-and-retrieve-app-data 14 | public static class SettingsStorageExtensions 15 | { 16 | private const string FileExtension = ".json"; 17 | 18 | public static bool IsRoamingStorageAvailable(this ApplicationData appData) 19 | { 20 | return appData.RoamingStorageQuota == 0; 21 | } 22 | 23 | public static async Task SaveAsync(this StorageFolder folder, string name, T content) 24 | { 25 | var file = await folder.CreateFileAsync(GetFileName(name), CreationCollisionOption.ReplaceExisting); 26 | var fileContent = await Json.StringifyAsync(content); 27 | 28 | await FileIO.WriteTextAsync(file, fileContent); 29 | } 30 | 31 | public static async Task ReadAsync(this StorageFolder folder, string name) 32 | { 33 | if (!File.Exists(Path.Combine(folder.Path, GetFileName(name)))) 34 | { 35 | return default(T); 36 | } 37 | 38 | var file = await folder.GetFileAsync($"{name}.json"); 39 | var fileContent = await FileIO.ReadTextAsync(file); 40 | 41 | return await Json.ToObjectAsync(fileContent); 42 | } 43 | 44 | public static async Task SaveAsync(this ApplicationDataContainer settings, string key, T value) 45 | { 46 | settings.SaveString(key, await Json.StringifyAsync(value)); 47 | } 48 | 49 | public static void SaveString(this ApplicationDataContainer settings, string key, string value) 50 | { 51 | settings.Values[key] = value; 52 | } 53 | 54 | public static async Task ReadAsync(this ApplicationDataContainer settings, string key) 55 | { 56 | object obj = null; 57 | 58 | if (settings.Values.TryGetValue(key, out obj)) 59 | { 60 | return await Json.ToObjectAsync((string)obj); 61 | } 62 | 63 | return default(T); 64 | } 65 | 66 | public static string ReadString(this ApplicationDataContainer settings, string key) 67 | { 68 | var st = settings.Values[key]; 69 | if(st != null) 70 | { 71 | return settings.Values[key].ToString(); 72 | } 73 | else 74 | { 75 | return String.Empty; 76 | } 77 | } 78 | 79 | public static async Task SaveFileAsync(this StorageFolder folder, byte[] content, string fileName, CreationCollisionOption options = CreationCollisionOption.ReplaceExisting) 80 | { 81 | if (content == null) 82 | { 83 | throw new ArgumentNullException("content"); 84 | } 85 | 86 | if (string.IsNullOrEmpty(fileName)) 87 | { 88 | throw new ArgumentException("File name is null or empty. Specify a valid file name", "fileName"); 89 | } 90 | 91 | var storageFile = await folder.CreateFileAsync(fileName, options); 92 | await FileIO.WriteBytesAsync(storageFile, content); 93 | return storageFile; 94 | } 95 | 96 | public static async Task ReadFileAsync(this StorageFolder folder, string fileName) 97 | { 98 | var item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false); 99 | 100 | if ((item != null) && item.IsOfType(StorageItemTypes.File)) 101 | { 102 | var storageFile = await folder.GetFileAsync(fileName); 103 | byte[] content = await storageFile.ReadBytesAsync(); 104 | return content; 105 | } 106 | 107 | return null; 108 | } 109 | 110 | public static async Task ReadBytesAsync(this StorageFile file) 111 | { 112 | if (file != null) 113 | { 114 | using (IRandomAccessStream stream = await file.OpenReadAsync()) 115 | { 116 | using (var reader = new DataReader(stream.GetInputStreamAt(0))) 117 | { 118 | await reader.LoadAsync((uint)stream.Size); 119 | var bytes = new byte[stream.Size]; 120 | reader.ReadBytes(bytes); 121 | return bytes; 122 | } 123 | } 124 | } 125 | 126 | return null; 127 | } 128 | 129 | public static void SaveCredentialsToLocker(string username, string password) 130 | { 131 | 132 | var vault = new PasswordVault(); 133 | 134 | //var credential = GetCredentialFromLocker(username); 135 | //if(credential != null) 136 | //{ 137 | // vault.Remove(credential); 138 | //} 139 | 140 | vault.Add(new PasswordCredential( 141 | Package.Current.DisplayName, username, password)); 142 | } 143 | 144 | 145 | public static PasswordCredential GetCredentialFromLocker(string username = "") 146 | { 147 | PasswordCredential credential = null; 148 | var resourceName = Package.Current.DisplayName; 149 | var vault = new PasswordVault(); 150 | try 151 | { 152 | 153 | var credentialList = vault.FindAllByResource(resourceName); 154 | if (credentialList.Count > 0) 155 | { 156 | if (credentialList.Count == 1) 157 | { 158 | credential = credentialList[0]; 159 | } 160 | else 161 | { 162 | // When there are multiple usernames, 163 | // retrieve the default username. If one doesn't 164 | // exist, then display UI to have the user select 165 | // a default username. 166 | 167 | credential = vault.Retrieve(resourceName, username); 168 | } 169 | credential.RetrievePassword(); 170 | } 171 | 172 | } catch 173 | { 174 | // 175 | } 176 | 177 | return credential; 178 | } 179 | 180 | public static void RemoveCredentialsFromLocker() 181 | { 182 | var resourceName = Package.Current.DisplayName; 183 | var vault = new PasswordVault(); 184 | var credentialList = vault.FindAllByResource(resourceName); 185 | foreach (var credential in credentialList) 186 | { 187 | vault.Remove(credential); 188 | } 189 | } 190 | 191 | private static string GetFileName(string name) 192 | { 193 | return string.Concat(name, FileExtension); 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /WordPressUWP/Helpers/Singleton.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | 4 | namespace WordPressUWP.Helpers 5 | { 6 | internal static class Singleton 7 | where T : new() 8 | { 9 | private static ConcurrentDictionary _instances = new ConcurrentDictionary(); 10 | 11 | public static T Instance 12 | { 13 | get 14 | { 15 | return _instances.GetOrAdd(typeof(T), (t) => new T()); 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /WordPressUWP/Helpers/SwipedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WordPressUWP.Helpers 8 | { 9 | public class SwipedEventArgs : EventArgs 10 | { 11 | public SwipeDirection Direction { get; set; } 12 | 13 | public SwipedEventArgs(SwipeDirection direction) 14 | { 15 | Direction = direction; 16 | } 17 | } 18 | 19 | public enum SwipeDirection 20 | { 21 | Left, 22 | Right, 23 | Up, 24 | Down 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /WordPressUWP/Helpers/TheadedCommentMarginConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Windows.UI.Xaml; 7 | using Windows.UI.Xaml.Data; 8 | 9 | namespace WordPressUWP.Helpers 10 | { 11 | class TheadedCommentMarginConverter : IValueConverter 12 | { 13 | public object Convert(object value, Type targetType, object parameter, string language) 14 | { 15 | if(value is int) 16 | { 17 | int v = (int)value; 18 | return new Thickness(v * 24, 12, 12, 24); 19 | } 20 | return new Thickness(0, 12, 12, 24); 21 | } 22 | 23 | public object ConvertBack(object value, Type targetType, object parameter, string language) 24 | { 25 | throw new NotImplementedException(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /WordPressUWP/Helpers/VisibleWhenZeroConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Windows.UI.Xaml; 3 | using Windows.UI.Xaml.Data; 4 | 5 | namespace WordPressUWP.Helpers 6 | { 7 | public class VisibleWhenZeroConverter : IValueConverter 8 | { 9 | public object Convert(object v, Type t, object p, string l) => 10 | Equals(0, (int)v) ? Visibility.Visible : Visibility.Collapsed; 11 | 12 | public object ConvertBack(object v, Type t, object p, string l) => null; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /WordPressUWP/Interfaces/IPushNotificationService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace WordPressUWP.Interfaces 4 | { 5 | public interface IPushNotificationService 6 | { 7 | Task EnablePushNotifications(); 8 | Task DisablePushNotificaitons(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /WordPressUWP/Interfaces/IWordPressService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Threading.Tasks; 5 | using WordPressPCL.Models; 6 | using WordPressUWP.Helpers; 7 | 8 | namespace WordPressUWP.Interfaces 9 | { 10 | public interface IWordPressService 11 | { 12 | bool IsAuthenticated { get; set; } 13 | 14 | bool IsLoadingPosts { get; set; } 15 | 16 | Task> GetLatestPosts(int page = 0, int perPage = 20); 17 | 18 | Task AuthenticateUser(string username, string password); 19 | 20 | Task IsUserAuthenticated(); 21 | 22 | User GetUser(); 23 | 24 | Task> GetCommentsForPost(int postid); 25 | 26 | Task PostComment(int postId, string text, int replyto = 0); 27 | Task Logout(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /WordPressUWP/Models/HtmlSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WordPressUWP.Models 8 | { 9 | public class HtmlSettings 10 | { 11 | public int FontSize { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /WordPressUWP/Models/SettingsLocality.cs: -------------------------------------------------------------------------------- 1 | namespace WordPressUWP.Models 2 | { 3 | public enum SettingLocality 4 | { 5 | Local, 6 | Roamed 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /WordPressUWP/Package.appxmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | WordPressUWP 19 | pente 20 | Assets\StoreLogo.png 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /WordPressUWP/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("WordPressUWP")] 10 | [assembly: AssemblyDescription("")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyProduct("WordPressUWP")] 14 | [assembly: AssemblyCopyright("Copyright © 2017")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | 18 | // Version information for an assembly consists of the following four values: 19 | // 20 | // Major Version 21 | // Minor Version 22 | // Build Number 23 | // Revision 24 | // 25 | // You can specify all the values or you can default the Build and Revision Numbers 26 | // by using the '*' as shown below: 27 | // [assembly: AssemblyVersion("1.0.*")] 28 | [assembly: AssemblyVersion("1.0.0.0")] 29 | [assembly: AssemblyFileVersion("1.0.0.0")] 30 | [assembly: ComVisible(false)] 31 | -------------------------------------------------------------------------------- /WordPressUWP/Properties/Default.rd.xml: -------------------------------------------------------------------------------- 1 |  17 | 18 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /WordPressUWP/Services/ActivationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | using Windows.ApplicationModel.Activation; 7 | using Windows.UI.Core; 8 | using Windows.UI.Xaml; 9 | using Windows.UI.Xaml.Controls; 10 | using Windows.UI.Xaml.Navigation; 11 | 12 | using WordPressUWP.Activation; 13 | using WordPressUWP.Helpers; 14 | 15 | namespace WordPressUWP.Services 16 | { 17 | // For more information on application activation see https://github.com/Microsoft/WindowsTemplateStudio/blob/master/docs/activation.md 18 | internal class ActivationService 19 | { 20 | private readonly App _app; 21 | private readonly UIElement _shell; 22 | private readonly Type _defaultNavItem; 23 | 24 | private ViewModels.ViewModelLocator Locator => Application.Current.Resources["Locator"] as ViewModels.ViewModelLocator; 25 | 26 | private NavigationServiceEx NavigationService => Locator.NavigationService; 27 | 28 | public ActivationService(App app, Type defaultNavItem, UIElement shell = null) 29 | { 30 | _app = app; 31 | _shell = shell ?? new Frame(); 32 | _defaultNavItem = defaultNavItem; 33 | } 34 | 35 | public async Task ActivateAsync(object activationArgs) 36 | { 37 | if (IsInteractive(activationArgs)) 38 | { 39 | // Initialize things like registering background task before the app is loaded 40 | await InitializeAsync(); 41 | 42 | // Do not repeat app initialization when the Window already has content, 43 | // just ensure that the window is active 44 | if (Window.Current.Content == null) 45 | { 46 | // Create a Frame to act as the navigation context and navigate to the first page 47 | Window.Current.Content = _shell; 48 | NavigationService.NavigationFailed += (sender, e) => 49 | { 50 | throw new Exception("Failed to load Page " + e.SourcePageType.FullName); 51 | }; 52 | NavigationService.Navigated += Frame_Navigated; 53 | if (SystemNavigationManager.GetForCurrentView() != null) 54 | { 55 | SystemNavigationManager.GetForCurrentView().BackRequested += ActivationService_BackRequested; 56 | } 57 | } 58 | } 59 | 60 | var activationHandler = GetActivationHandlers() 61 | .FirstOrDefault(h => h.CanHandle(activationArgs)); 62 | 63 | if (activationHandler != null) 64 | { 65 | await activationHandler.HandleAsync(activationArgs); 66 | } 67 | 68 | if (IsInteractive(activationArgs)) 69 | { 70 | var defaultHandler = new DefaultLaunchActivationHandler(_defaultNavItem); 71 | if (defaultHandler.CanHandle(activationArgs)) 72 | { 73 | await defaultHandler.HandleAsync(activationArgs); 74 | } 75 | 76 | // Ensure the current window is active 77 | Window.Current.Activate(); 78 | 79 | // Tasks after activation 80 | await StartupAsync(); 81 | } 82 | } 83 | 84 | private async Task InitializeAsync() 85 | { 86 | //await Singleton.Instance.EnableQueueAsync(); 87 | await ThemeSelectorService.InitializeAsync(); 88 | await Task.CompletedTask; 89 | } 90 | 91 | private async Task StartupAsync() 92 | { 93 | await WhatsNewDisplayService.ShowIfAppropriateAsync(); 94 | await FirstRunDisplayService.ShowIfAppropriateAsync(); 95 | //Singleton.Instance.SampleUpdate(); 96 | 97 | // TODO WTS: To use the HubNotificationService specific data related with your Azure Notification Hubs is required. 98 | // 1. Go to the HubNotificationsService class, in the InitializeAsync() method, provide the Hub Name and DefaultListenSharedAccessSignature. 99 | // 2. Uncomment the following line (an exception is thrown if it is executed before the previous information is provided). 100 | // Singleton.Instance.InitializeAsync(); 101 | ThemeSelectorService.SetRequestedTheme(); 102 | await Task.CompletedTask; 103 | } 104 | 105 | private IEnumerable GetActivationHandlers() 106 | { 107 | //yield return Singleton.Instance; 108 | yield return Singleton.Instance; 109 | yield return Singleton.Instance; 110 | } 111 | 112 | private bool IsInteractive(object args) 113 | { 114 | return args is IActivatedEventArgs; 115 | } 116 | 117 | private void Frame_Navigated(object sender, NavigationEventArgs e) 118 | { 119 | SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = NavigationService.CanGoBack ? 120 | AppViewBackButtonVisibility.Visible : AppViewBackButtonVisibility.Collapsed; 121 | } 122 | 123 | private void ActivationService_BackRequested(object sender, BackRequestedEventArgs e) 124 | { 125 | if (NavigationService.CanGoBack) 126 | { 127 | NavigationService.GoBack(); 128 | e.Handled = true; 129 | } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /WordPressUWP/Services/FirstRunDisplayService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | using Windows.ApplicationModel; 5 | 6 | using WordPressUWP.Helpers; 7 | using WordPressUWP.Views; 8 | 9 | namespace WordPressUWP.Services 10 | { 11 | public class FirstRunDisplayService 12 | { 13 | internal static async Task ShowIfAppropriateAsync() 14 | { 15 | bool hasShownFirstRun = false; 16 | hasShownFirstRun = await Windows.Storage.ApplicationData.Current.LocalSettings.ReadAsync(nameof(hasShownFirstRun)); 17 | 18 | if (!hasShownFirstRun) 19 | { 20 | await Windows.Storage.ApplicationData.Current.LocalSettings.SaveAsync(nameof(hasShownFirstRun), true); 21 | var dialog = new FirstRunDialog(); 22 | await dialog.ShowAsync(); 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /WordPressUWP/Services/HubNotificationsService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.WindowsAzure.Messaging; 4 | using Windows.ApplicationModel.Activation; 5 | using Windows.Networking.PushNotifications; 6 | using WordPressUWP.Activation; 7 | using WordPressUWP.ViewModels; 8 | using Windows.Storage; 9 | using WordPressUWP.Helpers; 10 | using CommonServiceLocator; 11 | 12 | namespace WordPressUWP.Services 13 | { 14 | internal class HubNotificationsService : ActivationHandler 15 | { 16 | public async void InitializeAsync() 17 | { 18 | //// See more about adding push notifications to your Windows app at 19 | //// https://docs.microsoft.com/azure/app-service-mobile/app-service-mobile-windows-store-dotnet-get-started-push 20 | 21 | // check if push should be registered 22 | var pushNotificationsEnabled = await ApplicationData.Current.LocalSettings.ReadAsync("PushNotificationsEnabled"); 23 | if (pushNotificationsEnabled) 24 | { 25 | 26 | //var result = await RegisterNotifications(); 27 | //if (result.RegistrationId != null) 28 | //{ 29 | // // RegistrationID let you know it was successful 30 | //} 31 | } 32 | else 33 | { 34 | //await UnregisterNotifications(); 35 | } 36 | 37 | // You can also send push notifications from Windows Developer Center targeting your app consumers 38 | // Documentation: https://docs.microsoft.com/windows/uwp/publish/send-push-notifications-to-your-apps-customers 39 | } 40 | 41 | protected override async Task HandleInternalAsync(ToastNotificationActivatedEventArgs args) 42 | { 43 | //// TODO WTS: Handle activation from toast notification, 44 | //// For more info handling activation see documentation at 45 | //// https://blogs.msdn.microsoft.com/tiles_and_toasts/2015/07/08/quickstart-sending-a-local-toast-notification-and-handling-activations-from-it-windows-10/ 46 | var navigationService = ServiceLocator.Current.GetInstance(); 47 | 48 | navigationService.Navigate(typeof(NewsViewModel).FullName, args.Argument); 49 | 50 | await Task.CompletedTask; 51 | } 52 | 53 | private async Task RegisterNotifications() 54 | { 55 | var hub = new NotificationHub(Config.HubName, Config.AccessSiganture); 56 | var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync(); 57 | return await hub.RegisterNativeAsync(channel.Uri); 58 | } 59 | 60 | private async Task UnregisterNotifications() 61 | { 62 | var hub = new NotificationHub(Config.HubName, Config.AccessSiganture); 63 | var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync(); 64 | await hub.UnregisterAllAsync(channel.Uri); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /WordPressUWP/Services/LiveTileService.Samples.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | using Microsoft.Toolkit.Uwp.Notifications; 5 | 6 | using Windows.UI.Notifications; 7 | using Windows.UI.StartScreen; 8 | 9 | namespace WordPressUWP.Services 10 | { 11 | internal partial class LiveTileService 12 | { 13 | public void SampleUpdate() 14 | { 15 | // See more information about Live Tiles Notifications 16 | // Documentation: https://docs.microsoft.com/windows/uwp/controls-and-patterns/tiles-and-notifications-sending-a-local-tile-notification 17 | 18 | // These would be initialized with actual data 19 | string from = "Jennifer Parker"; 20 | string subject = "Photos from our trip"; 21 | string body = "Check out these awesome photos I took while in New Zealand!"; 22 | 23 | // Construct the tile content 24 | var content = new TileContent() 25 | { 26 | Visual = new TileVisual() 27 | { 28 | Arguments = "Jennifer Parker", 29 | TileMedium = new TileBinding() 30 | { 31 | Content = new TileBindingContentAdaptive() 32 | { 33 | Children = 34 | { 35 | new AdaptiveText() 36 | { 37 | Text = from 38 | }, 39 | new AdaptiveText() 40 | { 41 | Text = subject, 42 | HintStyle = AdaptiveTextStyle.CaptionSubtle 43 | }, 44 | new AdaptiveText() 45 | { 46 | Text = body, 47 | HintStyle = AdaptiveTextStyle.CaptionSubtle 48 | } 49 | } 50 | } 51 | }, 52 | 53 | TileWide = new TileBinding() 54 | { 55 | Content = new TileBindingContentAdaptive() 56 | { 57 | Children = 58 | { 59 | new AdaptiveText() 60 | { 61 | Text = from, 62 | HintStyle = AdaptiveTextStyle.Subtitle 63 | }, 64 | new AdaptiveText() 65 | { 66 | Text = subject, 67 | HintStyle = AdaptiveTextStyle.CaptionSubtle 68 | }, 69 | new AdaptiveText() 70 | { 71 | Text = body, 72 | HintStyle = AdaptiveTextStyle.CaptionSubtle 73 | } 74 | } 75 | } 76 | } 77 | } 78 | }; 79 | 80 | // Then create the tile notification 81 | var notification = new TileNotification(content.GetXml()); 82 | UpdateTile(notification); 83 | } 84 | 85 | public async Task SamplePinSecondaryAsync(string pageName) 86 | { 87 | // TODO WTS: Call this method to Pin a Secondary Tile from a page. 88 | // You also must implement the navigation to this specific page in the OnLaunched event handler on App.xaml.cs 89 | var tile = new SecondaryTile(DateTime.Now.Ticks.ToString()); 90 | tile.Arguments = pageName; 91 | tile.DisplayName = pageName; 92 | tile.VisualElements.Square44x44Logo = new Uri("ms-appx:///Assets/Square44x44Logo.scale-200.png"); 93 | tile.VisualElements.Square150x150Logo = new Uri("ms-appx:///Assets/Square150x150Logo.scale-200.png"); 94 | tile.VisualElements.Wide310x150Logo = new Uri("ms-appx:///Assets/Wide310x150Logo.scale-200.png"); 95 | tile.VisualElements.ShowNameOnSquare150x150Logo = true; 96 | tile.VisualElements.ShowNameOnWide310x150Logo = true; 97 | await PinSecondaryTileAsync(tile); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /WordPressUWP/Services/LiveTileService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | 5 | using Windows.ApplicationModel.Activation; 6 | using Windows.Storage; 7 | using Windows.UI.Notifications; 8 | using Windows.UI.StartScreen; 9 | 10 | using WordPressUWP.Activation; 11 | using WordPressUWP.Helpers; 12 | 13 | namespace WordPressUWP.Services 14 | { 15 | internal partial class LiveTileService : ActivationHandler 16 | { 17 | private const string QueueEnabledKey = "NotificationQueueEnabled"; 18 | 19 | public async Task EnableQueueAsync() 20 | { 21 | var queueEnabled = await ApplicationData.Current.LocalSettings.ReadAsync(QueueEnabledKey); 22 | if (!queueEnabled) 23 | { 24 | TileUpdateManager.CreateTileUpdaterForApplication().EnableNotificationQueue(true); 25 | await ApplicationData.Current.LocalSettings.SaveAsync(QueueEnabledKey, true); 26 | } 27 | } 28 | 29 | public void UpdateTile(TileNotification notification) 30 | { 31 | TileUpdateManager.CreateTileUpdaterForApplication().Update(notification); 32 | } 33 | 34 | public async Task PinSecondaryTileAsync(SecondaryTile tile, bool allowDuplicity = false) 35 | { 36 | if (!await IsAlreadyPinnedAsync(tile) || allowDuplicity) 37 | { 38 | return await tile.RequestCreateAsync(); 39 | } 40 | 41 | return false; 42 | } 43 | 44 | private async Task IsAlreadyPinnedAsync(SecondaryTile tile) 45 | { 46 | var secondaryTiles = await SecondaryTile.FindAllAsync(); 47 | return secondaryTiles.Any(t => t.Arguments == tile.Arguments); 48 | } 49 | 50 | protected override async Task HandleInternalAsync(LaunchActivatedEventArgs args) 51 | { 52 | // If app is launched from a SecondaryTile, tile arguments property is contained in args.Arguments 53 | // var secondaryTileArguments = args.Arguments; 54 | 55 | // If app is launched from a LiveTile notification update, TileContent arguments property is contained in args.TileActivatedInfo.RecentlyShownNotifications 56 | // var tileUpdatesArguments = args.TileActivatedInfo.RecentlyShownNotifications; 57 | await Task.CompletedTask; 58 | } 59 | 60 | protected override bool CanHandleInternal(LaunchActivatedEventArgs args) 61 | { 62 | return LaunchFromSecondaryTile(args) || LaunchFromLiveTileUpdate(args); 63 | } 64 | 65 | private bool LaunchFromSecondaryTile(LaunchActivatedEventArgs args) 66 | { 67 | // If app is launched from a SecondaryTile, tile arguments property is contained in args.Arguments 68 | // TODO WTS: Implement your own logic to determine if you can handle the SecondaryTile activation 69 | return false; 70 | } 71 | 72 | private bool LaunchFromLiveTileUpdate(LaunchActivatedEventArgs args) 73 | { 74 | // If app is launched from a LiveTile notification update, TileContent arguments property is contained in args.TileActivatedInfo.RecentlyShownNotifications 75 | // TODO WTS: Implement your own logic to determine if you can handle the LiveTile notification update activation 76 | return false; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /WordPressUWP/Services/NavigationServiceEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | using Windows.UI.Xaml; 6 | using Windows.UI.Xaml.Controls; 7 | using Windows.UI.Xaml.Media.Animation; 8 | using Windows.UI.Xaml.Navigation; 9 | 10 | namespace WordPressUWP.Services 11 | { 12 | public class NavigationServiceEx 13 | { 14 | public event NavigatedEventHandler Navigated; 15 | 16 | public event NavigationFailedEventHandler NavigationFailed; 17 | 18 | private readonly Dictionary _pages = new Dictionary(); 19 | 20 | private Frame _frame; 21 | 22 | public Frame Frame 23 | { 24 | get 25 | { 26 | if (_frame == null) 27 | { 28 | _frame = Window.Current.Content as Frame; 29 | RegisterFrameEvents(); 30 | } 31 | 32 | return _frame; 33 | } 34 | 35 | set 36 | { 37 | UnregisterFrameEvents(); 38 | _frame = value; 39 | RegisterFrameEvents(); 40 | } 41 | } 42 | 43 | public bool CanGoBack => Frame.CanGoBack; 44 | 45 | public bool CanGoForward => Frame.CanGoForward; 46 | 47 | public void GoBack() => Frame.GoBack(); 48 | 49 | public void GoForward() => Frame.GoForward(); 50 | 51 | public bool Navigate(string pageKey, object parameter = null, NavigationTransitionInfo infoOverride = null) 52 | { 53 | lock (_pages) 54 | { 55 | if (!_pages.ContainsKey(pageKey)) 56 | { 57 | throw new ArgumentException($"Page not found: {pageKey}. Did you forget to call NavigationService.Configure?", "pageKey"); 58 | } 59 | 60 | var navigationResult = Frame.Navigate(_pages[pageKey], parameter, infoOverride); 61 | return navigationResult; 62 | } 63 | } 64 | 65 | public void Configure(string key, Type pageType) 66 | { 67 | lock (_pages) 68 | { 69 | if (_pages.ContainsKey(key)) 70 | { 71 | throw new ArgumentException($"The key {key} is already configured in NavigationService"); 72 | } 73 | 74 | if (_pages.Any(p => p.Value == pageType)) 75 | { 76 | throw new ArgumentException($"This type is already configured with key {_pages.First(p => p.Value == pageType).Key}"); 77 | } 78 | 79 | _pages.Add(key, pageType); 80 | } 81 | } 82 | 83 | public string GetNameOfRegisteredPage(Type page) 84 | { 85 | lock (_pages) 86 | { 87 | if (_pages.ContainsValue(page)) 88 | { 89 | return _pages.FirstOrDefault(p => p.Value == page).Key; 90 | } 91 | else 92 | { 93 | throw new ArgumentException($"The page '{page.Name}' is unknown by the NavigationService"); 94 | } 95 | } 96 | } 97 | 98 | private void RegisterFrameEvents() 99 | { 100 | if (_frame != null) 101 | { 102 | _frame.Navigated += Frame_Navigated; 103 | _frame.NavigationFailed += Frame_NavigationFailed; 104 | } 105 | } 106 | 107 | private void UnregisterFrameEvents() 108 | { 109 | if (_frame != null) 110 | { 111 | _frame.Navigated -= Frame_Navigated; 112 | _frame.NavigationFailed -= Frame_NavigationFailed; 113 | } 114 | } 115 | 116 | private void Frame_NavigationFailed(object sender, NavigationFailedEventArgs e) => NavigationFailed?.Invoke(sender, e); 117 | 118 | private void Frame_Navigated(object sender, NavigationEventArgs e) => Navigated?.Invoke(sender, e); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /WordPressUWP/Services/OnBackgroundEnteringEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WordPressUWP.Services 4 | { 5 | public class OnBackgroundEnteringEventArgs : EventArgs 6 | { 7 | public SuspensionState SuspensionState { get; set; } 8 | 9 | public Type Target { get; private set; } 10 | 11 | public OnBackgroundEnteringEventArgs(SuspensionState suspensionState, Type target) 12 | : base() 13 | { 14 | SuspensionState = suspensionState; 15 | Target = target; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /WordPressUWP/Services/PostsService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Toolkit.Collections; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using WordPressPCL.Models; 8 | using System.Threading; 9 | using WordPressUWP.Interfaces; 10 | using GalaSoft.MvvmLight.Ioc; 11 | using System.Diagnostics; 12 | using WordPressUWP.Models; 13 | using WordPressUWP.Helpers; 14 | 15 | namespace WordPressUWP.Services 16 | { 17 | public class PostsService : IIncrementalSource 18 | { 19 | private readonly IWordPressService _wordPressService; 20 | private readonly SettingsService _settings; 21 | 22 | public PostsService() 23 | { 24 | _wordPressService = SimpleIoc.Default.GetInstance(); 25 | _settings = SimpleIoc.Default.GetInstance(); 26 | } 27 | 28 | public async Task> GetPagedItemsAsync(int pageIndex, int pageSize, CancellationToken cancellationToken = default(CancellationToken)) 29 | { 30 | var posts = await _wordPressService.GetLatestPosts(pageIndex, pageSize); 31 | return PreparePosts(posts); 32 | } 33 | 34 | private IEnumerable PreparePosts(IEnumerable posts) 35 | { 36 | // Wrap the HTML content with headers, styles, scripts etc. 37 | int fontsize = _settings.GetSetting("fontsize", () => Config.DefaultFontSize, SettingLocality.Roamed); 38 | var settings = new HtmlSettings 39 | { 40 | FontSize = fontsize 41 | }; 42 | foreach(var post in posts) 43 | { 44 | post.Content.Rendered = HtmlTools.WrapContent(post, settings); 45 | } 46 | return posts; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /WordPressUWP/Services/PushNotificationService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.WindowsAzure.Messaging; 2 | using System; 3 | using System.Threading.Tasks; 4 | using Windows.Networking.PushNotifications; 5 | using WordPressUWP.Helpers; 6 | using WordPressUWP.Interfaces; 7 | 8 | namespace WordPressUWP.Services 9 | { 10 | public class PushNotificationService : IPushNotificationService 11 | { 12 | public async Task DisablePushNotificaitons() 13 | { 14 | try 15 | { 16 | var hub = new NotificationHub(Config.HubName, Config.AccessSiganture); 17 | var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync(); 18 | await hub.UnregisterAllAsync(channel.Uri); 19 | } 20 | catch 21 | { 22 | // error removing subcription 23 | } 24 | 25 | } 26 | 27 | public async Task EnablePushNotifications() 28 | { 29 | try 30 | { 31 | var hub = new NotificationHub(Config.HubName, Config.AccessSiganture); 32 | var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync(); 33 | var result = await hub.RegisterNativeAsync(channel.Uri); 34 | return result.RegistrationId != null; 35 | } 36 | catch 37 | { 38 | // error subscribing to push notifications 39 | return false; 40 | } 41 | 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /WordPressUWP/Services/SettingsService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using Windows.Storage; 5 | using WordPressUWP.Models; 6 | 7 | namespace WordPressUWP.Services 8 | { 9 | /// 10 | /// UWP implementation of settings service using ApplicationData 11 | /// 12 | public class SettingsService 13 | { 14 | /// 15 | /// Cache for settings that were already accessed 16 | /// 17 | private readonly Dictionary _settingCache = new Dictionary(); 18 | 19 | /// 20 | /// Gets a application setting 21 | /// 22 | /// Type of the value stored 23 | /// Key of the setting 24 | /// Builder of the default value 25 | /// Setting locality 26 | /// Forces the cache to be reset 27 | /// 28 | public T GetSetting( 29 | string key, 30 | Func defaultValueBuilder, 31 | SettingLocality locality = SettingLocality.Local, 32 | bool forceResetCache = false) 33 | { 34 | object result = null; 35 | if (forceResetCache || !_settingCache.TryGetValue(key, out result)) 36 | { 37 | var container = locality == SettingLocality.Roamed ? 38 | ApplicationData.Current.RoamingSettings : 39 | ApplicationData.Current.LocalSettings; 40 | _settingCache[key] = RetrieveSettingFromApplicationData(key, defaultValueBuilder, container); 41 | } 42 | return (T)_settingCache[key]; 43 | } 44 | 45 | /// 46 | /// Retrieves a setting from application data or 47 | /// returns a default based on a given builder Func 48 | /// 49 | /// Type of setting to retrieve 50 | /// Key of the setting 51 | /// Returns the default value if the setting is not present 52 | /// Settings container 53 | /// 54 | private T RetrieveSettingFromApplicationData(string key, Func defaultValueBuilder, ApplicationDataContainer container) 55 | { 56 | object result = null; 57 | if (container.Values.TryGetValue(key, out result)) 58 | { 59 | //get existing 60 | try 61 | { 62 | Debug.WriteLine($"getting {key} as {result}"); 63 | return (T)result; 64 | } 65 | catch 66 | { 67 | //invalid value for the given type, remove 68 | container.Values.Remove(key); 69 | } 70 | } 71 | return defaultValueBuilder(); 72 | } 73 | 74 | public void SetSetting(string key, T value, SettingLocality locality = SettingLocality.Local) 75 | { 76 | Debug.WriteLine($"saving {key} as {value}"); 77 | var container = locality == SettingLocality.Roamed ? ApplicationData.Current.RoamingSettings : ApplicationData.Current.LocalSettings; 78 | container.Values[key] = value; 79 | //ensure cache is updated 80 | _settingCache.Remove(key); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /WordPressUWP/Services/SuspendAndResumeService.cs: -------------------------------------------------------------------------------- 1 | using CommonServiceLocator; 2 | using System; 3 | using System.Threading.Tasks; 4 | using Windows.ApplicationModel.Activation; 5 | using Windows.Storage; 6 | using Windows.UI.Xaml.Controls; 7 | 8 | using WordPressUWP.Activation; 9 | using WordPressUWP.Helpers; 10 | 11 | namespace WordPressUWP.Services 12 | { 13 | internal class SuspendAndResumeService : ActivationHandler 14 | { 15 | //// TODO WTS: For more information regarding the application lifecycle and how to handle suspend and resume, please see: 16 | //// Documentation: https://docs.microsoft.com/windows/uwp/launch-resume/app-lifecycle 17 | 18 | private const string StateFilename = "suspensionState"; 19 | 20 | // TODO WTS: This event is fired just before the app enters in background. Subscribe to this event if you want to save your current state. 21 | public event EventHandler OnBackgroundEntering; 22 | 23 | public async Task SaveStateAsync() 24 | { 25 | var suspensionState = new SuspensionState() 26 | { 27 | SuspensionDate = DateTime.Now 28 | }; 29 | 30 | var target = OnBackgroundEntering?.Target.GetType(); 31 | var onBackgroundEnteringArgs = new OnBackgroundEnteringEventArgs(suspensionState, target); 32 | 33 | OnBackgroundEntering?.Invoke(this, onBackgroundEnteringArgs); 34 | 35 | await ApplicationData.Current.LocalFolder.SaveAsync(StateFilename, onBackgroundEnteringArgs); 36 | } 37 | 38 | protected override async Task HandleInternalAsync(LaunchActivatedEventArgs args) 39 | { 40 | await RestoreStateAsync(); 41 | } 42 | 43 | protected override bool CanHandleInternal(LaunchActivatedEventArgs args) 44 | { 45 | return args.PreviousExecutionState == ApplicationExecutionState.Terminated; 46 | } 47 | 48 | private async Task RestoreStateAsync() 49 | { 50 | var saveState = await ApplicationData.Current.LocalFolder.ReadAsync(StateFilename); 51 | if (saveState?.Target != null) 52 | { 53 | var navigationService = ServiceLocator.Current.GetInstance(); 54 | navigationService.Navigate(saveState.Target.FullName, saveState.SuspensionState); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /WordPressUWP/Services/SuspensionState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WordPressUWP.Services 4 | { 5 | public class SuspensionState 6 | { 7 | public object Data { get; set; } 8 | 9 | public DateTime SuspensionDate { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /WordPressUWP/Services/ThemeSelectorService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | using Windows.Storage; 5 | using Windows.UI.Xaml; 6 | using Windows.UI.Xaml.Media; 7 | 8 | using WordPressUWP.Helpers; 9 | 10 | namespace WordPressUWP.Services 11 | { 12 | public static class ThemeSelectorService 13 | { 14 | private const string SettingsKey = "RequestedTheme"; 15 | 16 | public static event EventHandler OnThemeChanged = (sender, args) => { }; 17 | 18 | public static ElementTheme Theme { get; set; } = ElementTheme.Default; 19 | 20 | private static readonly SolidColorBrush _baseBrush = Application.Current.Resources["ThemeControlForegroundBaseHighBrush"] as SolidColorBrush; 21 | 22 | public static SolidColorBrush GetSystemControlForegroundForTheme() 23 | { 24 | return _baseBrush; 25 | } 26 | 27 | public static async Task InitializeAsync() 28 | { 29 | Theme = await LoadThemeFromSettingsAsync(); 30 | } 31 | 32 | public static async Task SetThemeAsync(ElementTheme theme) 33 | { 34 | Theme = theme; 35 | 36 | SetRequestedTheme(); 37 | await SaveThemeInSettingsAsync(Theme); 38 | 39 | OnThemeChanged(null, Theme); 40 | } 41 | 42 | public static void SetRequestedTheme() 43 | { 44 | if (Window.Current.Content is FrameworkElement frameworkElement) 45 | { 46 | frameworkElement.RequestedTheme = Theme; 47 | } 48 | } 49 | 50 | private static async Task LoadThemeFromSettingsAsync() 51 | { 52 | ElementTheme cacheTheme = ElementTheme.Default; 53 | string themeName = await ApplicationData.Current.LocalSettings.ReadAsync(SettingsKey); 54 | 55 | if (!string.IsNullOrEmpty(themeName)) 56 | { 57 | Enum.TryParse(themeName, out cacheTheme); 58 | } 59 | 60 | return cacheTheme; 61 | } 62 | 63 | public static bool IsDarkMode() 64 | { 65 | return Theme == ElementTheme.Dark || GetSystemControlForegroundForTheme().Color.ToString() == "#FFFFFFFF"; 66 | } 67 | 68 | private static async Task SaveThemeInSettingsAsync(ElementTheme theme) 69 | { 70 | await ApplicationData.Current.LocalSettings.SaveAsync(SettingsKey, theme.ToString()); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /WordPressUWP/Services/WhatsNewDisplayService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | using Windows.ApplicationModel; 5 | 6 | using WordPressUWP.Helpers; 7 | using WordPressUWP.Views; 8 | 9 | namespace WordPressUWP.Services 10 | { 11 | // For instructions on testing this service see https://github.com/Microsoft/WindowsTemplateStudio/tree/master/docs/features/whats-new-prompt.md 12 | public class WhatsNewDisplayService 13 | { 14 | internal static async Task ShowIfAppropriateAsync() 15 | { 16 | var currentVersion = PackageVersionToReadableString(Package.Current.Id.Version); 17 | 18 | var lastVersion = await Windows.Storage.ApplicationData.Current.LocalSettings.ReadAsync(nameof(currentVersion)); 19 | 20 | if (lastVersion == null) 21 | { 22 | await Windows.Storage.ApplicationData.Current.LocalSettings.SaveAsync(nameof(currentVersion), currentVersion); 23 | } 24 | else 25 | { 26 | if (currentVersion != lastVersion) 27 | { 28 | await Windows.Storage.ApplicationData.Current.LocalSettings.SaveAsync(nameof(currentVersion), currentVersion); 29 | 30 | var dialog = new WhatsNewDialog(); 31 | await dialog.ShowAsync(); 32 | } 33 | } 34 | } 35 | 36 | private static string PackageVersionToReadableString(PackageVersion packageVersion) 37 | { 38 | return $"{packageVersion.Major}.{packageVersion.Minor}.{packageVersion.Build}.{packageVersion.Revision}"; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /WordPressUWP/Services/WordPressService.cs: -------------------------------------------------------------------------------- 1 | using GalaSoft.MvvmLight; 2 | using GalaSoft.MvvmLight.Messaging; 3 | using Newtonsoft.Json; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Threading.Tasks; 7 | using Windows.ApplicationModel.Resources; 8 | using Windows.Storage; 9 | using Windows.UI.Xaml; 10 | using WordPressPCL; 11 | using WordPressPCL.Models; 12 | using WordPressPCL.Utility; 13 | using WordPressUWP.Helpers; 14 | using WordPressUWP.Interfaces; 15 | using WordPressUWP.Models; 16 | 17 | namespace WordPressUWP.Services 18 | { 19 | public class WordPressService : ViewModelBase, IWordPressService 20 | { 21 | private readonly SettingsService _settingsService; 22 | private readonly ApplicationDataContainer _localSettings; 23 | private readonly WordPressClient _client; 24 | 25 | private bool _isAuthenticated; 26 | public bool IsAuthenticated 27 | { 28 | get { return _isAuthenticated; } 29 | set { Set(ref _isAuthenticated, value); } 30 | } 31 | 32 | private User _currentUser; 33 | public User CurrentUser 34 | { 35 | get { return _currentUser; } 36 | set { Set(ref _currentUser, value); } 37 | } 38 | 39 | private bool _isLoadingPosts; 40 | public bool IsLoadingPosts 41 | { 42 | get { return _isLoadingPosts; } 43 | set { Set(ref _isLoadingPosts, value); } 44 | } 45 | 46 | public WordPressService(SettingsService settingsService) 47 | { 48 | _settingsService = settingsService; 49 | _localSettings = ApplicationData.Current.LocalSettings; 50 | _client = new WordPressClient(Config.WordPressUri); 51 | Init(); 52 | } 53 | 54 | public async void Init() 55 | { 56 | IsAuthenticated = false; 57 | var username = _settingsService.GetSetting("Username", () => null, SettingLocality.Roamed); 58 | if (username != null) 59 | { 60 | // get password 61 | var jwt = SettingsStorageExtensions.GetCredentialFromLocker(username); 62 | if (jwt != null && !string.IsNullOrEmpty(jwt.Password)) 63 | { 64 | // set jwt 65 | _client.SetJWToken(jwt.Password); 66 | IsAuthenticated = await _client.IsValidJWToken(); 67 | if (IsAuthenticated) 68 | { 69 | CurrentUser = await _client.Users.GetCurrentUser(); 70 | } 71 | } 72 | } 73 | } 74 | 75 | public async Task AuthenticateUser(string username, string password) 76 | { 77 | _client.AuthMethod = AuthMethod.JWT; 78 | try 79 | { 80 | await _client.RequestJWToken(username, password); 81 | } 82 | catch 83 | { 84 | // Authentication failed 85 | } 86 | var isAuthenticated = await IsUserAuthenticated(); 87 | 88 | if (isAuthenticated) 89 | { 90 | // Store username & JWT token for logging in on next app launch 91 | _settingsService.SetSetting("Username", username, SettingLocality.Roamed); 92 | SettingsStorageExtensions.SaveCredentialsToLocker(username, _client.GetToken()); 93 | CurrentUser = await _client.Users.GetCurrentUser(); 94 | } 95 | 96 | return isAuthenticated; 97 | } 98 | 99 | public async Task> GetCommentsForPost(int postid) 100 | { 101 | var comments = await _client.Comments.GetAllCommentsForPost(postid); 102 | var isDesc = _settingsService.GetSetting("CommentsOrderDesc", () => true, SettingLocality.Roamed); 103 | return ThreadedCommentsHelper.GetThreadedComments(comments, 2, isDesc); 104 | } 105 | 106 | public async Task> GetLatestPosts(int page = 0, int perPage = 20) 107 | { 108 | IsLoadingPosts = true; 109 | page++; 110 | IEnumerable posts = new List(); 111 | try 112 | { 113 | posts = await _client.Posts.Query(new PostsQueryBuilder() 114 | { 115 | Page = page, 116 | PerPage = perPage, 117 | Embed = true 118 | }); 119 | } 120 | catch 121 | { 122 | var res = ResourceLoader.GetForCurrentView(); 123 | var msg = res.GetString("Notification_DownloadPostsFailed"); 124 | Messenger.Default.Send(new NotificationMessage(msg)); 125 | } 126 | IsLoadingPosts = false; 127 | return posts; 128 | } 129 | 130 | public User GetUser() 131 | { 132 | return CurrentUser; 133 | } 134 | 135 | public async Task IsUserAuthenticated() 136 | { 137 | IsAuthenticated = await _client.IsValidJWToken(); 138 | return IsAuthenticated; 139 | } 140 | 141 | public async Task PostComment(int postId, string text, int replyto = 0) 142 | { 143 | var comment = new Comment(postId, text); 144 | if (replyto != 0) 145 | { 146 | comment.ParentId = replyto; 147 | } 148 | return await _client.Comments.Create(comment); 149 | } 150 | 151 | public async Task Logout() 152 | { 153 | _client.Logout(); 154 | IsAuthenticated = await _client.IsValidJWToken(); 155 | SettingsStorageExtensions.RemoveCredentialsFromLocker(); 156 | return IsAuthenticated; 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /WordPressUWP/Styles/_Colors.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /WordPressUWP/Styles/_FontSizes.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 22 6 | 16 7 | 8 | 9 | -------------------------------------------------------------------------------- /WordPressUWP/Styles/_Thickness.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 6 | 0,0,12,7 7 | 0, 20, 0, 48 8 | 9 | 10 | 12,0,12,0 11 | 12,12,12,12 12 | 13 | 14 | 0, 8, 0, 0 15 | 16 | -------------------------------------------------------------------------------- /WordPressUWP/ViewModels/NewsDetailViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Input; 2 | using GalaSoft.MvvmLight; 3 | using GalaSoft.MvvmLight.Command; 4 | using Windows.UI.Xaml; 5 | using WordPressUWP.Services; 6 | using WordPressPCL.Models; 7 | using System.Collections.Generic; 8 | using System.Threading.Tasks; 9 | using WordPressUWP.Interfaces; 10 | using System.Collections.ObjectModel; 11 | using System; 12 | using Windows.ApplicationModel.DataTransfer; 13 | using System.Net; 14 | using System.Diagnostics; 15 | using CommonServiceLocator; 16 | using GalaSoft.MvvmLight.Messaging; 17 | using Windows.ApplicationModel.Resources; 18 | 19 | namespace WordPressUWP.ViewModels 20 | { 21 | public class NewsDetailViewModel : ViewModelBase 22 | { 23 | private IWordPressService _wordPressService; 24 | private DataTransferManager _dataTransferManager; 25 | 26 | public NavigationServiceEx NavigationService 27 | { 28 | get 29 | { 30 | return ServiceLocator.Current.GetInstance(); 31 | } 32 | } 33 | 34 | private const string NarrowStateName = "NarrowState"; 35 | private const string WideStateName = "WideState"; 36 | 37 | private ICommand _stateChangedCommand; 38 | 39 | public ICommand StateChangedCommand 40 | { 41 | get 42 | { 43 | if (_stateChangedCommand == null) 44 | { 45 | _stateChangedCommand = new RelayCommand(OnStateChanged); 46 | } 47 | 48 | return _stateChangedCommand; 49 | } 50 | } 51 | 52 | private Post _selectedPost; 53 | 54 | public Post SelectedPost 55 | { 56 | get { return _selectedPost; } 57 | set { Set(ref _selectedPost, value);} 58 | } 59 | 60 | private ObservableCollection _comments; 61 | public ObservableCollection Comments 62 | { 63 | get { return _comments; } 64 | set { Set(ref _comments, value); } 65 | } 66 | 67 | private bool _isCommentsLoading; 68 | public bool IsCommentsLoading 69 | { 70 | get { return _isCommentsLoading; } 71 | set { Set(ref _isCommentsLoading, value); } 72 | } 73 | 74 | private string _commentInput; 75 | public string CommentInput 76 | { 77 | get { return _commentInput; } 78 | set { Set(ref _commentInput, value); } 79 | } 80 | 81 | private Comment _commentReply; 82 | public Comment CommentReply 83 | { 84 | get { return _commentReply; } 85 | set { Set(ref _commentReply, value); } 86 | } 87 | 88 | private bool _isCommenting; 89 | public bool IsCommenting 90 | { 91 | get { return _isCommenting; } 92 | set { Set(ref _isCommenting, value); } 93 | } 94 | 95 | public NewsDetailViewModel(IWordPressService wordPressService) 96 | { 97 | _wordPressService = wordPressService; 98 | } 99 | 100 | internal async Task Init() 101 | { 102 | await GetComments(SelectedPost.Id); 103 | 104 | _dataTransferManager = DataTransferManager.GetForCurrentView(); 105 | _dataTransferManager.DataRequested += DataTransferManager_DataRequested; 106 | } 107 | 108 | private void OnStateChanged(VisualStateChangedEventArgs args) 109 | { 110 | if (args.OldState.Name == NarrowStateName && args.NewState.Name == WideStateName) 111 | { 112 | NavigationService.GoBack(); 113 | } 114 | } 115 | 116 | private async Task GetComments(int postid) 117 | { 118 | IsCommentsLoading = true; 119 | if (Comments != null) 120 | { 121 | Comments.Clear(); 122 | } 123 | 124 | var comments = await _wordPressService.GetCommentsForPost(postid); 125 | if (comments != null) 126 | { 127 | Comments = new ObservableCollection(comments); 128 | } 129 | IsCommentsLoading = false; 130 | } 131 | 132 | public async void RefreshComments() 133 | { 134 | await GetComments(SelectedPost.Id); 135 | } 136 | 137 | public async Task PostComment() 138 | { 139 | var res = ResourceLoader.GetForCurrentView(); 140 | try 141 | { 142 | IsCommenting = true; 143 | 144 | if (await _wordPressService.IsUserAuthenticated()) 145 | { 146 | int replyto = 0; 147 | if (CommentReply != null) 148 | replyto = CommentReply.Id; 149 | var comment = await _wordPressService.PostComment(SelectedPost.Id, CommentInput, replyto); 150 | if (comment != null) 151 | { 152 | MessengerInstance.Send(new NotificationMessage(res.GetString("Notification_CommentPosted"))); 153 | CommentInput = String.Empty; 154 | await GetComments(SelectedPost.Id); 155 | CommentReplyUnset(); 156 | } 157 | else 158 | { 159 | MessengerInstance.Send(new NotificationMessage(res.GetString("Notification_GenericError"))); 160 | } 161 | } 162 | else 163 | { 164 | MessengerInstance.Send(new NotificationMessage(res.GetString("Notification_Unauthenticated"))); 165 | } 166 | } 167 | finally 168 | { 169 | IsCommenting = false; 170 | } 171 | } 172 | 173 | public void CommentReplyUnset() 174 | { 175 | CommentReply = null; 176 | } 177 | 178 | public async void OpenInBrowser() 179 | { 180 | await Windows.System.Launcher.LaunchUriAsync(new Uri(SelectedPost.Link)); 181 | } 182 | 183 | public void SharePost() 184 | { 185 | DataTransferManager.ShowShareUI(); 186 | } 187 | 188 | private void DataTransferManager_DataRequested(DataTransferManager sender, DataRequestedEventArgs args) 189 | { 190 | DataRequest request = args.Request; 191 | try 192 | { 193 | request.Data.SetWebLink(new Uri(SelectedPost.Link)); 194 | request.Data.Properties.Title = WebUtility.HtmlDecode(SelectedPost.Title.Rendered); 195 | } 196 | catch 197 | { 198 | Debug.WriteLine("Share Failed"); 199 | //request.FailWithDisplayText("Enter the web link you would like to share and try again."); 200 | } 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /WordPressUWP/ViewModels/NewsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Windows.Input; 6 | using GalaSoft.MvvmLight; 7 | using GalaSoft.MvvmLight.Command; 8 | using Windows.UI.Xaml; 9 | using Windows.UI.Xaml.Controls; 10 | using WordPressUWP.Services; 11 | using WordPressPCL.Models; 12 | using WordPressUWP.Interfaces; 13 | using System.Diagnostics; 14 | using Microsoft.Toolkit.Uwp; 15 | using Windows.ApplicationModel.DataTransfer; 16 | using System.Net; 17 | using Windows.UI.Xaml.Navigation; 18 | using CommonServiceLocator; 19 | using GalaSoft.MvvmLight.Messaging; 20 | using Windows.ApplicationModel.Resources; 21 | 22 | namespace WordPressUWP.ViewModels 23 | { 24 | public class NewsViewModel : ViewModelBase 25 | { 26 | private IWordPressService _wordPressService; 27 | private int _postid; 28 | private bool _firstLaunch = true; 29 | private DataTransferManager _dataTransferManager; 30 | 31 | public NavigationServiceEx NavigationService 32 | { 33 | get 34 | { 35 | return ServiceLocator.Current.GetInstance(); 36 | } 37 | } 38 | 39 | private const string NarrowStateName = "NarrowState"; 40 | private const string WideStateName = "WideState"; 41 | 42 | private VisualState _currentState; 43 | 44 | private ICommand _itemClickCommand; 45 | 46 | public ICommand ItemClickCommand 47 | { 48 | get 49 | { 50 | if (_itemClickCommand == null) 51 | { 52 | _itemClickCommand = new RelayCommand(OnItemClick); 53 | } 54 | 55 | return _itemClickCommand; 56 | } 57 | } 58 | 59 | private ICommand _stateChangedCommand; 60 | 61 | public ICommand StateChangedCommand 62 | { 63 | get 64 | { 65 | if (_stateChangedCommand == null) 66 | { 67 | _stateChangedCommand = new RelayCommand(OnStateChanged); 68 | } 69 | 70 | return _stateChangedCommand; 71 | } 72 | } 73 | 74 | public IncrementalLoadingCollection Posts; 75 | 76 | private Post _selectedPost; 77 | public Post SelectedPost 78 | { 79 | get { return _selectedPost; } 80 | set { Set(ref _selectedPost, value); } 81 | } 82 | 83 | private ObservableCollection _comments; 84 | public ObservableCollection Comments 85 | { 86 | get { return _comments; } 87 | set { Set(ref _comments, value); } 88 | } 89 | 90 | private string _commentInput; 91 | public string CommentInput 92 | { 93 | get { return _commentInput; } 94 | set { Set(ref _commentInput, value); } 95 | } 96 | 97 | private Comment _commentReply; 98 | public Comment CommentReply 99 | { 100 | get { return _commentReply; } 101 | set { Set(ref _commentReply, value); } 102 | } 103 | 104 | private bool _isCommentsLoading; 105 | public bool IsCommentsLoading 106 | { 107 | get { return _isCommentsLoading; } 108 | set { Set(ref _isCommentsLoading, value); } 109 | } 110 | 111 | 112 | private bool _isCommenting; 113 | public bool IsCommenting 114 | { 115 | get { return _isCommenting; } 116 | set { Set(ref _isCommenting, value); } 117 | } 118 | 119 | public NewsViewModel(IWordPressService wordPressService) 120 | { 121 | _wordPressService = wordPressService; 122 | } 123 | 124 | internal async void Init(VisualState currentState, NavigationEventArgs e) 125 | { 126 | // Show post if there's an id being passed along 127 | if (e != null && e.Parameter is string id && _firstLaunch) 128 | { 129 | _firstLaunch = false; 130 | if (!string.IsNullOrEmpty(id)) 131 | { 132 | Int32.TryParse(id, out _postid); 133 | await RefreshPosts(); 134 | } 135 | } 136 | 137 | _dataTransferManager = DataTransferManager.GetForCurrentView(); 138 | _dataTransferManager.DataRequested += DataTransferManager_DataRequested; 139 | LoadDataAsync(currentState); 140 | } 141 | 142 | public async Task RefreshPosts() 143 | { 144 | Debug.WriteLine("RefreshPosts"); 145 | var res = ResourceLoader.GetForCurrentView(); 146 | if (Posts != null && !Posts.IsLoading && Posts.Count > 0 && !_wordPressService.IsLoadingPosts) 147 | { 148 | try 149 | { 150 | Posts.Refresh(); 151 | } 152 | catch (Exception ex) 153 | { 154 | MessengerInstance.Send(new NotificationMessage(res.GetString("Notification_RefreshFailed"))); 155 | } 156 | } else 157 | { 158 | Debug.WriteLine("Refresh denied"); 159 | } 160 | } 161 | 162 | private async Task GetComments(int postid) 163 | { 164 | CommentReply = null; 165 | IsCommentsLoading = true; 166 | if (Comments != null) 167 | { 168 | Comments.Clear(); 169 | } 170 | 171 | var comments = await _wordPressService.GetCommentsForPost(postid); 172 | if (comments != null) 173 | { 174 | Comments = new ObservableCollection(comments); 175 | } 176 | IsCommentsLoading = false; 177 | } 178 | 179 | public async Task PostComment() 180 | { 181 | var res = ResourceLoader.GetForCurrentView(); 182 | try 183 | { 184 | IsCommenting = true; 185 | 186 | if (await _wordPressService.IsUserAuthenticated()) 187 | { 188 | int replyto = 0; 189 | if (CommentReply != null) 190 | replyto = CommentReply.Id; 191 | var comment = await _wordPressService.PostComment(SelectedPost.Id, CommentInput, replyto); 192 | if (comment != null) 193 | { 194 | MessengerInstance.Send(new NotificationMessage(res.GetString("Notification_CommentPosted"))); 195 | CommentInput = String.Empty; 196 | await GetComments(SelectedPost.Id); 197 | CommentReplyUnset(); 198 | } 199 | else 200 | { 201 | MessengerInstance.Send(new NotificationMessage(res.GetString("Notification_GenericError"))); 202 | } 203 | } 204 | else 205 | { 206 | MessengerInstance.Send(new NotificationMessage(res.GetString("Notification_Unauthenticated"))); 207 | } 208 | } 209 | finally 210 | { 211 | IsCommenting = false; 212 | } 213 | } 214 | 215 | public void LoadDataAsync(VisualState currentState) 216 | { 217 | _currentState = currentState; 218 | if(Posts == null) 219 | { 220 | Posts = new IncrementalLoadingCollection(); 221 | Posts.OnEndLoading = PostsOnEndLoading; 222 | } 223 | } 224 | 225 | public void PostsOnEndLoading() 226 | { 227 | if (_postid != 0) 228 | { 229 | // a post id has been set by a push notification, show it 230 | var selectedPost = Posts.Where(x => x.Id == _postid).FirstOrDefault(); 231 | if (selectedPost != null) 232 | ShowPost(selectedPost); 233 | } 234 | } 235 | 236 | private void OnStateChanged(VisualStateChangedEventArgs args) 237 | { 238 | _currentState = args.NewState; 239 | } 240 | 241 | private void OnItemClick(ItemClickEventArgs args) 242 | { 243 | if (args?.ClickedItem is Post item) 244 | { 245 | ShowPost(item); 246 | } 247 | } 248 | 249 | private async void ShowPost(Post post) 250 | { 251 | if (_currentState.Name == NarrowStateName) 252 | { 253 | NavigationService.Navigate(typeof(NewsDetailViewModel).FullName, post); 254 | } 255 | else 256 | { 257 | SelectedPost = post; 258 | await GetComments(post.Id); 259 | } 260 | } 261 | 262 | public async void RefreshComments() 263 | { 264 | await GetComments(SelectedPost.Id); 265 | } 266 | 267 | public async void OpenInBrowser() 268 | { 269 | await Windows.System.Launcher.LaunchUriAsync(new Uri(SelectedPost.Link)); 270 | } 271 | 272 | public void SharePost() 273 | { 274 | DataTransferManager.ShowShareUI(); 275 | } 276 | 277 | public void CommentReplyUnset() 278 | { 279 | CommentReply = null; 280 | } 281 | 282 | private void DataTransferManager_DataRequested(DataTransferManager sender, DataRequestedEventArgs args) 283 | { 284 | DataRequest request = args.Request; 285 | try 286 | { 287 | request.Data.SetWebLink(new Uri(SelectedPost.Link)); 288 | request.Data.Properties.Title = WebUtility.HtmlDecode(SelectedPost.Title.Rendered); 289 | } 290 | catch 291 | { 292 | Debug.WriteLine("Share Failed"); 293 | //request.FailWithDisplayText("Enter the web link you would like to share and try again."); 294 | } 295 | } 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /WordPressUWP/ViewModels/SettingsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | using GalaSoft.MvvmLight; 4 | using GalaSoft.MvvmLight.Command; 5 | using Windows.ApplicationModel; 6 | using Windows.UI.Xaml; 7 | using WordPressUWP.Services; 8 | using WordPressUWP.Interfaces; 9 | using Windows.Storage; 10 | using WordPressUWP.Helpers; 11 | using Windows.UI.Xaml.Controls; 12 | using System.Diagnostics; 13 | using WordPressUWP.Models; 14 | 15 | namespace WordPressUWP.ViewModels 16 | { 17 | public class SettingsViewModel : ViewModelBase 18 | { 19 | // TODO WTS: Add other settings as necessary. For help see https://github.com/Microsoft/WindowsTemplateStudio/blob/master/docs/pages/settings.md 20 | private ElementTheme _elementTheme = ThemeSelectorService.Theme; 21 | private IPushNotificationService _pushNotificationService; 22 | private SettingsService _settingsService; 23 | 24 | public ElementTheme ElementTheme 25 | { 26 | get { return _elementTheme; } 27 | 28 | set { Set(ref _elementTheme, value); } 29 | } 30 | 31 | private string _versionDescription; 32 | 33 | public string VersionDescription 34 | { 35 | get { return _versionDescription; } 36 | 37 | set { Set(ref _versionDescription, value); } 38 | } 39 | 40 | private ICommand _switchThemeCommand; 41 | 42 | public ICommand SwitchThemeCommand 43 | { 44 | get 45 | { 46 | if (_switchThemeCommand == null) 47 | { 48 | _switchThemeCommand = new RelayCommand( 49 | async (param) => 50 | { 51 | await ThemeSelectorService.SetThemeAsync(param); 52 | }); 53 | } 54 | 55 | return _switchThemeCommand; 56 | } 57 | } 58 | 59 | private int _fontSize; 60 | public int SelectedFontSize 61 | { 62 | get { return _fontSize; } 63 | set { Set(ref _fontSize, value); } 64 | } 65 | 66 | private bool _commentsOrderDesc; 67 | public bool CommentsOrderDesc 68 | { 69 | get { return _commentsOrderDesc; } 70 | set { Set(ref _commentsOrderDesc, value); } 71 | } 72 | 73 | 74 | private bool _pushNotificationsEnabled; 75 | public bool PushNotificationsEnabled 76 | { 77 | get { return _pushNotificationsEnabled; } 78 | set { Set(ref _pushNotificationsEnabled, value); } 79 | } 80 | 81 | public SettingsViewModel(IPushNotificationService pushNotificationService, SettingsService settingsService) 82 | { 83 | _pushNotificationService = pushNotificationService; 84 | _settingsService = settingsService; 85 | } 86 | 87 | public void Initialize() 88 | { 89 | SelectedFontSize = _settingsService.GetSetting("fontsize", () => Config.DefaultFontSize, SettingLocality.Roamed); 90 | VersionDescription = GetVersionDescription(); 91 | PushNotificationsEnabled = _settingsService.GetSetting(nameof(PushNotificationsEnabled), () => true, SettingLocality.Local); 92 | CommentsOrderDesc = _settingsService.GetSetting(nameof(CommentsOrderDesc), () => true, SettingLocality.Roamed); 93 | } 94 | 95 | private string GetVersionDescription() 96 | { 97 | var package = Package.Current; 98 | var packageId = package.Id; 99 | var version = packageId.Version; 100 | 101 | return $"{package.DisplayName} - {version.Major}.{version.Minor}.{version.Build}.{version.Revision}"; 102 | } 103 | 104 | public async void ChangePushNotificationSettings(object sender, RoutedEventArgs e) 105 | { 106 | if (sender is ToggleSwitch ts && Config.NotificationsEnabled) 107 | { 108 | _settingsService.SetSetting(nameof(PushNotificationsEnabled), ts.IsOn, SettingLocality.Local); 109 | if (ts.IsOn) 110 | PushNotificationsEnabled = await _pushNotificationService.EnablePushNotifications(); 111 | else 112 | await _pushNotificationService.DisablePushNotificaitons(); 113 | } 114 | } 115 | 116 | public void ChangeFontSizeSettings(object sender, RoutedEventArgs e) 117 | { 118 | _settingsService.SetSetting("fontsize", SelectedFontSize, SettingLocality.Roamed); 119 | } 120 | 121 | public void ChangeCommentOrderSettings(object sender, RoutedEventArgs e) 122 | { 123 | if (sender is ToggleSwitch ts) 124 | { 125 | _settingsService.SetSetting("CommentsOrderDesc", ts.IsOn, SettingLocality.Roamed); 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /WordPressUWP/ViewModels/ShellNavigationItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using GalaSoft.MvvmLight; 4 | 5 | using Windows.UI.Xaml; 6 | using Windows.UI.Xaml.Controls; 7 | using Windows.UI.Xaml.Data; 8 | using Windows.UI.Xaml.Media; 9 | 10 | using WordPressUWP.Services; 11 | 12 | namespace WordPressUWP.ViewModels 13 | { 14 | public class ShellNavigationItem : ViewModelBase 15 | { 16 | 17 | public Action Action { get; private set; } 18 | 19 | private ShellNavigationItem(string label, Symbol symbol, Action action) 20 | : this(label, null) 21 | { 22 | Symbol = symbol; 23 | Action = action; 24 | } 25 | 26 | public string Label { get; set; } 27 | 28 | public Symbol Symbol { get; set; } 29 | 30 | public string ViewModelName { get; set; } 31 | 32 | private Visibility _selectedVis = Visibility.Collapsed; 33 | 34 | public Visibility SelectedVis 35 | { 36 | get { return _selectedVis; } 37 | 38 | set { Set(ref _selectedVis, value); } 39 | } 40 | 41 | public char SymbolAsChar 42 | { 43 | get { return (char)Symbol; } 44 | } 45 | 46 | private IconElement _iconElement = null; 47 | 48 | public IconElement Icon 49 | { 50 | get 51 | { 52 | var foregroundBinding = new Binding 53 | { 54 | Source = this, 55 | Path = new PropertyPath("SelectedForeground"), 56 | Mode = BindingMode.OneWay, 57 | UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged, 58 | }; 59 | 60 | if (_iconElement != null) 61 | { 62 | BindingOperations.SetBinding(_iconElement, IconElement.ForegroundProperty, foregroundBinding); 63 | 64 | return _iconElement; 65 | } 66 | 67 | var fontIcon = new FontIcon { FontSize = 16, Glyph = SymbolAsChar.ToString() }; 68 | 69 | BindingOperations.SetBinding(fontIcon, FontIcon.ForegroundProperty, foregroundBinding); 70 | 71 | return fontIcon; 72 | } 73 | } 74 | 75 | private bool _isSelected; 76 | 77 | public bool IsSelected 78 | { 79 | get 80 | { 81 | return _isSelected; 82 | } 83 | 84 | set 85 | { 86 | Set(ref _isSelected, value); 87 | 88 | SelectedVis = value ? Visibility.Visible : Visibility.Collapsed; 89 | 90 | SelectedForeground = IsSelected 91 | ? Application.Current.Resources["SystemControlForegroundAccentBrush"] as SolidColorBrush 92 | : GetStandardTextColorBrush(); 93 | } 94 | } 95 | 96 | private SolidColorBrush _selectedForeground = null; 97 | 98 | public SolidColorBrush SelectedForeground 99 | { 100 | get { return _selectedForeground ?? (_selectedForeground = GetStandardTextColorBrush()); } 101 | 102 | set { Set(ref _selectedForeground, value); } 103 | } 104 | 105 | public ShellNavigationItem(string label, Symbol symbol, string viewModelName) 106 | : this(label, viewModelName) 107 | { 108 | Symbol = symbol; 109 | } 110 | 111 | public ShellNavigationItem(string label, IconElement icon, string viewModelName) 112 | : this(label, viewModelName) 113 | { 114 | _iconElement = icon; 115 | } 116 | 117 | public ShellNavigationItem(string label, string viewModelName) 118 | { 119 | Label = label; 120 | ViewModelName = viewModelName; 121 | 122 | ThemeSelectorService.OnThemeChanged += (s, e) => 123 | { 124 | if (!IsSelected) 125 | { 126 | SelectedForeground = GetStandardTextColorBrush(); 127 | } 128 | }; 129 | } 130 | 131 | public static ShellNavigationItem ForAction(string label, Symbol symbol, Action action) 132 | { 133 | return new ShellNavigationItem(label, symbol, action); 134 | } 135 | 136 | private SolidColorBrush GetStandardTextColorBrush() 137 | { 138 | return ThemeSelectorService.GetSystemControlForegroundForTheme(); 139 | } 140 | 141 | public override string ToString() 142 | { 143 | return Label; 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /WordPressUWP/ViewModels/ShellViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | using System.Linq; 4 | using System.Windows.Input; 5 | 6 | using GalaSoft.MvvmLight; 7 | using GalaSoft.MvvmLight.Command; 8 | using Windows.UI.Xaml; 9 | using Windows.UI.Xaml.Controls; 10 | using Windows.UI.Xaml.Navigation; 11 | using WordPressUWP.Helpers; 12 | using WordPressUWP.Services; 13 | using WordPressUWP.Interfaces; 14 | using CommonServiceLocator; 15 | using GalaSoft.MvvmLight.Messaging; 16 | 17 | namespace WordPressUWP.ViewModels 18 | { 19 | public class ShellViewModel : ViewModelBase 20 | { 21 | private const string PanoramicStateName = "PanoramicState"; 22 | private const string WideStateName = "WideState"; 23 | private const string NarrowStateName = "NarrowState"; 24 | private const double WideStateMinWindowWidth = 640; 25 | private const double PanoramicStateMinWindowWidth = 1024; 26 | 27 | public NavigationServiceEx NavigationService 28 | { 29 | get 30 | { 31 | return ServiceLocator.Current.GetInstance(); 32 | } 33 | } 34 | 35 | private bool _isPaneOpen; 36 | 37 | public bool IsPaneOpen 38 | { 39 | get { return _isPaneOpen; } 40 | set { Set(ref _isPaneOpen, value); } 41 | } 42 | 43 | private SplitViewDisplayMode _displayMode = SplitViewDisplayMode.CompactInline; 44 | 45 | public SplitViewDisplayMode DisplayMode 46 | { 47 | get { return _displayMode; } 48 | set { Set(ref _displayMode, value); } 49 | } 50 | 51 | private object _lastSelectedItem; 52 | 53 | private ObservableCollection _primaryItems = new ObservableCollection(); 54 | 55 | public ObservableCollection PrimaryItems 56 | { 57 | get { return _primaryItems; } 58 | set { Set(ref _primaryItems, value); } 59 | } 60 | 61 | private ObservableCollection _secondaryItems = new ObservableCollection(); 62 | 63 | public ObservableCollection SecondaryItems 64 | { 65 | get { return _secondaryItems; } 66 | set { Set(ref _secondaryItems, value); } 67 | } 68 | 69 | private bool _isLoginPopupOpen; 70 | public bool IsLoginPopupOpen 71 | { 72 | get { return _isLoginPopupOpen; } 73 | set { Set(ref _isLoginPopupOpen, value); } 74 | } 75 | 76 | private bool _isLoggingIn; 77 | public bool IsLoggingIn 78 | { 79 | get { return _isLoggingIn; } 80 | set { Set(ref _isLoggingIn, value); } 81 | } 82 | 83 | private bool _showLoginError; 84 | public bool ShowLoginError 85 | { 86 | get { return _showLoginError; } 87 | set { Set(ref _showLoginError, value); } 88 | } 89 | 90 | private ICommand _openPaneCommand; 91 | 92 | public ICommand OpenPaneCommand 93 | { 94 | get 95 | { 96 | if (_openPaneCommand == null) 97 | { 98 | _openPaneCommand = new RelayCommand(() => IsPaneOpen = !_isPaneOpen); 99 | } 100 | 101 | return _openPaneCommand; 102 | } 103 | } 104 | 105 | private ICommand _itemSelected; 106 | 107 | public ICommand ItemSelectedCommand 108 | { 109 | get 110 | { 111 | if (_itemSelected == null) 112 | { 113 | _itemSelected = new RelayCommand(ItemSelected); 114 | } 115 | 116 | return _itemSelected; 117 | } 118 | } 119 | 120 | private ICommand _stateChangedCommand; 121 | private IWordPressService _wordPressService; 122 | 123 | public ICommand StateChangedCommand 124 | { 125 | get 126 | { 127 | if (_stateChangedCommand == null) 128 | { 129 | _stateChangedCommand = new RelayCommand(args => GoToState(args.NewState.Name)); 130 | } 131 | 132 | return _stateChangedCommand; 133 | } 134 | } 135 | 136 | public event EventHandler InAppNotificationRaised; 137 | 138 | public ShellViewModel(IWordPressService wordPressService) 139 | { 140 | _wordPressService = wordPressService; 141 | } 142 | 143 | private void GoToState(string stateName) 144 | { 145 | switch (stateName) 146 | { 147 | case PanoramicStateName: 148 | DisplayMode = SplitViewDisplayMode.CompactInline; 149 | break; 150 | case WideStateName: 151 | DisplayMode = SplitViewDisplayMode.CompactInline; 152 | IsPaneOpen = false; 153 | break; 154 | case NarrowStateName: 155 | DisplayMode = SplitViewDisplayMode.Overlay; 156 | IsPaneOpen = false; 157 | 158 | break; 159 | default: 160 | break; 161 | } 162 | } 163 | 164 | public void Initialize(Frame frame) 165 | { 166 | NavigationService.Frame = frame; 167 | NavigationService.Navigated += Frame_Navigated; 168 | PopulateNavItems(); 169 | 170 | InitializeState(Window.Current.Bounds.Width); 171 | } 172 | 173 | 174 | private void InitializeState(double windowWith) 175 | { 176 | if (windowWith < WideStateMinWindowWidth) 177 | { 178 | GoToState(NarrowStateName); 179 | } 180 | else if (windowWith < PanoramicStateMinWindowWidth) 181 | { 182 | GoToState(WideStateName); 183 | } 184 | else 185 | { 186 | GoToState(PanoramicStateName); 187 | } 188 | } 189 | 190 | private void PopulateNavItems() 191 | { 192 | _primaryItems.Clear(); 193 | _secondaryItems.Clear(); 194 | 195 | // TODO WTS: Change the symbols for each item as appropriate for your app 196 | // More on Segoe UI Symbol icons: https://docs.microsoft.com/windows/uwp/style/segoe-ui-symbol-font 197 | // Or to use an IconElement instead of a Symbol see https://github.com/Microsoft/WindowsTemplateStudio/blob/master/docs/projectTypes/navigationpane.md 198 | // Edit String/en-US/Resources.resw: Add a menu item title for each page 199 | _primaryItems.Add(new ShellNavigationItem("Shell_News".GetLocalized(), Symbol.Home, typeof(NewsViewModel).FullName)); 200 | 201 | 202 | if (Microsoft.Services.Store.Engagement.StoreServicesFeedbackLauncher.IsSupported()) 203 | { 204 | _secondaryItems.Add( 205 | ShellNavigationItem.ForAction( 206 | "Feedback", 207 | Symbol.Comment, 208 | async() => { 209 | var launcher = Microsoft.Services.Store.Engagement.StoreServicesFeedbackLauncher.GetDefault(); 210 | await launcher.LaunchAsync(); 211 | })); 212 | } 213 | _secondaryItems.Add(new ShellNavigationItem("Shell_Settings".GetLocalized(), Symbol.Setting, typeof(SettingsViewModel).FullName)); 214 | 215 | if (Config.EnableLogin) 216 | { 217 | _secondaryItems.Add( 218 | ShellNavigationItem.ForAction( 219 | "Shell_Me".GetLocalized(), 220 | Symbol.Contact, 221 | () => { 222 | OpenLoginPopup(); 223 | })); 224 | } 225 | } 226 | 227 | private void ItemSelected(ItemClickEventArgs args) 228 | { 229 | if (DisplayMode == SplitViewDisplayMode.CompactOverlay || DisplayMode == SplitViewDisplayMode.Overlay) 230 | { 231 | IsPaneOpen = false; 232 | } 233 | Navigate(args.ClickedItem); 234 | } 235 | 236 | private void Frame_Navigated(object sender, NavigationEventArgs e) 237 | { 238 | if (e != null) 239 | { 240 | var vm = NavigationService.GetNameOfRegisteredPage(e.SourcePageType); 241 | var navigationItem = PrimaryItems?.FirstOrDefault(i => i.ViewModelName == vm); 242 | if (navigationItem == null) 243 | { 244 | navigationItem = SecondaryItems?.FirstOrDefault(i => i.ViewModelName == vm); 245 | } 246 | 247 | if (navigationItem != null) 248 | { 249 | ChangeSelected(_lastSelectedItem, navigationItem); 250 | _lastSelectedItem = navigationItem; 251 | } 252 | } 253 | } 254 | 255 | private void ChangeSelected(object oldValue, object newValue) 256 | { 257 | if (oldValue != null) 258 | { 259 | (oldValue as ShellNavigationItem).IsSelected = false; 260 | } 261 | 262 | if (newValue != null) 263 | { 264 | (newValue as ShellNavigationItem).IsSelected = true; 265 | } 266 | } 267 | 268 | private void Navigate(object item) 269 | { 270 | if (item is ShellNavigationItem navigationItem) 271 | { 272 | if (navigationItem.Action != null) 273 | { 274 | navigationItem.Action.Invoke(); 275 | } 276 | else 277 | { 278 | NavigationService.Navigate(navigationItem.ViewModelName); 279 | } 280 | } 281 | } 282 | 283 | public void OpenLoginPopup() 284 | { 285 | 286 | IsLoginPopupOpen = true; 287 | } 288 | 289 | public void CloseLoginPopup() 290 | { 291 | IsLoginPopupOpen = false; 292 | } 293 | 294 | public async void Login(string username, string password) 295 | { 296 | IsLoggingIn = true; 297 | if(!String.IsNullOrEmpty(username) && !String.IsNullOrEmpty(password)) 298 | { 299 | var isAuth = await _wordPressService.AuthenticateUser(username, password); 300 | if (!isAuth) 301 | { 302 | ShowLoginError = true; 303 | } 304 | } 305 | IsLoggingIn = false; 306 | } 307 | 308 | public async void Logout() 309 | { 310 | await _wordPressService.Logout(); 311 | } 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /WordPressUWP/ViewModels/ViewModelLocator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GalaSoft.MvvmLight.Ioc; 3 | using WordPressUWP.Services; 4 | using WordPressUWP.Views; 5 | using WordPressUWP.Interfaces; 6 | using Microsoft.Toolkit.Collections; 7 | using WordPressPCL.Models; 8 | using CommonServiceLocator; 9 | 10 | namespace WordPressUWP.ViewModels 11 | { 12 | public class ViewModelLocator 13 | { 14 | public ViewModelLocator() 15 | { 16 | ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); 17 | 18 | SimpleIoc.Default.Register(() => new NavigationServiceEx()); 19 | SimpleIoc.Default.Register(); 20 | SimpleIoc.Default.Register(); 21 | SimpleIoc.Default.Register, PostsService>(); 22 | SimpleIoc.Default.Register(); 23 | SimpleIoc.Default.Register(); 24 | Register(); 25 | Register(); 26 | Register(); 27 | } 28 | 29 | public IWordPressService WordPressService => ServiceLocator.Current.GetInstance(); 30 | 31 | public IPushNotificationService PushNotificationService => ServiceLocator.Current.GetInstance(); 32 | 33 | public SettingsViewModel SettingsViewModel => ServiceLocator.Current.GetInstance(); 34 | 35 | public NewsDetailViewModel NewsDetailViewModel => ServiceLocator.Current.GetInstance(); 36 | 37 | public NewsViewModel NewsViewModel => ServiceLocator.Current.GetInstance(); 38 | 39 | public ShellViewModel ShellViewModel => ServiceLocator.Current.GetInstance(); 40 | 41 | public NavigationServiceEx NavigationService => ServiceLocator.Current.GetInstance(); 42 | 43 | public SettingsService SettingsSerive => ServiceLocator.Current.GetInstance(); 44 | 45 | 46 | 47 | public void Register() 48 | where VM : class 49 | { 50 | SimpleIoc.Default.Register(); 51 | 52 | NavigationService.Configure(typeof(VM).FullName, typeof(V)); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /WordPressUWP/Views/FirstRunDialog.xaml: -------------------------------------------------------------------------------- 1 |  11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Replace the content of this dialog with whatever content is appropriate to your app. 23 | You might want to explain key features or functionality. 24 | Don't feel restricted to just text. You can also include images and animations if you wish too. 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /WordPressUWP/Views/FirstRunDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using Windows.UI.Xaml.Controls; 4 | 5 | namespace WordPressUWP.Views 6 | { 7 | public sealed partial class FirstRunDialog : ContentDialog 8 | { 9 | public FirstRunDialog() 10 | { 11 | // TODO WTS: Update the contents of this dialog with any important information you want to show when the app is used for the first time. 12 | InitializeComponent(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /WordPressUWP/Views/NewsDetailControl.xaml: -------------------------------------------------------------------------------- 1 |  13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /WordPressUWP/Views/NewsDetailControl.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using Windows.System; 4 | using Windows.UI.Xaml; 5 | using Windows.UI.Xaml.Controls; 6 | using WordPressPCL.Models; 7 | using WordPressUWP.Helpers; 8 | 9 | namespace WordPressUWP.Views 10 | { 11 | public sealed partial class NewsDetailControl : UserControl 12 | { 13 | public Post MasterMenuItem 14 | { 15 | get { return GetValue(MasterMenuItemProperty) as Post; } 16 | set { SetValue(MasterMenuItemProperty, value); } 17 | } 18 | 19 | public static readonly DependencyProperty MasterMenuItemProperty = DependencyProperty.Register("MasterMenuItem", typeof(Post), typeof(NewsDetailControl), new PropertyMetadata(null)); 20 | 21 | public delegate void SwipeEventHandler(object sender, SwipedEventArgs e); 22 | 23 | public event SwipeEventHandler Swiped; 24 | 25 | public NewsDetailControl() 26 | { 27 | InitializeComponent(); 28 | } 29 | 30 | private async void WebView_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args) 31 | { 32 | if (args.Uri == null) 33 | return; 34 | var s = args.Uri.ToString(); 35 | args.Cancel = true; 36 | await Launcher.LaunchUriAsync(new Uri(s)); 37 | 38 | // TODO Show image popup 39 | 40 | //if (s.Contains(".jpg") || s.Contains(".jpeg") || s.Contains(".png") || s.Contains(".gif")) 41 | //{ 42 | // args.Cancel = true; 43 | // Debug.WriteLine(s); 44 | // //WindowWrapper.Current().Dispatcher.Dispatch(() => 45 | // //{ 46 | // // var modal = Window.Current.Content as ModalDialog; 47 | // // modal.CanBackButtonDismiss = true; 48 | // // modal.DisableBackButtonWhenModal = false; 49 | // // var view = modal.ModalContent as Busy; 50 | // // if (view == null) 51 | // // modal.ModalContent = view = new Busy(); 52 | // // modal.IsModal = view.IsBusy = true; 53 | // // view.BusyText = "text"; 54 | // //}); 55 | //} 56 | } 57 | 58 | private void PostWebView_ScriptNotify(object sender, NotifyEventArgs e) 59 | { 60 | SwipeDirection direction; 61 | if (e.Value.Equals("swipeleft")) 62 | direction = SwipeDirection.Left; 63 | else 64 | direction = SwipeDirection.Right; 65 | 66 | Swiped?.Invoke(this, new SwipedEventArgs(direction)); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /WordPressUWP/Views/NewsDetailPage.xaml: -------------------------------------------------------------------------------- 1 |  17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 91 | 92 | 93 | 94 | 95 | 96 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 114 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 130 | 133 | 150 | 151 | 158 | 159 | 173 | 174 | 175 | 182 | 188 | 193 | 194 | 195 | 196 | 197 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /WordPressUWP/Views/NewsDetailPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Windows.UI.Core; 3 | using Windows.UI.Xaml; 4 | using Windows.UI.Xaml.Controls; 5 | using Windows.UI.Xaml.Navigation; 6 | using WordPressUWP.Helpers; 7 | using WordPressUWP.ViewModels; 8 | 9 | namespace WordPressUWP.Views 10 | { 11 | public sealed partial class NewsDetailPage : Page 12 | { 13 | private NewsDetailViewModel ViewModel 14 | { 15 | get { return DataContext as NewsDetailViewModel; } 16 | } 17 | 18 | public NewsDetailPage() 19 | { 20 | InitializeComponent(); 21 | } 22 | 23 | protected override async void OnNavigatedTo(NavigationEventArgs e) 24 | { 25 | base.OnNavigatedTo(e); 26 | ViewModel.SelectedPost = e.Parameter as WordPressPCL.Models.Post; 27 | await ViewModel.Init(); 28 | } 29 | 30 | private void CommentToggleButton_Click(object sender, RoutedEventArgs e) 31 | { 32 | bool showCommentInput = CommentToggleButton.IsChecked ?? false; 33 | ToggleCommentInput(showCommentInput); 34 | } 35 | 36 | private void ToggleCommentInput(bool show) 37 | { 38 | CommentToggleButton.IsChecked = show; 39 | if (show) 40 | { 41 | CommentInputGrid.Height = double.NaN; 42 | } 43 | else 44 | { 45 | CommentInputGrid.Height = 0; 46 | } 47 | } 48 | 49 | private void ReplyButton_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e) 50 | { 51 | if (sender is HyperlinkButton button) 52 | { 53 | if (button.Tag is WordPressPCL.Models.CommentThreaded comment) 54 | { 55 | ViewModel.CommentReply = comment; 56 | ToggleCommentInput(true); 57 | CommentInput.Focus(FocusState.Pointer); 58 | } 59 | } 60 | } 61 | 62 | private void NewsDetailControl_Swiped(object sender, SwipedEventArgs e) 63 | { 64 | Debug.WriteLine(e.Direction); 65 | if (e.Direction.Equals(SwipeDirection.Left)) 66 | PostPivot.SelectedIndex = 1; 67 | else if (e.Direction.Equals(SwipeDirection.Right)) 68 | ViewModel.NavigationService.GoBack(); 69 | } 70 | 71 | private void FirstCommentBtn_Click(object sender, RoutedEventArgs e) 72 | { 73 | if (CommentListView.Items.Count > 0) 74 | CommentListView.ScrollIntoView(CommentListView.Items[0]); 75 | } 76 | 77 | private void LastCommentBtn_Click(object sender, RoutedEventArgs e) 78 | { 79 | var itemIndex = CommentListView.Items.Count - 1; 80 | if (itemIndex > 0) 81 | CommentListView.ScrollIntoView(CommentListView.Items[itemIndex]); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /WordPressUWP/Views/NewsPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using Windows.UI.Core; 2 | using Windows.UI.Xaml; 3 | using Windows.UI.Xaml.Controls; 4 | using Windows.UI.Xaml.Navigation; 5 | using WordPressUWP.ViewModels; 6 | 7 | namespace WordPressUWP.Views 8 | { 9 | public sealed partial class NewsPage : Page 10 | { 11 | private NewsViewModel ViewModel 12 | { 13 | get { return DataContext as NewsViewModel; } 14 | } 15 | 16 | public NewsPage() 17 | { 18 | InitializeComponent(); 19 | } 20 | 21 | protected override void OnNavigatedTo(NavigationEventArgs e) 22 | { 23 | ViewModel.Init(WindowStates.CurrentState, e); 24 | Window.Current.SizeChanged += Current_SizeChanged; 25 | } 26 | 27 | private void Current_SizeChanged(object sender, WindowSizeChangedEventArgs e) 28 | { 29 | if(Window.Current.Bounds.Width >= 1300) 30 | { 31 | CommentsColumn.Margin = new Thickness(0, 0, 0, 0); 32 | CommentToggleButton.IsChecked = false; 33 | } 34 | } 35 | 36 | private void CommentToggleButton_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e) 37 | { 38 | bool showComments = CommentToggleButton.IsChecked ?? false; 39 | if (showComments) 40 | { 41 | CommentsColumn.Margin = new Thickness(-400, 0, 0, 48); 42 | } else 43 | { 44 | CommentsColumn.Margin = new Thickness(0, 0, -400, 0); 45 | } 46 | } 47 | 48 | private void ReplyButton_Click(object sender, RoutedEventArgs e) 49 | { 50 | if(sender is HyperlinkButton button){ 51 | if (button.Tag is WordPressPCL.Models.CommentThreaded comment) 52 | { 53 | ViewModel.CommentReply = comment; 54 | CommentInput.Focus(FocusState.Pointer); 55 | } 56 | } 57 | } 58 | 59 | private void FirstCommentBtn_Click(object sender, RoutedEventArgs e) 60 | { 61 | if(CommentListView.Items.Count > 0) 62 | CommentListView.ScrollIntoView(CommentListView.Items[0]); 63 | } 64 | 65 | private void LastCommentBtn_Click(object sender, RoutedEventArgs e) 66 | { 67 | var itemIndex = CommentListView.Items.Count - 1; 68 | if (itemIndex > 0) 69 | CommentListView.ScrollIntoView(CommentListView.Items[itemIndex]); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /WordPressUWP/Views/SettingsPage.xaml: -------------------------------------------------------------------------------- 1 |  11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 23 | 24 | 27 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 42 | 47 | 48 | 49 | 50 | 53 | 54 | 55 | 60 | 61 | Light 62 | 63 | 64 | 69 | 70 | Dark 71 | 72 | 73 | 78 | 79 | Default 80 | 81 | 82 | 83 | 84 | 85 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | 102 | 104 | 105 | 106 | 109 | 110 | 111 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 125 | 126 | 129 | 130 | 133 | 134 | 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 | -------------------------------------------------------------------------------- /WordPressUWP/Views/SettingsPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using Windows.UI.Xaml.Controls; 4 | using Windows.UI.Xaml.Navigation; 5 | 6 | using WordPressUWP.ViewModels; 7 | 8 | namespace WordPressUWP.Views 9 | { 10 | public sealed partial class SettingsPage : Page 11 | { 12 | private SettingsViewModel ViewModel 13 | { 14 | get { return DataContext as SettingsViewModel; } 15 | } 16 | private bool PushNotificationEnabled 17 | { 18 | get { return Config.NotificationsEnabled; } 19 | } 20 | 21 | //// TODO WTS: Change the URL for your privacy policy in the Resource File, currently set to https://YourPrivacyUrlGoesHere 22 | 23 | public SettingsPage() 24 | { 25 | InitializeComponent(); 26 | } 27 | 28 | protected override void OnNavigatedTo(NavigationEventArgs e) 29 | { 30 | ViewModel.Initialize(); 31 | int[] sizes = { 12, 14, 16, 18, 20, 22, 24 }; 32 | FontSizeCB.ItemsSource = sizes; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /WordPressUWP/Views/ShellPage.xaml: -------------------------------------------------------------------------------- 1 |  15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 32 | 37 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 128 | 129 | 130 | 131 | 132 | 133 | 138 | 144 | 152 | 153 |