From a040e5c9143289da580dc73d7d76ff234e31f811 Mon Sep 17 00:00:00 2001 From: Nolan Waite Date: Wed, 22 Oct 2014 01:32:27 -0300 Subject: [PATCH] Native App-to-Web Browser Handoff. For thread lists, posts, and PMs. --- Source/Main/AwfulAppDelegate.m | 11 +++++ Source/Main/Handoff.h | 21 +++++++++ Source/Main/Handoff.m | 21 +++++++++ Source/Posts/PostsPageViewController.m | 45 ++++++++++++++++++- .../Private Messages/MessageViewController.m | 24 ++++++++++ .../BookmarkedThreadListViewController.m | 16 +++++++ Source/Threads/ThreadListViewController.m | 17 +++++++ Xcode/Awful.xcodeproj/project.pbxproj | 6 +++ Xcode/Info.plist | 6 +++ 9 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 Source/Main/Handoff.h create mode 100644 Source/Main/Handoff.m diff --git a/Source/Main/AwfulAppDelegate.m b/Source/Main/AwfulAppDelegate.m index 2c8dbad42..942024e72 100644 --- a/Source/Main/AwfulAppDelegate.m +++ b/Source/Main/AwfulAppDelegate.m @@ -22,6 +22,7 @@ #import "AwfulWaffleimagesURLProtocol.h" #import #import +#import "Handoff.h" #import #import "Awful-Swift.h" @@ -371,4 +372,14 @@ - (BOOL)application:(UIApplication *)application return [self openAwfulURL:URL]; } +#pragma mark Handoff + +- (void)application:(UIApplication *)application didUpdateUserActivity:(NSUserActivity *)userActivity +{ + // Bit of future-proofing. + [userActivity addUserInfoEntriesFromDictionary:@{HandoffInfoVersionKey: @(HandoffVersion)}]; +} + +static const NSInteger HandoffVersion = 1; + @end diff --git a/Source/Main/Handoff.h b/Source/Main/Handoff.h new file mode 100644 index 000000000..a3e8019fa --- /dev/null +++ b/Source/Main/Handoff.h @@ -0,0 +1,21 @@ +// Handoff.h +// +// Copyright 2014 Awful Contributors. CC BY-NC-SA 3.0 US https://github.com/Awful/Awful.app + +#import + +extern NSString * const HandoffInfoVersionKey; + +extern NSString * const HandoffActivityTypeBrowsingPosts; +extern NSString * const HandoffInfoThreadIDKey; +extern NSString * const HandoffInfoPostIDKey; +extern NSString * const HandoffInfoFilteredThreadUserIDKey; + +extern NSString * const HandoffActivityTypeListingThreads; +extern NSString * const HandoffInfoForumIDKey; +extern NSString * const HandoffInfoBookmarksKey; + +extern NSString * const HandoffActivityTypeReadingMessage; +extern NSString * const HandoffInfoMessageIDKey; + +extern NSString * const HandoffInfoPageKey; diff --git a/Source/Main/Handoff.m b/Source/Main/Handoff.m new file mode 100644 index 000000000..297e08fb5 --- /dev/null +++ b/Source/Main/Handoff.m @@ -0,0 +1,21 @@ +// Handoff.m +// +// Copyright 2014 Awful Contributors. CC BY-NC-SA 3.0 US https://github.com/Awful/Awful.app + +#import "Handoff.h" + +NSString * const HandoffInfoVersionKey = @"version"; + +NSString * const HandoffActivityTypeBrowsingPosts = @"com.awfulapp.Awful.activity.browsing-posts"; +NSString * const HandoffInfoThreadIDKey = @"threadID"; +NSString * const HandoffInfoPostIDKey = @"postID"; +NSString * const HandoffInfoFilteredThreadUserIDKey = @"filteredUserID"; + +NSString * const HandoffActivityTypeListingThreads = @"com.awfulapp.Awful.activity.listing-threads"; +NSString * const HandoffInfoForumIDKey = @"forumID"; +NSString * const HandoffInfoBookmarksKey = @"bookmarks"; + +NSString * const HandoffActivityTypeReadingMessage = @"com.awfulapp.Awful.activity.reading-message"; +NSString * const HandoffInfoMessageIDKey = @"messageID"; + +NSString * const HandoffInfoPageKey = @"page"; diff --git a/Source/Posts/PostsPageViewController.m b/Source/Posts/PostsPageViewController.m index 794014d3e..318c385a7 100644 --- a/Source/Posts/PostsPageViewController.m +++ b/Source/Posts/PostsPageViewController.m @@ -20,7 +20,8 @@ #import "AwfulWebViewNetworkActivityIndicatorManager.h" #import "BrowserViewController.h" #import -#import +#import +#import "Handoff.h" #import "MessageComposeViewController.h" #import #import "PostComposeViewController.h" @@ -150,6 +151,8 @@ - (void)loadPage:(NSInteger)page updatingCache:(BOOL)updateCache [self updateUserInterface]; + [self configureUserActivityIfPossible]; + if (!updateCache) { [self clearLoadingMessage]; return; @@ -194,6 +197,8 @@ - (void)loadPage:(NSInteger)page updatingCache:(BOOL)updateCache if (error) return; + [self configureUserActivityIfPossible]; + if (self.hiddenPosts == 0 && firstUnreadPost != NSNotFound) { self.hiddenPosts = firstUnreadPost; } @@ -1008,6 +1013,44 @@ - (void)viewDidAppear:(BOOL)animated __typeof__(self) self = weakSelf; [self loadNextPageOrRefresh]; } position:SVPullToRefreshPositionBottom]; + + [self configureUserActivityIfPossible]; +} + +- (void)configureUserActivityIfPossible +{ + if (self.page >= 1) { + self.userActivity = [[NSUserActivity alloc] initWithActivityType:HandoffActivityTypeBrowsingPosts]; + self.userActivity.needsSave = YES; + } else { + self.userActivity = nil; + } +} + +- (void)updateUserActivityState:(NSUserActivity *)activity +{ + activity.title = self.thread.title; + [activity addUserInfoEntriesFromDictionary:@{HandoffInfoThreadIDKey: self.thread.threadID, + HandoffInfoPageKey: @(self.page)}]; + if (self.author) { + [activity addUserInfoEntriesFromDictionary:@{HandoffInfoFilteredThreadUserIDKey: self.author.userID}]; + } + + NSMutableString *relativeString = [NSMutableString new]; + [relativeString appendFormat:@"/showthread.php?threadid=%@&perpage=40", self.thread.threadID]; + if (self.page > 1) { + [relativeString appendFormat:@"&pagenumber=%@", @(self.page)]; + } + if (self.author) { + [relativeString appendFormat:@"&userid=%@", self.author.userID]; + } + activity.webpageURL = [NSURL URLWithString:relativeString relativeToURL:[AwfulForumsClient client].baseURL]; +} + +- (void)viewDidDisappear:(BOOL)animated +{ + [super viewDidDisappear:animated]; + self.userActivity = nil; } #pragma mark - AwfulComposeTextViewControllerDelegate diff --git a/Source/Private Messages/MessageViewController.m b/Source/Private Messages/MessageViewController.m index c4cfdf4aa..301aec624 100644 --- a/Source/Private Messages/MessageViewController.m +++ b/Source/Private Messages/MessageViewController.m @@ -16,6 +16,7 @@ #import "AwfulWebViewNetworkActivityIndicatorManager.h" #import "BrowserViewController.h" #import +#import "Handoff.h" #import "MessageComposeViewController.h" #import "RapSheetViewController.h" #import @@ -319,6 +320,7 @@ - (void)viewDidLoad [self renderMessage]; [self.loadingView removeFromSuperview]; self.loadingView = nil; + self.userActivity.needsSave = YES; }]; } else { [self renderMessage]; @@ -337,6 +339,28 @@ - (void)themeDidChange self.loadingView.tintColor = theme[@"backgroundColor"]; } +- (void)viewDidAppear:(BOOL)animated +{ + [super viewDidAppear:animated]; + self.userActivity = [[NSUserActivity alloc] initWithActivityType:HandoffActivityTypeReadingMessage]; + self.userActivity.needsSave = YES; +} + +- (void)viewDidDisappear:(BOOL)animated +{ + [super viewDidDisappear:animated]; + self.userActivity = nil; +} + +- (void)updateUserActivityState:(NSUserActivity *)activity +{ + [activity addUserInfoEntriesFromDictionary:@{HandoffInfoMessageIDKey: self.privateMessage.messageID}]; + NSString *subject = self.privateMessage.subject; + activity.title = subject.length > 0 ? subject : @"Private Message"; + activity.webpageURL = [NSURL URLWithString:[NSString stringWithFormat:@"/private.php?action=show&privatemessageid=%@", self.privateMessage.messageID] + relativeToURL:[AwfulForumsClient client].baseURL]; +} + #pragma mark - UIWebViewDelegate - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType diff --git a/Source/Threads/BookmarkedThreadListViewController.m b/Source/Threads/BookmarkedThreadListViewController.m index d9f51be5a..997748047 100644 --- a/Source/Threads/BookmarkedThreadListViewController.m +++ b/Source/Threads/BookmarkedThreadListViewController.m @@ -7,6 +7,7 @@ #import "AwfulModels.h" #import "AwfulRefreshMinder.h" #import "AwfulSettings.h" +#import "Handoff.h" #import #import "Awful-Swift.h" @@ -84,6 +85,8 @@ - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self refreshIfNecessary]; + self.userActivity = [[NSUserActivity alloc] initWithActivityType:HandoffActivityTypeListingThreads]; + self.userActivity.needsSave = YES; } - (void)refreshIfNecessary @@ -118,6 +121,19 @@ - (void)loadPage:(NSInteger)page }]; } +- (void)updateUserActivityState:(NSUserActivity *)activity +{ + activity.title = @"Bookmarked Threads"; + [activity addUserInfoEntriesFromDictionary:@{HandoffInfoBookmarksKey: @YES}]; + activity.webpageURL = [NSURL URLWithString:@"/bookmarkthreads.php" relativeToURL:[AwfulForumsClient client].baseURL]; +} + +- (void)viewDidDisappear:(BOOL)animated +{ + [super viewDidDisappear:animated]; + self.userActivity = nil; +} + #pragma mark - AwfulFetchedResultsControllerDataSourceDelegate - (BOOL)canDeleteObject:(id)object atIndexPath:(NSIndexPath *)indexPath diff --git a/Source/Threads/ThreadListViewController.m b/Source/Threads/ThreadListViewController.m index 0437cd74f..33a83f3be 100644 --- a/Source/Threads/ThreadListViewController.m +++ b/Source/Threads/ThreadListViewController.m @@ -12,6 +12,7 @@ #import "AwfulSettings.h" #import "AwfulThreadTagLoader.h" #import "AwfulThreadTagPickerController.h" +#import "Handoff.h" #import "PostsPageViewController.h" #import #import "Awful-Swift.h" @@ -166,6 +167,8 @@ - (void)viewDidAppear:(BOOL)animated NSFetchedResultsController *fetchedResultsController = self.threadDataSource.fetchedResultsController; self.tableView.showsInfiniteScrolling = fetchedResultsController.fetchedObjects.count > 0; [self refreshIfNecessary]; + self.userActivity = [[NSUserActivity alloc] initWithActivityType:HandoffActivityTypeListingThreads]; + self.userActivity.needsSave = YES; } - (void)refreshIfNecessary @@ -211,6 +214,20 @@ - (void)loadPage:(NSInteger)page }]; } +- (void)updateUserActivityState:(NSUserActivity *)activity +{ + activity.title = self.forum.name; + [activity addUserInfoEntriesFromDictionary:@{HandoffInfoForumIDKey: self.forum.forumID}]; + activity.webpageURL = [NSURL URLWithString:[NSString stringWithFormat:@"/forumdisplay.php?forumid=%@", self.forum.forumID] + relativeToURL:[AwfulForumsClient client].baseURL]; +} + +- (void)viewDidDisappear:(BOOL)animated +{ + [super viewDidDisappear:animated]; + self.userActivity = nil; +} + #pragma mark - AwfulThreadTableViewController - (AwfulTheme *)theme diff --git a/Xcode/Awful.xcodeproj/project.pbxproj b/Xcode/Awful.xcodeproj/project.pbxproj index 14cc46f98..f4f310ea9 100644 --- a/Xcode/Awful.xcodeproj/project.pbxproj +++ b/Xcode/Awful.xcodeproj/project.pbxproj @@ -28,6 +28,7 @@ 1C270ABE16E0773300883DAA /* AwfulComposeField.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C270ABD16E0773300883DAA /* AwfulComposeField.m */; }; 1C28B3501729DDDA00254EE7 /* AwfulIconActionItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C28B34F1729DDDA00254EE7 /* AwfulIconActionItem.m */; }; 1C28B3591729F38400254EE7 /* AwfulIconActionCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C28B3581729F38400254EE7 /* AwfulIconActionCell.m */; }; + 1C2D20C419F74FC800F64577 /* Handoff.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C2D20C319F74FC800F64577 /* Handoff.m */; }; 1C3447C9174D728B007377C5 /* AwfulThreadTagView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C3447C7174D7284007377C5 /* AwfulThreadTagView.m */; }; 1C3447D2174D9B01007377C5 /* AwfulThreadTagButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C3447D1174D9B01007377C5 /* AwfulThreadTagButton.m */; }; 1C3447DD175D2D5E007377C5 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 1C3447DC175D2D5E007377C5 /* Settings.bundle */; }; @@ -443,6 +444,8 @@ 1C28B34F1729DDDA00254EE7 /* AwfulIconActionItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = AwfulIconActionItem.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 1C28B3571729F38400254EE7 /* AwfulIconActionCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AwfulIconActionCell.h; sourceTree = ""; }; 1C28B3581729F38400254EE7 /* AwfulIconActionCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AwfulIconActionCell.m; sourceTree = ""; }; + 1C2D20C219F74FC800F64577 /* Handoff.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Handoff.h; sourceTree = ""; }; + 1C2D20C319F74FC800F64577 /* Handoff.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Handoff.m; sourceTree = ""; }; 1C3447C6174D7284007377C5 /* AwfulThreadTagView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AwfulThreadTagView.h; sourceTree = ""; }; 1C3447C7174D7284007377C5 /* AwfulThreadTagView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AwfulThreadTagView.m; sourceTree = ""; }; 1C3447D0174D9B01007377C5 /* AwfulThreadTagButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AwfulThreadTagButton.h; sourceTree = ""; }; @@ -924,6 +927,8 @@ 1CBE1B1119CAAFA200510187 /* AwfulSplitViewController.swift */, 1C89A79218AF4DA500D75854 /* EmptyViewController.h */, 1C89A79318AF4DA500D75854 /* EmptyViewController.m */, + 1C2D20C219F74FC800F64577 /* Handoff.h */, + 1C2D20C319F74FC800F64577 /* Handoff.m */, 1CDD0A4619B7D89B009811C4 /* LaunchScreen.storyboard */, 1190F7F213BE4EDA00B9D271 /* main.m */, 1190F7F113BE4EDA00B9D271 /* Prefix.pch */, @@ -2368,6 +2373,7 @@ 1C8D116319B3F69D005D46CB /* ThreadCell.swift in Sources */, 1C9A746118EA62F900093E33 /* AwfulUnreadPrivateMessageCountScraper.m in Sources */, 1CE2D9D9166ABD0D0024AC1C /* AwfulNavigationBar.m in Sources */, + 1C2D20C419F74FC800F64577 /* Handoff.m in Sources */, 1CE2D9DC166ABD0D0024AC1C /* NSFileManager+UserDirectories.m in Sources */, 1CD82F2D19428D0900B0C5BD /* AwfulImageURLProtocol.m in Sources */, 1CBE1B1219CAAFA200510187 /* AwfulSplitViewController.swift in Sources */, diff --git a/Xcode/Info.plist b/Xcode/Info.plist index 30de9166f..aefce5373 100644 --- a/Xcode/Info.plist +++ b/Xcode/Info.plist @@ -44,6 +44,12 @@ 4db466addcb5cfc LSRequiresIPhoneOS + NSUserActivityTypes + + com.awfulapp.Awful.activity.browsing-posts + com.awfulapp.Awful.activity.listing-threads + com.awfulapp.Awful.activity.reading-message + UIInterfaceOrientation UIInterfaceOrientationPortrait UILaunchStoryboardName