Skip to content
This repository was archived by the owner on Jul 2, 2022. It is now read-only.

Commit 93ab92b

Browse files
committed
Added Push Notifications
1 parent 6415bfa commit 93ab92b

10 files changed

Lines changed: 244 additions & 60 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
*/bin
22
*/obj
33
*.suo
4+
*.userprefs

CodeHub.Core/CodeHub.Core.iOS.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
<Compile Include="ViewModels\Accounts\LoginViewModel.cs" />
123123
<Compile Include="ViewModels\CommitsViewModel.cs" />
124124
<Compile Include="Messages\NotificationCountMessage.cs" />
125+
<Compile Include="Services\IPushNotificationsService.cs" />
125126
</ItemGroup>
126127
<ItemGroup />
127128
<ItemGroup>

CodeHub.Core/Data/GitHubAccount.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,13 @@ public class GitHubAccount : Account
4949
/// description in list.
5050
/// </summary>
5151
/// <value><c>true</c> if hide repository description in list; otherwise, <c>false</c>.</value>
52-
public bool ShowRepositoryDescriptionInList { get; set; }
52+
public bool ShowRepositoryDescriptionInList { get; set; }
53+
54+
/// <summary>
55+
/// Gets or sets a value indicating whether this <see cref="CodeHub.Core.Data.GitHubAccount"/> push notifications enabled.
56+
/// </summary>
57+
/// <value><c>true</c> if push notifications enabled; otherwise, <c>false</c>.</value>
58+
public bool PushNotificationsEnabled { get; set; }
5359

5460
/// <summary>
5561
/// A transient record of the user's name
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
3+
namespace CodeHub.Core.Services
4+
{
5+
public interface IPushNotificationsService
6+
{
7+
void Register();
8+
9+
void Deregister();
10+
}
11+
}
12+

CodeHub.Core/ViewModels/App/SettingsViewModel.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
using Cirrious.MvvmCross.ViewModels;
44
using System.Linq;
55
using CodeFramework.Core.Services;
6+
using CodeHub.Core.Services;
7+
using System;
8+
using System.Threading.Tasks;
69

710
namespace CodeHub.Core.ViewModels.App
811
{
@@ -35,6 +38,38 @@ public bool AnalyticsEnabled
3538
}
3639
}
3740

41+
public bool PushNotificationsEnabled
42+
{
43+
get
44+
{
45+
return this.GetApplication().Account.PushNotificationsEnabled;
46+
}
47+
set
48+
{
49+
RegisterPushNotifications(value);
50+
}
51+
}
52+
53+
private async Task RegisterPushNotifications(bool enabled)
54+
{
55+
var notificationService = GetService<IPushNotificationsService>();
56+
57+
try
58+
{
59+
if (enabled)
60+
await Task.Run(() => notificationService.Register());
61+
else
62+
await Task.Run(() => notificationService.Deregister());
63+
this.GetApplication().Account.PushNotificationsEnabled = enabled;
64+
RaisePropertyChanged(() => PushNotificationsEnabled);
65+
}
66+
catch (Exception e)
67+
{
68+
ReportError(e);
69+
RaisePropertyChanged(() => PushNotificationsEnabled);
70+
}
71+
}
72+
3873
private void DeleteCache()
3974
{
4075
if (this.GetApplication().Account.Cache != null)

CodeHub.iOS/AppDelegate.cs

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
// --------------------------------------------------------------------------------------------------------------------
66

77
using CodeFramework.iOS;
8+
using System.Collections.Generic;
9+
using System; using Cirrious.CrossCore;
10+
using Cirrious.MvvmCross.Touch.Platform;
11+
using Cirrious.MvvmCross.ViewModels;
12+
using MonoTouch.Foundation;
13+
using MonoTouch.UIKit;
814

915
namespace CodeHub.iOS
1016
{
11-
using Cirrious.CrossCore;
12-
using Cirrious.MvvmCross.Touch.Platform;
13-
using Cirrious.MvvmCross.ViewModels;
14-
using MonoTouch.Foundation;
15-
using MonoTouch.UIKit;
16-
1717
/// <summary>
1818
/// The UIApplicationDelegate for the application. This class is responsible for launching the
1919
/// User Interface of the application, as well as listening (and optionally responding) to
@@ -27,6 +27,8 @@ public class AppDelegate : MvxApplicationDelegate
2727
/// </summary>
2828
private UIWindow window;
2929

30+
public string DeviceToken;
31+
3032
/// <summary>
3133
/// This is the main entry point of the application.
3234
/// </summary>
@@ -105,7 +107,55 @@ public override bool FinishedLaunching(UIApplication app, NSDictionary options)
105107

106108
this.window.MakeKeyAndVisible();
107109

110+
if (options != null)
111+
{
112+
if (options.ContainsKey(UIApplication.LaunchOptionsRemoteNotificationKey))
113+
{
114+
var remoteNotification = options[UIApplication.LaunchOptionsRemoteNotificationKey] as NSDictionary;
115+
if(remoteNotification != null) {
116+
HandleNotification(remoteNotification);
117+
}
118+
}
119+
}
120+
121+
122+
const UIRemoteNotificationType notificationTypes = UIRemoteNotificationType.Alert | UIRemoteNotificationType.Badge;
123+
UIApplication.SharedApplication.RegisterForRemoteNotificationTypes(notificationTypes);
124+
108125
return true;
109126
}
127+
128+
public override void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, System.Action<UIBackgroundFetchResult> completionHandler)
129+
{
130+
if (application.ApplicationState == UIApplicationState.Active)
131+
return;
132+
HandleNotification(userInfo);
133+
}
134+
135+
private void HandleNotification(NSDictionary data)
136+
{
137+
try
138+
{
139+
var viewDispatcher = Mvx.Resolve<Cirrious.MvvmCross.Views.IMvxViewDispatcher>();
140+
var request = MvxViewModelRequest<CodeHub.Core.ViewModels.Repositories.RepositoryViewModel>.GetDefaultRequest();
141+
var repoId = new CodeHub.Core.Utils.RepositoryIdentifier(data["r"].ToString());
142+
request.ParameterValues = new Dictionary<string, string>() {{"Username", repoId.Owner}, {"Repository", repoId.Name}};
143+
viewDispatcher.ShowViewModel(request);
144+
}
145+
catch (Exception e)
146+
{
147+
Console.WriteLine("Handle Notifications issue: " + e.ToString());
148+
}
149+
}
150+
151+
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
152+
{
153+
DeviceToken = deviceToken.Description.Trim('<', '>').Replace(" ", "");
154+
}
155+
156+
public override void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
157+
{
158+
MonoTouch.Utilities.ShowAlert("Error Registering for Notifications", error.LocalizedDescription);
159+
}
110160
}
111161
}

CodeHub.iOS/CodeHub.iOS.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@
179179
<Compile Include="Views\Filters\MyIssuesFilterViewController.cs" />
180180
<Compile Include="Views\Filters\RepositoriesFilterViewController.cs" />
181181
<Compile Include="Views\Filters\SourceFilterViewController.cs" />
182+
<Compile Include="Services\PushNotificationsService.cs" />
182183
</ItemGroup>
183184
<ItemGroup>
184185
<ProjectReference Include="..\lib\CodeFramework\CodeFramework.Core\CodeFramework.Core.iOS.csproj">
@@ -238,6 +239,9 @@
238239
<Reference Include="MonoTouch.Slideout">
239240
<HintPath>..\lib\CodeFramework\lib\iOS\MonoTouch.Slideout.dll</HintPath>
240241
</Reference>
242+
<Reference Include="RestSharp.MonoTouch">
243+
<HintPath>..\lib\GitHubSharp\lib\RestSharp\RestSharp.MonoTouch.dll</HintPath>
244+
</Reference>
241245
</ItemGroup>
242246
<ItemGroup>
243247
<Content Include="Images\anonymous%402x.png" />

CodeHub.iOS/Info.plist

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,9 @@
5454
<false/>
5555
<key>UIPrerenderedIcon</key>
5656
<true/>
57+
<key>UIBackgroundModes</key>
58+
<array>
59+
<string>remote-notification</string>
60+
</array>
5761
</dict>
5862
</plist>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System;
2+
using CodeHub.Core.Services;
3+
using MonoTouch.UIKit;
4+
5+
namespace CodeHub.iOS.Services
6+
{
7+
public class PushNotificationsService : IPushNotificationsService
8+
{
9+
private readonly object _lock = new object();
10+
11+
public void Register()
12+
{
13+
lock (_lock)
14+
{
15+
var del = (AppDelegate)UIApplication.SharedApplication.Delegate;
16+
17+
if (string.IsNullOrEmpty(del.DeviceToken))
18+
throw new InvalidOperationException("Push notifications has not been enabled for this app!");
19+
20+
var user = Cirrious.CrossCore.Mvx.Resolve<IApplicationService>().Account;
21+
22+
var client = new RestSharp.RestClient();
23+
var request = new RestSharp.RestRequest("http://codehub-push.herokuapp.com/register", RestSharp.Method.POST);
24+
request.AddParameter("token", del.DeviceToken);
25+
request.AddParameter("user", user.Username);
26+
request.AddParameter("oauth", user.OAuth);
27+
request.Timeout = 1000 * 30;
28+
var response = client.Execute(request);
29+
if (response.ErrorException != null)
30+
throw response.ErrorException;
31+
32+
if (response.StatusCode == System.Net.HttpStatusCode.OK || response.StatusCode == System.Net.HttpStatusCode.Created)
33+
return;
34+
throw new InvalidOperationException("Unable to register! Server returned a " + response.StatusCode + " status code");
35+
}
36+
}
37+
38+
public void Deregister()
39+
{
40+
lock (_lock)
41+
{
42+
var del = (AppDelegate)UIApplication.SharedApplication.Delegate;
43+
var user = Cirrious.CrossCore.Mvx.Resolve<IApplicationService>().Account;
44+
var client = new RestSharp.RestClient();
45+
var request = new RestSharp.RestRequest("http://codehub-push.herokuapp.com/unregister", RestSharp.Method.POST);
46+
request.AddParameter("token", del.DeviceToken);
47+
request.AddParameter("oauth", user.OAuth);
48+
request.Timeout = 1000 * 30;
49+
var response = client.Execute(request);
50+
if (response.ErrorException != null)
51+
throw response.ErrorException;
52+
53+
if (response.StatusCode == System.Net.HttpStatusCode.OK || response.StatusCode == System.Net.HttpStatusCode.NotFound)
54+
return;
55+
throw new InvalidOperationException("Unable to deregister! Server returned a " + response.StatusCode + " status code");
56+
}
57+
}
58+
}
59+
}
60+

CodeHub.iOS/Views/App/SettingsView.cs

Lines changed: 64 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -15,65 +15,76 @@ public SettingsView()
1515

1616
public override void ViewWillAppear(bool animated)
1717
{
18-
var application = Mvx.Resolve<IApplicationService>();
19-
var root = new RootElement(Title);
20-
var currentAccount = application.Account;
21-
var vm = (SettingsViewModel)ViewModel;
22-
23-
root.Add(new Section(string.Empty, "If disabled, CodeHub will prompt you for your password when you switch to this account".t()) {
24-
new TrueFalseElement("Remember Credentials".t(), !currentAccount.DontRemember, e => {
25-
currentAccount.DontRemember = !e.Value;
26-
application.Accounts.Update(currentAccount);
27-
})
28-
});
29-
30-
root.Add(new Section(string.Empty, "If enabled, your teams will be shown in the CodeHub slideout menu under Events".t()) {
31-
new TrueFalseElement("Show Organizations in Events".t(), currentAccount.ShowOrganizationsInEvents, e => {
32-
currentAccount.ShowOrganizationsInEvents = e.Value;
33-
application.Accounts.Update(currentAccount);
34-
})
35-
});
36-
37-
root.Add(new Section(string.Empty, "If enabled, every organization will be listed under Organizations".t()) {
38-
new TrueFalseElement("List Organizations".t(), currentAccount.ExpandOrganizations, e => {
39-
currentAccount.ExpandOrganizations = e.Value;
40-
application.Accounts.Update(currentAccount);
41-
})
42-
});
43-
44-
root.Add(new Section(string.Empty, "If enabled, repository descriptions will be shown in the list of repositories".t()) {
45-
new TrueFalseElement("Show Repo Descriptions".t(), currentAccount.ShowRepositoryDescriptionInList, e => {
46-
currentAccount.ShowRepositoryDescriptionInList = e.Value;
47-
application.Accounts.Update(currentAccount);
48-
})
49-
});
18+
var vm = (SettingsViewModel)ViewModel;
19+
vm.Bind(x => x.PushNotificationsEnabled, CreateTable);
20+
CreateTable();
21+
base.ViewWillAppear(animated);
22+
}
23+
24+
private void CreateTable()
25+
{
26+
var application = Mvx.Resolve<IApplicationService>();
27+
var vm = (SettingsViewModel)ViewModel;
28+
var root = new RootElement(Title);
29+
var currentAccount = application.Account;
30+
31+
root.Add(new Section(string.Empty, "If disabled, CodeHub will prompt you for your password when you switch to this account") {
32+
new TrueFalseElement("Push Notifications".t(), vm.PushNotificationsEnabled, e => vm.PushNotificationsEnabled = e.Value)
33+
});
34+
35+
root.Add(new Section(string.Empty, "If disabled, CodeHub will prompt you for your password when you switch to this account".t()) {
36+
new TrueFalseElement("Remember Credentials".t(), !currentAccount.DontRemember, e => {
37+
currentAccount.DontRemember = !e.Value;
38+
application.Accounts.Update(currentAccount);
39+
})
40+
});
41+
42+
root.Add(new Section(string.Empty, "If enabled, your teams will be shown in the CodeHub slideout menu under Events".t()) {
43+
new TrueFalseElement("Show Organizations in Events".t(), currentAccount.ShowOrganizationsInEvents, e => {
44+
currentAccount.ShowOrganizationsInEvents = e.Value;
45+
application.Accounts.Update(currentAccount);
46+
})
47+
});
48+
49+
root.Add(new Section(string.Empty, "If enabled, every organization will be listed under Organizations".t()) {
50+
new TrueFalseElement("List Organizations".t(), currentAccount.ExpandOrganizations, e => {
51+
currentAccount.ExpandOrganizations = e.Value;
52+
application.Accounts.Update(currentAccount);
53+
})
54+
});
55+
56+
root.Add(new Section(string.Empty, "If enabled, repository descriptions will be shown in the list of repositories".t()) {
57+
new TrueFalseElement("Show Repo Descriptions".t(), currentAccount.ShowRepositoryDescriptionInList, e => {
58+
currentAccount.ShowRepositoryDescriptionInList = e.Value;
59+
application.Accounts.Update(currentAccount);
60+
})
61+
});
5062

5163
var el = new StyledStringElement("Startup View", vm.DefaultStartupViewName, MonoTouch.UIKit.UITableViewCellStyle.Value1)
5264
{
5365
Accessory = MonoTouch.UIKit.UITableViewCellAccessory.DisclosureIndicator,
5466
};
5567
el.Tapped += () => vm.GoToDefaultStartupViewCommand.Execute(null);
56-
root.Add(new Section(string.Empty, "Select the default startup view after login".t()) { el });
57-
58-
root.Add(new Section(string.Empty, "If enabled, send anonymous usage statistics to build a better app".t()) {
59-
new TrueFalseElement("Send Anonymous Usage".t(), vm.AnalyticsEnabled, e => vm.AnalyticsEnabled = e.Value)
60-
});
61-
62-
var totalCacheSizeMB = vm.CacheSize.ToString("0.##");
63-
var cacheSection = new Section(string.Empty, string.Format("{0} MB of cache used".t(), totalCacheSizeMB));
64-
cacheSection.Add(new StyledStringElement("Delete Cache".t(), () =>
65-
{
66-
vm.DeleteAllCacheCommand.Execute(null);
67-
cacheSection.Footer = string.Format("{0} MB of cache used".t(), 0);
68-
ReloadData();
69-
}));
70-
root.Add(cacheSection);
71-
72-
//Assign the root
73-
Root = root;
74-
75-
base.ViewWillAppear(animated);
76-
}
68+
root.Add(new Section(string.Empty, "Select the default startup view after login".t()) { el });
69+
70+
root.Add(new Section(string.Empty, "If enabled, send anonymous usage statistics to build a better app".t()) {
71+
new TrueFalseElement("Send Anonymous Usage".t(), vm.AnalyticsEnabled, e => vm.AnalyticsEnabled = e.Value)
72+
});
73+
74+
var totalCacheSizeMB = vm.CacheSize.ToString("0.##");
75+
var cacheSection = new Section(string.Empty, string.Format("{0} MB of cache used".t(), totalCacheSizeMB));
76+
cacheSection.Add(new StyledStringElement("Delete Cache".t(), () =>
77+
{
78+
vm.DeleteAllCacheCommand.Execute(null);
79+
cacheSection.Footer = string.Format("{0} MB of cache used".t(), 0);
80+
ReloadData();
81+
}));
82+
root.Add(cacheSection);
83+
84+
//Assign the root
85+
Root = root;
86+
87+
}
7788
}
7889
}
7990

0 commit comments

Comments
 (0)