From 41fe52a007b45e764de4d251b9df3b1995fdc481 Mon Sep 17 00:00:00 2001 From: Ryan Petrich Date: Sat, 4 Jun 2011 18:53:53 -0400 Subject: [PATCH] first commit --- .gitignore | 6 + .gitmodules | 9 ++ ActionMenu.h | 89 +++++++++++++++ Instapaper/InstapaperAPI.h | 12 ++ Instapaper/InstapaperRequest.h | 28 +++++ Instapaper/InstapaperRequest.m | 103 ++++++++++++++++++ Instapaper/InstapaperSession.h | 21 ++++ Instapaper/InstapaperSession.m | 21 ++++ LICENSE | 12 ++ Makefile | 8 ++ ReadLater.m | 99 +++++++++++++++++ framework | 1 + layout/DEBIAN/control | 9 ++ .../Library/ActionMenu/Plugins/Instapaper.png | Bin 0 -> 196 bytes .../ActionMenu/Plugins/Instapaper@2x.png | Bin 0 -> 283 bytes .../Plugins/ReadLater.bundle/Info.plist | 36 ++++++ .../Plugins/ReadLater.bundle/ReadLater.plist | 61 +++++++++++ 17 files changed, 515 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 ActionMenu.h create mode 100644 Instapaper/InstapaperAPI.h create mode 100644 Instapaper/InstapaperRequest.h create mode 100644 Instapaper/InstapaperRequest.m create mode 100644 Instapaper/InstapaperSession.h create mode 100644 Instapaper/InstapaperSession.m create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 ReadLater.m create mode 160000 framework create mode 100644 layout/DEBIAN/control create mode 100644 layout/Library/ActionMenu/Plugins/Instapaper.png create mode 100644 layout/Library/ActionMenu/Plugins/Instapaper@2x.png create mode 100644 layout/Library/ActionMenu/Plugins/ReadLater.bundle/Info.plist create mode 100644 layout/Library/ActionMenu/Plugins/ReadLater.bundle/ReadLater.plist diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5417b8f --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +._* +*.deb +.debmake +_ +obj +.theos diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e5d8684 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "framework"] + path = framework + url = git://github.com/rpetrich/theos.git +[submodule "Localization"] + path = Localization + url = git://github.com/rpetrich/Localization.git +[submodule "newsyc"] + path = newsyc + url = git://github.com/newsyc/newsyc.git diff --git a/ActionMenu.h b/ActionMenu.h new file mode 100644 index 0000000..ebb70c6 --- /dev/null +++ b/ActionMenu.h @@ -0,0 +1,89 @@ +#import + +typedef enum { + AMMenuItemStyleDefault = 0, + // Default display style (what the user has selected) + AMMenuItemStyleText = 1, + // Always display text + AMMenuItemStyleImage = 2, + // Display image where available, otherwise display text +} AMMenuItemStyle; + +typedef enum { + AMActionMenuBehaviorDefault = 0, + // Default additional behaviors provided by Action Menu + AMActionMenuBehaviorDisableMenu = (1 << 0), + // Disable the popup menu that Action Menu has added +} AMActionMenuBehaviors; + + +#define kAMMenuItemPrioritySelect 100 +#define kAMMenuItemPrioritySelectAll 200 +#define kAMMenuItemPriorityCut 300 +#define kAMMenuItemPriorityCopy 400 +#define kAMMenuItemPriorityPaste 500 +#define kAMMenuItemPriorityDelete 600 +#define kAMMenuItemPriorityCorrect 900 +#define kAMMenuItemPriorityDefault 1000 + +#define kAMMenuItemPriorityCopyAll 450 +#define kAMMenuItemPriorityHistory 525 +#define kAMMenuItemPriorityFavorites 550 +#define kAMMenuItemPriorityDial 925 +#define kAMMenuItemPriorityLookup 950 +#define kAMMenuItemPriorityFind 975 +#define kAMMenuItemPriorityTweet 1100 +#define kAMMenuItemPriorityScroll 1200 + +#define kAMMenuItemPriorityBuiltins 510 + +@protocol AMMenuItem +@property (nonatomic, readonly) SEL action; + // Selector that will be sent to the target when menu item is pressed +@property (nonatomic, readonly) SEL canPerform; + // Selector that will be sent to the target to query whether menu item is valid +@property (nonatomic, copy) NSString *title; + // Localized title of the menu item +@property (nonatomic, assign) UIImage *image; + // Image that will be used to display the menu item +@property (nonatomic, assign) AMMenuItemStyle style; + // Style of the menu item +@property (nonatomic) NSInteger priority; + // Specify the priority of the item +@end + +@interface UIMenuController (ActionMenu) +// Plugin Registration functions +- (id)registerAction:(SEL)action title:(NSString *)title canPerform:(SEL)canPerform; +- (id)registerAction:(SEL)action title:(NSString *)title canPerform:(SEL)canPerform forPlugin:(NSString *)pluginName; + // action will be sent to the target when the menu item has been pressed + // canPerform will be sent to the target when the menu is about to be shown and we need to know if the menu item is valid + // title is the localized title of the menu item + // pluginName is the unlocalized title of the plugin +@end + +@interface UIResponder (ActionMenu) +- (NSString *)textualRepresentation; + // Called by plugins to get the text of the object; implement manually for custom views +- (NSString *)selectedTextualRepresentation; + // Returns only the selected text; if unimplemented, will call and return textualRepresentation +- (AMActionMenuBehaviors)actionMenuBehaviors; + // Control which additional ActionMenu behaviors are valid on this target +- (BOOL)always; + // Always returns YES (used by actions that are always valid) +@end + +@interface UIAlertView (ActionMenu) +- (void)showAboveExistingAlert; +@end + +/* + Notes: + Plugins will be loaded lazily from /Libary/ActionMenu/Plugins when the menu is first shown; this is usually much later than one might expect. + If the user disables a plugin after it has been loaded, the plugin will still be resident in memory, but its items will no longer show. + If the user enables a plugin, it will be loaded the next time the menu becomes visible. + + Plugins may be either .dylibs or .bundles + Dylib plugins get a ON/OFF switch in the preferences pane. + Bundle plugins get their own subpane to do what they please with. +*/ \ No newline at end of file diff --git a/Instapaper/InstapaperAPI.h b/Instapaper/InstapaperAPI.h new file mode 100644 index 0000000..54878d9 --- /dev/null +++ b/Instapaper/InstapaperAPI.h @@ -0,0 +1,12 @@ +// +// InstapaperAPI.h +// newsyc +// +// Created by Grant Paul on 3/10/11. +// Copyright 2011 Xuzz Productions, LLC. All rights reserved. +// +// Imported from the newsyc project and edited to remove dependencies + +#define kInstapaperAPIRootURL [NSURL URLWithString:@"https://instapaper.com/api/"] +#define kInstapaperAPIAuthenticationURL [NSURL URLWithString:[[kInstapaperAPIRootURL absoluteString] stringByAppendingString:@"authenticate"]] +#define kInstapaperAPIAddItemURL [NSURL URLWithString:[[kInstapaperAPIRootURL absoluteString] stringByAppendingString:@"add"]] diff --git a/Instapaper/InstapaperRequest.h b/Instapaper/InstapaperRequest.h new file mode 100644 index 0000000..3f549ed --- /dev/null +++ b/Instapaper/InstapaperRequest.h @@ -0,0 +1,28 @@ +// +// InstapaperRequest.h +// newsyc +// +// Created by Grant Paul on 4/7/11. +// Copyright 2011 Xuzz Productions, LLC. All rights reserved. +// + +#import "InstapaperAPI.h" + +@class RLAInstapaperSession; +@interface RLAInstapaperRequest : NSObject { + RLAInstapaperSession *session; +} + +@property (nonatomic, readonly) RLAInstapaperSession *session; + +- (void)addItemWithURL:(NSURL *)url title:(NSString *)title selection:(NSString *)selection; +- (void)addItemWithURL:(NSURL *)url title:(NSString *)title; +- (void)addItemWithURL:(NSURL *)url; + +- (id)initWithSession:(RLAInstapaperSession *)session_; + +@end + +#define kInstapaperRequestSucceededNotification @"rla-instapaper-request-completed" +#define kInstapaperRequestFailedNotification @"rla-instapaper-request-failed" + diff --git a/Instapaper/InstapaperRequest.m b/Instapaper/InstapaperRequest.m new file mode 100644 index 0000000..623ec44 --- /dev/null +++ b/Instapaper/InstapaperRequest.m @@ -0,0 +1,103 @@ +// +// InstapaperRequest.m +// newsyc +// +// Created by Grant Paul on 4/7/11. +// Copyright 2011 Xuzz Productions, LLC. All rights reserved. +// + +#import "InstapaperRequest.h" +#import "InstapaperSession.h" + +@implementation RLAInstapaperRequest +@synthesize session; + +- (void)dealloc { + [session release]; + + [super dealloc]; +} + +- (id)initWithSession:(RLAInstapaperSession *)session_ { + if ((self = [super init])) { + session = [session_ retain]; + } + + return self; +} + +- (void)succeed { + [[NSNotificationCenter defaultCenter] postNotificationName:kInstapaperRequestSucceededNotification object:self]; +} + +- (void)failWithError:(NSError *)error { + [[NSNotificationCenter defaultCenter] postNotificationName:kInstapaperRequestFailedNotification object:self userInfo:[NSDictionary dictionaryWithObject:error forKey:@"error"]]; +} + +- (void)failWithErrorText:(NSString *)text { + NSError *error = [NSError errorWithDomain:@"instapaper" code:0 userInfo:[NSDictionary dictionaryWithObject:text forKey:NSLocalizedDescriptionKey]]; + + [self failWithError:error]; +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { + [self failWithError:error]; + [connection cancel]; + [self autorelease]; +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection { + [connection cancel]; + [self autorelease]; +} + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { + int status = [(NSHTTPURLResponse *) response statusCode]; + if (status == 403) [self failWithErrorText:@"Invalid username or password."]; + if (status == 500) [self failWithErrorText:@"Internal error, try again later."]; + if (status == 201) [self succeed]; + } else { + [self failWithErrorText:@"Unknown error."]; + } +} + +static inline NSString *URLEncode(NSString *string) +{ + CFStringRef result = CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)string, NULL, (CFStringRef) @"!*'\"();:@&=+$,/?%#[]% ", kCFStringEncodingUTF8); + return [(NSString *)result autorelease]; +} + +- (void)addItemWithURL:(NSURL *)url title:(NSString *)title selection:(NSString *)selection { + NSString *password = [session password]; + NSString *username = [session username]; + + NSString *query = @""; + query = [query stringByAppendingFormat:@"username=%@&", URLEncode(username)]; + if (password != nil && [password length] > 0) query = [query stringByAppendingFormat:@"password=%@&", URLEncode(password)]; + if (title != nil && [title length] > 0) query = [query stringByAppendingFormat:@"title=%@&", URLEncode(title)]; + if (selection != nil && [selection length] > 0) query = [query stringByAppendingFormat:@"&title=%@&", URLEncode(selection)]; + query = [query stringByAppendingFormat:@"url=%@&", URLEncode([url absoluteString])]; + + NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] initWithURL:kInstapaperAPIAddItemURL] autorelease]; + NSData *data = [query dataUsingEncoding:NSUTF8StringEncoding]; + [request setHTTPMethod: @"POST"]; + [request setHTTPBody:data]; + [request setValue:[NSString stringWithFormat:@"%u", [data length]] forHTTPHeaderField:@"Content-Length"]; + [request setValue:@"application/x-www-form-urlencoded; charset=UTF-8" forHTTPHeaderField: @"Content-Type"]; + + NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; + [connection start]; + [connection autorelease]; + [self retain]; +} + +- (void)addItemWithURL:(NSURL *)url title:(NSString *)title { + [self addItemWithURL:url title:title selection:nil]; +} + +- (void)addItemWithURL:(NSURL *)url { + [self addItemWithURL:url title:nil selection:nil]; +} + +@end diff --git a/Instapaper/InstapaperSession.h b/Instapaper/InstapaperSession.h new file mode 100644 index 0000000..454a4f0 --- /dev/null +++ b/Instapaper/InstapaperSession.h @@ -0,0 +1,21 @@ +// +// InstapaperSession.h +// newsyc +// +// Created by Grant Paul on 3/10/11. +// Copyright 2011 Xuzz Productions, LLC. All rights reserved. +// + +#import "InstapaperAPI.h" + +@interface RLAInstapaperSession : NSObject { +@private + NSString *username; + NSString *password; +} + +@property (nonatomic, copy) NSString *username; +@property (nonatomic, copy) NSString *password; + +@end + diff --git a/Instapaper/InstapaperSession.m b/Instapaper/InstapaperSession.m new file mode 100644 index 0000000..0501d30 --- /dev/null +++ b/Instapaper/InstapaperSession.m @@ -0,0 +1,21 @@ +// +// InstapaperSession.m +// newsyc +// +// Created by Grant Paul on 3/10/11. +// Copyright 2011 Xuzz Productions, LLC. All rights reserved. +// + +#import "InstapaperSession.h" + +@implementation RLAInstapaperSession +@synthesize username, password; + +- (void)dealloc +{ + [username release]; + [password release]; + [super dealloc]; +} + +@end diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..74adbeb --- /dev/null +++ b/LICENSE @@ -0,0 +1,12 @@ +Copyright (c) 2011, Ryan Petrich + +Portions Copyright (c) 2011, Xuzz Productions, LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of the Xuzz Productions, LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6ca3bad --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +BUNDLE_NAME = ReadLater +ReadLater_FILES = ReadLater.m Instapaper/InstapaperRequest.m Instapaper/InstapaperSession.m +ReadLater_FRAMEWORKS = UIKit QuartzCore +ReadLater_PRIVATE_FRAMEWORKS = Preferences WebKit WebCore +ReadLater_INSTALL_PATH = /Library/ActionMenu/Plugins + +include framework/makefiles/common.mk +include framework/makefiles/bundle.mk diff --git a/ReadLater.m b/ReadLater.m new file mode 100644 index 0000000..409e5bd --- /dev/null +++ b/ReadLater.m @@ -0,0 +1,99 @@ +#import +#import +#import +#import +#import "ActionMenu.h" +#import "Instapaper/InstapaperSession.h" +#import "Instapaper/InstapaperRequest.h" + +// Additional Private APIs + +@interface UIWebBrowserView (WebPrivate) +- (WebFrame *)_focusedOrMainFrame; +@end + +@interface WebFrame (WebPrivate) +- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)string forceUserGesture:(BOOL)forceUserGesture; +@end + +@implementation UIWebBrowserView (ReadLaterAction) + +static inline void Alert(NSString *message) +{ + // Helper function + UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"Read Later" message:message delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; + [av show]; + [av release]; +} + +- (BOOL)canDoReadLaterAction:(id)sender +{ + WebThreadLock(); + WebFrame *webFrame = [self _focusedOrMainFrame]; + // Check to see if web view contains a URL we can Read Later + NSString *URL = [webFrame stringByEvaluatingJavaScriptFromString:@"location.href" forceUserGesture:NO]; + WebThreadUnlock(); + return [URL hasPrefix:@"http://"] || [URL hasPrefix:@"https://"]; +} + +- (void)performReadLaterAction:(id)sender +{ + // Load Settings + NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:@"/var/mobile/Library/Preferences/com.rpetrich.readlater.plist"]; + NSString *username = [settings objectForKey:@"RLLogin"]; + if ([username length] == 0) { + // Username is empty + Alert(@"Instapaper login not yet set.\nEnter your Instapaper email and password in ActionMenu settings."); + } else { + // Create a session + RLAInstapaperSession *session = [[RLAInstapaperSession alloc] init]; + session.username = username; + session.password = [settings objectForKey:@"RLPassword"]; + // Create a request + RLAInstapaperRequest *request = [[RLAInstapaperRequest alloc] initWithSession:session]; + [session release]; + // Add the item with specified URL + WebThreadLock(); + WebFrame *webFrame = [self _focusedOrMainFrame]; + [request addItemWithURL:[NSURL URLWithString:[webFrame stringByEvaluatingJavaScriptFromString:@"location.href" forceUserGesture:NO]] + title:[webFrame stringByEvaluatingJavaScriptFromString:@"document.title" forceUserGesture:NO]]; + WebThreadUnlock(); + [request release]; + } +} + ++ (void)readLaterActionSucceeded:(NSNotification *)notification +{ + Alert(@"Link submitted to Instapaper."); +} + ++ (void)readLaterActionFailed:(NSNotification *)notification +{ + Alert([[notification.userInfo objectForKey:@"error"] localizedDescription]); +} + ++ (void)load +{ + id menuItem = [[UIMenuController sharedMenuController] registerAction:@selector(performReadLaterAction:) title:@"Read Later" canPerform:@selector(canDoReadLaterAction:) forPlugin:@"ReadLater"]; + menuItem.priority = 1000; + menuItem.image = [UIImage imageWithContentsOfFile:([UIScreen mainScreen].scale == 2.0f) ? @"/Library/ActionMenu/Plugins/Instapaper@2x.png" : @"/Library/ActionMenu/Plugins/Instapaper.png"]; + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc addObserver:self selector:@selector(readLaterActionSucceeded:) name:kInstapaperRequestSucceededNotification object:nil]; + [nc addObserver:self selector:@selector(readLaterActionFailed:) name:kInstapaperRequestFailedNotification object:nil]; +} + +@end + +// Settings bundle controller + +@interface ReadLaterSettingsController : PSListController +@end + +@implementation ReadLaterSettingsController + +- (NSArray *)loadSpecifiersFromPlistName:(NSString *)plistName target:(id)target +{ + return [super loadSpecifiersFromPlistName:@"ReadLater" target:self]; +} + +@end diff --git a/framework b/framework new file mode 160000 index 0000000..be80a97 --- /dev/null +++ b/framework @@ -0,0 +1 @@ +Subproject commit be80a97e97e9f8c12c43b8353790fdf12ce9a537 diff --git a/layout/DEBIAN/control b/layout/DEBIAN/control new file mode 100644 index 0000000..bad86df --- /dev/null +++ b/layout/DEBIAN/control @@ -0,0 +1,9 @@ +Package: com.rpetrich.readlateraction +Priority: optional +Section: Tweaks +Architecture: iphoneos-arm +Version: 0.1 +Description: Submit articles to Instapaper via Action Menu +Name: Read Later Action +Depends: firmware (>= 4.0), mobilesubstrate, actionmenu +Author: Ryan Petrich diff --git a/layout/Library/ActionMenu/Plugins/Instapaper.png b/layout/Library/ActionMenu/Plugins/Instapaper.png new file mode 100644 index 0000000000000000000000000000000000000000..4400532e6beee1875176191b79a02b523d854645 GIT binary patch literal 196 zcmeAS@N?(olHy`uVBq!ia0vp^>_E)N!3HEB$FfNSDajJoh?3y^w370~qErUQl>DSr z1<%~X^wgl##FWaylc_d9MQNTcjv*Ddl2Q^9HZUfMv-xb8_3;1y|4;vKY!28Uq~QGF z>FMd|j!Ns}_uKJaKE=x!UIg06;OXk;vd$@?2>>1CMtcAN literal 0 HcmV?d00001 diff --git a/layout/Library/ActionMenu/Plugins/Instapaper@2x.png b/layout/Library/ActionMenu/Plugins/Instapaper@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..537a60fc8f60ab0346f805ade71aadbc3a46df59 GIT binary patch literal 283 zcmeAS@N?(olHy`uVBq!ia0vp^JU}eX!3HEfmgOA*Qj#UE5hcO-X(i=}MX3yqDfvmM z3ZA)%>8U}fi7AzZCsS>JiuQWCIEGZ*N=ivc=wMD#WqWi$E=}R1RL6bhkL3zthZk|E zl#6tnXZmQ{_W%F?_y7EDG}2jwjycTLbKqIzyzxc>e@D9G%gq`_4rldF9IO5FBBByrVXRU%8K!^v3t`hO|xL32(d={V@HhH7w}yJ7Aj9%1lw^>bP0l+XkKjRa{d literal 0 HcmV?d00001 diff --git a/layout/Library/ActionMenu/Plugins/ReadLater.bundle/Info.plist b/layout/Library/ActionMenu/Plugins/ReadLater.bundle/Info.plist new file mode 100644 index 0000000..07dead4 --- /dev/null +++ b/layout/Library/ActionMenu/Plugins/ReadLater.bundle/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ReadLater + CFBundleIdentifier + com.apple.ActionMenu.ReadLater + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ReadLater + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + iPhoneOS + + CFBundleVersion + 1.0 + DTPlatformName + iphoneos + DTSDKName + iphoneos3.0 + MinimumOSVersion + 3.0 + NSPrincipalClass + ReadLaterSettingsController + + diff --git a/layout/Library/ActionMenu/Plugins/ReadLater.bundle/ReadLater.plist b/layout/Library/ActionMenu/Plugins/ReadLater.bundle/ReadLater.plist new file mode 100644 index 0000000..4059a9c --- /dev/null +++ b/layout/Library/ActionMenu/Plugins/ReadLater.bundle/ReadLater.plist @@ -0,0 +1,61 @@ + + + + + title + ReadLater + items + + + cell + PSGroupCell + + + cell + PSSwitchCell + default + + defaults + com.booleanmagic.ActionMenu + key + AMPluginEnabled-ReadLater + label + Enabled + PostNotification + com.booleanmagic.ActionMenu.settingschange + + + cell + PSGroupCell + label + Instapaper Account + + + cell + PSEditTextCell + defaults + com.rpetrich.readlater + key + RLLogin + isEmail + + placeholder + Login + PostNotification + com.rpetrich.readlater.settingschanged + + + cell + PSSecureEditTextCell + defaults + com.rpetrich.readlater + key + RLPassword + placeholder + Password + PostNotification + com.rpetrich.readlater.settingschanged + + + +