Merged PR 15211: [iOS] Enable AAD login in GraphNotficationsSample

Hooking up the Login with AAD to be functional in the app. Restructured the code according to Rome sample guidelines, UI updated

Included code feedback from Github PRs
This commit is contained in:
Sudipta Dey (WDG) 2019-04-17 02:00:41 +00:00
Родитель 7f33a0975d
Коммит 0933fb099d
13 изменённых файлов: 602 добавлений и 709 удалений

Просмотреть файл

@ -3,15 +3,9 @@
//
#import <UIKit/UIKit.h>
#import <ConnectedDevices/ConnectedDevices.h>
#import <UserNotifications/UserNotifications.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate, UNUserNotificationCenterDelegate>
@property (strong, nonatomic) UIWindow* window;
@property (strong, nonatomic) MCDConnectedDevicesPlatform* platform;
- (void)initializePlatform;
@end

Просмотреть файл

@ -3,171 +3,103 @@
//
#import "AppDelegate.h"
#import "NotificationsManager.h"
#import "ConnectedDevicesPlatformManager.h"
void uncaughtExceptionHandler(NSException* uncaughtException)
{
NSLog(@"Uncaught exception: %@", uncaughtException.description);
}
@interface AppDelegate ()
@property (nonatomic) MCDConnectedDevicesNotificationRegistration* notificationRegistration;
@property (nonatomic) MCDConnectedDevicesAccount* pendingAccount;
@property (nonatomic) void (^pendingCallback)(MCDConnectedDevicesNotificationRegistrationResult*,NSError*);
- (void)createNotificationRegistrationWithToken:(NSString* _Nonnull)deviceToken;
@end
@implementation AppDelegate{
@implementation AppDelegate {
ConnectedDevicesPlatformManager* _platformManager;
}
- (void)initializePlatform
{
if (!_platformManager)
{
-(instancetype)init {
if (self = [super init]) {
_platformManager = [ConnectedDevicesPlatformManager sharedInstance];
}
return self;
}
- (void)createNotificationRegistrationWithToken:(NSString* _Nonnull)deviceToken
{
_notificationRegistration = [[MCDConnectedDevicesNotificationRegistration alloc] init];
_notificationRegistration.type = MCDNotificationTypeAPN;
_notificationRegistration.appId = [[NSBundle mainBundle] bundleIdentifier];
_notificationRegistration.appDisplayName = (NSString*)[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
_notificationRegistration.token = deviceToken;
NSLog(@"GraphNotifications Successfully created notification registration!");
NSLog(@"platformManager info %@", _platformManager);
[_platformManager setNotificationRegistration: _notificationRegistration];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
[self initializePlatform];
[_platformManager.platform.notificationRegistrationManager.notificationRegistrationStateChanged subscribe:^(__unused MCDConnectedDevicesNotificationRegistrationManager * _Nonnull manager, MCDConnectedDevicesNotificationRegistrationStateChangedEventArgs * _Nonnull args)
{
NSLog(@"GraphNotifications NotificationRegistrationState changed to %ld", args.state);
if ((args.state == MCDConnectedDevicesNotificationRegistrationStateExpired) || (args.state == MCDConnectedDevicesNotificationRegistrationStateExpiring))
{
[[NotificationsManager sharedInstance] refresh];
}
}];
// Set up Notifications
NSDictionary* userInfo = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
if (!userInfo)
{
// User launch app by tapping the App icon normal launch
NSLog(@"GraphNotifications launching without user info");
NSDictionary* notificationInfo = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
if (!notificationInfo) {
// User launched app by tapping the app icon
NSLog(@"GraphNotificationsSample launching without user info");
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
[center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error)
{
NSLog(@"GraphNotifications granted notification: %d error: %@", granted, error);
[center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionSound
| UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error) {
NSLog(@"GraphNotificationsSample granted: %d error: %@", granted, error);
}];
// User launch app by tapping the App icon normal launch
[application registerForRemoteNotifications];
center.delegate = self;
}
else
{
// app run in background and received the push notification, app is launched by user tapping the alert view
MCDConnectedDevicesNotification* notification = [MCDConnectedDevicesNotification tryParse:userInfo];
if (notification != nil)
{
[self.platform processNotificationAsync:notification completion:^(NSError* error __unused)
{
// NOTE: it may be useful to attach completion to this async in order to know when the notification is done being processed.
} else {
// App running in background and received a push notification, launched by user tapping the alert view
MCDConnectedDevicesNotification* notification = [MCDConnectedDevicesNotification tryParse:notificationInfo];
if (notification != nil) {
[_platformManager.platform processNotificationAsync:notification
completion:^(NSError* error __unused) {
// NOTE: it may be useful to attach completion to this async in order to know when the
// notification is done being processed.
// This would be a good time to stop a background service or otherwise cleanup.
}];
} else {
NSLog(@"Remote notification is not for ConnectedDevicesPlatform, skip processing");
}
}
return YES;
}
- (void)application:(__unused UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(nonnull NSError *)error
{
NSLog(@"GraphNotifications Failed to register with %@", error);
}
- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
- (void)application:(__unused UIApplication*)application
didFailToRegisterForRemoteNotificationsWithError:(nonnull NSError*)error {
NSLog(@"GraphNotificationsSample failed to register for remote notifications with %@", error);
#if TARGET_OS_SIMULATOR
// Simulator doesn't support APNS, use polling mode to run this sample
[self _initPlatformManager];
[_platformManager setNotificationRegistration:@"GraphNotificationsSamplePolling"];
#endif
}
- (void)application:(UIApplication*)application
didRegisterUserNotificationSettings:(__unused UIUserNotificationSettings*)notificationSettings
{
// actually registerForRemoteNotifications after registerUserNotificationSettings is finished
didRegisterUserNotificationSettings:(__unused UNNotificationSettings*)notificationSettings {
// Do registerForRemoteNotifications after registerUserNotificationSettings is finished
[application registerForRemoteNotifications];
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
}
- (void)application:(__unused UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
// when registerForRemoteNotifications, retrieve the deviceToken, convert it to HEX encoded NSString
- (void)application:(__unused UIApplication*)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
// Retrieve the deviceToken, convert it to HEX encoded NSString
NSMutableString* deviceTokenStr = [NSMutableString stringWithCapacity:deviceToken.length * 2];
const unsigned char* byteBuffer = deviceToken.bytes;
for (NSUInteger i = 0; i < deviceToken.length; ++i)
{
for (NSUInteger i = 0; i < deviceToken.length; ++i) {
[deviceTokenStr appendFormat:@"%02X", (unsigned int)byteBuffer[i]];
}
NSLog(@"GraphNotifications APNs token: %@", deviceTokenStr);
NSLog(@"GraphNotificationsSample APNs token: %@", deviceTokenStr);
@try
{
[self createNotificationRegistrationWithToken:deviceTokenStr];
}
@catch (NSException* exception)
{
NSLog(@"GraphNotifications Failed to update notification registration with exception %@", exception);
@try {
[_platformManager setNotificationRegistration:deviceTokenStr];
} @catch (NSException* exception) {
NSLog(@"Failed to update notification registration with exception %@", exception);
}
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notificationInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
{
// app run in foreground and received the push notification, pump notification into CDPPlatform
NSLog(@"GraphNotifications Received remote notification...");
[notificationInfo enumerateKeysAndObjectsUsingBlock:^( id _Nonnull key, id _Nonnull obj, __unused BOOL* _Nonnull stop) { NSLog(@"%@: %@", key, obj); }];
- (void)application:(UIApplication*)application
didReceiveRemoteNotification:(NSDictionary*)notificationInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
// App running in foreground and received a push notification
NSLog(@"GraphNotificationsSample received push notification...");
[notificationInfo enumerateKeysAndObjectsUsingBlock:^( id _Nonnull key, id _Nonnull obj, __unused BOOL* _Nonnull stop) {
NSLog(@"%@: %@", key, obj);
}];
MCDConnectedDevicesNotification* notification = [MCDConnectedDevicesNotification tryParse:notificationInfo];
if (notification != nil)
{
// Once all accounts that are in good standing have their subcomponents initialized, its safe to pump the notification information into the platform. Before that point, a notification
// may be for an account that isn't fully set up yet. This is more likely to happen when the app is launched as a result of the notification so there
// isn't much time to start the platform before needing to process the notification.
if (notification != nil) {
// Once all accounts that are in good standing have their subcomponents initialized, its safe to pump the notification
// information into the platform. Before that point, a notification
// may be for an account that isn't fully set up yet. This is more likely to happen when the app is launched as a result
// of the notification so there isn't much time to start the platform before needing to process the notification.
[AnyPromise promiseWithAdapterBlock:^(PMKAdapter _Nonnull adapter) {
[_platformManager.platform processNotificationAsync:notification completion:^(NSError* error)
{
[_platformManager.platform processNotificationAsync:notification completion:^(NSError* error) {
adapter(nil, error);
}];
}].then(^{
@ -180,15 +112,51 @@ didRegisterUserNotificationSettings:(__unused UIUserNotificationSettings*)notifi
}
}
- (void)userNotificationCenter:(__unused UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
{
completionHandler(UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound);
- (void)userNotificationCenter:(__unused UNUserNotificationCenter*)center
willPresentNotification:(UNNotification*)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
completionHandler(UNNotificationPresentationOptionAlert
| UNNotificationPresentationOptionBadge
| UNNotificationPresentationOptionSound);
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
{
[[NotificationsManager sharedInstance] dismissNotificationWithId:response.notification.request.identifier];
- (void)userNotificationCenter:(UNUserNotificationCenter*)center
didReceiveNotificationResponse:(UNNotificationResponse*)response
withCompletionHandler:(void (^)(void))completionHandler {
[_platformManager.notificationsManager dismissNotificationWithId:response.notification.request.identifier];
completionHandler();
}
- (void)applicationWillResignActive:(UIApplication*)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary
// interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the
// transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks.
// Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication*)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state
// information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication*)application {
// Called as part of the transition from the background to the active state; here you can undo many of the changes
// made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication*)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive.
// If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication*)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
void uncaughtExceptionHandler(NSException* uncaughtException) {
NSLog(@"Uncaught exception: %@", uncaughtException.description);
}
@end

Просмотреть файл

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="703-4V-yAM">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="703-4V-yAM">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -34,25 +34,37 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="nW6-tV-2PU">
<rect key="frame" x="0.0" y="280" width="375" height="30"/>
<rect key="frame" x="64" y="263" width="236" height="48"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Login with MSA"/>
<color key="backgroundColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<state key="normal" title="Login with MSA">
<color key="titleColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<connections>
<action selector="loginMSA" destination="S4R-Ja-viH" eventType="touchUpInside" id="JLQ-Q2-VCr"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fz1-y2-7rY">
<rect key="frame" x="0.0" y="318" width="375" height="30"/>
<rect key="frame" x="64" y="318" width="236" height="48"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Login with AAD"/>
<color key="backgroundColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<state key="normal" title="Login with AAD">
<color key="titleColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<connections>
<action selector="loginAAD" destination="S4R-Ja-viH" eventType="touchUpInside" id="jIC-Q3-hja"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Graph Notifications" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MiM-ed-uOi">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Graph Notifications Sample" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MiM-ed-uOi">
<rect key="frame" x="0.0" y="76" width="375" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Status" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ho4-ZN-PVf">
<rect key="frame" x="16" y="438" width="343" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
@ -62,6 +74,7 @@
</view>
<connections>
<outlet property="aadButton" destination="fz1-y2-7rY" id="W6O-WH-oAW"/>
<outlet property="loginStatusLabel" destination="ho4-ZN-PVf" id="4PX-7C-QDc"/>
<outlet property="msaButton" destination="nW6-tV-2PU" id="RsH-8N-pIc"/>
</connections>
</viewController>
@ -73,7 +86,7 @@
<scene sceneID="BEH-RM-JJY">
<objects>
<tableViewController storyboardIdentifier="NotificationsViewController" id="7uX-h6-yAZ" customClass="NotificationsViewController" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="bd2-mg-Jex">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="118" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="bd2-mg-Jex">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@ -81,17 +94,13 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Notifications" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Vts-Ah-6q5">
<rect key="frame" x="8" y="12" width="180" height="21"/>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="RnW-fA-Aoo">
<rect key="frame" x="0.0" y="6" width="99" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="right" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="RnW-fA-Aoo">
<rect key="frame" x="205" y="6" width="162" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Refresh"/>
<color key="backgroundColor" red="0.034717619499999998" green="0.14275715420000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<state key="normal" title="Refresh">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<connections>
<action selector="refresh" destination="7uX-h6-yAZ" eventType="touchUpInside" id="NRw-E6-hF8"/>
</connections>
@ -100,38 +109,78 @@
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="NotificationCell" id="5s3-lz-tU0">
<rect key="frame" x="0.0" y="72" width="375" height="44"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="NotificationCell" rowHeight="118" id="5s3-lz-tU0">
<rect key="frame" x="0.0" y="72" width="375" height="118"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="5s3-lz-tU0" id="UNp-yx-8lI">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="117.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="ID:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kmZ-du-CWU">
<rect key="frame" x="0.0" y="0.0" width="70" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" tag="1" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6GG-xT-kg7">
<rect key="frame" x="0.0" y="0.0" width="375" height="14"/>
<rect key="frame" x="75" y="0.0" width="300" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Content:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gRg-xu-4Hl">
<rect key="frame" x="0.0" y="25" width="70" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" tag="2" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MoY-k6-SpS">
<rect key="frame" x="0.0" y="11" width="375" height="17"/>
<rect key="frame" x="75" y="25" width="300" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="State:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fo8-j4-71E">
<rect key="frame" x="0.0" y="50" width="70" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" tag="3" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XA4-dL-enO">
<rect key="frame" x="0.0" y="26" width="181" height="18"/>
<rect key="frame" x="75" y="50" width="300" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" tag="4" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="oNv-VT-M1b">
<rect key="frame" x="189" y="20" width="186" height="30"/>
<rect key="frame" x="0.0" y="78" width="99" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Read"/>
<color key="backgroundColor" red="0.034717619500895247" green="0.14275715424236091" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<state key="normal" title="Mark Read">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
</button>
<button opaque="NO" tag="5" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="saP-8J-oBK">
<rect key="frame" x="108" y="78" width="99" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" red="0.034717619499999998" green="0.14275715420000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<state key="normal" title="Dismiss">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
</button>
<button opaque="NO" tag="6" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="7jd-9p-7fJ">
<rect key="frame" x="216" y="78" width="99" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" red="0.034717619499999998" green="0.14275715420000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<state key="normal" title="Delete">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
</button>
</subviews>
</tableViewCellContentView>
@ -145,7 +194,7 @@
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="gyR-70-0hf" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1309.5999999999999" y="160.56971514242881"/>
<point key="canvasLocation" x="1240.8" y="-243.32833583208398"/>
</scene>
</scenes>
</document>

Просмотреть файл

@ -2,14 +2,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
//
#import <ConnectedDevices/MCDConnectedDevicesPlatform.h>
#import <Foundation/Foundation.h>
#import <ConnectedDevices/MCDConnectedDevicesPlatform.h>
#import <PromiseKit/PromiseKit.h>
#import "MSAAccount.h"
#import "AADAccount.h"
#import "NotificationsManager.h"
#ifndef ConnectedDevicesPlatformManager_h
#define ConnectedDevicesPlatformManager_h
@class Account;
@ -28,6 +29,7 @@ typedef NS_ENUM(NSInteger, AccountRegistrationState) {
@interface Account : NSObject
- (instancetype)initWithMSAAccount:(MSAAccount*)msaAccount platform:(MCDConnectedDevicesPlatform*)platform apnsManager:(APNSManager*)apnsManager;
- (instancetype)initWithAADAccount:(AADAccount*)msaAccount platform:(MCDConnectedDevicesPlatform*)platform apnsManager:(APNSManager*)apnsManager;
- (instancetype)initWithMCDAccount:(MCDConnectedDevicesAccount*)account state:(AccountRegistrationState)state platform:(MCDConnectedDevicesPlatform*)platform apnsManager:(APNSManager*)apnsManager;
- (AnyPromise*)prepareAccountAsync:(ConnectedDevicesPlatformManager*)platformManager;
@ -39,7 +41,7 @@ typedef NS_ENUM(NSInteger, AccountRegistrationState) {
@property(nonatomic) MCDConnectedDevicesAccount* mcdAccount;
@property(nonatomic) MCDConnectedDevicesPlatform* platform;
@property(nonatomic) APNSManager* apnsManager;
@property(nonatomic) NotificationsManager* notificationsManager;
@end
@protocol ConnectedDevicesPlatformManagerDelegate
@ -51,16 +53,16 @@ typedef NS_ENUM(NSInteger, AccountRegistrationState) {
@interface ConnectedDevicesPlatformManager : NSObject
+ (instancetype)sharedInstance;
@property(nonatomic, weak) id<ConnectedDevicesPlatformManagerDelegate> delegate;
@property(nonatomic) MCDConnectedDevicesPlatform* platform;
@property(atomic) NSMutableArray<Account*>* accounts;
@property(nonatomic, weak) id<ConnectedDevicesPlatformManagerDelegate> delegate;
@property(nonatomic) APNSManager* apnsManager;
@property(nonatomic) AnyPromise* accountsPromise;
@property(nonatomic) APNSManager* apnsManager;
@property(nonatomic) NotificationsManager* notificationsManager;
- (AnyPromise*)signInMsaAsync;
- (AnyPromise*)signInAadAsync;
- (AnyPromise*)signOutAsync:(Account*)account;
- (NSMutableArray<Account*>*)deserializeAccounts;
- (void)setNotificationRegistration:(MCDConnectedDevicesNotificationRegistration*)registration;
- (void)setNotificationRegistration:(NSString*)token;
@end
#endif /* ConnectedDevicesPlatformManager_h */

Просмотреть файл

@ -3,12 +3,11 @@
//
#import "ConnectedDevicesPlatformManager.h"
#import <ConnectedDevicesUserData/ConnectedDevicesUserData.h>
#import <ConnectedDevicesUserDataUserActivities/ConnectedDevicesUserDataUserActivities.h>
#import "MSAAccount.h"
#import "Secrets.h"
// Known list of MSA scopes for this sample
static NSDictionary<NSString*, NSArray<NSString*>*>* s_msaScopeOverrides;
@implementation APNSManager {
NSMutableDictionary<NSString*, PMKAdapter>* _pendingOperationBlocks;
MCDConnectedDevicesNotificationRegistration* _notificationRegistration;
@ -39,7 +38,7 @@
}
}
- (void)setNotificationRegistration:(MCDConnectedDevicesNotificationRegistration*)registration accounts:(NSArray<Account*>*)accounts{
- (void)setNotificationRegistration:(MCDConnectedDevicesNotificationRegistration*)registration accounts:(NSArray<Account*>*)accounts {
NSMutableDictionary<NSString*, PMKAdapter>* pendingOperationBlocksToComplete;
BOOL needsUpdating = NO;
@synchronized (self) {
@ -76,6 +75,7 @@
@interface Account () {
MSAAccount* _msaAccount;
AADAccount* _aadAccount;
}
- (void)clearSubcomponents;
@ -110,6 +110,21 @@
return self;
}
- (instancetype)initWithAADAccount:(AADAccount *)aadAccount platform:(MCDConnectedDevicesPlatform*)platform apnsManager:(APNSManager*)apnsManager {
if (self = [super init]) {
_aadAccount = aadAccount;
if (!_aadAccount.isSignedIn) {
return nil;
}
self.mcdAccount = _aadAccount.mcdAccount;
self.platform = platform;
self.apnsManager = apnsManager;
}
return self;
}
- (AnyPromise*)prepareAccountAsync:(ConnectedDevicesPlatformManager*)platformManager {
// Accounts can be in 3 different scenarios:
// 1: cached account in good standing (initialized in the SDK and our token cache).
@ -178,7 +193,6 @@
- (AnyPromise*)registerWithSdkAsync
{
if (self.state != AccountRegistrationStateInAppCacheAndSdkCache) {
return [AnyPromise promiseWithValue:[NSError errorWithDomain:@"AccountException" code:0 userInfo:nil]];
}
@ -199,18 +213,9 @@
return [AnyPromise promiseWithValue:[NSError errorWithDomain:@"RegistrationError" code:result.status userInfo:nil]];
}
// Do operations that require notification registration now. Like saving UserDataFeed sync scopes.
MCDUserDataFeed* userDataFeed = [MCDUserDataFeed getForAccount:self.mcdAccount
platform:self.platform
activitySourceHost:APP_HOST_NAME];
// Initialize the UserNotification manager for this account
_notificationsManager = [[NotificationsManager alloc] initWithAccount:self.mcdAccount platform:self.platform];
// For UserDataFeed, adjust the sync scopes so that the types the app cares about synced down. Until this completes, the app will not
// get data of the desired type (UserActivities vs UserNotifications) and will not receive notifications when the data changes.
NSArray<MCDUserDataFeedSyncScope*>* syncScopes = @[ [MCDUserActivityChannel syncScope] ];
[userDataFeed subscribeToSyncScopesAsync:syncScopes
callback:^(BOOL success __unused, NSError* _Nullable error __unused) {
// Based on your app's needs this could be a good place to start syncing down activity feeds etc.
}];
// This sample simply kicks off registration of the UserDataFeed but does not return a meaningful promise
// here to gate other operations on completion on this step. This is more scenario dependent on what operations the app cares
// about.
@ -218,8 +223,7 @@
});
}
- (AnyPromise*)signOutAsync
{
- (AnyPromise*)signOutAsync {
// First remove the account out from the ConnectedDevices SDK. The SDK may call back for access tokens to perform
// unregistration with services
[self clearSubcomponents];
@ -229,28 +233,36 @@
// After its gone from the sdk, it is safe to sign out from the token library and clean up the account list.
self.state = AccountRegistrationStateInAppCacheOnly;
return [AnyPromise promiseWithAdapterBlock:^(PMKAdapter _Nonnull adapter) {
[_msaAccount signOutWithCompletionCallback:adapter];
if (_msaAccount != nil) {
[_msaAccount signOutWithCompletionCallback:adapter];
_msaAccount = nil;
} else {
[_aadAccount signOutWithCompletionCallback:adapter];
_aadAccount = nil;
}
}];
});
}
- (void)initializeSubcomponents {
// Do initial per account immediate initialization work. Like getting and handling a UserDataFeed.
MCDUserDataFeed* userDataFeed = [MCDUserDataFeed getForAccount:self.mcdAccount
platform:self.platform
activitySourceHost:APP_HOST_NAME];
[userDataFeed.syncStatusChanged subscribe:^(MCDUserDataFeed* _Nonnull sender, MCDUserDataFeedSyncStatusChangedEventArgs* _Nonnull __unused args) {
NSLog(@"SyncStatus is %ld", (long)sender.syncStatus);
}];
// Do operations that require notification registration now.
}
- (void)clearSubcomponents {
// If your app needs to stop using a sub component for some reason, this would be a good place to reset a user data feed for instance.
_notificationsManager = nil;
}
- (AnyPromise*)getAccessTokenAsync:(NSArray<NSString*>*)scopes {
return [AnyPromise promiseWithAdapterBlock:^(PMKAdapter _Nonnull adapter) {
[_msaAccount getAccessTokenForUserAccountIdAsync:self.mcdAccount.accountId scopes:scopes completion:adapter];
if (_msaAccount != nil) {
[_msaAccount getAccessTokenForUserAccountIdAsync:self.mcdAccount.accountId scopes:scopes completion:adapter];
} else if (_aadAccount != nil) {
[_aadAccount getAccessTokenForUserAccountIdAsync:self.mcdAccount.accountId scopes:scopes completion:adapter];
} else {
NSLog(@"Token requested by platform for unknown account");
}
}];
}
@ -267,7 +279,12 @@
static dispatch_once_t onceToken;
static ConnectedDevicesPlatformManager* sharedInstance;
dispatch_once(&onceToken, ^{ sharedInstance = [[ConnectedDevicesPlatformManager alloc] init]; });
dispatch_once(&onceToken, ^{
sharedInstance = [[ConnectedDevicesPlatformManager alloc] init];
s_msaScopeOverrides = @{@"https://activity.windows.com/UserActivity.ReadWrite.CreatedByApp"
: @[@"https://activity.windows.com/Notifications.ReadWrite.CreatedByApp"]
};
});
return sharedInstance;
}
@ -276,8 +293,9 @@
self.accounts = [NSMutableArray new];
self.apnsManager = [APNSManager new];
// Construct and initialize a platform. All we are doing here is hooking up event handlers before
// calling ConnectedDevicesPlatform Start. After Start is called events may begin to fire.
// Construct and initialize an instance of MCDConnectedDevicesPlatform.
// We are hooking up event handlers before calling ConnectedDevicesPlatform Start.
// After Start is called events may begin to fire.
self.platform = [MCDConnectedDevicesPlatform new];
__weak ConnectedDevicesPlatformManager* weakSelf = self;
@ -363,18 +381,32 @@
return self;
}
- (NotificationsManager*)notificationsManager {
// Since the sample is setup for single account, return the first account's notifications manager
return self.accounts[0].notificationsManager;
}
- (NSMutableArray<Account*>*)deserializeAccounts {
// Add all cached accounts from the platform.
// List of known accounts
NSMutableArray<Account*>* accountList = [NSMutableArray new];
// Get the accounts cached in the SDK.
NSMutableArray<MCDConnectedDevicesAccount*>* sdkCachedAccounts = [NSMutableArray arrayWithArray:self.platform.accountManager.accounts];
// Ideally the token library would support multiple app cached accounts; If this is nil then there is no account the app knows about.
Account* appCachedAccount = [[Account alloc] initWithMSAAccount:[[MSAAccount alloc] initWithClientId:MSA_CLIENT_ID scopeOverrides:@{}] platform:self.platform apnsManager:self.apnsManager];
// Get the account cached by the app (First MSA, then AAD)
Account* appCachedAccount = [[Account alloc] initWithMSAAccount:[[MSAAccount alloc] initWithClientId:MSA_CLIENT_ID
scopeOverrides:s_msaScopeOverrides] platform:self.platform apnsManager:self.apnsManager];
if (appCachedAccount == nil) {
appCachedAccount = [[Account alloc] initWithAADAccount:[[AADAccount alloc] initWithClientId:AAD_CLIENT_ID
redirectUri:[NSURL URLWithString:AAD_REDIRECT_URI]] platform:self.platform apnsManager:self.apnsManager];
}
NSMutableArray<Account*>* accountList = [NSMutableArray new];
// Cross-check the app-cached account with SDK-cached accounts
if (appCachedAccount != nil) {
MCDConnectedDevicesAccount* matchingAccount = nil;
for (MCDConnectedDevicesAccount* account in sdkCachedAccounts) {
if ([appCachedAccount.mcdAccount.accountId isEqualToString:account.accountId] && appCachedAccount.mcdAccount.type == account.type) {
if ([appCachedAccount.mcdAccount.accountId isEqualToString:account.accountId]
&& appCachedAccount.mcdAccount.type == account.type) {
matchingAccount = account;
break;
}
@ -390,9 +422,10 @@
[accountList addObject:appCachedAccount];
}
// Add the remaining SDK only accounts (these need to be removed from the SDK)
// Add the remaining SDK-cached accounts
for (MCDConnectedDevicesAccount* account in sdkCachedAccounts) {
[accountList addObject:[[Account alloc] initWithMCDAccount:account state:AccountRegistrationStateInSdkCacheOnly platform:self.platform apnsManager:self.apnsManager]];
[accountList addObject:[[Account alloc] initWithMCDAccount:account state:AccountRegistrationStateInSdkCacheOnly
platform:self.platform apnsManager:self.apnsManager]];
}
return accountList;
@ -421,7 +454,8 @@
}
- (AnyPromise*)signInMsaAsync {
MSAAccount* msaAccount = [[MSAAccount alloc] initWithClientId:MSA_CLIENT_ID scopeOverrides:@{}];
MSAAccount* msaAccount = [[MSAAccount alloc] initWithClientId:MSA_CLIENT_ID
scopeOverrides:s_msaScopeOverrides];
return [AnyPromise promiseWithAdapterBlock:^(PMKAdapter _Nonnull adapter) {
[msaAccount signInWithCompletionCallback:adapter];
}].then(^{
@ -434,6 +468,20 @@
});
}
- (AnyPromise*)signInAadAsync {
AADAccount* aadAccount = [[AADAccount alloc] initWithClientId:AAD_CLIENT_ID redirectUri:[NSURL URLWithString:AAD_REDIRECT_URI]];
return [AnyPromise promiseWithAdapterBlock:^(PMKAdapter _Nonnull adapter) {
[aadAccount signInWithCompletionCallback:adapter];
}].then(^{
Account* account = [[Account alloc] initWithAADAccount:aadAccount platform:self.platform apnsManager:self.apnsManager];
account.state = AccountRegistrationStateInAppCacheOnly;
[self.accounts addObject:account];
return [account prepareAccountAsync:self];
}).then(^{
[self accountListChanged];
});
}
- (AnyPromise*)signOutAsync:(Account*)account {
return [account signOutAsync].then(^{
[self.accounts removeObjectAtIndex:[self.accounts indexOfObjectPassingTest:^BOOL(Account * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
@ -444,7 +492,7 @@
});
}
- (void)setNotificationRegistration:(NSString*)tokenString {
- (void)setNotificationRegistration:(NSString*)token {
MCDConnectedDevicesNotificationRegistration* registration = [MCDConnectedDevicesNotificationRegistration new];
@ -459,7 +507,7 @@
registration.appId = [[NSBundle mainBundle] bundleIdentifier];
registration.appDisplayName = (NSString*)[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
registration.token = tokenString;
registration.token = token;
// The two cases of receiving a new notification token are:
// 1. A notification registration is asked for and now it is available. In this case there is a pending promise that was made
@ -471,5 +519,4 @@
[self.apnsManager setNotificationRegistration:registration accounts:self.accounts];
}
@end

Просмотреть файл

@ -3,13 +3,14 @@
//
#pragma once
#import <UIKit/UIKit.h>
@interface LoginViewController : UIViewController
- (IBAction)loginAAD;
@property (strong, nonatomic) IBOutlet UIButton* msaButton;
@property (strong, nonatomic) IBOutlet UIButton* aadButton;
@property (strong, nonatomic) IBOutlet UILabel* loginStatusLabel;
- (IBAction)loginMSA;
@property (strong, nonatomic) IBOutlet UIButton *aadButton;
@property (strong, nonatomic) IBOutlet UIButton *msaButton;
- (IBAction)loginAAD;
@end

Просмотреть файл

@ -3,248 +3,122 @@
//
#import "LoginViewController.h"
#import "NotificationsManager.h"
#import "Secrets.h"
#import "AppDelegate.h"
#import "ConnectedDevicesPlatformManager.h"
@interface LoginViewController ()
@property (nonatomic) AADAccount* aadAccount;
@property (nonatomic) MSAAccount* msaAccount;
@end
@implementation LoginViewController
typedef NS_ENUM(NSInteger, LoginState)
{
typedef NS_ENUM(NSInteger, LoginState) {
AAD,
MSA,
SIGNED_OUT
};
- (void)viewDidLoad
{
@implementation LoginViewController {
ConnectedDevicesPlatformManager* _platformManager;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.aadAccount = [[AADAccount alloc] initWithClientId:AAD_CLIENT_ID redirectUri:[NSURL URLWithString:AAD_REDIRECT_URI]];
self.msaAccount = [[MSAAccount alloc] initWithClientId:MSA_CLIENT_ID scopeOverrides:@{@"https://activity.windows.com/UserActivity.ReadWrite.CreatedByApp" : @[@"https://activity.windows.com/UserActivity.ReadWrite.CreatedByApp", @"https://activity.windows.com/Notifications.ReadWrite.CreatedByApp"]}];
_platformManager = [ConnectedDevicesPlatformManager sharedInstance];
[self _setButtonTextForState:[self _getState]];
}
AppDelegate* appDelegate = (AppDelegate*)([UIApplication sharedApplication].delegate);
__weak typeof(self) weakSelf = self;
[appDelegate.platform.accountManager.accessTokenRequested subscribe:^(MCDConnectedDevicesAccountManager * _Nonnull manager __unused, MCDConnectedDevicesAccessTokenRequestedEventArgs * _Nonnull args)
{
switch ([weakSelf getState])
{
case AAD:
{
[weakSelf.aadAccount getAccessTokenForUserAccountIdAsync:args.request.account.accountId scopes:args.request.scopes completion:^(NSString * _Nonnull token, NSError * _Nullable error)
{
if (error)
{
[args.request completeWithErrorMessage:error.description];
}
else
{
[args.request completeWithAccessToken:token];
}
}];
break;
}
case MSA:
{
[weakSelf.msaAccount getAccessTokenForUserAccountIdAsync:args.request.account.accountId scopes:args.request.scopes completion:^(NSString * _Nonnull token, NSError * _Nullable error)
{
if (error)
{
[args.request completeWithErrorMessage:error.description];
}
else
{
[args.request completeWithAccessToken:token];
}
}];
break;
}
case SIGNED_OUT:
{
[args.request completeWithErrorMessage:@"Not currently signed in!"];
break;
}
}
}];
[appDelegate.platform.accountManager.accessTokenInvalidated subscribe:^(MCDConnectedDevicesAccountManager * _Nonnull manager __unused, MCDConnectedDevicesAccessTokenInvalidatedEventArgs * _Nonnull args)
{
NSLog(@"Access token invalidated for %@ account for %@ scopes", args.account.accountId, args.scopes);
}];
[self setButtonTextForState];
switch([self getState])
{
case AAD:
[NotificationsManager sharedInstance].account = self.aadAccount.mcdAccount;
break;
case MSA:
[NotificationsManager sharedInstance].account = self.msaAccount.mcdAccount;
break;
case SIGNED_OUT:
// No need to do anything
break;
- (IBAction)loginMSA {
LoginState state = [self _getState];
if (state == SIGNED_OUT) {
[self _setStatusText:@"Signing in MSA..."];
// Perform MSA sign-in
[_platformManager signInMsaAsync].then(^{
[self _setStatusText:[NSString stringWithFormat:@"Currently signed in"]];
[self _setButtonTextForState:MSA];
}).catch(^(NSError* error){
NSLog(@"%@", error);
[self _setStatusText:[NSString stringWithFormat:@"MSA sign-in failed!"]];
});
} else {
[_platformManager signOutAsync:_platformManager.accounts[0]].then(^{
[self _setStatusText:[NSString stringWithFormat:@"Currently signed out"]];
[self _setButtonTextForState:SIGNED_OUT];
}).catch(^(NSError* error){
NSLog(@"%@", error);
[self _setStatusText:[NSString stringWithFormat:@"MSA sign-out failed!"]];
});
}
}
- (IBAction)loginAAD
{
__weak typeof(self) weakSelf = self;
switch ([self getState])
{
case AAD:
{
[self.aadAccount signOutWithCompletionCallback:^(MCDConnectedDevicesAccount* account, NSError* error)
{
if (!error)
{
dispatch_async(dispatch_get_main_queue(),
^{
[weakSelf setButtonTextForState];
[weakSelf.view setNeedsDisplay];
});
[NotificationsManager sharedInstance].account = nil;
}
else
{
NSLog(@"Failed to sign out AAD with reason %@", [error description]);
}
}];
break;
}
case SIGNED_OUT:
{
[self.aadAccount signInWithCompletionCallback:^(MCDConnectedDevicesAccount* account, NSError* error)
{
if (!error)
{
NSLog(@"Signed in to AAD with no error!");
dispatch_async(dispatch_get_main_queue(),
^{
[weakSelf setButtonTextForState];
[weakSelf.view setNeedsDisplay];
});
[NotificationsManager sharedInstance].account = account;
}
else
{
NSLog(@"Failed to sign in AAD with reason %@", [error description]);
}
}];
break;
}
default:
// Do nothing
break;
- (IBAction)loginAAD {
LoginState state = [self _getState];
if (state == SIGNED_OUT) {
[self _setStatusText:@"Signing in AAD..."];
// Perform AAD sign-in
[_platformManager signInAadAsync].then(^{
[self _setStatusText:[NSString stringWithFormat:@"Currently signed in"]];
[self _setButtonTextForState:AAD];
}).catch(^(NSError* error){
NSLog(@"%@", error);
[self _setStatusText:[NSString stringWithFormat:@"AAD sign-in failed!"]];
});
} else {
[_platformManager signOutAsync:_platformManager.accounts[0]].then(^{
[self _setStatusText:[NSString stringWithFormat:@"Currently signed out"]];
[self _setButtonTextForState:SIGNED_OUT];
}).catch(^(NSError* error){
NSLog(@"%@", error);
[self _setStatusText:[NSString stringWithFormat:@"AAD sign-out failed!"]];
});
}
}
- (IBAction)loginMSA
{
__weak typeof(self) weakSelf = self;
switch ([self getState])
{
case MSA:
{
[self.msaAccount signOutWithCompletionCallback:^(MCDConnectedDevicesAccount* account, NSError* error)
{
if (!error)
{
dispatch_async(dispatch_get_main_queue(),
^{
[weakSelf setButtonTextForState];
[weakSelf.view setNeedsDisplay];
});
[NotificationsManager sharedInstance].account = nil;
}
else
{
NSLog(@"Failed to sign out MSA with reason %@", [error description]);
}
- (LoginState)_getState {
@synchronized (self) {
Account* account = nil;
if (_platformManager.accounts.count > 0) {
NSInteger index = [_platformManager.accounts indexOfObjectPassingTest:^BOOL (Account* account, NSUInteger index, BOOL* stop) {
return account.state == AccountRegistrationStateInAppCacheAndSdkCache;
}];
break;
if (index != NSNotFound) {
account = [_platformManager.accounts objectAtIndex:index];
}
}
case SIGNED_OUT:
{
NSLog(@"Going to sign in with MSA");
[self.msaAccount signInWithCompletionCallback:^(MCDConnectedDevicesAccount* account, NSError* error)
{
if (!error)
{
NSLog(@"Signed in to MSA with no error!");
dispatch_async(dispatch_get_main_queue(),
^{
[weakSelf setButtonTextForState];
[weakSelf.view setNeedsDisplay];
});
[NotificationsManager sharedInstance].account = account;
}
else
{
NSLog(@"Failed to sign in MSA with reason %@", [error description]);
}
}];
break;
}
default:
// Do nothing
break;
}
}
- (void)setButtonTextForState
{
dispatch_async(dispatch_get_main_queue(),
^{
switch ([self getState])
{
case SIGNED_OUT:
[self.aadButton setTitle:@"Login Work/School Account" forState:UIControlStateNormal];
[self.msaButton setTitle:@"Login Personal Account" forState:UIControlStateNormal];
break;
case AAD:
[self.aadButton setTitle:@"Logout" forState:UIControlStateNormal];
[self.msaButton setTitle:@"" forState:UIControlStateNormal];
break;
case MSA:
[self.aadButton setTitle:@"" forState:UIControlStateNormal];
[self.msaButton setTitle:@"Logout" forState:UIControlStateNormal];
break;
}
});
}
- (LoginState)getState
{
@synchronized (self)
{
if (self.aadAccount.isSignedIn)
{
return AAD;
}
else if (self.msaAccount.isSignedIn)
{
return MSA;
}
else
{
if (account != nil) {
return account.mcdAccount.type == MCDConnectedDevicesAccountTypeAAD ? AAD : MSA;
} else {
return SIGNED_OUT;
}
}
}
- (void)_setButtonTextForState:(LoginState)state {
dispatch_async(dispatch_get_main_queue(), ^{
switch (state) {
case SIGNED_OUT:
[self.aadButton setTitle:@"Login with AAD" forState:UIControlStateNormal];
[self.msaButton setTitle:@"Login with MSA" forState:UIControlStateNormal];
self.loginStatusLabel.text = @"Currently signed-out";
break;
case AAD:
[self.aadButton setTitle:@"Logout" forState:UIControlStateNormal];
[self.msaButton setTitle:@"" forState:UIControlStateNormal];
self.loginStatusLabel.text = @"Currently signed-in with AAD";
break;
case MSA:
[self.aadButton setTitle:@"" forState:UIControlStateNormal];
[self.msaButton setTitle:@"Logout" forState:UIControlStateNormal];
self.loginStatusLabel.text = @"Currently signed-in with MSA";
break;
}
});
}
- (void)_setStatusText:(NSString*)text {
NSLog(@"%@", text);
dispatch_async(dispatch_get_main_queue(), ^{ self.loginStatusLabel.text = text; });
}
@end

Просмотреть файл

@ -8,23 +8,18 @@
#import <ConnectedDevices/ConnectedDevices.h>
#import <ConnectedDevicesUserData/ConnectedDevicesUserData.h>
#import <ConnectedDevicesUserDataUserNotifications/ConnectedDevicesUserDataUserNotifications.h>
#import "ConnectedDevicesPlatformManager.h"
#import "AADAccount.h"
#import "MSAAccount.h"
@interface NotificationsManager : NSObject
+ (instancetype)sharedInstance;
- (instancetype)initWithAccount:(MCDConnectedDevicesAccount*)account
platform:(MCDConnectedDevicesPlatform*)platform;
@property (nonatomic, readonly) NSArray<MCDUserNotification*>* notifications;
@property (nonatomic) MCDConnectedDevicesAccount* account;
- (instancetype)initWithPlatformManager:(ConnectedDevicesPlatformManager*)platform;
- (NSInteger)addNotificationsChangedListener:(void(^)(void))listener;
- (void)removeListener:(NSInteger)token;
- (void)forceRead;
- (void)refresh;
- (void)markRead:(MCDUserNotification*)notification;
- (void)deleteNotification:(MCDUserNotification*)notification;
- (void)dismissNotification:(MCDUserNotification*)notification;
- (void)dismissNotificationWithId:(NSString*)notificationId;
- (void)readNotification:(MCDUserNotification*)notification;
@end

Просмотреть файл

@ -4,230 +4,207 @@
#import "NotificationsManager.h"
#import "Secrets.h"
#import "AppDelegate.h"
#import <UserNotifications/UserNotifications.h>
static NotificationsManager* s_manager;
@interface NotificationsManager ()
{
NSInteger _listenerValue;
MCDEventSubscription* _readerSubscription;
@implementation NotificationsManager {
NSMutableArray<MCDUserNotification*>* _notifications;
NSInteger _listenerValue;
NSMutableDictionary<NSNumber*, void(^)(void)>* _listenerMap;
MCDUserDataFeed* _feed;
MCDUserNotificationChannel* _channel;
MCDUserNotificationReader* _reader;
MCDEventSubscription* _readerSubscription;
}
@property (nonatomic) NSMutableDictionary<NSNumber*, void(^)(void)>* listenerMap;
@property (nonatomic) MCDConnectedDevicesPlatform* platform;
@property (nonatomic) MCDUserNotificationChannel* channel;
@property (nonatomic) MCDUserNotificationReader* reader;
@property (nonatomic) BOOL platformStarted;
@end
@implementation NotificationsManager
- (instancetype)initWithPlatformManager:(ConnectedDevicesPlatformManager*)platformManager
{
if (self = [super init])
{
- (instancetype)initWithAccount:(MCDConnectedDevicesAccount*)account
platform:(MCDConnectedDevicesPlatform*)platform {
if (self = [super init]) {
_notifications = [NSMutableArray array];
_listenerValue = 0;
_listenerMap = [NSMutableDictionary dictionary];
_platform = platformManager.platform;
_platformStarted = NO;
MCDUserDataFeed* _userDataFeed = [MCDUserDataFeed getForAccount:platformManager.accounts[0].mcdAccount
platform:self.platform
activitySourceHost:MSA_CLIENT_ID];
[_userDataFeed startSync];
self.channel = [MCDUserNotificationChannel channelWithUserDataFeed:_userDataFeed];
self.reader = [self.channel createReader];
_readerSubscription = [self.reader.dataChanged subscribe:^(__unused MCDUserNotificationReader* source, __unused MCDUserNotificationReaderDataChangedEventArgs* args){
// Initialize the feed and subscribe for notifications
_feed = [MCDUserDataFeed getForAccount:account
platform:platform
activitySourceHost:APP_HOST_NAME];
[_feed.syncStatusChanged subscribe:^(MCDUserDataFeed* _Nonnull sender,
__unused MCDUserDataFeedSyncStatusChangedEventArgs* _Nonnull args) {
NSLog(@"SyncStatus is %ld", (long)sender.syncStatus);
}];
NSArray<MCDUserDataFeedSyncScope*>* syncScopes = @[ [MCDUserNotificationChannel syncScope] ];
[_feed subscribeToSyncScopesAsync:syncScopes
callback:^(BOOL success __unused, NSError* _Nullable error __unused) {
// Start syncing down notifications
[_feed startSync];
}];
// Create the channel and reader
_channel = [MCDUserNotificationChannel channelWithUserDataFeed:_feed];
_reader = [_channel createReader];
_readerSubscription = [_reader.dataChanged subscribe:^(MCDUserNotificationReader* source,
__unused MCDUserNotificationReaderDataChangedEventArgs* args){
{
NSLog(@"GraphNotifications Got an update!");
NSLog(@"GraphNotificationsSample got an update!");
[self _readFromCache:source];
};
}];
[self forceRead];
[self _readFromCache:_reader];
}
return self;
}
- (void)forceRead
{
[self.reader readBatchAsyncWithMaxSize:NSUIntegerMax completion:^(NSArray<MCDUserNotification *> * _Nullable notifications, NSError * _Nullable error)
{
if (error)
{
NSLog(@"GraphNotifications Failed to read batch with error %@", error);
}
else
{
NSLog(@"GraphNotifications NotificationsManager got %ld notifications", notifications.count);
[self _handleNotifications:notifications];
for (void (^listener)(void) in self.listenerMap.allValues)
{
listener();
}
}
}];
- (NSInteger)addNotificationsChangedListener:(void(^)(void))listener {
@synchronized (self) {
_listenerMap[[NSNumber numberWithInteger:(++_listenerValue)]] = listener;
return _listenerValue;
}
}
- (void)readNotification:(MCDUserNotification*)notification
{
@synchronized (self)
{
- (void)removeListener:(NSInteger)token {
@synchronized (self) {
[_listenerMap removeObjectForKey:[NSNumber numberWithInteger:token]];
}
}
- (void)refresh {
@synchronized (self) {
[_feed startSync];
[self _readFromCache:_reader];
}
}
- (void)markRead:(MCDUserNotification*)notification {
if (notification.readState == MCDUserNotificationReadStateUnread) {
NSLog(@"Marking notification %@ as read", notification.notificationId);
notification.readState = MCDUserNotificationReadStateRead;
[notification saveAsync:^(__unused MCDUserNotificationUpdateResult * _Nullable result, __unused NSError * _Nullable err)
{
NSLog(@"GraphNotifications Read notification with result %d error %@", result.succeeded, err);
[notification saveAsync:^(__unused MCDUserNotificationUpdateResult * _Nullable result, NSError * _Nullable error) {
if (error) {
NSLog(@"Failed to mark the notification as read with error %@", error);
} else {
NSLog(@"Successfully marked the notification as read");
}
}];
}
}
- (void)dismissNotificationFromTrayWithId:(NSString *)notificationId
{
- (void)deleteNotification:(MCDUserNotification*)notification {
NSLog(@"Deleting notification %@", notification.notificationId);
[_channel
deleteUserNotificationAsync:notification.notificationId
completion:^(__unused MCDUserNotificationUpdateResult* _Nullable result, NSError* _Nullable error) {
if (error) {
NSLog(@"Failed to delete notifications with error %@", error);
} else {
NSLog(@"Successfully deleted the notification");
}
}];
}
- (void)dismissNotification:(MCDUserNotification*)notification {
if (notification.userActionState == MCDUserNotificationUserActionStateNoInteraction) {
NSLog(@"Dismissing notification %@", notification.notificationId);
[self dismissNotificationFromTrayWithId:notification.notificationId];
notification.userActionState = MCDUserNotificationUserActionStateDismissed;
[notification saveAsync:^(__unused MCDUserNotificationUpdateResult * _Nullable result, __unused NSError * _Nullable error) {
if (error) {
NSLog(@"Failed to dismiss notifications with error %@", error);
} else {
NSLog(@"Successfully dismissed the notification");
}
}];
}
}
- (void)dismissNotificationFromTrayWithId:(NSString *)notificationId {
[[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers:@[notificationId]];
[[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers:@[notificationId]];
}
- (void)dismissNotificationWithId:(NSString *)notificationId
{
@synchronized (self)
{
for (MCDUserNotification* notification in self.notifications)
{
if ([notification.notificationId isEqualToString:notificationId])
{
- (void)dismissNotificationWithId:(NSString *)notificationId {
@synchronized (self) {
for (MCDUserNotification* notification in self.notifications) {
if ([notification.notificationId isEqualToString:notificationId]) {
[self dismissNotification:notification];
}
}
}
}
- (void)dismissNotification:(MCDUserNotification*)notification
{
@synchronized (self)
{
[self dismissNotificationFromTrayWithId:notification.notificationId];
notification.userActionState = MCDUserNotificationUserActionStateActivated;
[notification saveAsync:^(__unused MCDUserNotificationUpdateResult * _Nullable result, __unused NSError * _Nullable err)
{
NSLog(@"GraphNotifications Dismiss notification with result %d error %@", result.succeeded, err);
}];
}
- (void)_readFromCache:(MCDUserNotificationReader*)reader {
NSLog(@"Read notifications from cache");
[reader readBatchAsyncWithMaxSize:100 completion:^(NSArray<MCDUserNotification *> * _Nullable notifications,
NSError * _Nullable error) {
if (error) {
NSLog(@"Failed to read notifications with error %@", error);
} else {
NSLog(@"NotificationsManager got %ld notifications to process", notifications.count);
[self _handleNotifications:notifications];
// Notify the listeners
for (void (^listener)(void) in _listenerMap.allValues) {
listener();
}
}
}];
}
+ (instancetype)sharedInstance
{
@synchronized (self)
{
return s_manager;
}
}
- (NSInteger)addNotificationsChangedListener:(void(^)(void))listener
{
@synchronized (self)
{
_listenerMap[[NSNumber numberWithInteger:(++_listenerValue)]] = listener;
return _listenerValue;
}
}
- (void)removeListener:(NSInteger)token
{
@synchronized (self)
{
[_listenerMap removeObjectForKey:[NSNumber numberWithInteger:token]];
}
}
- (NSArray<MCDUserNotification*>*)notifications
{
return _notifications;
}
- (void)_handleNotifications:(NSArray<MCDUserNotification*>*)notifications
{
@synchronized (self)
{
NSLog(@"GraphNotifications Got %ld notifications!", notifications.count);
for (MCDUserNotification* notification in notifications)
{
for (NSUInteger i = 0; i < _notifications.count; ++i)
{
if ([_notifications[i].notificationId isEqualToString:notification.notificationId])
{
NSLog(@"GraphNotifications Found a match for %@", notification.notificationId);
[_notifications removeObjectAtIndex:i];
break;
}
- (void)_handleNotifications:(NSArray<MCDUserNotification*>*)notifications {
@synchronized (self) {
for (MCDUserNotification* notification in notifications) {
NSUInteger index = [_notifications
indexOfObjectPassingTest:^BOOL(MCDUserNotification* existingNotification, NSUInteger __unused innerIndex, BOOL* stop) {
if ([existingNotification.notificationId isEqualToString:notification.notificationId]) {
*stop = YES;
return YES;
}
return NO;
}];
if (index != NSNotFound) {
[_notifications removeObjectAtIndex:index];
}
if (notification.status == MCDUserNotificationStatusActive)
{
NSLog(@"GraphNotifications Notification is active %@", notification.notificationId);
if (notification.status == MCDUserNotificationStatusActive) {
NSLog(@"Notification %@ is active", notification.notificationId);
[_notifications insertObject:notification atIndex:0];
if ((notification.userActionState == MCDUserNotificationUserActionStateNoInteraction) && (notification.readState == MCDUserNotificationReadStateUnread))
{
if ((notification.userActionState == MCDUserNotificationUserActionStateNoInteraction)
&& (notification.readState == MCDUserNotificationReadStateUnread)) {
UNMutableNotificationContent* content = [UNMutableNotificationContent new];
content.title = @"New MSGraph Notification";
content.title = @"New Graph Notification";
content.body = notification.content;
UNTimeIntervalNotificationTrigger* trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats:NO];
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:notification.notificationId content:content trigger:trigger];
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error)
{
if (error)
{
NSLog(@"GraphNotifications Failed to post local notification with error %@", error);
}
else
{
NSLog(@"GraphNotifications Successfully posted local notification request");
UNTimeIntervalNotificationTrigger* trigger = [UNTimeIntervalNotificationTrigger
triggerWithTimeInterval:1 repeats:NO];
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:notification.notificationId
content:content trigger:trigger];
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request
withCompletionHandler:^(NSError * _Nullable error) {
if (error) {
NSLog(@"Failed to post local notification with error %@", error);
} else {
NSLog(@"Successfully posted local notification request");
}
}];
}
else
{
} else {
[self dismissNotificationFromTrayWithId:notification.notificationId];
}
}
else
{
NSLog(@"GraphNotifications Notification is deleted %@", notification.notificationId);
} else {
NSLog(@"Notification %@ is deleted", notification.notificationId);
[self dismissNotificationFromTrayWithId:notification.notificationId];
}
}
NSLog(@"GraphNotifications NotificationsManager now has %ld notifications", _notifications.count);
NSLog(@"NotificationsManager now has %ld notifications", _notifications.count);
}
}
- (void)_clearNotifications
{
@synchronized (self)
{
- (void)_clearAll {
@synchronized (self) {
[_notifications removeAllObjects];
[[UNUserNotificationCenter currentNotificationCenter] removeAllPendingNotificationRequests];
[[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications];
for (void (^listener)(void) in self.listenerMap.allValues)
{
listener();
}
}
}
- (void)refresh
{
NSLog(@"GraphNotifications Refreshing NotificationsManager");
@synchronized (self)
{
[self _clearNotifications];
// Starts setup based on new account, which since this is the current account will just refresh all of the actual setup
[self setAccount:self.account];
}
}

Просмотреть файл

@ -5,8 +5,7 @@
#pragma once
#import <UIKit/UIKit.h>
#import "NotificationsManager.h"
@interface NotificationsViewController : UITableViewController
@interface NotificationsViewController : UITableViewController<UITableViewDelegate, UITableViewDataSource>
- (IBAction)refresh;
@end

Просмотреть файл

@ -4,107 +4,106 @@
#import <Foundation/Foundation.h>
#import "NotificationsViewController.h"
#import "NotificationsManager.h"
#import "ConnectedDevicesPlatformManager.h"
@interface NotificationsViewController()
{
@implementation NotificationsViewController {
NotificationsManager* _notificationsManager;
}
@property (nonatomic) NSMutableArray<MCDUserNotification*>* notifications;
- (void)updateData;
- (void)handleTap:(UITapGestureRecognizer*)recognizer;
@end
@implementation NotificationsViewController
- (void)viewDidLoad
{
- (void)viewDidLoad {
[super viewDidLoad];
_notifications = [NSMutableArray array];
// Do any additional setup after loading the view, typically from a nib.
__weak typeof(self) weakSelf = self;
[[NotificationsManager sharedInstance] addNotificationsChangedListener:
^{
NSLog(@"GraphNotifications NotificationsViewController notified of changes!");
dispatch_async(dispatch_get_main_queue(),
^{
[weakSelf updateData];
[weakSelf.tableView reloadData];
});
}];
dispatch_async(dispatch_get_main_queue(),
^{
[self updateData];
[self.tableView reloadData];
});
[self refresh];
}
- (NSInteger)numberOfSectionsInTableView:(__unused UITableView*)tableView
{
- (void)_initNotificationsManager {
@synchronized (self) {
if (_notificationsManager == nil) {
_notificationsManager = [[ConnectedDevicesPlatformManager sharedInstance] notificationsManager];
__weak typeof(self) weakSelf = self;
[_notificationsManager addNotificationsChangedListener: ^{
NSLog(@"NotificationsViewController notified of changes!");
[weakSelf _updateView];
}];
}
}
}
- (void)_updateView {
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
- (IBAction)refresh {
[self _initNotificationsManager];
[_notificationsManager refresh];
}
- (NSInteger)numberOfSectionsInTableView:(__unused UITableView*)tableView {
return 1;
}
- (nonnull UITableViewCell*)tableView:(nonnull UITableView*)tableView cellForRowAtIndexPath:(nonnull NSIndexPath*)indexPath
{
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"NotificationCell" forIndexPath:indexPath];
- (NSInteger)tableView:(nonnull __unused UITableView*)tableView numberOfRowsInSection:(__unused NSInteger)section {
return _notificationsManager.notifications.count;
}
MCDUserNotification* notification = self.notifications[indexPath.row];
- (nonnull UITableViewCell*)tableView:(nonnull UITableView*)tableView cellForRowAtIndexPath:(nonnull NSIndexPath*)indexPath {
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"NotificationCell" forIndexPath:indexPath];
MCDUserNotification* notification = _notificationsManager.notifications[indexPath.row];
NSLog(@"NotificationsViewController updating cell for notification %@", notification.notificationId);
UILabel* idLabel = (UILabel*)[cell viewWithTag:1];
[idLabel setText:notification.notificationId];
[idLabel setFont:[UIFont boldSystemFontOfSize:20]];
UILabel* contentLabel = (UILabel*)[cell viewWithTag:2];
[contentLabel setText:notification.content];
UILabel* userActionStateLabel = (UILabel*)[cell viewWithTag:3];
[userActionStateLabel setText:((notification.userActionState == MCDUserNotificationUserActionStateNoInteraction) ? @"No Interaction" : @"Activated")];
UILabel* actionStateLabel = (UILabel*)[cell viewWithTag:3];
UIButton* dismissButton = (UIButton*)[cell viewWithTag:5];
[dismissButton setTag:indexPath.row];
if (notification.userActionState == MCDUserNotificationUserActionStateNoInteraction) {
[actionStateLabel setText:@"No Interaction"];
dismissButton.enabled = YES;
[dismissButton addTarget:self action:@selector(handleDismiss:) forControlEvents:UIControlEventTouchUpInside];
} else {
[actionStateLabel setText:@"Dismissed"];
dismissButton.enabled = NO;
}
UIButton* readButton = (UIButton*)[cell viewWithTag:4];
if (notification.readState == MCDUserNotificationReadStateUnread)
{
[readButton setTag:indexPath.row];
if (notification.readState == MCDUserNotificationReadStateUnread) {
[idLabel setTextColor:[UIColor greenColor]];
readButton.enabled = YES;
[readButton addTarget:self action:@selector(handleRead:) forControlEvents:UIControlEventTouchDown];
}
else
{
[readButton addTarget:self action:@selector(handleRead:) forControlEvents:UIControlEventTouchUpInside];
} else {
[idLabel setTextColor:[UIColor redColor]];
readButton.enabled = NO;
}
if (notification.userActionState == MCDUserNotificationUserActionStateNoInteraction)
{
[cell addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)]];
}
UIButton* deleteButton = (UIButton*)[cell viewWithTag:6];
[deleteButton setTag:indexPath.row];
[deleteButton addTarget:self action:@selector(handleDelete:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
- (NSInteger)tableView:(nonnull __unused UITableView*)tableView numberOfRowsInSection:(__unused NSInteger)section
{
return self.notifications.count;
- (IBAction)handleRead:(UIButton*)button {
MCDUserNotification* selected = _notificationsManager.notifications[button.tag];
[_notificationsManager markRead:selected];
}
- (void)handleTap:(UITapGestureRecognizer *)recognizer
{
[[NotificationsManager sharedInstance] dismissNotification:(self.notifications[[self.tableView indexPathForCell:(UITableViewCell*)recognizer.view].row])];
- (IBAction)handleDismiss:(UIButton*)button {
MCDUserNotification* selected = _notificationsManager.notifications[button.tag];
[_notificationsManager dismissNotification:selected];
}
- (void)updateData
{
@synchronized (self)
{
[self.notifications removeAllObjects];
[self.notifications addObjectsFromArray:[NotificationsManager sharedInstance].notifications];
NSLog(@"GraphNotifications Got %ld valid objects for NotificationsViewController out of %ld", self.notifications.count, [NotificationsManager sharedInstance].notifications.count);
}
- (IBAction)handleDelete:(UIButton*)button {
MCDUserNotification* selected = _notificationsManager.notifications[button.tag];
[_notificationsManager deleteNotification:selected];
}
- (void)handleRead:(id)button
{
[[NotificationsManager sharedInstance] readNotification:(self.notifications[[self.tableView indexPathForCell:(UITableViewCell*)((UIView*)button).superview].row])];
}
- (IBAction)refresh
{
[[NotificationsManager sharedInstance] refresh];
}
@end

Просмотреть файл

@ -2,11 +2,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
//
#pragma once
#import <UIKit/UIKit.h>
@interface RootViewController : UIViewController
@property (strong, nonatomic) UITabBarController *tabBarController;
@end

Просмотреть файл

@ -4,45 +4,33 @@
#import "RootViewController.h"
#import "LoginViewController.h"
#import "NotificationsManager.h"
#import "NotificationsViewController.h"
@interface RootViewController ()
@end
@implementation RootViewController
- (void)viewDidLoad
{
- (void)viewDidLoad {
[super viewDidLoad];
LoginViewController* loginViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"LoginViewController"];
NotificationsViewController* notificationsViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"NotificationsViewController"];
UITabBarItem* loginItem = [[UITabBarItem alloc] initWithTitle:@"Login" image:nil tag:1];
UITabBarItem* loginItem = [[UITabBarItem alloc] initWithTitle:@"Accounts" image:nil tag:1];
UITabBarItem* notificationsItem = [[UITabBarItem alloc] initWithTitle:@"Notifications" image:nil tag:2];
loginViewController.tabBarItem = loginItem;
notificationsViewController.tabBarItem = notificationsItem;
self.tabBarController = [UITabBarController new];
self.tabBarController.viewControllers = @[loginViewController, notificationsViewController];
self.tabBarController.selectedIndex = 0;
[self addChildViewController:self.tabBarController];
[self.view addSubview:self.tabBarController.view];
}
- (void)didReceiveMemoryWarning
{
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end